Chapter 6.1 Advanced Char Driver Operations ioctl 블로킹 I/O 비블록킹동작 scullpipe 구현예 순천향대학교컴퓨터학부이상정 1 ioctl 순천향대학교컴퓨터학부이상정 2
하드웨어제어 디바이스드라이버는읽기쓰기외에하드웨어제어도수행 하드웨어제어방식 write 메쏘드를사용한제어명령의특수한시퀀스로제어방식 콘솔드라이버에서사용되는데이스케이프시퀀스 (escape sequence) 문자를쓰기하여커서이동, 색변경및다른설정태스크등을수행 제어명령을위해예약된일부문자들은데이터로사용할수없다는단점 ANSI 터미널이스케이프시퀀스예 x1b[#;#;..; 명령문자 로표시, 1B는 <ESC> 의 ASCII 코드 x1b[3a : 커서를위로 3행이동 x1b[2c : 커서를오른쪽으로 3칸이동 ioctl 메쏘드를사용하여하드웨어의제어를수행 순천향대학교컴퓨터학부이상정 3 ioctl 시스템콜 사용자영역에서 iotcl 함수시스템콜 int ioctl(int fd, int cmd,...); 커널의 ioctl 메쏘드 int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); inode 와 filp 포인터는응용프로그램의파일디스크립터 fd 와일치하는인수 cmd 인수는명령을나타내는응용프로그램의인수전달 arg 인수는명령실행의결과데이터가전달되는 unsigned long 형의정수또는포인터 대부분의 ioctl 메쏘드구현은 cmd 인수값에따라올바른동작을선택하는 switch 문으로구성 순천향대학교컴퓨터학부이상정 4
명령 cmd 상수값 (1) ioctl 구현시해당디바이스의명령들에대한고유의상수값을지정 include/asm/ioctl.h 정의 타입, 시퀀스번호, 전송방향, 인수의크기등을표시하는비트필드로표현 비트필드의조합으로고유한상수의명령표시 타입 (type) 서로중복되지않는 8 비트의고유번호 ( 매직넘버 ) 시퀀스번호 (sequence number) 8 비트크기의시퀀스번호 방향 (direction) 명령의 read,write 등데이터의전송방향을표시 크기 (size) 사용자데이터의크기 순천향대학교컴퓨터학부이상정 5 명령 cmd 상수값 (2) 명령의상수값지정을위한매크로 <linux/ioctl.h> 헤더파일에정의 _IO(type,nr) _IOR(type, nr, dataitem) _IOW(type, nr, dataitem) _IOWR(type. nr, dataitem) type 은매직넘버, nr 은시퀀스번호, dataitem 의전송되는데이터형으로 sizeof 를적용하여크기를추출 순천향대학교컴퓨터학부이상정 6
관련코드 : scull.h (1) #include <linux/ioctl.h> /* needed for the _IOW etc stuff used later */ /* Use 'k' as magic number */ #define SCULL_IOC_MAGIC 'k #define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0) /* * S means "Set" through a ptr, * T means "Tell" directly with the argument value * G means "Get": reply by setting through a pointer * Q means "Query": response is on the return value * X means "exchange": switch G and S atomically * H means "shift": switch T and Q atomically */ #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int) #define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int) #define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3) #define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4) #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int) #define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int) #define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7) #define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8) 순천향대학교컴퓨터학부이상정 7 관련코드 : scull.h (2) #define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int) #define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11) #define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) /* * The other entities only have "Tell" and "Query", because they're * not printed in the book, and there's no need to have all six. * (The previous stuff was only there to show different ways to do it. */ #define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC, 13) #define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC, 14) /*... more to come */ #define SCULL_IOC_MAXNR 14 순천향대학교컴퓨터학부이상정 8
scull 명령종류 quantum(4000), qset(1000) 값을읽거나새로운값으로지정 Set: arg 의포인터를사용하여새로운값지정 Tell: arg의값으로직접새로운값지정 Get: arg 포인터를사용하여읽음 Query: 리턴값으로읽음 exchange: arg 포인터를사용하여새로운값을지정하고이전값읽음, Set + Get shift: arg 값으로지정하고, 이전값을리턴하여읽음, Tell + Query 순천향대학교컴퓨터학부이상정 9 사용자프로그램의시스템콜 #include <stdio.h> #include <fcntl.h> // O_RDONLY #include <linux/ioctl.h> #define SCULL_IOC_MAGIC 'k' #define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int) #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int) #define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8) #define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) void main() int fd; int quantum = 0, qset = 500, val; fd = open("/dev/scull1", O_RDONLY); ioctl(fd, SCULL_IOCGQUANTUM, &quantum); printf("quantum: %d n", quantum); val = ioctl(fd, SCULL_IOCHQSET, qset); printf("old qset: %d n", val); # cc o myioctl myioctl.c #./myioctl quantum: 4000 old qset: 1000 new qset: 500 # val = ioctl(fd, SCULL_IOCQQSET); printf("new qset: %d n", val); // restore qset qset = 1000; ioctl(fd, SCULL_IOCSQSET, &qset); 순천향대학교 컴퓨터학부이상정 10
관련코드 : scull.c (1) int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) int err = 0, tmp; int retval = 0; /* * extract the type and number bitfields, and don't decode * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() */ if (_IOC_TYPE(cmd)!= SCULL_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY; /* * the direction is a bitmask, and VERIFY_WRITE catches R/W * transfers. `Type' is user-oriented, while * access_ok is kernel-oriented, so the concept of "read" and * "write" is reversed */ if (_IOC_DIR(cmd) & _IOC_READ) err =!access_ok(verify_write, (void user *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err =!access_ok(verify_read, (void user *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; switch(cmd) case SCULL_IOCRESET: scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; case SCULL_IOCSQUANTUM: /* Set: arg points to the value */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; retval = get_user(scull_quantum, (int user *)arg); break; 순천향대학교컴퓨터학부이상정 11 관련코드 : scull.c (2) case SCULL_IOCTQUANTUM: /* Tell: arg is the value */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; scull_quantum = arg; break; case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */ retval = put_user(scull_quantum, (int user *)arg); break; case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */ return scull_quantum; case SCULL_IOCXQUANTUM: /* exchange: use arg as pointer */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; retval = get_user(scull_quantum, (int user *)arg); if (retval == 0) retval = put_user(tmp, (int user *)arg); break; case SCULL_IOCHQUANTUM: /* shift: like Tell + Query */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; scull_quantum = arg; return tmp; case SCULL_IOCSQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; retval = get_user(scull_qset, (int user *)arg); break; case SCULL_IOCTQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; scull_qset = arg; break; 순천향대학교컴퓨터학부이상정 12
관련코드 : scull.c (3) case SCULL_IOCGQSET: retval = put_user(scull_qset, (int user *)arg); break; case SCULL_IOCQQSET: return scull_qset; case SCULL_IOCXQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_qset; retval = get_user(scull_qset, (int user *)arg); if (retval == 0) retval = put_user(tmp, (int user *)arg); break; case SCULL_IOCHQSET: if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_qset; scull_qset = arg; return tmp; /* * The following two change the buffer size for scullpipe. * The scullpipe device uses this same ioctl method, just to * write less code. Actually, it's the same driver, isn't it? */ case SCULL_P_IOCTSIZE: scull_p_buffer = arg; break; case SCULL_P_IOCQSIZE: return scull_p_buffer; default: /* redundant, as cmd was checked against MAXNR */ return -ENOTTY; return retval; 순천향대학교컴퓨터학부이상정 13 ioctl 인수체크 ioctl 의 arg 인수포인터가사용자영역을가리킬때는사용자주소가유효하고해당페이지가현재매핑되어있는지의확인필요 커널코드가범위를벗어난주소를참조하면프로세서는예외상황 (exception) 을이슈 커널 2.2 이상에서주소의확인은 <asm/uaccess.h> 에선언된 access_ok 함수를사용 int access_ok(int type, const void *addr, unsigned long size); type 인수는사용자영역메모리에쓰기, 읽기여부를나타내는 VERIFY_READ, VERIFY_WRITE 등이기술 읽기 / 쓰기모두사용하는경우 VERIFY_WRITE 를기술 addr 인수는사용자영역주소 size 는동작되는바이트크기 참조가유효하면 1 을리턴하고, 실패이면 0 을리턴, 0 을리턴하는경우드라이버는호출한프로그램에 EFAULT 를리턴 순천향대학교컴퓨터학부이상정 14
데이터전송함수 access_ok 호출후에드라이버는안전하게실제데이터전송을수행 copy_from_user, copy_to_user 함수외에데이터크기에따라최적화된다음의함수들을사용 (<asm/uaccess.h> 에정의 ) put_user(datum, ptr), put_user(datum, ptr) 사용자영역에데이터를쓰기를수행하는매크로로단일값을전송하여 copy_to_user 보다도빠름 전송되는데이터의크기는 ptr 인수의타입에따라다르다. 예를들어 ptr 이 char 포인터이면한바이트가전송 put_user 는쓰기할메모리영역을체크 (access_ok 를호출 ) 하고성공하면 0, 에러이면 EFAULT 를리턴 put_user 는메모리영역이가용한지체크하지않음 get_user(local,ptr), get_user(local,ptr) 사용자영역에서데이터하나를읽는매크로 읽은값은국소변수 local 에저장 마찬가지로 get_user 는주소를체크하지않으므로주소가이미 access_ok 로검사된경우에사용 순천향대학교컴퓨터학부이상정 15 권한설정 (capabilities) (1) 2.2 이상의리눅스커널은보다유연한권한설정기능을제공하여각오퍼레이션단위로사용권한을설정 설정되는권한의종류는 <linux/capability.h> 에정의 CAP_DAC_OVERRIDE 파일이나디렉토리상의액세스를오버라이드 (override) 하는권한 CAP_NET_ADMIN 네트워크인터페이스에영향을주는것들을포함하여네트워크관리태스크를수행하는권한 CAP_SYS_MODULE 커널모듈을로드또는제거할수있는권한 CAP_SYS_RAWIO raw I/O 동작을수행할수있는권한. 예를들어디바이스포트에의접근또는 USB 디바이스들과직접통신등 순천향대학교컴퓨터학부이상정 16
권한설정 (capabilities) (2) CAP_SYS_ADMIN 많은시스템관리동작에로의접근을제공하는강력한 (catch-all) 권한 CAP_SYS_TTY_CONFIG tty 구성태스크 (configuration task) 를수행하는권한 디바이스드라이버는다음과같은 capable 함수를사용하여호출하는프로세스가해당하는권한이설정되었는가를체크 (<sys/sched.h> 에정의 ) int capable(int capability); scull 샘플드라이버에서사용자는퀀텀과퀀텀세트의크기를문의 권한이설정된사용자만이이값들을변경 ioctl 의 scull 코드에서는필요하면사용자의권한수준을다음과같이체크 if (! capable (CAP_SYS_ADMIN)) return -EPERM; 순천향대학교컴퓨터학부이상정 17 블로킹 I/O (Blocking I/O) 순천향대학교컴퓨터학부이상정 18
블로킹 I/O 개요 읽기동작시읽을데이터가도착하지않는경우프로세스는데이터를기다리기위해수면 (sleep) 후나중에깨어나야 (wake up) 한다. 수면 (sleep) 프로세스가이벤트 ( 데이터의도착또는프로세스의종료 ) 를위해기다려야만할때마다프로세스는수면 수면은프로세스가실행을중지하고다른사용을위해프로세서의사용을해제 (release) 깨움 (wake up) 이후기다리던이벤트가발생하면프로세스는깨어나서후속작업을계속수행 순천향대학교컴퓨터학부이상정 19 대기큐 (wait queue) 대기큐 (wait queue) 는이벤트를위해대기하고있는프로세스들의큐 대기큐헤드 (wait queue head) 에의해관리 수면및깨움은대기큐상에서발생 대기큐타입은 wait_queue_head_t 하나의잠금변수 (lock variable) 와수면중인프로세스들의링크리스트로구성 대기큐선언및초기화 #include <linux/wait.h> wait_queue_head_t my_queue; init_waitqueue_head (&my_queue); DECLARE_WAIT_QUEUE_HEAD (my_queue); 순천향대학교컴퓨터학부이상정 20
수면관련함수 수면관련함수 void wait_event(wait_queue_head_t queue, int condition); int wait_event_interruptible(wait_queue_head_t queue, int condition); int wait_event_timeout(wait_queue_head_t queue, int condition, int time); int wait_event_interruptible_timeout(wait_queue_head_t queue, int condition, int time); 조건 condition 이참이될때까지수면 interruptible 인경우시그널에의한인터럽트를받아들임 timeout 인경우지정된시간동안만기다리고, 시간이만료되면 0 을리턴 시간은 jiffies 로표현 (7 장 ) 함수가성공한경우 0 을, 루프가시그널에의한인터럽트로실패한경우 ERESTARTSYS 를리턴 순천향대학교컴퓨터학부이상정 21 깨움관련함수 수면후에이벤트가발생하면드라이버는프로세스를깨어야하고대개인터럽트핸들러에서수면중인프로세스를깨움 깨움관련함수 wake_up(wait_queue_head_t *queue); wake_up_interruptible(wait_queue_head_t *queue); 해당이벤크큐상에서기다리고있는모든프로세스를깨움 wiait_event_interruptibe() 함수호출로수면중인경우 wake_up_interruptible() 함수를사용하여깨움 순천향대학교컴퓨터학부이상정 22
sleepy 샘플코드예 sleepy 샘플코드 소스 : source/misc-modules/sleepy.c 대기큐사용예로한사용자가디바이스로부터읽으려고할때프로세스가수면에들어가고, 다른사용자가디바이스에쓰기를하면앞의프로세스를깨우는코드 Makefile obj-m := sleepy.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 순천향대학교컴퓨터학부이상정 23 sleepy 실행예 # make # insmod sleepy.ko # lsmod Module Size Used by sleepy 2440 0 scull 13368 0 PhaseDvfs 15316 0 SetDvfs 2900 0 # more /proc/devoces 162 raw 180 usb 254 sleepy # mknod /dev/sleepy c 254 0 # ls l /dev/sleepy crw-r--r-- 1 root root 254, 0 11월 25 16:43 /dev/sleepy # cat /dev/sleepy > test.out & => 디바이스읽기, 수면진입 [1] 32355 # ls > /dev/sleepy => 디바이스쓰기, 깨움 [1]+ Done cat /dev/sleepy >test.out 순천향대학교컴퓨터학부이상정 24
sleepy.c 코드.. #include <linux/wait.h> static int sleepy_major = 0; static DECLARE_WAIT_QUEUE_HEAD(wq); static int flag = 0; ssize_t sleepy_read (struct file *filp, char user *buf, size_t count, loff_t *pos) printk(kern_debug "process %i (%s) going to sleep n", current->pid, current->comm); wait_event_interruptible(wq, flag!= 0); flag = 0; printk(kern_debug "awoken %i (%s) n", current->pid, current->comm); return 0; /* EOF */ ssize_t sleepy_write (struct file *filp, const char user *buf, size_t count, loff_t *pos) printk(kern_debug "process %i (%s) awakening the readers... n", current->pid, current->comm); flag = 1; wake_up_interruptible(&wq); return count; /* succeed, to avoid retrial */ 순천향대학교컴퓨터학부이상정 25 struct file_operations sleepy_fops =.owner = THIS_MODULE,.read = sleepy_read,.write = sleepy_write, ; int sleepy_init(void) int result; /* * Register your major, and accept a dynamic number */ result = register_chrdev(sleepy_major, "sleepy", &sleepy_fops); if (result < 0) return result; if (sleepy_major == 0) sleepy_major = result; /* dynamic */ return 0; void sleepy_cleanup(void) unregister_chrdev(sleepy_major, "sleepy"); module_init(sleepy_init); module_exit(sleepy_cleanup); 비블록킹동작 (nonblocking operation) 순천향대학교컴퓨터학부이상정 26
블로킹동작 (blocking operation) 커널의 read, write 메쏘드는디폴트로블로킹으로동작 읽기블로킹 프로세스가 read 를호출하고데이터가아직도착하지않았을때프로세스는블록킹 데이터가도착하자마자프로세스가깨어나고, 요청된 count 크기보다작은데이터가도착해도리턴 쓰기블로킹 프로세스가 write 를호출하고버퍼에저장할공간이없는경우프로세스는블로킹 일부데이터가 ( 버퍼에서 ) 하드웨어디바이스로쓰기하여출력버퍼에저장할일부공간만생기게되어도프로세스는깨어나서 write 호출을계속수행 순천향대학교컴퓨터학부이상정 27 비블로킹동작 (nonblocking operation) flip->f_flags에 O_NONBLOCK 플래그를세팅하면비블로킹으로동작 비블로킹 read와 write 동작 데이터가도착하지않았을때프로세스가 read를호출하거나버퍼가차있을때 write를호출하면단순히 EAGAIN을리턴 즉, 비블록킹오퍼레이션들은즉시리턴하여응용에서데이터가용여부를조사 (poll) O_NONBLOCK 은 open 메쏘드에도의미 아직쓰기가일어나지않은 FIFO 나보류되어잠금된 (pending lock) 디스크접근을오픈하는경우외부이벤트를기다리지않고바로리턴 순천향대학교컴퓨터학부이상정 28
nbtest 샘플코드예 (1) nbtest 샘플코드 소스 : source/misc-progs/nbtest.c 비블록킹테스트를위해비블록킹 I/O 와재시도간의지연을통해단순히입력을출력으로복사하는프로그램 명령행인수로지연시간을기술하고디폴트값은 1 초 순천향대학교컴퓨터학부이상정 29 nbtest 샘플코드예 (2) #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <errno.h> char buffer[4096]; int main(int argc, char **argv) int delay = 1, n, m = 0; if (argc > 1) delay=atoi(argv[1]); fcntl(0, F_SETFL, fcntl(0,f_getfl) O_NONBLOCK); /* stdin */ fcntl(1, F_SETFL, fcntl(1,f_getfl) O_NONBLOCK); /* stdout */ while (1) n = read(0, buffer, 4096); if (n >= 0) m = write(1, buffer, n); if ((n < 0 m < 0) && (errno!= EAGAIN)) break; sleep(delay); perror(n < 0? "stdin" : "stdout"); exit(1); 순천향대학교컴퓨터학부이상정 30
nbtest 샘플코드예 (3) 실행 $ cc nbtest.c -o nbtest $./nbtest 10 1234567 => 입력 1234567 => 10 초후에출력 결과설명 비블록킹 read 에서입력기다리지않고바로 sleep 을실행하므로 10 초기다림 데이터가도착하지않았을때 read 가호출되면 EAGAIN 을리턴하므로루프를빠져나가지않음 만약위의 fnctl 함수를코멘트처리하여블로킹입출력으로하면 read 가데이터를기다린후도착하면바로출력하므로 10 초기다리지않고바로출력한후 sleep 을실행 순천향대학교컴퓨터학부이상정 31 scullpipe 구현예 순천향대학교컴퓨터학부이상정 32
scullpipe 소개 일반적인드라이버동작 read 호출로블로킹된프로세스는데이터가도착하면깨어남 일반적으로하드웨어가이벤트신호를주기위해인터럽트를이슈하고, 드라이버는인터럽트를처리하는과정에서대기하고있는프로세스들을깨움 scull 드라이버는특정하드웨어나인터럽트처리기없이실행되어야하기때문에위와다르게동작하도록작성 /dev/scullpipe0-4 디바이스들은 scull 모듈의일부로블로킹 I/O 구현한예 read 시버퍼에데이터가없으면수면후버퍼에 write 되면깨어나읽기진행 write 시버퍼에데이터가입력하고수면중인프로세스깨움 결과적으로 FIFO( 또는파이프 ) 와같은기능 소스 : source/scull/pipe.c Makefile scull_load scull_unload scull.h main.c 순천향대학교컴퓨터학부이상정 33 scullpipe 실행예 Terminal 1 # cd scull # make # sh scull_load <- scull 등록, /dev/scull0-3 생성 # cat /proc/devices Character devices: 1 mem 2 pty 3 ttyp 210 phase 211 setdvfs 254 scull 254 scullp 254 sculla # mknod /dev/scullpipe0 c 254 4 # mknod /dev/scullpipe1 c 254 5 # mknod /dev/scullpipe2 c 254 6 # mknod /dev/scullpipe3 c 254 7 # ls > /dev/scullpipe0 <- 2. 디바이스쓰기, 1 의수면 1 을깨움 순천향대학교컴퓨터학부이상정 34 Terminal 2 # cat /dev/scullpipe0 & <- 1. 디바이스읽기, 수면 1 에들어감 [2] 15619 access.c access.o main.c main.o myioctl <- 3. 2 의쓰기로깨어나 1 의 cat 실행 # cat /dev/scullpipe2 > test.out & <- 4. 디바이스읽기, 수면 2 [3] 15622 # ls -x.. > /dev/scullpipe2 <- 5. 디바이스쓰기, 4 의수면 2 깨워서파일에출력 # cat test.out access.c access.o main.c
scull_pipe 등록코드 : main.c int scull_init_module(void) if (scull_major) dev = MKDEV(scull_major, scull_minor); result = register_chrdev_region(dev, scull_nr_devs, "scull"); else result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); scull_major = MAJOR(dev); /* Initialize each device. */ for (i = 0; i < scull_nr_devs; i++) scull_devices[i].quantum = scull_quantum; scull_devices[i].qset = scull_qset; init_mutex(&scull_devices[i].sem); scull_setup_cdev(&scull_devices[i], i); /* At this point call the init function for any friend device */ dev = MKDEV(scull_major, scull_minor + scull_nr_devs); dev += scull_p_init(dev); dev += scull_access_init(dev); return result; 순천향대학교컴퓨터학부이상정 35 scull_pipe 등록코드 : pipe.c static int scull_p_nr_devs = SCULL_P_NR_DEVS; /* number of pipe devices */ struct file_operations scull_pipe_fops =.owner = THIS_MODULE,.llseek = no_llseek,.read = scull_p_read,.write = scull_p_write,.poll = scull_p_poll,.ioctl = scull_ioctl,.open = scull_p_open,.release = scull_p_release,.fasync = scull_p_fasync, ; static void scull_p_setup_cdev(struct scull_pipe *dev, int index) int err, devno = scull_p_devno + index; cdev_init(&dev->cdev, &scull_pipe_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add (&dev->cdev, devno, 1); /* Fail gracefully if need be */ if (err) printk(kern_notice "Error %d adding scullpipe%d", err, index); 순천향대학교컴퓨터학부이상정 36 int scull_p_init(dev_t firstdev) int i, result; result = register_chrdev_region(firstdev, scull_p_nr_devs, "scullp"); if (result < 0) printk(kern_notice "Unable to get scullp region, error %d n", result); return 0; scull_p_devno = firstdev; scull_p_devices = kmalloc(scull_p_nr_devs * sizeof(struct scull_pipe), GFP_KERNEL); if (scull_p_devices == NULL) unregister_chrdev_region(firstdev, scull_p_nr_devs); return 0; memset(scull_p_devices, 0, scull_p_nr_devs * sizeof(struct scull_pipe)); for (i = 0; i < scull_p_nr_devs; i++) init_waitqueue_head(&(scull_p_devices[i].inq)); init_waitqueue_head(&(scull_p_devices[i].outq)); init_mutex(&scull_p_devices[i].sem); scull_p_setup_cdev(scull_p_devices + i, i); #ifdef SCULL_DEBUG create_proc_read_entry("scullpipe", 0, NULL, scull_read_p_mem, NULL); #endif return scull_p_nr_devs;
scull_pipe 구조체 디바이스드라이버는두개의대기큐와하나의버퍼를갖는디바이스구조체 scull_pipe를사용 버퍼의크기는컴파일, 로드또는실행시에각각설정 pipe.c struct scull_pipe wait_queue_head_t inq, outq; /* read and write queues */ char *buffer, *end; /* begin of buf, end of buf */ int buffersize; /* used in pointer arithmetic */ char *rp, *wp; /* where to read, where to write */ int nreaders, nwriters; /* number of openings for r/w */ struct fasync_struct *async_queue; /* asynchronous readers */ struct semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */ ; 순천향대학교컴퓨터학부이상정 37 버퍼구성 4000 바이트버퍼로버퍼의시작은 buffer, 끝은 end가가리키고, 읽기시작지점은 rp, 쓰기시작지점은 wp가가리킴 버퍼가비어있는경우 rp == wp n 바이트읽거나쓰면 rp, wp 가각각 n 바이트증가 rp, wp가버퍼의끝에도달하면다시버퍼의시작을가리킨다. bufffer end 초기상태 rp,wp n 바이트쓰기 rp wp n 바이트읽기 rp,wp 순천향대학교컴퓨터학부이상정 38
읽기동작 코드의주요부분의보호를위해세마포가사용 세마포를획득한후에수면에들어가지않도록유의 그렇지않으면쓰기동작으로데이터를삽입할수없게되어수면에들어간이프로세스를깨우지못하고모두교착상태 (deadlock) 에빠짐 데이터의도착을기다리고자할때 wait_event_interruptible 를사용 순천향대학교컴퓨터학부이상정 39 읽기동작 : pipe.c static ssize_t scull_p_read (struct file *filp, char user *buf, size_t count, loff_t *f_pos) struct scull_pipe *dev = filp->private_data; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; while (dev->rp == dev->wp) /* nothing to read */ up(&dev->sem); /* release the lock */ if (filp->f_flags & O_NONBLOCK) return -EAGAIN; if (wait_event_interruptible(dev->inq, (dev->rp!= dev->wp))) return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ /* otherwise loop, but first reacquire the lock */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* ok, data is there, return something */ if (dev->wp > dev->rp) count = min(count, (size_t)(dev->wp - dev->rp)); else /* the write pointer has wrapped, return data up to dev->end */ count = min(count, (size_t)(dev->end - dev->rp)); if (copy_to_user(buf, dev->rp, count)) up (&dev->sem); return -EFAULT; dev->rp += count; if (dev->rp == dev->end) dev->rp = dev->buffer; /* wrapped */ up (&dev->sem); /* finally, awake any writers and return */ wake_up_interruptible(&dev->outq); 순천향대학교 return count; 컴퓨터학부이상정 40
매뉴얼수면 간단한수면인경우다음과같은순서로직접대기큐관리하는수면코드작성 my_wait 라는대기큐엔트리를선언 DEFINE_WAIT(my_wait); 다음코드와동일 wait_queue_t my_wait; init_wait(&my_wait); 큐에선언된대기큐엔트리를추가 void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state); 큐 queue 에엔트리 wait 추가 state 는수면의상태를표시 TASK_INTERRUPTIBLE, TASK_UNINTERRUPTIBLE schedule() 을호출하여다른프로세스에 CPU 를양도 현재프로세스는수면에돌입 schedule() 에서리턴 ( 수면에서깨움 ) 후큐에서제거 void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait); 순천향대학교컴퓨터학부이상정 41 매뉴얼수면 : pipe.c /* Wait for space for writing; caller must hold device semaphore. On * error the semaphore will be released before returning. */ static int scull_getwritespace(struct scull_pipe *dev, struct file *filp) while (spacefree(dev) == 0) /* full */ DEFINE_WAIT(wait); return 0; up(&dev->sem); if (filp->f_flags & O_NONBLOCK) return -EAGAIN; PDEBUG(" "%s " writing: going to sleep n",current->comm); prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE); if (spacefree(dev) == 0) schedule(); finish_wait(&dev->outq, &wait); if (signal_pending(current)) // 시그널에의해깨워진경우 return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* How much space is free? */ static int spacefree(struct scull_pipe *dev) if (dev->rp == dev->wp) return dev->buffersize - 1; return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1; 순천향대학교컴퓨터학부이상정 42
쓰기동작 write 구현도 read 와거의비슷 wp 와 rp 가같아서버퍼가비어있는경우와구분하기위해 write 는버퍼를모두채우지않고 1 바이트남겨놓는다. 순천향대학교컴퓨터학부이상정 43 쓰기동작 : pipe.c static ssize_t scull_p_write(struct file *filp, const char user *buf, size_t count, loff_t *f_pos) struct scull_pipe *dev = filp->private_data; int result; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* Make sure there's space to write */ result = scull_getwritespace(dev, filp); if (result) return result; /* scull_getwritespace called up(&dev->sem) */ /* ok, space is there, accept something */ count = min(count, (size_t)spacefree(dev)); if (dev->wp >= dev->rp) count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */ else /* the write pointer has wrapped, fill up to rp-1 */ count = min(count, (size_t)(dev->rp - dev->wp - 1)); if (copy_from_user(dev->wp, buf, count)) up (&dev->sem); return -EFAULT; dev->wp += count; if (dev->wp == dev->end) dev->wp = dev->buffer; /* wrapped */ up(&dev->sem); /* finally, awake any reader */ wake_up_interruptible(&dev->inq); /* blocked in read() and select() */ /* and signal asynchronous readers, explained later */ if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); return count; 순천향대학교컴퓨터학부이상정 44