Chapter 3. Char Drivers II scull 코드소개 scull: 디바이스번호할당 scull: 메모리디바이스구조 scull: 파일오퍼레이션 scull: open and release scull: read and write 순천향대학교컴퓨터학부이상정 1 scull 코드소개 순천향대학교컴퓨터학부이상정 2
scull 코드소개 (1) scull (simple character utility for loading localities) scull 은메모리영역을디바이스처럼사용하는 char 드라이버 다양한형태의 scull 구현 scull0 ~ scull3 메모리영역으로구성된 4개의디바이스 광역 (global) 이고지속적 (persistent) 디바이스가여러번오픈되는경우디바이스내의각데이터는오픈한모든파일디스크립터 (file descriptor) 간에공유된다는점에서광역 디바이스가종결 (close) 되고다시오픈되어도데이터가손실되지않고유지된다는점에서지속적 기존의셀명령 cp, cat 및셀 I/O 재방향 (redirection) 등을사용하여액세스하고테스트 순천향대학교컴퓨터학부이상정 3 scull 코드소개 (2) scullpipe0 ~ scullpipe3 파이프 (pipe) 처럼동작하는 4 개의 FIFO(first-in-first-out) 디바이스 한프로세스가쓰기한것을다른프로세스가읽음 만약여러프로세스들이같은디바이스읽기를시도하면서로경쟁 scullpipe 는블록킹 (blocking), 비블록킹 (non-blocking) 읽기와쓰기동작예 실제드라이버는인터럽트을이용하여동기 (10 장소개 ) scullsingle, scullpriv, sculluid, scullwuid scull 과는달리오픈시에몇가지제약이부가 scullsingle 은한번에한프로세스만드라이버사용 scullpriv 는디바이스가가상콘솔 ( 또는 X 터미널세션 ) 에국한 (private) sculluid 와 scullwuid 는한번에한사용자만이오픈 sculluid 는다른사용자가디바이스를잠그면 (locking) Device Busy 에러를리턴 scullwuid 는블록킹오픈을구현 이장에서는 scull0 ~ scull3 만을소개 나머지디바이스들은 5 장에서소개 순천향대학교컴퓨터학부이상정 4
scull 소스구성 (1) scull.h main.c main.o pipe.c Makefile pipe.o access.c scull.ko access.o 순천향대학교컴퓨터학부이상정 5 scull 소스구성 (2) 소스 scull.h main.c: 기본 scull pipe.c: scullpipe access.c: scullsingle, scullpriv, sculluid, scullwuid scull_load scull_unload: 모듈적재, 해제스크립트 mywrite.c: 사용자프로그램 Makefile scull-objs:= main.opipe.oaccess.o obj-m := scull.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 순천향대학교컴퓨터학부이상정 6
scull 실행 (1) # make make -C /lib/modules/2.6.19-gentoo-r5/build M=/home/slee/test/scull modules make[1]: Entering directory `/usr/src/linux-2.6.19-gentoo-r5' Building modules, stage 2. MODPOST 1 modules make[1]: Leaving directory `/usr/src/linux-2.6.19-gentoo-r5 # ls Makefile access.c main.c pipe.c scull.h scull.ko scull.mod.o scull_load Module.symvers access.o main.o pipe.o scull.init scull.mod.c scull.o scull_unload # cat /proc/devices Character devices: 1 mem 2 pty 3 ttyp 210 phase 211 setdvfs Block devices: 1 ramdisk # lsmod Module Size Used by PhaseDvfs 13396 0 SetDvfs 2900 0 # sh scull_load # lsmod Module Size Used by scull 13368 0 PhaseDvfs 13396 0 SetDvfs 2900 0 순천향대학교컴퓨터학부이상정 7 scull 실행 (2) # sh scull_load # cat /proc/devices Character devices: 1 mem 2 pty 3 ttyp 210 phase 211 setdvfs 254 scull 254 scullp 254 sculla # cat > /dev/scull0 Soonchunhyang Univ. Cpmputer ^D # cat /dev/scull0 Soonchunhyang Univ. Cpmputer # ls -l /dev/scull* crw-rw-r-- 1 root wheel 254, 0 Mar 6 23:05 /dev/scull0 crw-rw-r-- 1 root wheel 254, 1 Mar 6 23:05 /dev/scull1 crw-rw-r-- 1 root wheel 254, 2 Mar 6 23:05 /dev/scull2 crw-rw-r-- 1 root wheel 254, 3 Mar 6 23:05 /dev/scull3 순천향대학교컴퓨터학부이상정 8
사용자프로그램의시스템콜 mywrite.c #include <fcntl.h> #include <stdio.h> #include <string.h> char buffer[1024] = "!!! Test writing to scull device??? n"; int main(int argc, char *argv[]) int fd, n; fd = open("/dev/scull1", O_WRONLY, 0); if ((n=write(fd, buffer, strlen(buffer))) < strlen(buffer)) printf("!! only %d bytes write n", n); else printf("!! writing success to /dev/scull1 n"); return 0; 순천향대학교컴퓨터학부이상정 9 사용자프로그램의시스템콜이용 (2) $ cc mywrite.c -o mywrite #./mywrite!! writing success to /dev/scull1 # cat /dev/scull1!!! Test writing to scull device??? # 순천향대학교컴퓨터학부이상정 10
scull: 디바이스번호할당 순천향대학교컴퓨터학부이상정 11 관련코드 : scull.h #ifndef SCULL_MAJOR #define SCULL_MAJOR 0 /* dynamic major by default */ #endif #ifndef SCULL_NR_DEVS #define SCULL_NR_DEVS 4 /* scull0 through scull3 */ #endif /* * Split minors in two parts */ #define TYPE(minor) (((minor) >> 4) & 0xf) /* high nibble */ #define NUM(minor) ((minor) & 0xf) /* low nibble */ 순천향대학교컴퓨터학부이상정 12
관련코드 : main.c #include <linux/fs.h> #include <linux/types.h> #include "scull.h" int scull_major = SCULL_MAJOR; int scull_minor = 0; int scull_nr_devs = SCULL_NR_DEVS; void scull_cleanup_module(void) dev_t devno = MKDEV(scull_major, scull_minor); /* cleanup_module is never called if registering failed */ unregister_chrdev_region(devno, scull_nr_devs); int scull_init_module(void) int result, i; dev_t dev = 0; /* * Get a range of minor numbers to work with, asking for * dynamic major unless directed otherwise at load time. */ 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); if (result < 0) printk(kern_warning "scull: can't get major %d n", scull_major); return result; module_init(scull_init_module); module_exit(scull_cleanup_module); 순천향대학교컴퓨터학부이상정 13 디바이스번호표현 dev_t 타입 <linux/types.h> 에정의 커널 2.6 에서정의된 32 비트값 12비트주번호 (major number) 20비트부번호 (minor number) 관련매크로 <linux/kdev_t.h> 에정의 MAJOR(dev_t dev): dev_t 구조체에서주번호를추출 MINOR(dev_t dev): dev_t 구조체에서부번호를추출 MKDEV(int major, int minor): dev_t 구조체형성 순천향대학교컴퓨터학부이상정 14
디바이스번호등록, 할당 <linux/fs.h> 에함수선언 지정된갯수의디바이스번호등록 int register_chrdev_region(dev_t first, unsigned int count, char *name); first: 등록하고자하는첫번째디바이스번호 count: 연속적인디바이스수 name: /proc/devices 에표시되는디바이스의이름 등록성공시 0 또는양수를, 실패시음수를리턴 지정된갯수의디바이스번호동적할당 int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); dev: 함수실행결과로할당된첫번째디바이스번호 firstminor: 첫번째부번호 count: 연속적인디바이스수 name: /proc/devices 에표시되는디바이스의이름 디바이스번호해제 void unregister_chrdev_region(dev_t first, unsigned int count); 순천향대학교컴퓨터학부이상정 15 노드생성 디바이스번호의동적할당시미리디바이스번호를알수없어서디바이스노드를생성할수없는것이단점 동적할당된주번호는 /proc/devices 를검색 awk 등을사용하여한스크립트를작성하여노드를생성 순천향대학교컴퓨터학부이상정 16
scull_load 스크립트!/bin/sh module="scull" device="scull" mode="664" # invoke insmod /sbin/insmod./$module.ko $* exit 1 # retrieve major number major=`awk " $2== "$module " print $1" /proc/devices` # Remove stale nodes and replace them, # then give gid and perms rm -f /dev/$device[0-3] # Group: since distributions do it differently, # look for wheel or use staff if grep -q '^staff:' /etc/group; then group="staff" else group="wheel" fi mknod /dev/$device0 c $major 0 mknod /dev/$device1 c $major 1 mknod /dev/$device2 c $major 2 mknod /dev/$device3 c $major 3 chgrp $group /dev/$device[0-3] chmod $mode /dev/$device[0-3] 순천향대학교컴퓨터학부이상정 17 scull: 메모리디바이스구조 순천향대학교컴퓨터학부이상정 18
관련코드 : scull.h #define SCULL_QUANTUM 4000 #define SCULL_QSET 1000 /* * Representation of scull quantum sets. */ struct scull_qset void **data; struct scull_qset *next; ; struct scull_dev struct scull_qset *data; /* Pointer to first quantum set */ int quantum; /* the current quantum size */ int qset; /* the current array size */ unsigned long size; /* amount of data stored here */ unsigned int access_key; /* used by sculluid and scullpriv */ struct semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */ ; 순천향대학교컴퓨터학부이상정 19 scull 디바이스 scull_dev 구조체의링크리스트 (linked list) 로구성 각구조체는 1000 개의 4000 바이트메모리영역들로구성되어최대 4M 바이트의크기 각메모리영역을퀀텀 (quantum), 이들을가리키는 1000 개의배열을퀀텀세트 (quantum set, qset) 라함 scull 에서한바이트쓰기 퀀텀은 4K 퀀텀세트는 4K 또는 8K 로 ( 주소가 32 비트또는 64 비트여부에따라포인터의크기가달라짐 ) 총 8K 또는 12K 바이트의메모리를소모 퀀텀과퀀텀의크기는 scull.h 에서정의 SCULL_QUANTUM, SCULL_QSET 값으로정의 순천향대학교컴퓨터학부이상정 20
scull 디바이스구조 순천향대학교컴퓨터학부이상정 21 scull: 파일오퍼레이션 순천향대학교컴퓨터학부이상정 22
관련코드 : main.c (1) struct file_operations scull_fops =.owner = THIS_MODULE,.llseek = scull_llseek,.read = scull_read,.write = scull_write,.ioctl = scull_ioctl,.open = scull_open,.release = scull_release, ; /* * Set up the char_dev structure for this device. */ static void scull_setup_cdev(struct scull_dev *dev, int index) int err, devno = MKDEV(scull_major, scull_minor + index); cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; err = cdev_add (&dev->cdev, devno, 1); /* Fail gracefully if need be */ if (err) printk(kern_notice "Error %d adding scull%d", err, index); 순천향대학교컴퓨터학부이상정 23 관련코드 : main.c (2) int scull_init_module(void) /* allocate the devices */ scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL); if (!scull_devices) result = -ENOMEM; goto fail; /* Make this more graceful */ /* 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); memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev)); 순천향대학교컴퓨터학부이상정 24
scull 메쏘드 struct module *owner; 이필드는이 file_operation 구조체를소유하는모듈의포인터. 커널이모듈의사용횟수를관리하기위해사용 loff_t (*llseek) (struct file *, loff_t, int); llseek 함수는파일의현재읽기 / 쓰기위치를변경하여새로운위치를리턴 loff_t 는 long offset 을의미하며적어도 64 비트로표현 int (*open) (struct inode *, struct file *); 디바이스파일상에수행되는첫번째연산으로디바이스오픈 int (*release) (struct inode *, struct file *); 파일구조체가해제될때호출된다. ssize_t (*read) (struct file *, char *, size_t, loff_t *); 디바이스로부터데이터를읽음. 실패시 EINVAL, 성공하면읽어들인바이트수를리턴 ssize_t (*write) (struct file *, const char *, size_t, loff_t *); 디바이스로데이터쓰기를수행. 실패시 EINVAL, 성공하면데이터쓰기의바이트수를리턴 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); 디바이스관련특정명령을이슈 ( 예를들어플로피디스크포맷등과같이읽기, 쓰기가아닌명령들 ). 실패시 ENOTTY, 성공시양수를리턴 순천향대학교컴퓨터학부이상정 25 Char 디바이스등록 커널이디바이스오퍼레이션을시작하려면먼저디바이스를등록해야함 struct cdev 구조체에파일오퍼레이션등을등록 struct cdev 구조체관련함수 #include <linux/cdev.h> void cdev_init(struct cdev *dev, struct file_operations *fops); struct cdev 구조체의메모리를할당하고 dev 가할당된메모리가리킴 int cdev_add(struct cdev *dev, dev_t num, unsigned int count); cdev 에디바이스번호추가 struct cdev *cdev_alloc(void); struct cdev 구조체의메모리를할당하고포인터반환 void cdev_del(struct cdev *dev); 할당된 struct cdev 구조체해제 순천향대학교컴퓨터학부이상정 26
scull: read and write 순천향대학교컴퓨터학부이상정 27 관련코드 : main.c (1) /* Open and close */ int scull_open(struct inode *inode, struct file *filp) struct scull_dev *dev; /* device information */ dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; /* for other methods */ /* now trim to 0 the length of the device if open was write-only */ if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) if (down_interruptible(&dev->sem)) return -ERESTARTSYS; scull_trim(dev); /* ignore errors */ up(&dev->sem); return 0; /* success */ int scull_release(struct inode *inode, struct file *filp) return 0; 순천향대학교컴퓨터학부이상정 28
scull_open() 함수 inode 의 i_cdev 필드를이용하여오픈되는디바이스파일의디바이스정보검색 dev = container_of(inode->i_cdev, struct scull_dev, cdev); scull_dev의 cdev 필드값이 inode->i_cdev 와같은 scull_dev( 디바이스정보 ) 를찾아서그포인터를리턴 container_of(pointer, container_type, container_field) 매크로 #include <linux/kernel.h> container_type구조체의필드container_filed의값이pointer의값과같은 container_type 구조체를찾아서포인터리턴 디바이스가쓰기로오픈된경우크기를 0으로세팅 일반파일의새로운오픈시크기가 0이되는것과같음 scull_trim(dev) 호출 읽기를위해오픈되는경우어떤동작도수행되지않음 down_interruptible과 up은 5장에서설명 순천향대학교컴퓨터학부이상정 29 관련코드 : main.c (2) /* Empty out the scull device; must be called with the device semaphore held. */ int scull_trim(struct scull_dev *dev) struct scull_qset *next, *dptr; int qset = dev->qset; /* "dev" is not-null */ int i; for (dptr = dev->data; dptr; dptr = next) /* all the list items */ if (dptr->data) for (i = 0; i < qset; i++) kfree(dptr->data[i]); kfree(dptr->data); dptr->data = NULL; next = dptr->next; kfree(dptr); dev->size = 0; dev->quantum = scull_quantum; dev->qset = scull_qset; dev->data = NULL; return 0; 순천향대학교컴퓨터학부이상정 30
scull_trim() 함수 scull 메모리디바이스의연결리스트를따라검색하여메모리해제 앞의 scull: 메모리디바이스구조참조 순천향대학교컴퓨터학부이상정 31 scull: read and write 순천향대학교컴퓨터학부이상정 32
관련코드 : main.c (1) ssize_t scull_read(struct file *filp, char user *buf, size_t count, loff_t *f_pos) struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; /* the first listitem */ int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; /* how many bytes in the listitem */ int item, s_pos, q_pos, rest; ssize_t retval = 0; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; if (*f_pos >= dev->size) goto out; if (*f_pos + count > dev->size) count = dev->size - *f_pos; /* find listitem, qset index, and offset in the quantum */ item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; 순천향대학교컴퓨터학부이상정 33 /* follow the list up to the right position (defined elsewhere) */ dptr = scull_follow(dev, item); if (dptr == NULL!dptr->data! dptr->data[s_pos]) goto out; /* don't fill holes */ /* read only up to the end of this quantum */ if (count > quantum - q_pos) count = quantum - q_pos; if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) retval = -EFAULT; goto out; *f_pos += count; retval = count; out: up(&dev->sem); return retval; 관련코드 : main.c (2) /* Follow the list */ struct scull_qset *scull_follow(struct scull_dev *dev, int n) struct scull_qset *qs = dev->data; /* Allocate first qset explicitly if need be */ if (! qs) qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); if (qs == NULL) return NULL; /* Never mind */ memset(qs, 0, sizeof(struct scull_qset)); /* Then follow the list */ while (n--) if (!qs->next) qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); if (qs->next == NULL) return NULL; /* Never mind */ memset(qs->next, 0, sizeof(struct scull_qset)); qs = qs->next; continue; return qs; 순천향대학교컴퓨터학부이상정 34
read, write 메쏘드프로토타입 ssize_t read(struct file *filp, char *buff, size_t count, loff_t *offp); ssize_t write(struct file *filp, const char *buff, size_t count, loff_t *offp); flip는파일포인터 count는전송되는데이터의크기 buff는송수신되는사용자버퍼 offp는사용자가액세스하는파일의위치를표시 signed size type 을리턴 순천향대학교컴퓨터학부이상정 35 scull_read() 함수 동작 한번에지정된모든데이터를읽기위해루프를사용하지않고부분읽기를사용하여한번에데이터퀀텀한개씩읽음 응용프로그램이더많은데이터를원하면루프를사용하여다시호출 코드분석 *f_pos 는현재파일의위치로이전까지읽은데이터의바이트수 count 는현재읽어야할바이트수 *f_pos 로부터 qset, 퀀텀및퀀텀내의위치를계산 itemsize 는 scull_qset 구조체의한노드가저장하는총바이트수 (4M) item 은현재읽어야할 qset 의위치를가리킴 rest 는현재 item 이가리키는 qset 내에서바이트수 ( 위치 ) item 노드중 s_pos 는퀀텀의위치, q_pos 는퀀텀내에서의위치를가리킴 copy_to_user() 함수를사용하여계산된위치에서의데이터를읽음 scull_follow() 함수는현재 item 이가리키는 qset 위치의포인터반환 순천향대학교컴퓨터학부이상정 36
ssize_t scull_write(struct file *filp, const char user *buf, size_t count, loff_t *f_pos) struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; int item, s_pos, q_pos, rest; ssize_t retval = -ENOMEM; /* value used in "goto out" statements */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* find listitem, qset index and offset in the quantum */ item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; /* follow the list up to the right position */ dptr = scull_follow(dev, item); if (dptr == NULL) goto out; if (!dptr->data) dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL); 순천향대학교컴퓨터학부이상정 37 관련코드 : main.c (3) if (!dptr->data) goto out; memset(dptr->data, 0, qset * sizeof(char *)); if (!dptr->data[s_pos]) dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if (!dptr->data[s_pos]) goto out; /* write only up to the end of this quantum */ if (count > quantum - q_pos) count = quantum - q_pos; if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) retval = -EFAULT; goto out; *f_pos += count; retval = count; /* update the size */ if (dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return retval; scull_write() 함수 동작 scull_read() 와마찬가지로한번에데이터퀀텀한개씩쓰기를수행 scull_read() 와마찬가지로 *f_pos 로부터 qset, 퀀텀및퀀텀내의위치를계산 copy_from_user() 함수를사용하여계산된위치에데이터를쓰기 커널메모리할당 #include <linux/slab.h> void *kmalloc(unsigned int size, int priority); 물리메모리의연속적인영역을할당 할당된영역의이전의값을클리어하지않음 첫번째인수는할당될블록의크기이고, 두번째인수는할당플래그 GFP_KERNEL(get_free_pages) 플래그 가장자주사용되는플래그로현재프로세스상에서시스템콜을수행 메모리가부족한경우페이지를기다리는수면에들어감 순천향대학교컴퓨터학부이상정 38