Showing posts with label Module. Show all posts
Showing posts with label Module. Show all posts

2016-03-24

리눅스 드라이버 종류 및 구성

리눅스 드라이버 종류 및 구성






  • 리눅스 디바이스 드라이버 구성 및 구조



<디바이스 드라이버 기본 구조>

  • 저레벨 함수를 통한 캐릭터 디바이스 드라이버 실행.



  1. mknod 통해 캐릭터 디바이스 설정.

mknod 명령으로 캐릭터 디바이스 파일 생성
# mknod /dev/calldev c 240 0
# ls -al /dev/calldev
crw-r--r-- 1 root root 240, 1 Mar 24 01:48 /dev/callde

  1. 디바이스 드라이버 모듈 작성



call_dev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>          
#include <linux/errno.h>       
#include <linux/types.h>       
#include <linux/fcntl.h>       

#define   CALL_DEV_NAME            "calldev"
#define   CALL_DEV_MAJOR            240      // 디바이스 주넘버 230까지는 정해진 사용자가 있다.

int call_open (struct inode *inode, struct file *filp)
{
   int num = MINOR(inode->i_rdev);
   printk( "call open -> minor : %d\n", num );
   return 0;
}

loff_t call_llseek (struct file *filp, loff_t off, int whence )
{
   printk( "call llseek -> off : %08X, whenec : %08X\n", off, whence );
   return 0x23;
}

ssize_t call_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
   printk( "call read -> buf : %08X, count : %08X \n", buf, count );
   return 0x33;
}

ssize_t call_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
   printk( "call write -> buf : %08X, count : %08X \n", buf, count );
   return 0x43;
}

int call_release (struct inode *inode, struct file *filp)
{
   printk( "call release \n" );
   return 0;
}

// file_operations 구조체에 실제 실행될 저레벨 함수를 연결하기 위한 리소트를 저장한다
struct file_operations call_fops =
{
   .owner    = THIS_MODULE,
   .llseek   = call_llseek,   
   .read     = call_read,     
   .write    = call_write,    
   .open     = call_open,     
   .release  = call_release,  //...... 모든 구현을 다 할 필요는 없다.
};

int __init call_init(void)
{
   int result;

   printk( "call call_init \n" );    

// 커널에 문자 디바이스를 등록한다
// 이때, 맵핑되는 저레벨 입출력함수들을 등록한 file_operations 구조체를 커널에 등록한다.
   result = register_chrdev( CALL_DEV_MAJOR, CALL_DEV_NAME, &call_fops);
   if (result < 0) return result;

   return 0;
}

void __exit call_exit(void)
{
   printk( "call call_exit \n" );
   unregister_chrdev( CALL_DEV_MAJOR, CALL_DEV_NAME );   // 커널에서 문자 디바이스를 제거한다.
}

module_init(call_init);
module_exit(call_exit);

MODULE_AUTHOR("root");
MODULE_DESCRIPTION("The test for calling a character device.");
MODULE_LICENSE("BSD");


※ file_operations 구조체는 커널 버전 별로 다양한 저베렐 입출력 함수에 대하여 맵핑 할수 있도록 지원한다.
file_operation.PNG


  1. “/dev/calldev” 캐릭터 디바이스를 저레벨 입출력 함수로 동작 시키기 위한 어플리케이션 작성



call_app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>

#define DEVICE_FILENAME  "/dev/calldev"

int main()
{
   int dev;
   char buff[128];
   int ret;

   printf( "1) device file open\n");
 
   dev = open( DEVICE_FILENAME, O_RDWR|O_NDELAY );
   if( dev >= 0 )
   {
       printf( "2) seek function call\n");
       
       ret = lseek( dev, 0x20, SEEK_SET );
       printf( "ret = %08X\n", ret );
    
       printf( "3) read function call\n");
       
       ret = read(dev,0x30, 0x31 );              
       printf( "ret = %08X\n", ret );
   
       printf( "4) write function call\n");
       ret = write(dev,0x40,0x41 );
       printf( "ret = %08X\n", ret );

       printf( "6) device file close\n");
       ret = close(dev);
       printf( "ret = %08X\n", ret );
   }

   return 0;
}
※ CentOS7 에서 테스트를 진행 했는데, 3.14 로 커널 버전을 업데이트 했다.
3.14 버전에서는 캐릭터 디바이스 드라이버에 대한 다양한 입출력 함수중 ioctl 에 대한 함수를 제공하지 않는다. 2.6 커널 버전에서는 지원한다.


  1. 커널 모듈을 작성하기 위한 Makefile 작성



Makefile
KERNEL_DIR := /lib/modules/`uname -r`/build
BUILD_DIR := `pwd`
VERBOSE   := 1

obj-m := call_dev.o

all:
make -C $(KERNEL_DIR) SUBDIRS=$(BUILD_DIR) KBUILD_VERBOSE=$(VERBOSE) modules

clean:
rm -rf  *.o *.ko *.mod.c *.symvers *.order .tmp_versions .call_dev.c.*


  1. call_app으 통한 저레벨 함수 사용 예제

call_app 실행 결과
# gcc call_app.c
# ./a.out
1) device file open
2) seek function call
ret = 00000023
3) read function call
ret = 00000033
4) write function call
ret = 00000043
6) device file close
ret = 00000000

  • 동작 순서


  1. 사용자 어플리케이션에서, 해당 캐릭터 디바이스 파일을  표준 파일 시스템을 통해 (저레벨 파일 입출력함수) 접근한다.
  2. 파일 시스템은 파일에 대한 입출력을, 연결된 디바이스 파일의 이름과 주(major)넘버를 통해 커널에서 심볼을 찾는다.
  3. 커널은 심볼을 통해 실제 커널 디바이스 드라이버 모듈을 찾고, files_operations 구조체를 통해  파일시스템으로 부터온 저레벨 함수 에 대한 커널 드라이버의 구현 부분을 호출한다.


※ 위 소스 코드의 출처는 한빛 미디어 " 리눅스 디바이스 드라이버" 의 예제 소스를 기본으로 centOS7 ,Kernel 3.14에서 동작 하도록 수정한 것이다.

2016-03-16

Linux Kernel Module Makefile 디버깅




정말 간단한 HelloWorld 커널을 모듈하면서, 시행착오가 많았다.
정리하면 소스 자체는 간단하니 문제가 없었지만, Makefile및 환경 설정에서 문제가 많이 발생했다.



  • CASE 1
원인 : 리눅스는 Makefile의 대소문자를 구분한다. 그냥 makefile로 파일을 생성하면
다음과 같은 에러를 얻는다.

# make
make -C /lib/modules/3.10.0-229.el7.x86_64/build SUBDIRS=/root/drivers KBUILD_VERBOSE=0 modules
make[1]: Entering directory `/usr/src/kernels/3.10.0-229.el7.x86_64'
scripts/Makefile.build:44: /root/drivers/Makefile: No such file or directory
make[2]: *** No rule to make target `/root/drivers/Makefile'.  Stop.
make[1]: *** [_module_/root/drivers] Error 2
make[1]: Leaving directory `/usr/src/kernels/3.10.0-229.el7.x86_64'
make: *** [all] Error 2
해결 : 파일이름을 makefile -> Makefile 로 대소문자 구분하도록 수정한다.




  • CASE 2
원인 : 커널 모듈 컴파일시 'make'가 사용하는 모듈 설치 도구가 없는 경우 발생.
나는 CentOS7.1의 기본 커널 버전인 3.10 을 3.14.64 버전으로 업데이트 했는데,
이때 모듈 설치 도구는 컴파일에서 제외되어 있었음.

# make -d
 Successfully remade target file `FORCE'.
   Finished prerequisites of target file `/root/workspace/drivers/src/helloworld.o'.
   Prerequisite `/root/workspace/drivers/src/helloworld.c' is older than target `/root/workspace/drivers/src/helloworld.o'.
   Prerequisite `/usr/src/kernels/linux-3.14.64/scripts/recordmcount.c' is older than target `/root/workspace/drivers/src/helloworld.o'.
   Prerequisite `/usr/src/kernels/linux-3.14.64/scripts/recordmcount.h' is older than target `/root/workspace/drivers/src/helloworld.o'.
   Prerequisite `FORCE' of target `/root/workspace/drivers/src/helloworld.o' does not exist.
  Must remake target `/root/workspace/drivers/src/helloworld.o'.
Invoking recipe from scripts/Makefile.build:314 to update target `/root/workspace/drivers/src/helloworld.o'.
Putting child 0x1267170 (/root/workspace/drivers/src/helloworld.o) PID 78769 on the chain.
Live child 0x1267170 (/root/workspace/drivers/src/helloworld.o) PID 78769 
  CC [M]  /root/workspace/drivers/src/helloworld.o
/bin/sh: /usr/src/kernels/linux-3.14.64/scripts/recordmcount: No such file or directory
Reaping losing child 0x1267170 PID 78769 
make[2]: *** [/root/workspace/drivers/src/helloworld.o] Error 1
Removing child 0x1267170 PID 78769 from chain.
Reaping losing child 0x9cb2d0 PID 78766 
make[1]: *** [_module_/root/workspace/drivers/src] Error 2
Removing child 0x9cb2d0 PID 78766 from chain.
make[1]: Leaving directory `/usr/src/kernels/linux-3.14.64'
Reaping losing child 0x2341fd0 PID 78546 
make: *** [all] Error 2
Removing child 0x2341fd0 PID 78546 from chain.
※ 위의 recordmcount 뿐만 아닐라 모듈 관리에 설치된 툴들이 계속 없다고 설치 중지되므로 다  개별 처리 하거나 한꺼번에 처리 해줘야 한다.

해결 : 모듈 설치 도구를 컴파일해 준다.
개별 컴파일도 가능하다.
# cd /usr/src/kernels/linux-3.14.64
# make modules_prepare
 make[1]: Nothing to be done for `all'.
make[1]: Nothing to be done for `relocs'.
  CHK     include/config/kernel.release
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
  CALL    scripts/checksyscalls.sh
  HOSTCC  scripts/genksyms/genksyms.o
  SHIPPED scripts/genksyms/lex.lex.c
  SHIPPED scripts/genksyms/keywords.hash.c
  SHIPPED scripts/genksyms/parse.tab.h
  HOSTCC  scripts/genksyms/lex.lex.o
  SHIPPED scripts/genksyms/parse.tab.c
  HOSTCC  scripts/genksyms/parse.tab.o
  HOSTLD  scripts/genksyms/genksyms
  CC      scripts/mod/empty.o
  HOSTCC  scripts/mod/mk_elfconfig
  MKELF   scripts/mod/elfconfig.h
  CC      scripts/mod/devicetable-offsets.s
  GEN     scripts/mod/devicetable-offsets.h
  HOSTCC  scripts/mod/file2alias.o
  HOSTCC  scripts/mod/modpost.o
  HOSTCC  scripts/mod/sumversion.o
  HOSTLD  scripts/mod/modpost
  HOSTCC  scripts/selinux/genheaders/genheaders
  HOSTCC  scripts/selinux/mdp/mdp
  HOSTCC  scripts/kallsyms
  HOSTCC  scripts/pnmtologo
  HOSTCC  scripts/conmakehash
  HOSTCC  scripts/recordmcount
  HOSTCC  scripts/sortextable
  HOSTCC  scripts/asn1_compiler

CentOS 7 : Kernel 3.14 : HelloWorld Module 빌드하기.

CentOS 7 : Kernel 3.14 : HelloWorld Module 빌드하기.
아주 간단한 리눅스 커널 모듈을 만들어 보자.
목표는 모듈 등록과 삭제후 “dmseg | tail”  명령으로 메세지를 확이 할수 있게 하는것이다.

  1. 먼저 커널 모듈을 작성한다.
임의의 디렉토리 “/roor/Desktop/helloworld”에 모듈 소스를 작성한다.

helloworld.c
#include <linux/module.h>
#include <linux/init.h>

static int __init helloworld_init(void)
{
   pr_info("Hello World!\n");
   return 0;
}

static void __exit helloworld_exit(void)
{
   pr_info("Goodby World.\n");
}

module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_AUTHOR("root");
MODULE_DESCRIPTION("Hello World Module");

※  기타 MODULE_XXXX
: modinfo 명령으로 해당 모듈을 조회시 표현되는 정보들을 등록 한다.
modinfo_list.PNG

※   커널 시스템 메세지 출력
pr_info("Hello World!\n");
을 사용하거나

incude<kernel.h>
printk(“Hello World!\n");
을 사용한다.

    

  1. 커널 모듈을 빌드하기 위한 Makefile 작성

Makefile
KERNEL_DIR := /lib/modules/`uname -r`/build
BUILD_DIR := `pwd`
VERBOSE   := 1

obj-m := helloworld.o

all:
make -C $(KERNEL_DIR) SUBDIRS=$(BUILD_DIR) KBUILD_VERBOSE=$(VERBOSE) modules

clean:
rm -rf  *.o *.ko *.mod.c *.symvers *.order .tmp_versions .helloworld.c.*

※  Makefile 필수 문법.
: Makefile 에서 사용하는 시스템 명령어는 반드시 {TAB}으로 띄어져 있어야 한다.

  1. 커널 모듈을 작성 한다.
# make
make -C /lib/modules/3.14.64/build SUBDIRS=/root/drivers KBUILD_VERBOSE=0 modules
make[1]: Entering directory `/usr/src/kernels/3.14.64'
 CC [M]  /root/drivers/helloworld.o
 Building modules, stage 2.
 MODPOST 1 modules
 CC      /root/drivers/helloworld.mod.o
 LD [M]  /root/drivers/helloworld.ko
make[1]: Leaving directory `/usr/src/kernels/3.14.64'
# ls
helloworld.c   helloworld.mod.c  helloworld.o  Makefile~      Module.symvers
helloworld.ko  helloworld.mod.o  Makefile      modules.order

※  Makefile 디버깅
: maked -d 로 실행 하면 자세한 상황을 확인 할수 있다.
  1. 동작확인

  • 모듈을 커널에 등록.
# insmod helloworld.ko
# echo $?
0

  • 커널에 로드된 모듈을 조회
# lsmod | grep helloworld
helloworld             12430  0

< 모듈 이름>         <모듈이 사용하는 메모리 사이즈 > < 해당 모듈을 참조하는 모듈의 수>

  • 커널 모듈 파일의 정보 조회
# modinfo helloworld.ko
filename:       /root/workspace/drivers/src/helloworld.ko
license:        BSD
description:    Hello World Module
author:         root
srcversion:     EB07C4DCC99CCF81F69125E
depends:        
vermagic:       3.14.64 SMP mod_unload modversions

  • 커널에서 로드된 모듈 삭제
# rmmod helloworld
# echo $?
0

  • 커널 정상 동작 확인
# dmesg | tail
[ 7921.789879] Hello World!
[ 7983.689416] Goodby World.