4. Enhanced Char Driver Operations

Download Report

Transcript 4. Enhanced Char Driver Operations

Chapter 5. Enhanced Char Driver Operations

          ioctl 블로킹 I/O(blocking I/O) 대기 큐 (wait queue) 재진입 가능 코드 (reentrant code) 비블로킹 동작 (nonblocking operation) scullpipe 구현 예 poll과 select 비동기 통보(asynchronous notification) 디바이스 탐색(seeking a device) 디바이스 파일의 접근제어 순천향대학교 정보기술공학부 이 상 정

1

순천향대학교 정보기술공학부 이 상 정

2

ioctl

LINUX Device Drivers

하드웨어 제어

디바이스 드라이버는 읽기 쓰기 외에 하드웨어 제어 도 수행

하드웨어 제어 방식

• • ioctl 메쏘드 를 사용하여 하드웨어의 제어를 수행 제어명령의 특수한 시퀀스로 제어 • 콘솔 드라이버에서 사용되는데 이스케이프 시퀀스(escape sequence) 문자를 쓰기하여 커서 이동, 색 변경 및 다른 설정 태스크 등을 수행 • 제어명령을 위한 일부 문자들을 미리 예약해 놓고 데이터로 사용할 수 없 다는 단점 • ANSI 터미널 이스케이프 시퀀스 예 – “\x1B[#;#;

..;명령문자” 로 표시, 1B는 의 ASCII 코드 – \x1B[3A : 커서를 위로 3행 이동 – \x1B[2C : 커서를 오른쪽으로 3칸 이동 순천향대학교 정보기술공학부 이 상 정 3 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

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 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

명령 cmd 상수 값 (1)

 ioctl 구현 시 해당 디바이스의 • • 명령들에 대한 고유의 상수 값 을 지정 타입, 시퀀스 번호, 전송방향, 인수의 크기 등을 표시하는 비트필드로 표현 include/asm/ioctl.h와 Documentation/ioctl-number.txt를 체크해서 중복되 지 않도록 드라이버의 ioctl 번호를 선정    타입(type) • 서로 중복되지 않는 8비트의 고유 번호(매직 넘버) • Documentation/ioctl-number.txt를 참조하여 선정 (_IOC_TYPEBITS) 시퀀스 번호(sequence number) • 8비트 크기의 시퀀스 번호 (_IOC_NRBITS) 방향(direction) • 명령의 데이터의 전송 방향을 표시 • _IOC_NONE(데이터 전송이 없음), _IOC_READ, _IOC_WRITE, _IOC_READ | _IOC_WRITE (양방향 전송) 등의 값을 가짐 순천향대학교 정보기술공학부 이 상 정 5 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

명령 cmd 상수 값 (2)

 크기(size) • 사용자 데이터의 크기 (_IOC_SIZEBITS)  명령의 상수값 지정을 위한 매크로 • 에 포함된 헤더 파일에 정의 • • • • • _IO(type,nr) _IOR(type,nr,dataitem) _IOW(type,nr,dataitem) _IOWR(type.nr, dataitem) type은 매직넘버, nr은 시퀀스번호, dataitem의 전송되는 데이터 형으로 sizeof를 적용하여 크기를 추출 순천향대학교 정보기술공학부 이 상 정 6 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scull ioctl 명령, scull.h (1)

/* 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": G and S atomically * H means "sHift": T and Q atomically */ #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, scull_quantum) #define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, scull_qset) #define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3) #define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4) #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, scull_quantum) 순천향대학교 정보기술공학부 이 상 정 7 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scull ioctl 명령, scull.h (2)

#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, scull_qset) #define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7) #define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8) #define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, scull_quantum) #define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, scull_qset) #define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11) #define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) #define SCULL_IOCHARDRESET _IO(SCULL_IOC_MAGIC, 15) /* debugging tool */ #define SCULL_IOC_MAXNR 15 순천향대학교 정보기술공학부 이 상 정 8 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

ioctl 인수 체크

 ioctl의 arg 인수 포인터가 사용자 영역을 가리킬 때는 사용자 주소가 유 효하고 해당 페이지가 현재 매핑되어 있는지의 확인 필요 • 커널 코드가 범위를 벗어난 주소를 참조하면 프로세서는 예외상황 (exception)을 이슈  커널 2.2 이상에서 주소의 확인은 에 선언된 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를 리턴 순천향대학교 정보기술공학부 이 상 정 9 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scull 인수 체크

int err = 0, tmp; int ret = 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 *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; 순천향대학교 정보기술공학부 이 상 정 10 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

데이터 전송 함수

 access_ok 호출 후에 드라이버는 안전하게 실제 데이터 전송을 수행  copy_from_user, copy_to_user 함수 외에 데이터 크기에 따라 최적화된 다음의 함수들을 사용 (에 정의) • • 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로 검사된 경우에 사용 순천향대학교 정보기술공학부 이 상 정 11 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

권한설정(capabilities) (1)

 2.2 이상의 리눅스 커널은 보다 유연한 권한설정 기능을 제공하여 각 오 퍼레이션 단위로 사용권한을 설정  설정되는 권한의 종류는 에 정의 • • • • CAP_DAC_OVERRIDE 파일이나 디렉토리 상의 액세스를 오버라이드(override) 하는 권한 CAP_NET_ADMIN 네트워크 인터페이스에 영향을 주는 것들을 포함하여 네트워크 관리 태스크 를 수행하는 권한 CAP_SYS_MODULE 커널 모듈을 로드 또는 제거할 수 있는 권한 CAP_SYS_RAWIO

raw

I/O 동작을 수행할 수 있는 권한. 예를들어 디바이스 포트에의 접근 또 는 USB 디바이스들과 직접 통신 등 순천향대학교 정보기술공학부 이 상 정 12 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

권한설정(capabilities) (2)

• • CAP_SYS_ADMIN 많은 시스템 관리 동작에로의 접근을 제공하는 강력한(catch-all) 권한 CAP_SYS_TTY_CONFIG tty 구성 태스크(configuration task)를 수행하는 권한  디바이스 드라이버는 다음과 같은 capable 함수를 사용하여 호출하는 프 로세스가 해당하는 권한이 설정되었는가를 체크 (에 정의) int capable(int capability);  scull 샘플 드라이버에서 사용자는 퀀텀과 퀀텀 세트의 크기를 문의 • 권한이 설정된 사용자 만이 이 값들을 변경 • ioctl의 scull 코드에서는 필요하면 사용자의 권한 수준을 다음과 같이 체크 if (! capable (CAP_SYS_ADMIN)) return -EPERM; 순천향대학교 정보기술공학부 이 상 정 13 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scull ioctl 명령의 구현 (1)

 scull에서 ioctl 명령 구현 • • 사용자를 기준으로 명령의 이름을 지어 set의 의미는 사용자->시스템, get은 사용자<-시스템을 의미 반면 커널함수에서는 get은 사용자->시스템, put은 사용자<-시스템을 의미 switch(cmd) { case SCULL_IOCRESET: scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; case SCULL_IOCSQUANTUM: /* Set: arg points to the value */ scull_quantum = get_user((int *)arg); break; case SCULL_IOCTQUANTUM: /* Tell: arg is the value */ scull_quantum = arg; break; 순천향대학교 정보기술공학부 이 상 정 14 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scull ioctl 명령의 구현 (2)

} case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */ put_user(scull_quantum, (int *)arg); break; case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */ return scull_quantum; case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */ tmp = scull_quantum; scull_quantum = get_user((int *)arg); put_user(tmp, (int *)arg); break; case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */ tmp = scull_quantum; scull_quantum = arg; return tmp; default: /* redundant, as cmd was checked against MAXNR */ return -ENOTTY; 순천향대학교 정보기술공학부 이 상 정 15 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scull 사용자 프로그램 ioctl 시스템 콜

int quantum; ioctl(fd,SCULL_IOCSQUANTUM, &quantum); ioctl(fd,SCULL_IOCTQUANTUM, quantum); ioctl(fd,SCULL_IOCGQUANTUM, &quantum); quantum = ioctl(fd,SCULL_IOCQQUANTUM); ioctl(fd,SCULL_IOCXQUANTUM, &quantum); quantum = ioctl(fd,SCULL_IOCHQUANTUM, quantum); 순천향대학교 정보기술공학부 이 상 정 16 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scull ioctl 코드 실행 (1)

 사용자 소스: myIoctl.c

#include #include // O_RDONLY #include "/usr/src/linux-2.4.20-8/include/asm/ioctl.h" // _IOR() #define SCULL_IOC_MAGIC 'k' #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, quantum) int main(int argc, char *argv[]) { int fd; int quantum = 0; fd = open("/dev/scull1", O_RDONLY, 0); if (!ioctl(fd, SCULL_IOCGQUANTUM, &quantum)) printf("quantum: %d\n", quantum); else printf("!! Something must be wrong\n

); return 0; } 순천향대학교 정보기술공학부 이 상 정 17 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

실행

# cc myIoctl.c -o myIoctl # ./myIoctl quantum: 4000

scull ioctl 코드 실행 (2)

순천향대학교 정보기술공학부 이 상 정 18 Ch.5 Enhanced Char Driver Operations

블로킹 I/O (Blocking I/O)

순천향대학교 정보기술공학부 이 상 정

19

LINUX Device Drivers

블로킹 I/O 개요

읽기 동작 시 읽을 데이터가 도착하지 않는 경우 프로세스는 데이터를 기다리기 위해 수면(sleep) 후 나중에 깨어나야 (wake up) 한다.

수면 (sleep)

• 프로세스가 이벤트(데이터의 도착 또는 프로세스의 종료)를 위해 기 다려야만 할 때마다 프로세스는 수면 • 수면은 프로세스가 실행을 중지하고 다른 사용을 위해 프로세서의 사 용을 해제(release) 

깨움(wake up)

• • 이 후 기다리던 이벤트가 발생하면 프로세스는 깨어나서 후속작업 을 계속 수행 이 절에서는 2.4 기준으로 수면과 깨어나는 방법을 기술 순천향대학교 정보기술공학부 이 상 정 20 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

수면 (1)

   리눅스에서 수면 및 깨움은 대기 큐(wait queue) 상에서 일어나며 타입 은 wait_queue_head_t 대기 큐는 이벤트를 위해 대기하고 있는 프로세스들의 큐로 다음과 같이 선언되고 초기화 wait_queue_head_t my_queue; init_waitqueue_head (&my_queue); 대기 큐가 정적으로 선언된 경우 다음과 같이 컴파일 시 큐를 초기화 DECLARE_WAIT_QUEUE_HEAD (my_queue);  대기 큐가 선언되고 초기화 되었으면 프로세스는 수면할 수 있다. 수면의 정도에 따라 다음과 같은 함수들이 제공 sleep_on(wait_queue_head_t *queue); • • 수면을 위해 프로세스를 큐에 삽입 이 함수를 사용하면 프로세스는 인터럽트를 허용하지 않아서 기다리고 있는 이벤트가 발생하지 않는 경우 프로세스를 죽일 수 없어서 교착상태에 빠질 수 있음 순천향대학교 정보기술공학부 이 상 정 21 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

수면 (2)

interruptible_sleep_on(wait_queue_head_t *queue); • 시그널에 의한 인터럽트를 수용한다는 점 외에는 위의 sleep_on과 동일 sleep_on_timeout(wait_queue_head_t *queue, long timeout); interruptible_sleep_on_timeout(wait_queue_head_t *queue, • long timeout); 이 함수들은 주어진 시간 동안에만 수면한다는 점 외에는 앞의 두 함수와 동 일 void wait_event(wait_queue_head_t queue, int condition); int wait_event_interruptible(wait_queue_head_t queue, int condition); • • • • 이들 매크로는 이벤트를 위한 대기와 레이스 조건을 없애기 위해 이벤트 도 착의 테스트를 결합 조건 condition이 참이 될 때까지 수면 매크로는 while 루프로 확장되어 시간이 지나면 조건을 다시 평가한다. 두번째 매크로는 성공한 경우 0을 ,루프가 시그널에 의한 인터럽트로 실패한 경우

ERESTARTSYS를 평가하도록 구현 순천향대학교 정보기술공학부 이 상 정 22 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

깨움

  수면 후에 이벤트가 발생하면 드라이버는 프로세스를 깨어야 하고 대개 인터럽트 핸들러에서 이를 수행 프로세스를 깨우기 위해 다음과 같은 함수가 사용 wake_up(wait_queue_head_t *queue); • 해당 이벤크 큐 상에서 기다리고 있는 모든 프로세스를 깨움 wake_up_interruptible(wait_queue_head_t *queue); • • 인터럽트를 받아들이는 프로세스들만 깨움 인터럽트를 받아들이지 않는 함수나 매크로를 사용하여 대기 큐에서 수면하 는 프로세스들은 깨우지 않음 wake_up_sync(wait_queue_head_t *queue); wake_up_interruptible_sync(wait_queue_head_t *queue); • • wake_up을 호출하면 바로 CPU의 재스케쥴을 수행하여 wake_up 으로부터 리턴하기 전에 다른 프로세스들이 실행될 수 있다. 반면 Synchronous를 사 용하면 깨어난 프로세스들을 실행 가능하게 하고 CPU를 재스케쥴을 하지 않 음 대기 큐 사용에 주의해야 할 것은 깨어남이 항상 이벤트가 발생한 것을 의미 하지 않는다. 프로세스는 시그널 같은 다른 이유로 깨어날 수 있다. 따라서 수면으로부터 리턴 후에 조건을 테스트 하는 루프를 수행해야 한다. 순천향대학교 정보기술공학부 이 상 정 23 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

sleepy 샘플 코드 예 (1)

 sleepy 샘플 코드 • • 소스: source/misc-modules/sleepy.c

대기 큐 사용 예로 한 사용자가 디바이스로부터 읽으려고 할 때 프로세스가 수면에 들어가고, 다른 사용자가 디바이스에 쓰기를 하면 앞의 프로세스를 깨우는 코드  코드 내용 DECLARE_WAIT_QUEUE_HEAD(wq); ssize_t sleepy_read (struct file *filp, char *buf, size_t count,loff_t *pos) { printk(KERN_DEBUG "process %i (%s) going to sleep\n", current->pid, current->comm); interruptible_sleep_on(&wq); printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm); return 0; /* EOF */ } 순천향대학교 정보기술공학부 이 상 정 24 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

sleepy 샘플 코드 예 (2)

ssize_t sleepy_write (struct file *filp, const char *buf, size_t count, loff_t *pos) { printk(KERN_DEBUG "process %i (%s) awakening the readers...\n", current->pid, current->comm); wake_up_interruptible(&wq); return count; /* succeed, to avoid retrial */ }  실행 # gcc -Wall -D__KERNEL__ -DMODULE -I/usr/src/linux-2.4.20-8/include -O2 -I.. -c -o sleepy.o sleepy.c # /sbin/insmod sleepy.o

# more /proc/modules sleepy 1576 0 (unused) parport_pc 19076 1 (autoclean) ………………………… 순천향대학교 정보기술공학부 이 상 정 25 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

sleepy 샘플 코드 예 (3)

# more /proc/devices ……… 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

순천향대학교 정보기술공학부 이 상 정 26 Ch.5 Enhanced Char Driver Operations

대기 큐 (wait queue)

순천향대학교 정보기술공학부 이 상 정

27

LINUX Device Drivers

대기 큐(wait queue) 타입

wait_queue_head_t 타입

• • • • • 에서 정의 하나의 잠금변수(lock variable) 와 수면 중인 프로세스들의 링크 리스 트로 구성 각 리스트는 에 정의된 고유의 이터 wait_queue_t 타입의 데 wait_queue_t 구조체 interruptible_sleep_on 등과 같은 함수에 의해 스택 상에 할당되고 함수의 종료 시 해제 순천향대학교 정보기술공학부 이 상 정 28 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

대기 큐(wait queue) 동작

 다음은 간소화된 버전의 interruptible_sleep_on 내부 코드 void simplified_sleep_on(wait_queue_head_t *queue) { wait_queue_t wait; => 스택 상에 wait_queue_t 타입의 wait 변수 생성 init_waitqueue_entry(&wait, current); => wait 변수 초기화 current->state = TASK_INTERRUPTIBLE; => 인터럽트 가능한 수면임을 의미하는 TASK_INTERRUPTIBLE로 세팅 add_wait_queue(queue, &wait); => queue가 가리키는 대기 큐에 엔트리 wait를 삽입 schedule(); => 프로세서를 다른 태스크에 양도하는 schedule 함수를 호출 => 프로세스가 깨어날 때 schedule로부터 리턴되고 상태는 TASK_RUNNING으로 세팅 remove_wait_queue (queue, &wait); => 대기 큐로부터 엔트리 wait가 제거하여 수면이 종료 } 순천향대학교 정보기술공학부 이 상 정 29 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

대기 큐(wait queue) 구조체

순천향대학교 정보기술공학부 이 상 정 30 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

독점적 수면 (1)

 여러 프로세스들이 한 이벤트를 기다리는 경우 wake_up 호출이 되면 모 든 프로세스들이 실행 시도 • • • 단지 한 프로세스만이 데이터를 읽고 나머지는 단순히 깨어있는 상태가 되고, 데이터가 가용하지 않음을 알고 다시 수면에 진입 아무 일도 하지 않는 실행 프로세스들을 남발하여 많은 컨텍스트 스위치와 프로세서 오버헤드를 초래 따라서 이들 프로세스들은 그대로 수면상태로 남아있게 하는 것이 좋음  커널 2.3버전부터는 독점적 수면(exclusive sleep) 의 개념이 추가 • • • 프로세스가 독점 모드의 수면에 들어가면 커널은 이들 중 하나만을 깨움 프로세스가 독점적 대기 상태임을 표시하기 위해 를 추가 TASK_EXCLUSIVE 플래그 add_wait_queue_exclusive 를 호출 순천향대학교 정보기술공학부 이 상 정 31 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

독점적 수면 (2)

void simplified_sleep_exclusive(wait_queue_head_t *queue) { wait_queue_t wait; init_waitqueue_entry(&wait, current); current->state = TASK_INTERRUPTIBLE | TASK_EXCLUSIVE ; add_wait_queue_exclusive(queue, &wait); schedule(); remove_wait_queue (queue, &wait); } 순천향대학교 정보기술공학부 이 상 정 32 Ch.5 Enhanced Char Driver Operations

재진입 가능 코드 (reentrant code)

순천향대학교 정보기술공학부 이 상 정

33

LINUX Device Drivers

재진입 문제

프로세스가 수면모드에 들어가도 드라이버는 살아 있어서 다 른 프로세스가 이를 호출 가능

응용 프로그램이 tty1 상에서 키보드 입력을 기다리고 있는 동안에 사용자는 tty2로 전환하여 새로운 셀을 생성하는 콘 솔 드라이버의 예

• • 두 셀 모두는 서로 다른 대기 큐(tty1의 대기 큐와 tty2의 대기 큐) 상 에서 수면하면서 콘솔 드라이버 상에서 키보드 입력을 기다림 각 프로세스는 interruptible_sleep_on 함수 내에서 블록킹 되었지만 드라이버는 여전히 다른 tty 들의 요청을 받아 응답 

동시에 진입하는 프로세스 간에 상태정보 등이 서로 간섭과 영향을 미치지 않게 해야 함 재진입 가능 코드(reentrant code) 작성

순천향대학교 정보기술공학부 이 상 정 34 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

재진입 가능 코드 작성 (1)

다음 조건에 해당되어 가능한 코드로 작성 수면 에 들어가는 경우 함수는 재진입

• • 호출된 sleep_on 또는 wake_up 이 schedule을 호출하여 수면하는 경우 사용자 영역의 데이터를 접근하여 페이지 폴트를 유발할 수 있는 경 우.

• 커널이 페이지 폴트를 처리하는 동안 프로세스는 수면 

SMP 시스템에서는 수면 중이 아니어도 같은 드라이버를 동 시에 여러 번 호출할 수 있으므로 재진입 가능 코드로 작성해 야 함

순천향대학교 정보기술공학부 이 상 정 35 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

재진입 가능 코드 작성 (2)

재진입 가능 코드에서는 광역변수에 상태정보를 저장하지 않 음으로써 이들 정보가 이 코드에 동시에 진입하는 프로세스 간에 서로 뒤섞여서 영향을 미치지 않게 작성

• 모든 상태정보들이 프로세스에 국한되면 어떤 간섭들도 발생하지 않 음 • • 상태정보는 드라이버 함수 내 지역변수 에 저장하거나(각 프로세스는 커널 영역에 지역변수가 저장되는 서로 다른 스택 페이지를 갖는다) 파일을 접근하는 filp 내의 private_data 에 저장 큰 상태 데이터를 저장하는 경우에는 메모리는 kmalloc로 할당하고 지역변수에 이의 포인터를 저장 

수면에 들어가는 코드는 프로세스가 수면 중에 시스템의 상 태가 변경될 수 있다는 것을 고려하여 드라이버는 수면 중에 변경될 가능성이 있는 환경들을 체크하는 것에 유의

순천향대학교 정보기술공학부 이 상 정 36 Ch.5 Enhanced Char Driver Operations

비블록킹 동작 (nonblocking operation)

순천향대학교 정보기술공학부 이 상 정

37

LINUX Device Drivers

블로킹 동작(blocking operation)

커널의 read, write 메쏘드는 디폴트로 flip->f_flags에 O_NONBLOCK 플래그를 세팅하여 블로킹으로 동작

읽기 블로킹

• • 프로세스가 read를 호출하고 데이터가 아직 도착하지 않았을 때 프로 세스는 블록킹 데이터가 도착하자마자 프로세스가 깨어나고, 요청된 count 크기보 다 작은 데이터가 도착해도 리턴 

쓰기 블로킹

• 프로세스가 write를 호출하고 버퍼에 저장할 공간이 없는 경우 프로 세스는 블로킹 • 일부 데이터가 (버퍼에서) 하드웨어 디바이스로 쓰기하여 출력 버퍼 에 저장할 일부 공간만 생기게 되어도 프로세스는 깨어나서 write 호 출을 계속 수행 순천향대학교 정보기술공학부 이 상 정 38 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

비블로킹 동작(nonblocking operation)

flip->f_flags에 으로 동작 O_NONBLOCK 플래그를 세팅하면 비블로킹

비블로킹 read와 write 동작

• • 데이터가 도착하지 않았을 때 프로세스가 read를 호출하거나 버퍼가 차있을 때 write를 호출하면 단순히

EAGAIN을 리턴 즉, 비블록킹 오퍼레이션들은 즉시 리턴하여 응용에서 데이터 가용여 부를 조사(poll) 

O_NONBLOCK은 open 메쏘드에도 의미가 있다. 즉 아직 쓰 기가 일어나지 않은 FIFO나 보류되어 잠금된(pending lock) 디스크 접근을 오픈하는 경우 외부 이벤트를 기다리지 않고 바로 리턴

순천향대학교 정보기술공학부 이 상 정 39 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

nbtest 샘플 코드 예 (1)

 nbtest 샘플 코드 • 소스: source/misc-progs/nbtest.c

• • 비블록킹 테스트를 위해 비블록킹 I/O와 재시도 간의 지연을 통해 단 순히 입력을 출력으로 복사하는 프로그램 명령행 인수로 지연시간을 기술하고 디폴트 값은 1초  코드 내용 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 */ 순천향대학교 정보기술공학부 이 상 정 40 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

nbtest 샘플 코드 예 (2)

} 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); 순천향대학교 정보기술공학부 이 상 정 41 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

nbtest 샘플 코드 예 (3)

실행

$ cc nbtest.c -o nbtest $ ./nbtest 10 1234567 1234567 …… => 입력 => 10초 후에 출력 

결과 설명

• • • 비블록킹 read에서 입력 기다리지 않고 바로 sleep을 실행하므로 10 초 기다림 데이터가 도착하지 않았을 때 read가 호출되면 로 루프를 빠져나가지 않음

EAGAIN을 리턴하므 만약 위의 fnctl 함수를 코멘트 처리하여 블로킹 입출력으로 하면 read 가 데이터를 기다린 후 도착하면 바로 출력하므로 10초 기다리 지 않고 바로 출력한 후 sleep을 실행 순천향대학교 정보기술공학부 이 상 정 42 Ch.5 Enhanced Char Driver Operations

scullpipe 구현 예

순천향대학교 정보기술공학부 이 상 정

43

LINUX Device Drivers

scullpipe 소개

   일반적인 드라이버 동작 • • read 호출로 블로킹된 프로세스는 데이터가 도착하면 깨어남 일반적으로 하드웨어가 이벤트 신호를 주기 위해 인터럽트를 이슈하고, 드라 이버는 인터럽트를 처리하는 과정에서 대기하고 있는 프로세스들을 깨움 scull 드라이버는 특정 하드웨어나 인터럽트 처리기 없이 실행되어야 하 기 때문에 위와 다르게 동작하도록 작성 /dev/scullpipe 디바이스 들은 scull 모듈의 일부로 블로킹 I/O 구현 예 • • • • read 시 버퍼에 데이터가 없으면 수면 후 버퍼에 write 되면 깨어나 읽기 진행 write 시 버퍼에 데이터가 입력하고 수면 중인 프로세스 깨움 결과적으로 FIFO(또는 파이프) 와 같은 기능 소스: source/scull/pipe.c Makefile scull_load scull_unload scull.h main.c

순천향대학교 정보기술공학부 이 상 정 44 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

Scull_Pipe 구조체

 디바이스 드라이버는 두 개의 대기 큐와 하나의 버퍼를 갖는 디바이스 구 조체 Scull_Pipe를 사용 • 버퍼의 크기는 컴파일, 로드 또는 실행 시에 각각 설정 typedef 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 */ devfs_handle_t handle; /* only used if devfs is there */ } Scull_Pipe; 순천향대학교 정보기술공학부 이 상 정 45 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

버퍼 구성

 4000 바이트 버퍼로 버퍼의 시작은 buffer, 끝은 end가 가리키고, 읽기 시작지점은 rp, 쓰기 시작지점은 wp가 가리킴 • • • 버퍼가 비어 있는 경우 rp == wp n 바이트 읽거나 쓰면 rp, wp 가 각각 n 바이트 증가 rp, wp가 버퍼의 끝에 도달하면 다시 버퍼의 시작을 가리킨다.

bufffer 초기상태 rp,wp end n 바이트 쓰기 rp n 바이트 읽기 wp 순천향대학교 정보기술공학부 이 상 정 rp,wp 46 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

읽기 동작 (1)

  코드의 주요 부분의 보호를 위해 세마포가 사용 • • 세마포를 획득 한 후에 수면에 들어가지 않도록 유의 그렇지 않으면 쓰기 동작으로 데이터를 삽입할 수 없게 되어 수면에 들어간 이 프로세스를 깨우지 못하고 모두 교착상태(deadlock)에 빠짐 데이터의 도착을 기다리고자 할 때 wait_event_interruptible를 사용 ssize_t scull_p_read (struct file *filp, char *buf, size_t count, loff_t *f_pos) { Scull_Pipe *dev = filp->private_data; => 디바이스 데이터 if (f_pos != &filp->f_pos) return -ESPIPE; if (down_interruptible(&dev->sem)) => 세마포 획득 return -ERESTARTSYS; while (dev->rp == dev->wp) { => 버퍼가 비어 있으면 반복 up(&dev->sem); /* release the lock */ if (filp->f_flags & O_NONBLOCK) => 비블록킹이면 바로 리턴 return -EAGAIN; PDEBUG("\"%s\" reading: going to sleep\n", current->comm); 순천향대학교 정보기술공학부 이 상 정 47 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

읽기 동작 (2)

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; } /* while */ /* ok, data is there, return something */ if (dev->wp > dev->rp) count = min(count, dev->wp - dev->rp); else /* the write pointer has wrapped, return data up to dev->end */ count = min(count, 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); 순천향대학교 정보기술공학부 이 상 정 48 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

읽기 동작 (3)

} /* finally, awake any writers and return */ wake_up_interruptible(&dev->outq); => 데이터를 읽어 버퍼가 비기를 기다리는 outq 대기 큐를 깨움 PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count); return count; 순천향대학교 정보기술공학부 이 상 정 49 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

쓰기 동작 (1)

 write 구현도 read와 거의 비슷하다. wp와 rp가 같아서 버퍼가 비어 있는 경우와 구분하기 위해 write는 버퍼를 모두 채우지 않고 1바이트 남겨 놓는다.

static inline int spacefree(Scull_Pipe *dev) { if (dev->rp == dev->wp) return dev->buffersize - 1; return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1; } ssize_t scull_p_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { Scull_Pipe *dev = filp->private_data; => 디바이스 데이터 if (f_pos != &filp->f_pos) return -ESPIPE; if (down_interruptible(&dev->sem)) => 세마포 획득 return -ERESTARTSYS; 순천향대학교 정보기술공학부 이 상 정 50 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

쓰기 동작 (2)

/* Make sure there's space to write */ while (spacefree(dev) == 0) { /* full */ up(&dev->sem); if (filp->f_flags & O_NONBLOCK) => 비블록킹이면 리턴 return -EAGAIN; PDEBUG("\"%s\" writing: going to sleep\n",current->comm); if (wait_event_interruptible(dev->outq, spacefree(dev) > 0)) => 데이터 읽기 기다리면 수면 return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; } /* ok, space is there, accept something */ count = min(count, spacefree(dev)); if (dev->wp >= dev->rp) count = min(count, dev->end - dev->wp); /* up to end-of-buffer */ else /* the write pointer has wrapped, fill up to rp-1 */ count = min(count, dev->rp - dev->wp - 1); 순천향대학교 정보기술공학부 이 상 정 51 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

쓰기 동작 (3)

} PDEBUG("Going to accept %li bytes to %p from %p\n", (long)count, dev->wp, buf); 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() */ => 데이터 쓰기를 하여 버퍼가 채워지기를 기다리는 inq 대기 큐를 깨움 /* and signal asynchronous readers, explained late in chapter 5 */ if (dev->async_queue) => 비동기 통보 kill_fasync(&dev->async_queue, SIGIO, POLL_IN); PDEBUG("\"%s\" did write %li bytes\n",current->comm, (long)count); return count; 순천향대학교 정보기술공학부 이 상 정 52 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

실행

# cat /dev/scullpipe & [2] 15619 => 디바이스 읽기, 수면 # ls -x > /dev/scullpipe => 디바이스 쓰기, 수면에서 깨어남, 위의 cat 실행, 화면에 출력 Makefile access.c access.o alpha.checkthem empty.c main.c main.o

myIoctl myIoctl.c myWrite myWrite.c pipe.c pipe.o scull.h

scull.init scull.o scull_load scull_unload # cat /dev/scullpipe > test.out & [3] 15622 => 디바이스 읽기, 수면 # ls -x .. > /dev/scullpipe => 디바이스 쓰기, 수면에서 깨어남, 위의 cat 실행, 파일에 출력 # cat test.out

Changes LICENSE Makefile Makefile.alpha Makefile.arm

Makefile.ia64 Makefile.m68k Makefile.mips Makefile.ppc Makefile.sparc

…… 순천향대학교 정보기술공학부 이 상 정 53 Ch.5 Enhanced Char Driver Operations

poll과 select

순천향대학교 정보기술공학부 이 상 정

54

LINUX Device Drivers

poll과 select 시스템 콜

poll과 select 시스템 콜은 프로세스가 하나 이상의 오픈 파 일들로부터 블로킹 없이 읽기 또는 쓰기를 할 수 있는지를 조 사

• 여러 입출력 스트림을 사용해야 하는 응용들에서 자주 사용 • BSD 유닉스에서는 select, 시스템 V에서는 poll로 구현 

poll과 select 시스템 콜은 다음 poll 메쏘드를 호출 unsigned int (*poll) (struct file *, poll_table *);

• • • 사용자 프로그램에서 오픈된 파일 디스크립터 인수 조사되는 디바이스의 대기 큐의 엔트리를 저장하는 체 인수 (에서 정의) poll_table 구조 블로킹 되지 않고 바로 실행될 수 있는 동작의 비트 마스크 를 리턴 순천향대학교 정보기술공학부 이 상 정 55 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

poll 메쏘드

poll 메쏘드에서는 다음 두 단계를 수행

• • 폴 상태 변화를 표시하는 하나 이상의 대기 큐 들 상에서 poll_wait를 호출 블로킹 없이 바로 실행할 수 있는 동작들을 표시하는 비트 마스크를 리턴 

poll_wait 함수

• void poll_wait (struct file *, wait_queue_head_t *, poll_table *); • • 이 함수에서는 디바이스의 동작 상태가 변경된 모든 이벤트를 poll_table 구조체에 삽입 모든 드라이버들이 블로킹되어 I/O 동작을 할 수 없으면 대기 큐 중 하나가 깨어날 때까지 poll은 수면에 들어 감 순천향대학교 정보기술공학부 이 상 정 56 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

poll 플래그

디바이스 동작 상태를 표시하는 플래그가 에 정의

• • • • • • • POLLIN: 디바이스가 블로킹 없이 읽을 수 있으면 이 비트가 세트 POLLRDNORM: 정상적인 데이터 읽기가 가용하면 이 비트가 세트. 읽기 가능한 디바이스는 (POLLIN | POLLRDNORM) 을 리턴 POLLPRI: 높은 우선순위의 데이터(out-of-band)는 블로킹 없이 읽 을 수 있음을 표시 POLLHUP: 디바이스를 읽는 프로세스가 EOF(end-of-file)를 읽었을 때 드라이버는 POLLHUP(hang-up)을 세트 POLLERR: 디바이스에 에러조건이 발생하였음을 알림 POLLOUT: 트 디바이스가 블로킹 없이 쓰기할 수 있으면 이 비트가 세 POLLWRNORM: POLLOUT과 같은 의미를 갖는다. 쓰기 가능한 디바 이스는 (POLLOUT | POLLWRNORM)을 리턴 순천향대학교 정보기술공학부 이 상 정 57 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scullpipe poll 메쏘드

unsigned int scull_p_poll(struct file *filp, poll_table *wait) { Scull_Pipe *dev = filp->private_data; unsigned int mask = 0; /* * The buffer is circular; it is considered full * if "wp" is right behind "rp". "left" is 0 if the * buffer is empty, and it is "1" if it is completely full.

*/ int left = (dev->rp + dev->buffersize - dev->wp) % dev->buffersize; poll_wait(filp, &dev->inq, wait); poll_wait(filp, &dev->outq, wait); if (dev->rp != dev->wp) mask |= POLLIN | POLLRDNORM; /* readable */ if (left != 1) mask |= POLLOUT | POLLWRNORM; /* writable */ return mask; } 순천향대학교 정보기술공학부 이 상 정 58 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

poll 시스템 콜 테스트 (1)

poll 시스템 콜

#include int poll(struct pollfd *ufds, unsigned int nfds, int timeout); • ufds: struct pollfd 형 구조체 배열을 가리키는 포인터 struct pollfd { int fd; // 파일 기술자 short events; // 요구된 이벤트 short revents; // 반환된 이벤트 } • • nfds: 구조체 배열의 크기, 테스트하고자 하는 파일 디스크립터의 수 timeout: timeout 시간 경과 후에 이벤트 리턴, ms 단위 순천향대학교 정보기술공학부 이 상 정 59 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

poll 시스템 콜 테스트 (2)

 myPoll.c

• • scullpipe0~3 데이터 읽기 poll 시스템 콜로 읽기가 가능한 경우(POLLIN)에만 읽고, 아니면 waiting.. 출 력  소스 int main(int argc, char **argv) { struct pollfd pfds[N]; int ret; pfds[0].fd = open("/dev/scullpipe0", O_RDONLY, 0); pfds[1].fd = open("/dev/scullpipe1", O_RDONLY, 0); pfds[2].fd = open("/dev/scullpipe2", O_RDONLY, 0); pfds[3].fd = open("/dev/scullpipe3", O_RDONLY, 0); pfds[0].events = pfds[1].events = pfds[2].events = pfds[3].events = POLLIN; => 조사될 이벤트 등록 순천향대학교 정보기술공학부 이 상 정 60 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

poll 시스템 콜 테스트 (3)

} while (1) { ret = poll(pfds, N, TIMEOUT*1000); => poll 시스템 콜 switch (ret) { case -1: /* error */ perror("poll"); exit(1); case 0: /* no event, timeout */ printf("waiting...\n"); break; default: ReadDevices(pfds); } } void ReadDevices(struct pollfd *pfds) { char buffer[4096]; int i, n; for (i=0; i 반환된 이벤트가 읽기 가능인가 조사 n = read(pfds[i].fd, buffer, sizeof(buffer)); printf("Read %d bytes from scullpipe%d\n", n, i); } } } 순천향대학교 정보기술공학부 이 상 정 61 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

poll 시스템 콜 테스트 (4)

 실행 # cc myPoll.c -o myPoll # ./myPoll waiting...

waiting...

Read 13 bytes from scullpipe2 => 다른 tty에서 cat > /dev/scullpipe2 로 Test Input!! 입력 Test Input!!

waiting...

waiting...

Read 14 bytes from scullpipe0 => 다른 tty에서 cat > /dev/scullpipe0 로 Another Input 입력 Another Input waiting...

………… 순천향대학교 정보기술공학부 이 상 정 62 Ch.5 Enhanced Char Driver Operations

비동기 통보 (asynchronous notification)

순천향대학교 정보기술공학부 이 상 정

63

LINUX Device Drivers

비동기 통보

   입력 디바이스에서 발생하는 시그널을 비동기적으로 통보 • 예를들어 입력장치에서 입력 시 시그널(SIGIO)을 발생하여 사용자 응용 프로 그램에 통보 사용자 프로그램은 입력파일로부터 비동기 통보를 위해 다음 과정 기술 • 첫째, 프로세스의 소유자(

owner

)를 기술. fcntl 시스템 콜 을 사용하여 F_SETOWN 명령 을 실행. 커널은 소유자의 프로세스 ID를 filp->f_owner에 저장 • • 둘째, F_SETFL fcntl 명령을 사용하여 FASYNC 플래그 를 세트 셋째, 두 콜이 실행된 후 입력파일은 새로운 데이터가 도착할 때마다 SIGIO 전달을 요청. • 시그널은 filp->f_owner에 저장된 프로세스나 프로세스 그룹에 보내짐 비동기 통보에서의 문제는 프로세스가 SIGIO를 받았을 때 어느 입력파 일에 해당하는 입력인지를 구분하지 못한다는 것이다. • 하나 이상의 파일이 프로세스들에게 보류된 입력을 비동기적으로 통보해야 하는 경우에는 poll 또는 select 를 사용 순천향대학교 정보기술공학부 이 상 정 64 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

사용자 비동기 통보 코드 예

다음은 stdin 입력파일 상에서 현재 프로세스에 비동기 통보 를 가능하게 하는 프로그램 코드 예

signal(SIGIO, &input_handler); /* dummy sample; sigaction() is better */ fcntl(STDIN_FILENO, F_SETOWN, getpid()); oflags = fcntl(STDIN_FILENO, F_GETFL); fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); 순천향대학교 정보기술공학부 이 상 정 65 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

드라이버 비동기통보

  디바이스가 드라이버는 다음과 같이 비동기 시그널링을 구현 • • F_SETOWN이 실행되면 flip->f_owner에 값이 지정 FASYNC를 세팅하기 위해 F_SETFL이 실행되면 드라이버의 출 fasync 메쏘드 가 호 • 이 메쏘드는 flip->f_flags의 FASYNC 값이 변경될 때마다 드라이버에게 이 변경을 알 려서 적절하게 대응 • 데이터가 도착하면 비동기 통보를 위해 등록된 모든 프로세스들에게 SIGIO 시그 널이 전송 비동기 통보를 위해 리눅스에서는 일반적으로 한 개의 자료구조와 두 개의 함수를 사용하여 구현 • • • 관련된 헤더는 이고 자료구조는 struct fasync_struct int fasync_helper(int fd, struct file *filp,int mode, struct fasync_struct **fa); • fasync_helper는 오픈 파일에서 FASYNC 플래그가 변경되어 비동기 리스트에서 파일 을 추가하거나 제거할 때 실행 void kill_fasync(struct fasync_struct **fa, int sig, int band); • kill_fasync는 데이터가 도착했을 때 해당 프로세스에 시그널을 보내기 위해 사용 • 인수로는 전송되는 시그널(대개 SIGIO)과 POLL_IN 값을 갖는 band이다. 순천향대학교 정보기술공학부 이 상 정 66 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scullpipe의 코드 예

 fasync 메쏘드를 구현하는 scullpipe의 코드 int scull_p_fasync(fasync_file fd, struct file *filp, int mode) { Scull_Pipe *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue); }   scullpipe에서는 프로세스가 write를 수행함으로써 읽기를 위한 데이터 를 생성하기 때문에 scullpipe의 write 메쏘드에서 비동기 시그널 전송 if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); 파일을 종료할 때 활성화된 비동기 리더(active asynchronous reader)의 리스트로부터 파일을 제거하기 위해 fasync 메쏘드를 실행한다. 다음은 scullpipe의 close 메쏘드 코드 부분이다.

/* remove this filp from the asynchronously notified filp

s */ scull_p_fasync(-1, filp, 0); 순천향대학교 정보기술공학부 이 상 정 67 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

비동기 통보 시스템 콜 예 (1)

 misc-progs/asynctest.c 예 • stdin 입력 시 SIGIO 시그널을 발생시켜 수면에서 깨어나 stdout에 출력하는 예 • 이 프로그램은 cat과 같지만 파일의 끝에서 종료하지 않는다. 입력의 부재가 아닌 입 력 시에만 응답한다. #include #include #include #include #include #include int gotdata=0; void sighandler(int signo) => 시그널 핸들러 { if (signo==SIGIO) gotdata++; return; } 순천향대학교 정보기술공학부 이 상 정 68 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

비동기 통보 시스템 콜 예 (2)

char buffer[4096]; int main(int argc, char **argv) { int count; struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = sighandler; => 시그널 핸들러 설정 action.sa_flags = 0; sigaction(SIGIO, &action, NULL); fcntl(STDIN_FILENO, F_SETOWN, getpid()); => 프로세스 소유자 설정 fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | FASYNC); => 비동기통보 설정 순천향대학교 정보기술공학부 이 상 정 69 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

비동기 통보 시스템 콜 예 (3)

} } while(1) { /* this only returns if a signal arrives */ sleep(86400); /* one day */ => 수면, SIGIO 시그널 발생(키보드 입력, gotdata != 0) 시 깨어남 if (!gotdata) continue; count=read(0, buffer, 4096); /* buggy: if avail data is more than 4kbytes... */ write(1,buffer,count); gotdata=0; 순천향대학교 정보기술공학부 이 상 정 70 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

비동기 통보 시스템 콜 예 (4)

실행 # cc asynctest.c -o asynctest # ./asynctest 12345 => 엔터입력 12345 a 12345 => 엔터를 입력하지 않으면 SIGIO는 발생되지만 입력은 되지 않고 write만 실행하여 이전 버퍼내용 출력 bc 12345 de 12345 f abcdef => 엔터입력, abcdef 입력되어 출력

순천향대학교 정보기술공학부 이 상 정 71 Ch.5 Enhanced Char Driver Operations

디바이스 탐색 (seeking a device)

순천향대학교 정보기술공학부 이 상 정

72

LINUX Device Drivers

llseek 메쏘드

 llseek 메쏘드는 lseek과 llseek 시스템 콜을 구현 • llseek 메쏘드가 생략되면 커널은 파일의 시작에서부터, 그리고 파일 내의 현재 읽기/ 쓰기 위치인 filp->f_pos가 표시하는 현재 위치에서 탐색을 수행  scull 드라이버의 코드 예 loff_t scull_llseek(struct file *filp, loff_t off, int whence) { Scull_Dev *dev = filp->private_data; loff_t newpos; switch(whence) { case 0: /* SEEK_SET */ newpos = off; break; case 1: /* SEEK_CUR */ newpos = filp->f_pos + off; break; case 2: /* SEEK_END */ newpos = dev->size + off; break; 순천향대학교 정보기술공학부 이 상 정 73 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scull 드라이버의 코드 예

default: /* can't happen */ return -EINVAL; } if (newpos<0) return -EINVAL; filp->f_pos = newpos; return newpos; }  대부분의 디바이스는 데이터의 영역보다는 데이터의 흐름만을 제공하여 (예를들면 직렬포트나 키보드) 이들 디바이스의 탐색은 의미가 없다.

• 그러나 디폴트 메쏘드가 탐색을 허용하므로 llseek 오퍼레이션을 막을 수는 없음 • 다음의 scullpipe 에서의 코드(pipe.c)와 같이 탐색할 수 없음을 리턴하도록 작성 loff_t scull_p_llseek(struct file *filp, loff_t off, int whence) { return -ESPIPE; /* unseekable */ } 순천향대학교 정보기술공학부 이 상 정 74 Ch.5 Enhanced Char Driver Operations

디바이스 파일의 접근제어

순천향대학교 정보기술공학부 이 상 정

75

LINUX Device Drivers

단일 오픈 디바이스

접근제어

• 인가되지 않는 사용자가 디바이스를 사용하는 것을 제한(파일 시스템 의 허용 비트에 의해 제한) 

단일 오픈 디바이스

• 접근제어의 가장 단순한 방법은 한번에 한 프로세스만을 오픈하도록 제한 • • 그러나 실제로는 한 프로세스가 쓰기한 것을 다른 프로세스가 읽기하 는 방식으로 여러 프로세스가 오픈해야 하므로 이 방식은 바람직하지 않지만 가장 간단한 방식이므로 여기서 소개 scullsingle 은 scull의 단일 오픈 디바이스 버전으로 접근제어를 한 다는 점을 제외하고는 기본적으로 scull 디바이스와 같음 순천향대학교 정보기술공학부 이 상 정 76 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scullsingle 코드 예 (1)

Open 메쏘드에서 scull_s_count 광역변수 근거하여 접근을 제한

• • 변수의 값을 테스트하여 0이 아니면 접근 거절 디바이스를 사용할 때 변수가 증가된다. 단일 프로세스 시스템에서는 어떤 다른 프로세스도 이들 두 동작 사이에 수행되지 않으므로 안전 

SMP에서 두 프로세스가 동시에 디바이스를 오픈한다면 scull_s_count를 변경하기 전에 둘 다 모두 테스트를 하는 경 우가 발생

• 광역변수의 동시 접근을 차단하기 위해 세마포 대신 잠금 메커니즘 사용 스핀락(spinlock) 순천향대학교 정보기술공학부 이 상 정 77 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scullsingle 코드 예 (2)

int scull_s_open(struct inode *inode, struct file *filp) { Scull_Dev *dev = &scull_s_device; /* device information */ int num = NUM(inode->i_rdev); } if (!filp->private_data && num > 0) return -ENODEV; /* not devfs: allow 1 device only */ spin_lock(&scull_s_lock); => 스핀락 설정 if (scull_s_count) { => 카운트가 0이 아니면 다른 프로세스가 이미 오픈하여 사용 spin_unlock(&scull_s_lock); => 스핀락 해제 return -EBUSY; /* already open */ } scull_s_count++; => 카운트 증가하여 사용 중 표시 spin_unlock(&scull_s_lock); scull_trim(dev); if (!filp->private_data) => 스핀락 해제 /* then, everything else is copied from the bare scull device */ if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) filp->private_data = dev; MOD_INC_USE_COUNT; return 0; /* success */ 순천향대학교 정보기술공학부 이 상 정 78 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scullsingle 코드 예 (3)

int scull_s_release(struct inode *inode, struct file *filp) { scull_s_count--; /* release the device */ => 카운트 감소하여 사용 종료 표시 MOD_DEC_USE_COUNT; return 0; } 순천향대학교 정보기술공학부 이 상 정 79 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

스핀락(spinlock) (1)

 일반적으로 세마포의 사용은 호출한 프로세스가 수면에 들어가기 때문에 사용 경비가 높음 • 이는 단순히 상태변수의 빠른 체크를 보호하는 문제에 대해서는 너무 과중한 방식  스핀락(spinlock) • 스핀락에서는 프로세스가 수면에 들어가지 않고 잠금이 가용하지 않으면 잠 금이 해제될 때까지 계속 재시도를 반복(여기서 spin 용어 유래, 돌고 돔) • • • • • 스핀락은 잠금 오버헤드가 작은 반면 다른 사용자가 잠금을 독점하면 프로세 서는 오랜 기간 기다려야 하는 문제점 세마포와 비교하여 스핀락의 또 다른 장점은 단일 프로세서에서는 컴파일 될 때 빈 코드가 되도록 구현(이들 문제는 SMP에서만 발생). 세마포는 단일 프로세서뿐만 아니라 SMP에서도 적용 가능한 일반적인 방식 이기 때문에 단일 프로세서에 최적화된 해법은 아님 스핀락은 작고 중요한 코드의 부분(critical section)에 이상적인 메커니즘 프로세서들은 가능한 한 짧은 시간 동안에 스핀락을 보유하고 스핀락 잠금 상태에서 채 수면에 들어가자 말아야 함 순천향대학교 정보기술공학부 이 상 정 80 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

스핀락(spinlock) (2)

스핀락은 에 정의된 spinlock_t 형으로 선 언

스핀락 함수

• • 사용하기 전에 다음과 같이 초기화 spin_lock_init(spinlock_t *lock); 주요부분에 들어가는 프로세스는 spin_lock으로 락을 획득 spin_lock(spinlock_t *lock); • 락은 spin_unlock으로 해제 spin_unlock(spinlock_t *lock); 순천향대학교 정보기술공학부 이 상 정 81 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

Scullsingle 테스트 # cat > /dev/scullsingle => 실행 중 ------------- # cat /dev/scullsingle cat: /dev/scullsingle: 장치나 자원이 동작 중

순천향대학교 정보기술공학부 이 상 정 82 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

단일 사용자 접근제어

앞의 단일 프로세스 접근을 넘어서 한 사용자가 여러 프로세 스를 오픈하는 sculluid

• 한 사용자가 한 번에 여러 프로세스로부터 읽기와 쓰기를 허용 

open 메쏘드

• 정상적인 허용 체크 후에 소유자 및 그룹 허용 비트를 체크 • • 오픈 카운트 외에 디바이스 소유자의 uid가 추가 이 코드는 또한 오픈을 시도하는 프로세스가 파일의 접근 허용권한을 수정(override)할 수 있는지를 체크하여 이 권한이 있으면 프로세스 가 디바이스의 소유자가 아니더라도 오픈을 허용 순천향대학교 정보기술공학부 이 상 정 83 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

sculluid 코드 예

다음은 access.c의 scull_u_open 코드의 일부분

spin_lock(&scull_u_lock); if (scull_u_count && (scull_u_owner != current->uid) && /* allow user */ (scull_u_owner != current->euid) && /* allow whoever did su */ !capable(CAP_DAC_OVERRIDE)) { /* still allow root */ spin_unlock(&scull_u_lock); return -EBUSY; /* -EPERM would confuse the user */ } if (scull_u_count == 0) scull_u_owner = current->uid; /* grab it */ scull_u_count++; spin_unlock (&scull_u_lock); 순천향대학교 정보기술공학부 이 상 정 84 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

오픈 시 디바이스를 복사(cloning)

 디바이스를 오픈하는 프로세스 마다 독자적인 가상 디바이스를 생성 • • • 이는 하드웨어 디바이스가 아닌 scull과 같은 소프트웨어 디바이스에서 가능 한 방법 /dev/tty도 비슷한 방법을 사용 이와 같이 소프트웨어 드라이버에 의해 만들어지는 드라이버를 스(virtual device) 라 함 가상 디바이   scull 가상 디바이스로 • /dev/scullpriv scullpriv 구현에서 가상 디바이스를 접근하는 키 값으로 프로세스를 제어하 는 tty의 부번호(minor number)를 사용 • 각 사용자마다 가상 디바이스를 만들려면 uid를, 프로세스마다 가상 디바이스를 만들려면 pid를 키 값으로 사용하면 됨 다음은 access.c의 scullpriv 코드 • • scull_c_lookfor_device 함수 • 가상 디바이스의 리스트에서 해당 키의 디바이스를 검색하는 함수로 없으면 가상 디바이스를 만들어 리스트에 삽입 오픈 시 scull_c_open 함수에 의해 scull_c_lookfor_device 함수를 호출하여 가상 디바이스를 검색하고 생성 순천향대학교 정보기술공학부 이 상 정 85 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scullpriv 코드 예 (1)

/* The clone-specific data structure includes a key field */ struct scull_listitem { Scull_Dev device; int key; struct scull_listitem *next; }; /* The list of devices, and a lock to protect it */ struct scull_listitem *scull_c_head; spinlock_t scull_c_lock; /* Look for a device or create one if missing */ static Scull_Dev *scull_c_lookfor_device(int key) { struct scull_listitem *lptr, *prev = NULL; for (lptr = scull_c_head; lptr && (lptr->key != key); lptr = lptr->next) => 해당 키의 디바이스 검색 prev=lptr; if (lptr) return &(lptr->device); => 디바이스 등록되어 있으면 리턴 /* not found */ lptr = kmalloc(sizeof(struct scull_listitem), GFP_ATOMIC); => 없으면 새 디바이스 생성 if (!lptr) return NULL; 순천향대학교 정보기술공학부 이 상 정 86 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scullpriv 코드 예 (2)

} /* initialize the device */ memset(lptr, 0, sizeof(struct scull_listitem)); lptr->key = key; scull_trim(&(lptr->device)); /* initialize it */ sema_init(&(lptr->device.sem), 1); /* place it in the list */ if (prev) prev->next = lptr; => 새 디바이스 등록 else scull_c_head = lptr; return &(lptr->device); int scull_c_open(struct inode *inode, struct file *filp) { Scull_Dev *dev; int key, num = NUM(inode->i_rdev); if (!filp->private_data && num > 0) return -ENODEV; /* not devfs: allow 1 device only */ if (!current->tty) { PDEBUG("Process \"%s\" has no ctl tty\n",current->comm); return -EINVAL; } 순천향대학교 정보기술공학부 이 상 정 87 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scullpriv 코드 예 (3)

key = MINOR(current->tty->device); => tty의 부번호를 키 값으로 함 /* look for a scullc device in the list */ spin_lock(&scull_c_lock); dev = scull_c_lookfor_device(key); => 디바이스 검색 및 생성 spin_unlock(&scull_c_lock); if (!dev) return -ENOMEM; /* then, everything else is copied from the bare scull device */ if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) scull_trim(dev); filp->private_data = dev; MOD_INC_USE_COUNT; return 0; /* success */ } int scull_c_release(struct inode *inode, struct file *filp) { /* * Nothing to do, because the device is persistent.

* A `real' cloned device should be freed on last close */ MOD_DEC_USE_COUNT; return 0; } 순천향대학교 정보기술공학부 이 상 정 88 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

scullpriv 테스트

# ls -x > /dev/scullpriv # cat /dev/scullpriv Makefile access.c access.o alpha.checkthem empty.c main.c

main.o myIoctl myIoctl.c myPoll myPoll.c myWrite myWrite.c pipe.c pipe.o scull.h scull.init scull.o

scull_load scull_unload scullsingle test.out

-------------- # cat /dev/scullpriv => 위와 독립적인 가상 디바이스이므로 출력 결과 없음 순천향대학교 정보기술공학부 이 상 정 89 Ch.5 Enhanced Char Driver Operations

LINUX Device Drivers

과제

코드 분석

• • 5장의 내용에 해당하는 코드 분석 myIoctl.c sleepy nbtest scullpipe myPoll.c asynctest.c scullsingle sculluid scullpriv 

실행 및 결과분석

• • 강의에서 제시된 예와 차별화 시스템 콜 호출 사용 차별화 • 예를들어 myIoctl.c에서 다른 ioctl 명령 사용 순천향대학교 정보기술공학부 이 상 정 90 Ch.5 Enhanced Char Driver Operations