Module 10: 디바이스드라이버기초 ESP30076 임베디드시스템프로그래밍 (Embedded System Programming) 조윤석 전산전자공학부
주차별목표 디바이스드라이버란? 디바이스드라이버주요함수알아보기 디바이스드라이버기본골격구성하기 2
디바이스드라이버프로그래밍 리눅스커널의구조 커널프로그래밍 리눅스커널의핵심 (core) 기능추가 리눅스커널알고리즘개선 리눅스커널모듈프로그래밍 ( 또는디바이스드라이버프로그래밍 ) 3
디바이스드라이버란? 디바이스 (device) 하드디스크, USB, 프린터, 단말기, 스캐너, 네트워크어댑터, 터치스크린, 오디오등컴퓨터시스템이외의다른주변장치들을말함 디바이스드라이버 (device driver, 이하 DD 라고도함 ) 위의디바이스들을동작시키기위해서는구동용소프트웨어가필요하며, 이러한프로그램을디바이스드라이버라고함 응용프로그램에서하드웨어장치를이용해서데이터를직접읽고쓰거나제어해야하는경우에 device driver 이용 하드웨어를구동하기위한디바이스뿐만아니라, 소프트웨어적인디바이스를만들어디바이스드라이버를만들수도있음 4
디바이스드라이버의주요특징 주요특징 디바이스와시스템사이에데이터를주고받기위한인터페이스를제공하는커널내부기능중의하나임 일반적으로위쪽으로는파일시스템과인터페이스를가지며, 아래쪽으로는실제디바이스하드웨어와인터페이스를가짐 커널의일부분으로내장되어커널모드에서실행 메모리에상주하면서스왑되지않음 디바이스드라이버는디바이스를하나의파일로추상화시켜줌 이를통해사용자는디바이스를디바이스파일 (/dev/ ) 을통해파일처럼액세스가능 따라서사용자는파일에대한연산 (File Operation) 만하면됨 디바이스의고유한특성을내포하고있음 5
디바이스드라이버의주요특징 디바이스마다고유의번호를가지고있고, 이번호로각각의디바이스를구분 이번호는 32 비트로구성되어있음 ( 커널 2.6) 주번호 (Major number, 12-bit) 부번호 (Minor number, 20-bit) 디바이스드라이버는커널함수로모듈로커널에로딩 디바이스드라이버를만들기위해전체커널을컴파일할필요는없음 모듈방식으로드라이버를추가 / 제거할수있음 % insmod [ 드라이버명 ].ko % rmmod [ 드라이버명 ] 6
커널모듈 모듈 (Module) 리눅스에서디바이스드라이버는모듈로커널에 loading 됨 여러함수와자료구조로이루어진하나의독립된프로그램 설치과정을통해커널에링크되어커널에서실행되는함수역할을함 리눅스모듈은처음커널이시작될때설치되는정적로딩방법과커널이실행되는중간에설치되는동적로딩방법에의해커널에 loading 됨 /proc/modules 현재시스템에설치되어져있는모듈을보여줌 커널모듈빌드 ( 커널 2.4) gcc를사용하여커널모듈을생성함. 모듈은컴파일된오브젝트코드 (.o) 임 커널모듈빌드 ( 커널 2.6) 커널모듈생성시 kbuild를사용함. 컴파일된모듈의확장자는.ko임 커널소스헤더파일지정 -I/usr/src/linux-`uname r`/include 또는 makefile 내에다음과같은커널디렉토리를가르키는변수명을지정하여사용하면편리함 KERNELDIR=/lib/modules/$(shell uname r)/build 위의파일은커널빌드과정에서 % make module_install 을실행했을때생성되며, 커널소스를가르키도록소프트링크되어있음 -I$(KERNELDIR)/include 7
커널과응용프로그램과의차이 Address Space Application Program 과 Kernel Program 은서로다른메모리매핑법을가지고있으며, 프로그램코드는서로다른 address space 를가지고있다. Kernel address space 1 G byte 4 G byte User address space 3 G byte 8
커널과응용프로그램과의차이 Namespace pollution Application Program: 현재개발하는프로그램에서만각함수와변수의이름을구별하여주면된다. Kernel Program: 현재개발하는모듈외에도커널전반적으로함수와변수의이름이충돌하지않도록하여야한다. 9
커널프로그래밍시주의사항 Namespace pollution 외부파일와 link 하지않을모든심볼을 static 으로선언또는외부파일과 link 할 symbol 을 symbol table 등록 EXPORT_NO_SYMBOLS; EXPORT_SYMBOL(name); 전역변수는잘정의된 prefix 를붙여준다. Ex: sys_open() /proc/ksyms Symbol table 을가지고있는텍스트형태의파일 Library stdio.h 와같은일반프로그램에서사용하는헤더파일을 include 해서는안된다. 오직 /usr/include/linux 와 /usr/include/asm 아래에선언된헤더파일만을 include 한다. 10
커널프로그래밍시주의사항 Fault handling Kernel 은하드웨어접근에대해어떠한제한도없기때문에커널에서의에러는시스템에치명적인결과를발생시킨다. 함수호출등의작업시모든에러코드를검사하고처리해야한다. Address space 커널이사용하는 stack 의크기는제한되어있고, 인터럽트핸들러도동일한스택을사용하므로큰배열을사용하거나, recursion 이많이일어나지않도록주의해야한다. 응용프로그램과데이터를주고받기위해 (call by reference) 특별한함수를사용하여야한다. ( 뒷부분에서이를위한몇가지함수를소개한다.) 기타 실수연산이나 MMX 연산을사용할수없다. 11
디바이스구분 문자디바이스 (Character device) 자료의순차성을지닌장치로버퍼를사용하지않고바로읽고쓸수있는장치 직렬포트, 병렬포트, 마우스, PC 스피커, 터미널등 블록디바이스 (Block device) 버퍼캐시 (cache) 를통해블록단위로입출력되며, 랜덤액세스가가능하고, 파일시스템을구축할수있음 플로피디스크, 하드디스크, CD-ROM, RAM 디스크등 네트워크디바이스 (Network device) 네트워크통신을통해네트워크패킷을주고받을수있는디바이스 Ethernet, PPP, ATM, ISDN, NIC (Network Interface Card) 등 12
디바이스드라이버작성하려면 하드웨어에대한분명한이해가있어야함 소프트웨어구조에대한이해 예를들어직렬디바이스 (UART) 에대한 device driver 를작성한다면다음의사항들을분명히알아야함 ( 일부나열 ) UART 는세종류의레지스터를가지고있음 Data registers, control registers, status registers 하나의디바이스주소에하나이상의디바이스레지스터들이있을수있음 디바이스는 control register 의 bit 들을설정함으로써초기화하거나, 설정을변경할수있음 디바이스는 control register 의 bit 들을리셋함으로써 close 하거나리셋을할수있음 13
디바이스드라이버작성하려면 Control register 비트들은 UART 의모든동작을제어할수있음 따라서 control register 의각비트들의목적정확히알아야함 Status register 비트들은디바이스의현재상태 (status) 에대한정보를가지고있고, action 이일어날때마다해당플래그의값들이변경됨 ( 예 )TRH 버퍼레지스터의내용이모든전송된후새로전송할비트가생긴다고할경우, 두상태사이에 transmitter empty flag 의설정이변함 Status register 에서각 status flag 의목적이무엇인지정확히알아야함 각레지스터에대한주소를알아야함 예를들어 IBM PC 의경우 Timer : 0x0040 ~ 0x005F Serial COM1: 0x03F8 ~ 0x03FF Serial COM2: 0x02F8 ~ 0x02FF 14
디바이스드라이버관련리눅스명령어 명령어 insmod rmmod lsmod mknod depmod modinfo modprobe ksyms nm 기능 커널에모듈을올림 ( 예 ) % insmod test_dd.ko 커널에서모듈을삭제함 ( 예 ) % rmmod test_dd 커널에적재된모듈목록보여줌장치특수파일을만듦 ( 예 ) % mknod /dev/test_dd c 253 0 커널모듈간의의존성을검사함모듈들을검사하여관련된정보보여줌 depmod 명령으로생성된종속성정보를이용하여지정된디렉토리의모듈들과연관된모듈들을자동으로로딩함 Export되어있는커널의심볼목록 (/proc/ksyms) 을보여줌오브젝트파일에있는심볼들의목록을보여줌 15
간단한모듈프로그래밍 디바이스드라이버프로그래밍이무엇인지를알아보기위해가장간단한커널프로그래밍해보기 커널에모듈이로딩될때 Loading my first device driver 라는문자열출력되게하기 커널에서모듈을삭제할때 Unloading my first device driver 라는문자열출력하기 16
hello.c % cd ~/work/dd/hello % vi hello.c 17 /* filename: hello.c */ #include <linux/module.h> MODULE_LICENSE("GPL"); int init_module(void) /* module_init() */ { printk("loading my first device driver...\n"); return 0; 모듈초기화성공을의미함 } void cleanup_module(void) /* module_exit() */ { printk("unloading my first device driver..\n"); }
컴파일 % vi Makefile % make obj-m = hello.o #KDIR = /usr/src/linux-headers-2.6.32-38-generic #KDIR = /usr/src/linux-headers-`uname -r` KDIR = /lib/modules/$(shell uname r)/build PWD = $(shell pwd) all: module module: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf *.ko rm -rf *.o rm -rf *.symvers *.order *.mod.o 18
실행 커널에로드하기 % insmod hello.ko 커널에서모듈제거하기 % rmmod hello ( 주의 ) 모듈을제거할때는 hello.o 가아닌 hello 를사용해야함 모듈 load/unload 시메시지가출력되었는가? 19
Warning: loading driver will taint the kernel: no license 문제해결 리눅스커널 2.4 이상버전부터라이센스정의를위한방법을고안하였고, 이러한내용은 linux/module.h 파일에정의되어있고, MODULE_LICENSE() 매크로를통해수행됨 이러한문제를해결하는간단한방법은프로그램의앞부분에다음한줄을추가하기 MODULE_LICENSE( GPL ); 20
커널에서출력하는메시지보기 커널에서출력하는메시지는 console 화면 (standard out) 에나타나지않는다 /var/log/messages 커널에서출력하는메시지를담고있는파일 % tail /var/log/messages 파일의맨뒷부분의내용을출력해줌. Foreground 로동작 % tail f /var/log/messages 계속추가되는모든메시지보기 중단하려면 ctrl+c 를누른다 % tail f /var/log/messages & Background mode 로동작. Messages 파일이변경될때마다그내용을화면에출력해줌. 백그라운드수행을종료하고싶을경우 jobs, kill 이라는명령어를이용하여프로그램종료시킴 dmesg 21
C 프로그램과커널프로그램의차이 [hello.c] #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE( GPL ); int init_module(void) { printk( Loading my first device driver \n ); return 0; } void cleanup_module(void) { printk( Unloading my first device driver!!!\n ); } C 프로그램에서의실행시작점은 main() 함수가없음. 그러나커널프로그램에는 main() 함수가없음. 그렇다면이프로그램은어떻게실행되는것일까? 22
디바이스드라이버구성요소 초기화인터페이스 초기화함수 <linux/init.h> int init_module(void), void cleanup_module(void) 또는 module_init(), module_exit() 디바이스등록및해제함수 등록 : register_mmmdev(), 해제 : unregister_mmmdev() 여기에서 mmm 은 chr, blk, net 파일시스템인터페이스 리눅스에서는모든장치 (device) 들을파일로간주하며, 이파일들은일반적으로 /dev 디렉토리밑에있음. 따라서어떤장치를 open 한다고하는것은파일구조를다루는것과동일 커널에서는디바이스드라이버에액세스하기위해 file_operations 라는구조체를사용함 파일구조체 file_operations 에대한선언은 <linux/fs.h> 에되어있으며, 이구조체의주요멤버로는 open(), release(), read(), write(), ioctl(), mmap() 등임 하드웨어인터페이스 Memory mapped I/O: 메모리연산 Special in/out instruction: in(), out() 23
디바이스등록및해제함수 함수명 int register_chrdev (unsigned int major, const char *nam e,struct file_operations *fops) int unregister_chrdev (unsigned int major, const char *na me) int register_blkdev (unsigned int major, const char *nam e,struct file_operations *fops) int unregister_blkdev (unsigned int major, const char *na me) int register_netdev(const char *name) 설명 문자 (character) 디바이스를주어진주번호로등록주어진주번호에등록되어있는문자디바이스등록을해제블록 (block) 디바이스를주어진주번호로등록주어진주번호에등록되어있는블록디바이스등록을해제네트워크디바이스를등록 int unregister_netdev(const char *name) 네트워크디바이스등록을해제 MAJOR(kdev_t dev) MINOR(kdev_t dev) 장치번호 dev로부터주번호구하기장치번호 dev로부터부번호구하기 24
file 구조체 (/usr/src/linux/include/linux/fs.h) struct file { struct list_head struct dentry struct vfsmount struct file_operations atomic_t unsigned int mode_t loff_t unsigned long struct fown_struct unsigned int int unsigned long f_list; *f_dentry; *f_vfsmnt; *f_op; f_count; f_flags; f_mode; f_pos; f_reada, f_ramax, f_raend, f_ralen, f_rawin; f_owner; f_uid, f_gid; f_error; f_version; /* needed for tty driver, and maybe others */ void *private_data; }; /* preallocated helper kiobuf to speedup O_DIRECT */ struct kiobuf *f_iobuf; long f_iobuf_lock; 25
file_operations 구조체 26 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); };
응용프로그램에서의시스템콜함수와디바이스드라이버함수와의관계 struct file_operations test_fops = { open: device_open, read: device_read, write: device_write, ioctl: device_ioctl, release: device_release, }; struct file_operations test_fops = {.open = device_open,.read = device_read,.write = device_write,.ioctl = device_ioctl,.release = device_release, }; ANSI C99 에서표준으로제공 응용프로그램에서의시스템콜 open() close() read() write() ioctl() 디바이스드라이버에서의실행함수 device_open() device_release() device_read() device_write() device_ioctl() 27
Device Driver 에서사용하는함수들 open() 해당디바이스에연산을가하기위해해당디바이스파일을열기위한함수. 사용수증가 read() 해당디바이스로부터데이터를얻은데이터를커널영역에서사용자영역으로복사하기위한함수 write() 사용자영역의데이터를커널영역으로복사하기위한함수 release() 해당드라이버가응용프로그램에의해닫힐때호출하는함수. 사용수감소 ioctl() 읽기 / 쓰기이외의부가적인연산을위한인터페이스 - 디바이스설정및하드웨어제어 ( 향상된문자드라이버작성가능 ) 28
open() 응용프로그램에서의함수선언 int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); 디바이스드라이버에서의함수선언 int (*open) (struct inode *, struct file *); 응용프로그램에서 open() 함수는시스템콜을통해 sys_open() 을호출하며, sys_open() 내부에서는가상파일시스템 (VFS) 과관련된여러처리과정을통해디바이스드라이버함수에필요한 inode 와파일구조체정보로변환하여전달된다. 그다음단계는 file_operations 구조체의 open() 함수포인터에등록된함수를사용하여요청한파일시스템에맞는연산을수행한다. 응용프로그램에서의함수인자 pathname 은생성하고자하는파일이름 flags 는파일을어떤한모드로 open 할것인지를결정하기위해사용함. 읽기전용 (O_RDONLY), 쓰기전용 (O_WRONLY), 읽기 / 쓰기 (O_RDWR) 모드로열수있음 성공하면해당파일을지시하는 int 형의파일지시자 (file descriptor) 를되돌려줌 29
read() 응용프로그램에서의함수선언 ssize_t read(int fd, void *buf, size_t count); 디바이스드라이버에서의함수선언 ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*read) (struct file *filp, char *buf, size_t count, loff_t *f_pos); 함수인자 응용프로그램에서의 read() 함수는파일기술자인 fd 로부터 count 바이트만큼읽어서그값을 buf 에저장하는시스템콜이다. 성공하면읽어들인바이트크기를반환하며, 실패하면 -1 을반환하고 errno 를설정한다. 디바이스드라이버에서의함수선언중파일구조체포인터 filp 는디바이스파일이어떤형식으로열렸는가에대한정보를가지고있다. loff_t 타입은 64 비트길이의 long offset 으로, (32-bit 플랫폼의경우에도 ) 메모리접근주소를지정하며현재의읽기와쓰기위치를저장한다. 문자디바이스드라이버에서는특성상큰의미가없다. 30
write() 응용프로그램에서의함수선언 ssize_t write (int fd, const char *buf, size_t count); 디바이스드라이버에서의함수선언 ssize_t (*write) (struct file *, const char *, size_t, loff_t *); ssize_t (*write) (struct file *filp, const char *buf, size_t count, loff_t *f_pos); 응용프로그램에서 write() 함수는 buf 로부터 count 바이트만큼읽어서 fd 가가리키는파일에쓰는시스템콜이다. 성공하면쓴바이트크기를반환하고, 실패하면 -1 을반환한다. f_ops 는메모리접근주소를가리킨다. 함수인자 char *buf : 응용프로그램에서전달한버퍼의주소 size_t count : 응용프로그램에서요청한데이터의크기 struct file *filp : 디바이스파일이어떤형식으로열렸는가에대한정보를저장 loff_t *f_pos : 메모리접근주소 31
ioctl() 응용프로그램에서의함수선언 int ioctl (int fd, int request, ); 디바이스드라이버에서의함수선언 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); ioctl() 시스템콜은디바이스를제어하기위해특별한명령을주고자할때사용한다. 예를들어디스크를포맷 ( 읽기 / 쓰기동작아님 ) 하거나직렬포트의통신속도등을설정하는것이다. 이외에도 ioctl() 은시스템콜을추가하지않고커널영역과상호작용하는다양한함수들을개발하는데사용하기도한다. 응용프로그램에서의파일기술자 fd 는해당파일에대한 inode 와 file 구조체로변환되어디바이스드라이버함수로전달된다. 응용프로그램함수선언부분에서세번째인자부분은 으로표시되어있는데이는인자의개수가여러개일수있다는것을의미한다. 시스템콜이성공적으로실행되면음수아닌값을반환하며, 반환된값은요청했을때사용한값과동일하다. 32
release() 응용프로그램에서의함수선언 int close(int fd); 디바이스드라이버에서의함수선언 int (*release) (struct inode *, struct file *); 시스템콜 close() 함수는파일기술자를닫을때사용한다. 응용프로그램에서사용자에의해 close() 함수가호출되면디바이스드라이버내에서는해당파일구조체내에선언된 release() 함수가실행된다. 참고 : Note that release isn t invoked every time a process calls close. Like open, release can be missing. Whenever a file structure is shared (for example, after a fork or a dup), release won t be invoked until all copies are closed. If you need to flush pending data when any copy is closed, you should implement the flush method. 33
커널인터페이스함수 주의사항 Kernel program 은일반적인 library 를사용하지못하고 kernel 에서 export 해준함수들만을사용할수있다. Kernel interface 함수분류 Kernel 에서제공하는함수중 kernel programming 에자주사용되는함수는다음과같이분류할수있다. Port I/O Interrupt Memory Synchronization Kernel message 출력 Device Driver register 34
커널인터페이스함수 : 입출력디바이스 I/O device 와 data 를주고받기위한함수들 unsigned inb(unsigned port) Port 에서 1byte 를읽는다. unsigned inw(unsigned port) Port 에서 2byte 를읽는다. unsigned inl(unsigned port) Port 에서 4byte 를읽는다. unsigned outb(char value, unsigned port) Port 에 1byte value 를쓴다. unsigned outw(short int value, unsigned port) Port 에 2byte value 를쓴다. unsigned outl(long int value, unsigned port) Port 에 4byte value 를쓴다 35
커널인터페이스함수 : 입출력디바이스 I/O device 와 data 를주고받기위한함수들 void insb(unsigned port, void *addr, unsigned long count) Port 에서 count bytes 를읽어서메모리의 addr 주소부터저장 void insw(unsigned port, void *addr, unsigned long count) Port 에서 16bit * count 만큼읽어서메모리의 addr 주소부터저장 void insl(unsigned port, void *addr, unsigned long count) Port 에서 32bit * count 만큼읽어서메모리의 addr 주소부터저장 void outsb(unsigned port, void *addr, unsigned long count) Memory 의 addr 번지에서부터 count bytes 를읽어서 port 에쓴다. void outsw(unsigned port, void *addr, unsigned long count) Memory 의 addr 번지에서부터 count * 16bit 를읽어서 port 에쓴다. void outsl(unsigned port, void *addr, unsigned long count) Memory 의 addr 번지에서부터 count * 32bit 를읽어서 port 에쓴다. 36
커널인터페이스함수 : 입출력디바이스 I/O device 와 data 를주고받기위한함수들 Pausing I/O 입출력이너무빠르면 device 에서처리할수없는경우가발생할수있기때문에한번의입출력후잠시멈추어줄수있다. 앞에설명한함수의이름뒤에 _p 를붙인이름의함수로구현되어있다. 예 ) inb() 함수의경우 inb_p() 37
커널인터페이스함수 : 인터럽트설정 인터럽트의설정및처리에관한함수 (or 매크로 ) cli()/sti() 전체인터럽트를금지하거나가능하게해주는매크로 (clear/set interrupt enable) save_flags(unsigned long flag), restore_flags(unsigned long flag) 상태레지스터 (status register) 의내용을저장하고복원 Status register 는시스템의각상태를가지고있는레지스터로서, 일반적으로인터럽트가 enable 상태인지또는직전연산에서올림수 (carry) 가발생했는지등에대한정보를가지고있다 save_flags(), restore_flags() 두매크로는같은함수안에서호출되어야한다. flag 를다른함수로 pass 해서는안된다. 38
커널인터페이스함수 : 인터럽트설정 인터럽트의설정및처리에관한함수 (or 매크로 ) int request_irq(unsigned int irq, void (*handler)(int),unsigned long flags, const char *device) 사용자가인터럽트를추가하고자할때사용 커널에현재사용하고있지않은 IRQ 를요청하여등록하고, 이러한인터럽트가발생했을때수행될이 IRQ 에대한 interrupt handler 함수 ( 또는 interrupt service routine) 를등록 void free_irq(unsigned int irq) request_irq() 에서획득한 irq 를반납함 39
커널인터페이스함수 : 인터럽트설정 동기화 void sleep_on(struct wait_queue **q) q 의번지를 event 로 sleep 하며, uninterruptible 여기에서 uninterruptible 은 wake_up 함수호출외의다른 signal 에의해 process 가깨어날수없음을의미 void sleep_in_interruptible(struct wait_queue **q) q 의번지를 event 로 sleep 하며, interruptible Interruptible 은 wake_up 함수가호출되지않아도임의로 process 에서 signal 을주어깨어나게할수있다는의미 void wake_up(struct wait_queue **q) sleep_on(q) 로 sleep 된 task 를 wakeup void wake_up_interruptible(struct wait_queue **q) sleep_on_interruptible(q) 로 sleep 된 task 를 wakeup 40
커널인터페이스함수 : 메모리할당 Kernel 에서동적메모리를할당할때사용하는함수들 void * kmalloc(unsigned int len, int priority) 커널메모리할당. 128~131056byte 까지가능 priority: GFP_KERNEL, GFP_BUFFER, GFP_ATOMIC, GFP_USER GFP_KERNEL : 일반적인커널메모리할당. 할당가능한 Memory 가부족할경우 sleep 할수도있다. GFP_BUFFER : 버퍼캐쉬를관리할때사용되므로할당자가 sleep 상태로갈수있다. I/O 서브시스템이스스로메모리를필요로할때데드락을피하도록하기위해디스크에 dirty page 를 disk 에플러쉬함으로서메모리를 free 한다는점에서 GFP_KERNEL 과다르다. GFP_ATOMIC : 인터럽트핸들러등프로세스컨텍스트외부코드에서메모리를할당할때사용한다. 결코 sleep 상태가되지않는다. GFP_USER : 사용자들에게메모리할당할때사용. 낮은우선순위를가진다. GFP_HIGHUSER High memory 에서할당할때사용한다. 물리적으로연속적인메모리를할당한다. void kfree(void *obj) kmalloc() 에서할당받은커널메모리를반납 41
커널인터페이스함수 : 메모리할당 Kernel 에서동적메모리를할당할때사용하는함수들 void * vmalloc(unsigned int len) 커널메모리할당 크기제한없음 가상주소공간에서연속적인메모리영역을할당 void vmfree(void *addr) vmalloc() 에서할당받은커널메모리를반납 42
커널인터페이스함수 : 데이터공유 사용자공간과커널공간사이에데이터를공유하기위한함수 unsigned long copy_from_user(void *to, const void *from, unsigned long n) 사용자주소공간에서 n byte 만큼 data 복사. unsigned long copy_to_user(void *to, const void *from, unsigned long n) 사용자주소공간에 n byte 만큼 data 복사 void * memset(void *s, char c, size_t count) 메모리 s 에 c 를 count 만큼복사 put_user(void *datum, const void *addr) 사용자공간에 datum 을전달 get_user(void *datum, const void *addr) 사용자공간의 datum 을커널영역으로전달 43
커널인터페이스함수 : 메시지출력 Standard out 으로메시지를출력하기위한함수 printk(const char *fmt,.) 예 printf 의커널버전 printk(log_level message) LOG_LEVEL» KERN_EMERG, KERN_ALERT, KERN_ERR,KERN_WARNING, KERN_INFO, KERN_DEBUG» console_loglevel 의값보다우선순위가낮다면 console 에출력되지않는다. console_loglevel 은 sys_syslog 시스템콜로값을바꿀수있다.» LOG_LEVEL 은 <include/linux/kernel.h> 헤더에정의되어있다. printk( <1>Hello, World ); printk(kern_warning warning \n ); 44
커널인터페이스함수 : 디바이스등록 / 해제 디바이스드라이버등록및해제함수 int register_xxxdev (unsigned int major, const char *name,struct file_operations *fops) character/block driver 를 xxxdev[major] 에등록 xxx: chr/blk int unregister_xxxdev (unsigned int major, const char *name) xxxdevs[major] 에등록되있는 device driver 를제거 int register_netdev(const char *name) int unregister_netdev(const char *name) MAJOR(kdev_t dev)/minor(kdev_t dev) 장치번호 dev 로부터 major/minor 번호를구함 45
Device Driver 에서주로사용하는헤더파일 #include <linux/kernel.h> printk() 에서사용되는 LOG_LEVEL 에대한정의 ( 예 : KERNEL_ALERT) #include <linux/module.h> MODULE_LICENSE( GPL ); #include <linux/init.h> module_init(), module_exit(), init, exit #include <linux/fs.h> struct file_operations #include <linux/fcntl.h> 파일제어관련 ( 예 : O_RDWR, O_RDONLY, O_WRONLY) #include <linux/errno.h> 에러에관한헤더파일 ( 에러번호관리 ) 46
커널에모듈올리기 / 내리기 커널에제작한디바이스드라이버올리기 % insmod 드라이버명.ko 예 ) insmod led_dd.ko init_module() 또는 module_init() 함수실행 디바이스드라이버삭제 % rmmod 드라이버명 예 )rmmod led_dd cleanup_module() 또는 module_exit() 함수실행 적재된디바이스드라이버목록보기 % lsmod 47
디바이스접근용노드파일생성 커널에적재된디바이스드라이버에사용자가접근할수있도록스페셜디바이스노드생성 일반적으로 /dev 밑에만듬 사용자는이파일을통해해당디바이스에액세스함 노드생성하기 % mknod /dev/ 파일이름드라이버특성주번호부번호 예 ) mknod /dev/iom_led c 245 0 생성후속성변경 : chmod ug+w /dev/iom_led 48
디바이스드라이버에연결된장치파일 % ls -l /dev/hd[ab][12] /dev/tty[01] /dev/fb[01] brw-rw---- 1 root disk 3, 1 1월 30 2003 /dev/hda1 brw-rw---- 1 root disk 3, 2 1월 30 2003 /dev/hda2 brw-rw---- 1 root disk 3, 65 1월 30 2003 /dev/hdb1 brw-rw---- 1 root disk 3, 66 1월 30 2003 /dev/hdb2 crw--w---- 1 root root 4, 0 1월 30 2003 /dev/tty0 crw------- 1 root root 4, 1 9월 25 13:44 /dev/tty1 crw------- 1 root root 29, 0 1월 30 2003 /dev/fb0 crw------- 1 root root 29, 1 1월 30 2003 /dev/fb1 디바이스종류 주번호 (major number) 부번호 (minor number) 장치파일명 49
디바이스번호 각디바이스들은주번호 (major number) 와부번호 (minor number) 로구성된고유의 32-bit 번호를가짐 Major number (12-bit) 물리적인디바이스를구분하는데사용 [KERNEL_DIR]/include/linux/major.h 헤더파일에정의되어있음 Minor number (20-bit) 부번호는동일한디바이스가여러개인경우, 이들디바이스들을구분하는용도로사용함. 즉같은종류의디바이스의경우주번호는같고, 부번호를다르게할당 부번호는커널에서사용하지않고디바이스드라이버내에서디바이스를구분하기위해사용 커널 2.6 typedef u32 kernel_dev_t; typedef kernel_dev_t dev_t; /* 32 비트 */ 디바이스번호에 32 비트를할당하며, 주번호는 12 비트로, 부번호는 20 비트로표현함. 따라서커널 2.6 에서는 4096 개의주번호를사용할수있음 50
[KERNEL]/include/linux/major.h #define MAX_CHRDEV 255 #define MAX_BLKDEV 255 #define UNNAMED_MAJOR 0 #define MEM_MAJOR 1 #define RAMDISK_MAJOR 1 #define FLOPPY_MAJOR 2 #define PTY_MASTER_MAJOR 2 #define IDE0_MAJOR 3 #define PTY_SLAVE_MAJOR 3 #define HD_MAJOR IDE0_MAJOR #define TTY_MAJOR 4 51
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() 52
디바이스드라이버호출 응용프로그램 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 53
디바이스드라이버의기본골격 (1) % vi /root/work/dd/basic_device.c /* basic_device.c */ #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/module.h> #define IOM_MYDEVICE_MAJOR_NUM 0 #define DEV_NAME "/dev/mydevice" MODULE_LICENSE("GPL"); MODULE_AUTHOR("HGU"); int mydevice_init(void); void mydevice_exit(void); module_init(mydevice_init); module_exit(mydevice_exit); int mydevice_open (struct inode *, struct file *); int mydevice_release (struct inode *, struct file *); ssize_t mydevice_read (struct file *, char user *, size_t, loff_t *); ssize_t mydevice_write (struct file *, const char user *, size_t, loff_t *); int mydevice_ioctl (struct inode *, struct file *, unsigned int, unsigned long); 54
디바이스드라이버의기본골격 (2) struct file_operations mydevice_fops = {.owner = THIS_MODULE,.open = mydevice_open,.release = mydevice_release,.read = mydevice_read,.write = mydevice_write,.ioctl = mydevice_ioctl, }; int mydevice_open (struct inode *inode, struct file *filp) { return 0; } int device_release (struct inode *inode, struct file *filp) { return 0; } ssize_t device_read (struct file *filp, char user *buf, size_t count, loff_t *f_pos) { return 0; } 55
디바이스드라이버의기본골격 (3) 56 ssize_t device_write (struct file *filp, const char user *buf, size_t count, loff_t *f_pos) { return 0; } int device_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { return 0; } int init mydevice_init(void) { int major_num; major_num = register_chrdev(iom_mydevice_major_num, DEV_NAME, &mydevice_fops); if ( major_num < 0 ) { printk(kern_warning"%s: can't get or assign major number %d\n", DEV_NAME, IOM_MYDEVICE_MAJOR_NUM ); return major_num; } printk("success to load the device %s. Major number is %d\n", DEV_NAME, IOM_MYDEVICE_MAJOR_NUM ); return 0; } void exit mydevice_exit(void) { unregister_chrdev(iom_mydevice_major_num, DEV_NAME); printk("success to unload the device %s...\n", DEV_NAME); }
장치등록과해제 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) 57