Module 11: 가상디바이스드라이버작성 ESP30076 임베디드시스템프로그래밍 (Embedded System Programming) 조윤석 전산전자공학부
주차별목표 디바이스드라이버의실행과정이해하기 크로스컴파일러를이용하여가상의디바이스드라이버생성하기 Kbuild 에대해이해하기 2
file_operations 구조체 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); }; 3
응용프로그램에서의시스템콜함수와디바이스드라이버함수와의관계 struct file_operations test_fops = {.open = ccd_open,.read = ccd_read,.write = ccd_write,.ioctl = ccd_ioctl,.release = ccd_release, }; ANSI C99 에서표준으로제공 응용프로그램에서의시스템콜 open() close() read() write() ioctl() 디바이스드라이버에서의실행함수 ccd_open() ccd_release() ccd_read() ccd_write() ccd_ioctl() 4
Device Driver 와응용프로그램과의호출관계 Application Device Driver % insmod % rmmod open() close() read() write() ioctl() struct file_operations open release read write ioctl module init() module exit() ccd_open() ccd_release() ccd_read() ccd_write() ccd_ioctl() 5
디바이스드라이버호출 응용프로그램 insmod open, close 장치파일파일핸들주번호, 부번호 read, write, ioctl rmmod 디바이스드라이버 주번호 module_init request_region register_chrdev struct file_operations open, release read, write, ioctl module_exit release_region unregister_chrdev 6
간단한디바이스드라이버연습하기 가상의디바이스드라이버를작성해보기 가상의디바이스에대한디바이스파일명 /dev/test_dd 디바이스 open 시다음의메시지출력 Simple device driver programming: open virtual device!!! read 시다음의메시지출력 "Access device_read() function through struct file_operations 응용프로그램에서 ioctl() 호출시다음의문자열출력 Access device_ioctl() function 작업위치 % mkdir /root/work/dd/test_dd % cd!$ 7
간단한디바이스드라이버연습 (test_dd.c) % vi test_dd.c :1,$s/mydevice/test_dd/g /* test_dd.c */ #include <linux/kernel.h> /* KERNEL_ALERT */ #include <linux/init.h> /* init */ #include <linux/fs.h> #include <linux/module.h> #define DEVICE_MAJOR_NUM 0 #define DEV_NAME "/dev/test_dd" MODULE_LICENSE("GPL"); MODULE_AUTHOR("HGU"); int test_dd_init(void); void test_dd_exit(void); module_init(test_dd_init); module_exit(test_dd_exit); 8 int test_dd_open (struct inode *, struct file *); int test_dd_release (struct inode *, struct file *); ssize_t test_dd_read (struct file *, char user *, size_t, loff_t *); ssize_t test_dd_write (struct file *, const char user *, size_t, loff_t *); int test_dd_ioctl (struct inode *, struct file *, unsigned int, unsigned long);
test_dd.c (2) struct file_operations test_dd_fops = {.owner = THIS_MODULE,.open = test_dd_open,.release = test_dd_release,.read = test_dd_read,.write = test_dd_write,.ioctl = test_dd_ioctl, }; int test_dd_open (struct inode *inode, struct file *filp) { printk("simple device driver programming: oepn virtual device!!!\n"); return 0; } int test_dd_release (struct inode *inode, struct file *filp) { return 0; } ssize_t test_dd_read (struct file *filp, char user *buf, size_t count, loff_t *f_pos) { printk("access read() function through \"struct file_operations\"\n"); return 0; } 9
test_dd.c (3) 10 ssize_t test_dd_write (struct file *filp, const char user *buf, size_t count, loff_t *f_pos) { return 0; } int test_dd_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { printk("access device_ioctl() function...\n"); return 0; } int init test_dd_init(void) { int major_num; major_num = register_chrdev(device_major_nuim, DEV_NAME, &test_dd_fops); if ( major_num < 0 ) { printk(kern_warning"%s: can't get or assign major number %d\n", DEV_NAME, DEVICE_MAJOR_NUM); return major_num; } printk("success to load the device %s. Major number is %d\n", DEV_NAME, DEVICE_MAJOR_NUM); return 0; } void exit test_dd_exit(void) { unregister_chrdev(device_major_num, DEV_NAME); printk("success to unload the device %s...\n", DEV_NAME); }
테스트를위한응용프로그램작성 (test_app.c) % vi test_app.c /* filename: test_app.c */ #include <stdio.h> #include <fcntl.h> #include <string.h> #include <errno.h> #define DEV_NAME "/dev/test_dd" int main(int argc, char **argv) { int fd; fd = open(dev_name, O_RDWR); if ( fd < 0 ) { fprintf(stderr, "%s device file open error!!!... %s\n", DEV_NAME, strerror(errno)); return 0; } read(fd, 0, 0); ioctl(fd, NULL, 0); close(fd); 11 } return 0;
컴파일 % vi Makefile % make obj-m = test_dd.o # Kernel Source Directory of Achro-210T board KDIR = /root/work/kernel-2.6.35 FILE = test_app PWD = $(shell pwd) CC = arm-linux-gcc all: module app module: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules app: $(CC) -o $(FILE) $(FILE).c clean: rm -rf *.ko rm -rf *.o rm -rf $(FILE) *.mod.* rm -rf modules.order Module.symvers 12
register_chrdev() Char Device Driver 등록방법 외부와 device driver 는 file interface (node 를의미 ) 를통해연결 Device driver 는자신을구별하기위해고유의 major number 를사용 장치등록과해제 등록 : int register_chrdev (unsigned int major, const char *name, stuct file_operations *fops) major: 등록할 major number. 0 이면사용하지않는번호중자동으로할당 name: device 의이름 fops: device 에대한 file 연산함수들 해제 : int unregister_chrdev (unsigned int major, const char *name) 13
제작한디바이스드라이버검증파일만들기 (test_app.c) kernel 응용프로그램 (test_app.c) open close read write system call device driver test_dd.ko file_operations device_open device_release device_read device_write device 14
Build 기존 RTOS Kernel 과 Application program 을하나의 image 로 build ( 생성 ) Kernel 과 Application program 을구분하지않음 Linux 커널의 address space 와 application program 의 address space 가나뉘어져있어, 서로관련이없음. 따라서커널을빌드하는것과응용프로그램을빌드하는것이분리됨 Application program 생성 응용프로그램에서사용하는 header file 과 C library 가제공되면생성가능 Kernel build 15
Kernel Build Kbuild 커널 2.6 에포함된주요변경사항중의하나 GNU make 프로그램사용 모든과정은 make 명령을통해이루어짐 Kbuild 는커널을쉽게 build 할수있도록간단한 build 과정을제공함 확장및추가하는것이용이 예 : 시스템콜추가시 makefile 내에 obj-y 에 object code 파일명을추가하는것으로충분
Kernel build 단계 (1~4) Cross-development 환경설정 리눅스는많은하드웨어아키텍처를지원함으로, 커널이미지와모듈을빌드하기위한 target architecture 를설정해야함 Build 시포함될목록선택 프로세서, 보드, 드라이버, 일반적인커널옵션선택 설정을위한명령어들 ( 커널 2.4 와 2.6 대상 ) % make config % make menuconfig Curses library 를기반으로제공하는대화형커널설정 % make xconfig X 윈도우그래픽기반의커널설정. 커널 2.4 에서는 X library 사용. 커널 2.6 에서는 QT library 를사용함 % make oldconfig 기존설정에서일부만변경하고자할때사용. 기존의설정값은유지하고, 새로운변경사항만선택하도록보여줌. 커널업그레이드시에종종사용
Kernel build 단계 (1~4) Object file 을 build 하고 link 하여커널이미지생성 기존코드삭제 % make clean ( 커널 2.4와 2.6 공통 ) ( 참고 ) % make mrproper Clean 과동일한일을수행한후설정정보도삭제함 커널이미지생성 % make vmlinux 또는 zimage 파일생성됨 동적으로 load 할수있는모듈생성 % make modules
빌드과정의이해 (1) Makefile 을사용한하위 directory 들을 recursive 탐색을하며빌드 커널소스내의최상위 Makefile 커널이미지와 module 을 build 하는작업담당 커널소스트리의하위디렉토리들을재귀적으로탐색하여빌드 하위의 makefile 들은상위디렉토리에서빌드하기위한규칙을상속받음 설정과정에서선택할요소들의목록제공 arch/$arch/config.in for kernel 2.4 arch/$arch/kconfig for kernel 2.6 위의파일들을분석하여사용자에게각요소들을선택하도록요구함 목록에포함된내용 프로세서모델, 하드웨어보드, 보드종속적인하드웨어설정등 커널서브시스템요소, 네트워크스택과같이모든아키텍쳐에대해어느정도공통적인요소들
빌드과정의이해 (2) 모든아키텍처는아키텍처에종속적인고유빌드정보들을갖는 makefile 을제공해야함 arch/$(arch) 하위디렉토리에있는 makefile 툴 (assembler, compiler, linker 등 ) 에전달되어야하는플래그들 커널빌드시포함시킬하위디렉토리들 이미지가빌드된후수행해야할부가적인작업들 ( 예 ) [kernel]/arch/arm/makefile
빌드과정의이해 (3) 커널 2.4 와 2.6 에서의 kbuild 시스템의주요차이점 커널 2.6 에서는커널설정및빌드를위한별도의프레임워크를가지고있음 커널 2.4 에서는 architecture 에종속적인 makefile 은정해진표준이없었기에, architecture 별로서로다른형식을사용함 커널 2.6 에서는 kbuild framework 에서동일한형태로관리함 소스와 object 파일분리지원 커널 2.4: object 파일은동일한디렉토리에생성 커널 2.6: 소스트리와 object 트리분리가능 make 명령호출시 O=dir 형태의옵션사용. dir 은 object 가위치할 directory
설정과정 (1) 설정파일 Config.in(2.4) vs Kconfig (2.6) 설정파일의내용 커널의모든하위 directory 는별도파일을통해설정규칙정의 예 : 네트워크설정 net/kconfig (cf) net/config.in 설정변수 : 행은 config 로시작되며그뒤에오는이름 설정아이템은 name=value 의형태로저장되며, 아이템이름은 CONFIG_ 로시작되며, 지정된 name 이합쳐지는모양임 설정변수가가질수있는값의목록 bool: y or n tristate: y or n or m string, integer, hexadecimal 도움말 (help 로시작 ) 지정가능하며, 해당변수의이름뒤에저장 변수정의시의존성도정의할수있음
설정과정 (2) Setup 과정에서선택된요소들의목록이 kbuild 시스템에알려주는과정 설정과정에서커널최상위디렉토리에.config 파일생성됨 name=value 의형식으로표현 CONFIG_ARM=y CONFIG_HAVE_PWM=y CONFIG_SYS_SUPPORTS_ARM_EMULATION=y 빌드할소스파일검사과정에서 value 값을사용하여설정 ( 예 ) test.c 인경우 설정과정에서 CONFIG_TEST 라는이름으로처리되고, make config 명령을통해설정과정이이루어지면 [y/n] 의문장이출력됨 Y 라고입력하면해당 makefile 에 obj-$(config_test) += test.o 가추가됨 Kbuild 시스템은소스트리탐색중위문장을만나면 obj-y += test.o 로변환시킴 만일모듈 (module) 로 build 하도록선택한경우에는모듈 build 과정에서 obj-m += test.o 로변환됨 kbuild 시스템은 obj-m 타겟을 build 하기위한규칙도가지고있음
설정과정 (3) include/linux/autoconf.h 커널소스코드내에서도선택된요소들의목록을인식할수있도록되어있음 kbuild 시스템은 name=value 의정보를 include/linux/autoconf.h 파일내의매크로정의로변환함 즉 kbuild 시스템은소스파일에서도어떠한요소들이선택되었는지를알수있는방법을제공함
커널 Makefile 프레임워크 (v2.4) drivers/net/makefile (2.4) obj-y := 커널에직접포함될 object 목록 obj-m := 모듈로빌드될 object 목록 obj-n := 빌드과정에서사용되지않음 obj- := 빌드과정에서사용되지않음 mod-subdirs := appletalk arcnet fc irda wan O_TARGET := net.o 최종결과물 export-objs := 8390.o arlan.o mii.o 모듈에게 symbol을 export( 공개 ) 할수있는파일목록 list-multi := rcpci.o rcpci-obj := rcpci45.o rclanmtl.o ifeq ($(CONFIG_TULIP),y) obj-y += tulip/tulip.o endif subdir-$(config_net_pcmcia) += pcmcia obj-$(config_plip) += plip.o... include $(TOPDIR)/Rules.make clean: rm f core *.o *.a *.s rcpci.o: $(rcpci-objs) $(LD) r o $@ $(rcpci-objs)
커널 Makefile 프레임워크 (v2.6) drivers/net/makefile (2.6) rcpci-objs := rcpci45.o rclanmtl.o ifeq ($(CONFIG_ISDN_PPP), y) obj-$(config_isdn) += slhc.o endif obj-$(config_e1000) += e100 obj-$(config_plip) += plip.o obj-$(config_irda) += irda