박상원 (swpark@hufs.ac.kr) 한국외국어대학교 정보통신공학과 Linux device driver Flash translation layer MTD 2 공짜 모든 구성 요소를 완전히 재구성할 수 있다 값싼 하드웨어에서도 동작 강력하다 소스 코드 품질을 위해 표준을 따른다 커널은 매우 작고 아담하게 만들 수도 있다 여러 대중적인 운영체제와 잘 호환된다 지원이 잘 된다 5 Monolithic kernel ◦ microkernel Module Kernel thread Multithread 응용 프로그램 지원 Preemptive vs. Nonpreemptive Multiprocessor File system ◦ ext2fs, ext3fs (journaling file system) 6 Linux version ◦ ◦ ◦ ◦ 2.4.x 2.5.x 2.6.x cat /proc/version Kernel 7 프린터 포트 ◦ 총 25핀, 17개의 신호선과 8개의 접지선 ◦ 신호선 : 4개의 제어선, 5개의 상태선, 8개의 데이터선 ◦ 각 레지스터는 PC의 I/O 주소 공간에 매핑 0x378, 0x379, 0x37A ◦ 프린터 포트의 인터럽트는 7번 사용 8 9 10 Linux ◦ ◦ ◦ ◦ ◦ Linus-B. Torvalds에 의해 개발 Minix 1991년 0.02 버전 1994년 1.0 버전 현재 2.6 버전 ◦ ◦ ◦ ◦ FSF(Free Software Foundation) 리차드 스톨만 GCC, Emacs 등의 다양한 유틸리티 GPL(General Public License) GNU Project 12 Monolithic kernel 비선점형(커널 2.4)과 선점형(커널 2.6) 가상 메모리 시스템(VM) MMU 없는 시스템도 지원 가상 파일 시스템(VFS) 모듈을 이용한 커널 확장 커널 스레드 멀티스레드 지원 멀티 프로세서 지원 강력한 네트워크 지원 GPL 라이센스 13 시스템이 지원하는 하드웨어를 응용 프로그램에서 사용할 수 있도록 커널에서 제공하는 라이브러리 하드웨어는 커널의 입장에서는 자원 응용 프로그램이 커널에게 자원 처리를 요청하는 방법 ◦ System call ◦ 파일 입출력 형식을 이용한 device driver 14 소프트웨어 인터럽트 서비스 이용 각 기능별로 번호를 할당 ◦ 이 번호에 해당하는 제어 루틴을 커널 내부에 정의 ◦ 응용 프로그램은 원하는 기능 번호를 레지스터에 저장한 후 소프트웨어 인터럽트 서비스를 호출하여 커널에 제어 를 넘김 처음 운영체제가 설계될 당시에는 시스템 호출 방 식만 존재 하드웨어 증가에 따른 한계 15 파일 입출력 함수로 하드웨어 제어 하드웨어를 표현하는 디바이스 파일 ◦ 응용 프로그램이 입출력을 시도 ◦ 커널 내의 해당 디바이스 파일에 연결된 디바이스 드라이 버의 서비스 루틴 호출 16 초기에는 디바이스 드리이버를 커널 소스에 포함시켜 야 ◦ 긴 커널 컴파일 시간 ◦ 수정할 때마다 시스템 재부팅 커널이 부팅되어 동작중인 상태에서 디바이스 드라이 버를 동적으로 추가하거나 제거 ◦ 디바이스 드라이버 개발 시간 단축 ◦ 필요없는 기능을 커널에서 제거하여 커널 자원의 효율적 사용 MMU가 있는 시스템에서만 지원, 커널 버전이 동일해 야 17 시스템의 모든 자원을 파일 형식으로 표현 ◦ 램, 프로세스, 태스크 등 ◦ 예) /dev/mouse, /dev/console 디바이스 파일 정보 ◦ 문자/블록 ◦ 주번호, 부번호 ◦ mknod로 생성 18 문자 디바이스 드라이버 ◦ 임의의 길이를 갖는 문자열을 다루는 디바이스 드라이버 ◦ 응용 프로그램에서 직접적으로 호출 ◦ 버퍼없음 블록 디바이스 드라이버 ◦ 일정 크기의 버퍼를 통해 데이터를 처리하는 디바이스 ◦ 커널 내부의 파일 시스템에서 관리 ◦ 내부적인 버퍼가 있음 네트워크 디바이스 드라이버 ◦ 네트워크 층과 연결되는 디바이스 드라이버 19 일반 파일과 가장 유사한 방식으로 처리 ◦ open, close, read, write 함수 이용 파일 포인터를 이용하여 특정 처리 위치 지정 응용 프로그램의 호출과 1:1로 대응 대부분의 하드웨어는 문자 디바이스 드라이버로 구현 가능 20 파일 시스템을 지원하는 구조이므로 응용 프로그 램은 파일 시스템을 통해 접근 효율적인 처리를 위해 커널 내부 버퍼 이용 블록 처리도 가능하지만 스트림 처리도 가능 ◦ open, close, read, write 함수 이용하여 접근 가능 21 ls /dev/ hdj32 hdj4 hdj5 hdj6 hdj7 hdj8 hdj9 hdk hdk1 nst28a nst28l nst28m nst29 nst29a nst29l nst29m nst2a nst2l sdam7 sdcf8 sddz9 sdft sdhm1 sdt10 ttySR1 xdb54 sdam8 sdcf9 sde sdft1 sdhm10 sdt11 ttySR10 xdb55 sdam9 sdcg sde1 sdft10 sdhm11 sdt12 ttySR11 xdb56 sdan sdcg1 sde10 sdft11 sdhm12 sdt13 ttySR12 xdb57 sdan1 sdcg10 sde11 sdft12 sdhm13 sdt14 ttySR13 xdb58 sdan10 sdcg11 sde12 sdft13 sdhm14 sdt15 ttySR14 xdb59 sdan11 sdcg12 sde13 sdft14 sdhm15 sdt2 ttySR15 xdb6 sdan12 sdcg13 sde14 sdft15 sdhm2 sdt3 ttySR16 xdb60 sdan13 sdcg14 sde15 sdft2 sdhm3 sdt4 ttySR17 xdb61 mknod [디바이스 파일명][디바이스 파일형][주번 호][부번호] ◦ mknod /dev/devfile c 240 1 23 파일 입출력 함수 기능 fopen, open 파일을 연다 fread, read 파일을 읽는다 fwrite, write 파일에 데이터를 쓴다 fclose, close 파일을 닫는다 24 시스템 콜을 라이브러리 함수로 만든 것 저수준 파일 입출력 함수 기능 open( ) 파일이나 장치를 연다 close( ) 열린 파일을 닫는다 read( ) 파일에서 데이터를 읽어온다 write( ) 파일에 데이터를 쓴다 lseek( ) 파일의 쓰기나 읽기 위치를 변경한다 ioctl( ) read( ), write( )로 다루지 않는 특수한 제어 를 한다 fsync( ) 파일에 쓴 데이터와 실제 하드웨어의 동기를 맞춘다 25 int fd = -1; fd = open(“/dev/mem”, O_RDWR | O_NONBLOCK); if (fd < 0) { // 에러 처리 } … close(fd); O_NONBLOCK, O_NDELAY ◦ 읽기나 쓰기가 완료되지 않더라도 저수준 파일 입출력 함 수가 즉시 종료되도록 26 방법 ◦ ret_num = read(fd, buff, 10); ◦ ret_num = write(fd, buff, 10); 예 ret_num = read(fd, buff, 10); if (ret_num < 0) { // 에러 처리 } if (ret_num != 10) { // 요구된 것과 다를 때 처리 } 27 off_t ret_pos; … ret_pos = lseek(fd, 1234, SEEK_CUR); Options ◦ SEEK_CUR ◦ SEEK_SET ◦ SEEK_END 28 모든 제어를 read, write 만으로는 곤란 디바이스 파일에만 적용되는 연산 10장. 디바이스의 제어 29 디바이스의 구현 방식에 따라 쓰는 방식 다를 수 있음 ◦ 내부에 버퍼를 두는 경우 int ret; … ret = fsync(fd); 주의점 ◦ 실행 시간이 길어지거나 함수가 종료되지 않을 수 있음 30 int mknod(const char *pathname, mode_t mode, dev_t dev); 다음 헤더를 포함해야 #include #include #include #include 디바이스 파일 종류 ◦ ◦ ◦ ◦ <sys/types.h> <sys/stat.h> <fcntl.h> <unistd.h> S_IFCHR : 문자 디바이스 S_IFBLK : 블록 디바이스 S_IRWXU : 사용자는 읽기 쓰기 권한이 있다 S_IRWXG : 그룹은 읽기 쓰기 권한이 있다 예 ◦ [ root@ ] # mknod /dev/test c 240 1 ◦ mknod(“/dev/test”, S_IRWXU|S_IRWXG|S_IFCHR, (240<<8)|1); 31 프로그램의 디버깅에 사용 전역변수 참조 fd = open(“/dev/ram”, O_RDONLY); if (fd < 0) { perror(“open”); } 에러 번호를 해석하여 문자열로 표현 ◦ include/asm/errno.h 32 33 함수 ◦ lseek : 접근할 I/O 주소를 지정 ◦ read : 지정된 I/O에서 데이터를 읽어온다 ◦ write : 지정된 I/O에 데이터를 쓴다 [root@islab dev]# ls -al /dev/port crw-r----1 root kmem 1, 4 [ root@ ] # mknod /dev/port c 1 4 1월 30 2003 /dev/port 34 int main( int argc, char **argv ) { int fd; int lp; unsigned char buff[128]; fd = open( "/dev/port", O_RDWR ); if( fd < 0 ) { perror( "/dev/port open error" ); exit(1); } 35 for( lp = 0; lp < 10; lp++ ) { lseek( fd, 0x378, SEEK_SET ); buff[0] = 0xFF; write( fd, buff, 1 ); sleep( 1 ); lseek( fd, 0x378, SEEK_SET ); buff[0] = 0x00; write( fd, buff, 1 ); sleep( 1 ); } close( fd ); return 0; } 36 int main( int argc, char **argv ) { int fd; int prnstate; int lp; unsigned char buff[128]; fd = open( "/dev/lp0", O_RDWR | O_NDELAY ); if( fd < 0 ) { perror( "open error" ); exit(1); } 37 while( 1 ) { ioctl( fd, LPGETSTATUS, &prnstate ); // 13 Pin <--> GND Pin if( prnstate & LP_PSELECD ) printf( "ON\n" ); else printf( "OFF\n" ); usleep( 50000 ); } close( fd ); return 0; } 38 40 #define MODULE #include <linux/module.h> #include <linux/kernel.h> int init_module(void) { printk("Hello, world\n"); return 0; } void cleanup_module(void) { printk("Goodbye world\n"); } 41 KERNELDIR = /lib/modules/$(shell uname -r)/build CFLAGS = -D__KERNEL__ -DMODULE -I$(KERNELDIR)/include -O all: test.o clean: rm -rf *.o 42 [ [ [ [ root@ root@ root@ root@ ] ] ] ] # # # # make insmod test.o dmesg rmmod test 43 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> static int hello_init(void) { printk("Hello, world \n"); return 0; } static void hello_exit(void) { printk("Goodbye, world\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("Dual BSD/GPL"); 44 obj-m := test.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf *.ko rm -rf *.mod.* rm -rf .*.cmd rm -rf *.o 45 [ [ [ [ root@ root@ root@ root@ ] ] ] ] # # # # make insmod test.ko dmesg rmmod test.ko 46 47 [ root@ ] # cat /proc/ksyms … #c0134a50 register_chardev_R1a5f156e #c0134ae0 unregister_chrdev_Rc192d491 48 유틸리티 기능 insmod 모듈을 커널에 적재한다. rmmod 커널에서 모듈을 제거한다. lsmod 커널에 적재된 모듈 목록을 보여준다. depmod 모듈간 의존성 정보를 생성한다. modprobe 모듈을 커널에 적재하거나 제거한다. 49 커널 2.4 커널 2.6 int init_module(void); module_init(hello_init); void cleanup_module(void); module_exit(hello_exit); 50 MODULE_LICENSE(“Dual BSD/GPL”); ◦ 생략하거나 “Proprietary”를 사용하면 커널 내부의 몇몇 API에 접근할 수 없다 라이센스 풀어쓰기 GPL GNU Public License v2 or later GPL v2 GNU Public License v2 GPL and additional rights GNU Public License v2 rights and more Dual BSD/GPL GNU Public License v2 or BSD license choice Dual MPL/GPL GNU Public License v2 or Mozilla license choice Proprietary Non free products 51 모듈 매개변수 ◦ 초기값 중에서 외부에서 변경할 수 있는 것 ◦ I/O 포트, IRQ 번호 등 52 #include #include #include #include <linux/init.h> <linux/module.h> <linux/kernel.h> <linux/moduleparam.h> static int onevalue = 1; static char *twostring = NULL; module_param(onevalue, int, 0); module_param(twostring, charp, 0); 53 static int hello_init(void) { printk("Hello, world [onevalue=%d:twostring=%s]\n", onevalue, twostring ); return 0; } static void hello_exit(void) { printk("Goodbye, world\n"); } module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("You Young-chang frog@falinux.com"); MODULE_DESCRIPTION("Module Parameter Test Module"); MODULE_LICENSE("Dual BSD/GPL"); 54 module_param(변수명, 변수 타입, 접근 속성) 변수 타입 C 타입 short short ushort unsigned short int int uint unsigned int long long ulong unsigned long charp char * bool int invbool int intarray int * 55 [ root@ ] # insmod test.ko onevalue=0x27 twostring=“Oh my godrmmod test” [ root@ ] # rmmod test.ko [ root@ ] # dmesg . . . Hello, world [ onevalue=38:twostring=Oh my godrmmod test] Goodbye, world 56 레벨 ◦ printk(KERN_INFO “system ok\n”); ◦ printk(“<6>” “system ok\n”); ◦ printk(“<6> system ok\n”); 레벨을 표시하지 않을 경우 다음은 동일한 의미 ◦ ◦ ◦ ◦ printk(KERN_WARNING “system ok\n”); printk(“<4>” “system ok\n”); printk(“<4> system ok\n”); printk(“system ok\n”); ◦ 반드시 \n을 포함해야 출력 ◦ 수행시간이 많이 걸리는 함수 57 상수 선언문 의미 KERN_EMERG “<0>” /* 시스템이 동작하지 않는다 */ KERN_ALERT “<1>” /* 항상 출력된다. */ KERN_CRIT “<2>” /* 치명적인 정보 */ KERN_ERR “<3>” /* 오류 정보 */ KERN_WARNING “<4>” /* 경고 정보 */ KERN_NOTICE “<5>” /* 정상적인 정보 */ KERN_INFO “<6>” /* 시스템 정보 */ KERN_DEBUG “<7>” /* 디버깅 정보 */ 58 지역 변수, 전역 변수 모두 커널 메모리 공간에 배 치 지역 변수 ◦ 가급적 지역 변수를 사용하는 것이 좋다. 전역 변수 ◦ 컴파일 시점에 크기와 주소가 정의 ◦ 디바이스 드라이버가 커널에 적재되고 해제되는 시점까 지 유지해야 하는 정보 60 변수 명이나 함수 명이 중복 정의되는 경우 static 으로 중복 방지 static int checkout = 0; // 변수 선언 예 static int dev_app_check(void) { // 함수 선언 예 } 61 32비트에서 64비트로 전환되는 시점 플랫폼간 호환성 사용하는 변수의 데이터 형을 명확히 기술 62 가장 문제가 되는 타입 : int ◦ ◦ ◦ ◦ 크기는 프로세스에 의존적 16 비트 : 2 바이트 32 비트 : 32 비트 64 비트 : 64 비트 ◦ #include <asm/types.h> 부호 있는 정수 부호 없는 정수 __s8, s8 8비트 __u8, u8 __s16, s16 16비트(워드) __u16, u16 __s32, s32 32비트 __u32, u32 32비트 __s64, s64 64비트 __u64, u64 64비트 8비트 16비트(워드) 63 Alignment 무시한 구조체 필요 ◦ packed typedef struct { u16 index; u16 data; u8 data2; } __attribute__ ((packed)) testctl_t; 64 Little endian vs. Big endian #include <asm/byteorder.h> ◦ #define __LITTLE_ENDIAN ◦ #define __BIG_ENDIAN 65 메모리 번지를 이용하는 I/O 접근 처리 ◦ u32 *ptr = (u32 *) 0xE0000300; ◦ *ptr = 0x1234; ◦ *ptr = *ptr & 0xFF; 컴파일러 입장에서는 최적화 시도 ◦ *ptr = 0x1245 & 0xFF; 항상 만족하게 하려면 최적화 방지 ◦ volatile u32 *ptr = (volatile u32 *) 0xE0000300; 66 일반적인 C에서 #include <stdlib.h> char *buff; buff = malloc(1024); if (buff == null) exit(1); . . . free(buf); 67 프로세스 사용자 메모리 공간과 다른 커널 메모리 공간 PAGE_SIZE 단위로 할당하는 특성 ◦ 보통 4KB 시스템에 가용 메모리가 없는 상황 가상 메모리 기법에 의해 접근하고자 하는 메모리가 보 조 장치에 존재하는 경우 ◦ swapping DMA 같은 디바이스가 사용하는 연속된 물리 메모리 주 소가 필요한 경우 인터럽트 상태에서 메모리를 할당하는 경우 ◦ 커널 내에 메모리가 부족하여 sleep 하면 안됨 68 kmalloc( ), kfree( ) __get_free_pages( ), free_pages( ) vmalloc( ), vfree( ) 69 할당 가능한 크기 : 32 x PAGE_SIZE ◦ 일반적으로 PAGE_SIZE는 4K이므로 131072 bytes 이상 은 곤란 #include <linux/slab.h> char * buff; buff = kmalloc(1024, GFP_KERNEL); if (buff != NULL) kfree(buff); 70 GFP_KERNEL ◦ 동적 메모리 할당이 항상 성공하도록 요구 ◦ 충분한 메모리가 없을 때 sleep ◦ 인터럽트 서비스에 사용할 때는 사용하면 안됨 GFP_ATOMIC ◦ 커널에 할당 가능한 메모리가 있으면 무조건 할당 ◦ 없으면 즉시 NULL 반환 ◦ sleep 하는 경우가 없음 GFP_DMA ◦ 연속된 물리 메모리를 할당방을 때 사용 ◦ 디바이스 드라이버가 동작하는 메모리 공간은 물리적인 메모리가 아닌 가상 주소 메모리 실제 물리적 공간은 분할되어 있을 수 있음 ◦ DMA 콘트롤러를 사용할 때 71 가상 공간이 허용하는 한 크기 제한 없이 할당 ◦ 가상 주소 공간이므로 할당할 메모리가 디스크에 있을 수 있음 ◦ 커다란 연속 공간을 할당하기 위해 가상 메모리 관리 루틴이 실행 되므로 kmalloc 보다 느리다 ◦ 인터럽트 서비스 함수 안에서 사용 불가 #include <linux/vmalloc.h> char *buff; buff = vmalloc(1024); . . . vfree(buff) 72 페이지의 2의 승수 크기로 할당 ◦ MAX_ORDER 11 ◦ 보통은 5 이하 (32 x PAGE_SIZE) char *buff; int order; order = get_order(8192); buff = __get_free_pages(GFP_KERNEL, order); . . . free_pages(buff, order); 73 void kmalloc_test( void ) { char *buff; printk( "kmalloc test\n" ); buff = kmalloc( 1204, GFP_KERNEL ); if( buff != NULL ) { sprintf( buff, "test memory\n" ); printk( buff ); kfree( buff ); } buff = kmalloc( 32 * PAGE_SIZE, GFP_KERNEL ); if( buff != NULL ) { printk( "Big Memory Ok\n" ); kfree( buff ); } } 74 void vmalloc_test( void ) { char *buff; printk( "vmalloc test\n" ); buff = vmalloc( 33 * PAGE_SIZE ); if( buff != NULL ) { sprintf( buff, "vmalloc test ok\n" ); printk( buff ); vfree( buff ); } } 75 void get_free_pages_test( void ) { char *buff; int order; printk( "get_free_pages test\n" ); order = get_order(8192*10); buff = __get_free_pages( GFP_KERNEL, order ); if( buff != NULL) { sprintf( buff, "__get_free_pages test ok [%d]\n", order ); printk( buff ); free_pages(buff, order); } } 76 int memtest_init(void) { char *data; printk("Module Memory Test\n" ); kmalloc_test(); vmalloc_test(); get_free_pages_test(); return 0; } void memtest_exit(void) { printk("Module Memory Test End\n"); } module_init(memtest_init); module_exit(memtest_exit); MODULE_LICENSE("Dual BSD/GPL"); 77 커널 2.6 엔터프라이즈 환경에 적합하도록 개선 시도 메모리가 부족해지면 가상 파일시스템이 동작하지만 느림 사전에 예측되는 최소한의 메모리를 미리 할당하고, 관리 관리자 생성과 소멸 API ◦ mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn, mempool_free_t *free_fn, void *pool_data); ◦ void mempool_destroy(mempool_t *pool); ◦ typedef void * (mempool_alloc_t)(int gfp_mask, void *pool_data); ◦ typedef void (mempool_free_t)(void *element, void *pool_data); 할당과 해제 API ◦ void *mempool_alloc(mempool_t *pool, int gfp_mask); ◦ void mempool_free(void *element, mempool_t *pool) 78 mempool_alloc 함수로 할당 실패시 사전에 미리 할당받았던 메모리 반환 그래도 실패시는 gfp_mask 옵션에 따라 실패 혹 은 slepp 79 #define MIN_ELEMENT #define TEST_ELEMENT 4 4 typedef struct { int number; char string[128]; } TMemElement; int elementcount = 0; void *mempool_alloc_test(int gfp_mask, void *pool_data) { TMemElement *data; printk( "----> mempool_alloc_test\n" ); data = kmalloc( sizeof( TMemElement ), gfp_mask ); if( data != NULL ) data->number = elementcount++; return data; } 80 void mempool_free_test(void *element, void *pool_data) { printk( "----> call mempool_free_test\n" ); if( element != NULL ) kfree( element ); } int mempool_init(void) { mempool_t *mp; TMemElement *element[TEST_ELEMENT]; int lp; printk("Module MEMPOOL Test\n" ); memset( element, 0, sizeof( element ) ); printk( "call mempool_create\n" ); mp = mempool_create( MIN_ELEMENT, mempool_alloc_test, mempool_free_test, NULL ); 81 printk( "mempool allocate\n" ); for( lp=0; lp < TEST_ELEMENT; lp++ ) { element[lp] = mempool_alloc(mp, GFP_KERNEL ); if( element[lp] == NULL ) printk( "allocte fail\n" ); else { sprintf( element[lp]->string, "alloc data %d\n", element[lp]->number ); printk( element[lp]->string ); } } printk( "mempool free\n" ); for( lp=0; lp < TEST_ELEMENT; lp++ ) { if( element[lp] != NULL ) mempool_free( element[lp], mp ); } printk( "call mempool_destroy\n" ); mempool_destroy( mp ); return 0; } void mempool_exit(void) { printk("Module MEMPOOL Test End\n"); } module_init(mempool_init); module_exit(mempool_exit); 82 Module MEMPOOL Test call mempool_create ----> mempool_alloc_test ----> mempool_alloc_test ----> mempool_alloc_test ----> mempool_alloc_test mempool allocate ----> mempool_alloc_test alloc data 4 ----> mempool_alloc_test alloc data 5 ----> mempool_alloc_test alloc data 6 ----> mempool_alloc_test alloc data 7 mempool free ----> call mempool_free_test ----> call mempool_free_test ----> call mempool_free_test ----> call mempool_free_test call mempool_destroy ----> call mempool_free_test ----> call mempool_free_test ----> call mempool_free_test ----> call mempool_free_test Module MEMPOOL Test End 83 응용 프로그램 ◦ 사용자 공간에서 프로세스로 동 작 ◦ 하드웨어에 직접 접근 못함 디바이스 파일 디바이스 드라이버 ◦ 문자 디바이스 드라이버의 경우 응용 프로그램에서 해당 디바이 스 드라이버와 연결된 디바이스 파일을 통해 호출 ◦ 블록 디바이스 드라이버나 네트 워크 디바이스 드라이버는 커널 에서 직접 호출 85 86 시리얼 입출력 ◦ 시작과 끝이 없음 ◦ 처리 용량, 보존 여부 불명확 87 88 커널은 디바이스 파일에 기록된 ◦ 디바이스 타입과 주번호를 이용해 ◦ 커널 내에 등록된 디바이스 드라이버 함수를 연결 ◦ struct char_device_struct chrdevs[MAX_PROBE_HASH]; 여기에 struct file_operations *fops 필드 관리 89 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 *); }; 90 91 반환값 : 정상 (0), 오류값 92 반환값 : 성공 (0), 오류값 93 xxx_read에 전달된 버퍼의 주소값은 프로세스 메모리 공간의 주소이므로 직접 사용할 수 없음 파일 포인터 f_pos 관리 반환값 : 처리된 데이터 수나 오류 값 94 xxx_write에 전달된 버퍼의 주소값은 프로세스 메모리 공간의 주소이므로 직접 사용할 수 없음 파일 포인터 f_pos 관리 반환값 : 처리된 데이터 수 혹은 오류값 95 96 97 file_operations 구조체 커널에 등록 ◦ register_chrdev : 디바이스 등록 ◦ unregister_chrdev : 등록된 디바이스 제거 int register_chrdev(unsigned int major, const char *name, struct file_operations *fops) ◦ major : 주번호, 응용 프로그램에서 디바이스 파일을 이용해 디바 이스 드라이버를 찾을 때 사용 ◦ name : 디바이스 드라이버 명 ◦ fops : 함수 포인터 int unregister_chrdev(unsigned int major, const char *name) 98 int xxx_open(struct inode *inode, struct file *filp) { } // close 처리 int xxx_release(struct inode *inode, struct file *filp) { } struct file_operations xxx_fops = { .owner = THIS_MODULE, .open = xxx_open, .close = xxx_release, }; int xxx_init(void) { register_chrdev( 240, “char_dev”, &xxx_fops ); } void xxx_exit(void) { unregister_chrdev( 240, “char_dev” ); } module_init(xxx_init); module_exit(xxx_exit); 99 10 0 #define #define CALL_DEV_NAME CALL_DEV_MAJOR "calldev" 240 int call_open (struct inode *inode, struct file *filp) { int num = MINOR(inode->i_rdev); printk( "call open -> minor : %d\n", num ); return 0; } loff_t call_llseek (struct file *filp, loff_t off, int whence ) { printk( "call llseek -> off : %08X, whenec : %08X\n", off, whence ); return 0x23; } 10 1 ssize_t call_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { printk( "call read -> buf : %08X, count : %08X \n", buf, count ); return 0x33; } ssize_t call_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos) { printk( "call write -> buf : %08X, count : %08X \n", buf, count ); return 0x43; } int call_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { printk( "call ioctl -> cmd : %08X, arg : %08X \n", cmd, arg ); return 0x53; } int call_release (struct inode *inode, struct file *filp) { printk( "call release \n" ); return 0; } 10 2 struct file_operations call_fops = { .owner = THIS_MODULE, .llseek = call_llseek, .read = call_read, .write = call_write, .ioctl = call_ioctl, .open = call_open, .release = call_release, }; int call_init(void) { int result; printk( "call call_init \n" ); result = register_chrdev( CALL_DEV_MAJOR, CALL_DEV_NAME, &call_fops); if (result < 0) return result; return 0; } void call_exit(void) { printk( "call call_exit \n" ); unregister_chrdev( CALL_DEV_MAJOR, CALL_DEV_NAME ); } module_init(call_init); module_exit(call_exit); 10 3 #define DEVICE_FILENAME "/dev/calldev" int main() { int dev; char buff[128]; int ret; printf( "1) device file open\n"); dev = open( DEVICE_FILENAME, O_RDWR|O_NDELAY ); if( dev >= 0 ) { printf( "2) seek function call\n"); ret = lseek( dev, 0x20, SEEK_SET ); printf( "ret = %08X\n", ret ); printf( "3) read function call\n"); ret = read(dev,0x30, 0x31 ); printf( "ret = %08X\n", ret ); 10 4 printf( "4) write function call\n"); ret = write(dev,0x40,0x41 ); printf( "ret = %08X\n", ret ); printf( "5) ioctl function call\n"); ret = ioctl(dev, 0x51, 0x52 ); printf( "ret = %08X\n", ret ); printf( "6) device file close\n"); ret = close(dev); printf( "ret = %08X\n", ret ); } return 0; } 10 5 [ root@ ] # mknod /dev/calldev c 240 32 [ root@ ] # insmod call_dev.ko [ root@ ] # lsmod Module Size Used by call_dev 2432 0 autofs 15438 0 [ root@ ] # gcc –o call_app call_app.c [ root@ ] # ./call_app 1) device file open 2) seek function call ret = 00000023 3) read function call ret = 00000033 4) write function call ret = 00000043 5) ioctl function call ret = 00000053 6) device file close 10 6 [ root@ ] # dmesg call call_init call open -> minor : 32 call llseek -> off : 00000020, whenec : 00000000 call read -> buf : 00000030, count : 00000031 call write -> buf : 00000040, count : 00000041 call ioctl -> cmd : 00000051, arg : 00000052 call release [ root@ ] # rmmod call_dev 10 7 10 8 디바이스 드라이버의 등록과 해제 디바이스 드라이버의 내부 구조체의 메모리 할당과 해제 여러 프로세스가 하나의 디바이스에 접근할 때 필요한 사전 처리 및 종료 시 처리 하드웨어 검출 처리 및 에러 처리 하드웨어 초기화와 제거 가능한 하드웨어의 제거 처리 응용 프로그램에서 디바이스 드라이버를 사용하는 경우 의 초기 처리 및 사용 종료 처리 부 번호에 관련된 프로세스별 처리 프로세스별 메모리 할당과 해제 사용하는 모듈 수의 관리 10 9 모듈 적재와 커널 부팅 처리 과정 또는 제거 과정 ◦ insmod 명령 : module_init – 모듈 적재 과정 ◦ rmmod 명령 : module_exit – 모듈 제거 과정 응용 프로그램이 디바이스 파일을 여는 과정과 닫는 과 정 ◦ open( ) 함수 : file_operations.open – 디바이스 파일을 여는 과 정 ◦ close( ) 함수 : file.operations.release – 디바이스 파일을 닫는 과정 11 0 디바이스 드라이버의 등록 디바이스 드라이버에 내부 구조체의 메모리 할당 ◦ 전역 변수 등의 할당받은 메모리 초기화 여러 프로세스가 하나의 디바이스에 접근하는 경우에 필 요한 사전 처리 ◦ 20장. 다중 프로세스 환경의 디바이스 드라이버 주 번호에 종속된 부 번호를 관리하기 위한 사전 처리 ◦ 9장. 주 번호와 부 번호의 처리 하드웨어 검출 처리 및 에러 처리 ◦ 하드웨어가 검출되지 않거나 수행이 곤란한 에러가 발생할 시 모 듈이 커널에 적재되는 시점에 거부하는 것이 좋음 하드웨어 초기화 11 1 디바이스 드라이버의 해제 ◦ 커널 자원을 점유하고 있기 때문에 반드시 해제 디바이스 드라이버에 할당된 모든 메모리 해제 ◦ 적절히 제거하지 않으면 가용 메모리가 부족해 짐 하드웨어 제거에 따른 처리 11 2 static xxx_info *info = NULL; int xxx_init(void) { xxx_probe(... xxx_setup(... register_chrdev(... info = kmalloc(... xxx_setupinfo(... xxx_setupminor(... } void xxx_exit(void) { kfree(... unregister_chrdev(... xxx_shutdown(... } // // // // // // 하드웨어 검출 처리 및 에러 처리 하드웨어 초기화 디바이스 드라이버의 등록 디바이스 드라이버 동작에 필요한 메모리 할당 여러 프로세스가 디바이스 하나에 접근하는 경우 사전 처리 주 번호에 종속된 부 번호를 관리하기 위한 사전 처리 // 디바이스 드라이버에 할당된 모든 메모리 해제 // 디바이스 드라이버 해제 // 하드웨어 제거에 따른 처리 module_init(xxx_init); module_exit(xxx_exit); 11 3 int fd fd = open(DEVICE_FILENAME, O_RDWR|O_NDELAY); if (fd < 0) { printf(“error number %d”, error); exit(1); } struct file_operations call_fops = { .open = xxx_open, ... }; int xxx_open(struct inode *inode, struct file *filp) { int err = 0; // open 시 처리 내용들 ... return err; } 11 4 디바이스 드라이버가 처음 열렸을 때 하드웨어 초 기화 디바이스 드라이버의 동작에 필요한 에러 체크 부 번호에 대한 처리가 필요한 경우 파일 오퍼레이 션 구조체 갱신 프로세스별 메모리 할당과 초기화 모듈의 사용 횟수 증가 (커널 2.4) 11 5 if (fd >= 0) close(fd); struct file_operations call_fops = { .release = xxx_release, ... }; int xxx_release(struct inode *inode, struct file *filp) { // close 시 처리 내용 return 0; } 11 6 프로세스별 할당 메모리 해제 모듈 사용 횟수 감소 (커널 2.4) 11 7 11 8 read() 함수의 구현 (1) int fd; char buff[128]; fd = open(“/dev/dio”, O_RDWR|O_NDElAY); … read(fd, buff, 16); // 하드웨어 데이터 읽기 … write(fd, buff, 32); // 하드웨어 데이터 쓰기 … close(fd); 디바이스파일의 주 번호에 의해 연결된 커널 내에 디바이스 드라이버 file_operations 구조체 함수 필드 중 read 필드와 write필드가 각각 호출됨 디지털 입출력을 처리하는 디바이스파일 “/dev/dio”에 데이터를 써넣거나 읽기 read() 함수의 구현 (2) ssize_t xxx_read(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { // 하드웨어에서 데이터 읽기 } ssize_t write(struct file *filp, const char *buf, size_t count, loff_t *fpos) { // 하드웨어에 데이터 쓰기 } struct file_operations xxx_fops = { … •사용자 메모리 공간과 커널 메모리 공간 사이의 .read = xxx_read, 데이터 이동 .write = xxx_write, •처리 조건이 되지 않을 때의 블록 처리 … •하드웨어 제어 함수 }; •여러 프로세스가 동시에 접근했을 때의 경쟁 처리 •인터럽트 서비스 함수와의 경쟁 처리 디바이스 드라이버의 형식 read() 함수의 구현 (3) I/O 제어함수 inb, inw, inl .. readb .. read(fd,buff,size) read 메모리 복사 함수 put_user, copy_to_user Device file 하 드 file_operation buffer interrupt service 웨 어 write(fd,buff,size) 메모리 복사 함수 get_user, copy_from_user write I/O 제어함수 outb, outw, outl .. writeb .. read() 함수의 구현 (4) 데이터 전달 함수 copy_to_user(to, from, n) put_user(x, ptr) fd = open( const char *pathname, int flags); ret = read(int fd, void *buf, size_t count); 응용프로그램에서 전달한 버퍼의 주소 ssize_t xxx_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { return ret; 응용프로그램에서 } 요청한 데이터의 크기 read() 함수의 구현 (5) 응용 프로그램으로의 데이터 전달 하드웨어 정보를 읽어 들이거나 인터럽트 처리 루틴과 같이 read()이외에서 발생되어 저장된 버퍼 데이터를 이용 시간적으로 굳이 동기화가 필요없는 간단한 하드웨어 처리 장치라면 직접 하드웨어에서 읽어서 전달하는 구조로 작성 시간적으로 민감한 경우라면 인터럽트를 이용한 버퍼구조 로 작성 개발자의 설계방식에 따라 선택적 하드웨어를 다룬다면 다음 함수 이용 inb, inw, inl, insb, insw, insl, outb, outw, outl, outsb, outsw, outsl readb, readw, readl, writeb, writew, writel, memset_io, memcpy_fromio, memcpy_toio read() 함수의 구현 (6) 디바이스 드라이버의 read() 함수가 반환하는 값은 0 이상인 경우와 0 미만인 경우 반환값은 응용프로그램에서 호출한 read()함수에 그대로 전 달되므로 응용 프로그램은 반환값을 보고 read()함수 결과처 리 0 이상은 read() 함수가 정상적으로 수행됨 이 값은 디바이스 드라이버의 read()함수가 응용프로그램에 전달한 데이터의 개수(count)가 됨 Read() 함수가 수행되는 시점에 하드웨어에서 발생한 데이터 의 개수가 count 보다 적을 경우 count 값이 만족될 때까지 기다릴것인지 아니면 발생된 데이 터만 처리하고 종료할 것인지는 응용프로그램에서 디바이스 파일을 열었을 때 어떤 옵션을 주었는가에 따라 달라짐. read()함수의 매개변수중에서 struct file *filp를 참조하여 판 단 read() 함수의 구현 (7) ssize_t xxx_read(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { … If(filp->f_flags & O_NONBLOCK) { // 즉시 처리한다. } else { // 블록 처리한다. } •응용프로그램이 O_NONBLOCK이나 O_NDELAY … 를 지정한 상태로 디바이스 파일을 열었다면 현재 } 발생된 데이터만 버퍼에 써넣고 함수 종료. •그렇지 야 함. 않으면 count 값이 만족될 때까지 기다려 read() 함수의 구현 (8) 반환값이 요구된 값과 항상 일치하지 않음 O_NONBLOCK이나 O_NDELAY 옵션을 주고 파일을 열었 을 때 read() 함수의 반환값이 음수값일 때 EAGAIN : O_NONBLOCK으로 열렸지만 즉시 읽을 수 있 는 데이터가 없슴. EIO : I/O 에러가 발생 EFAULT : buf는 접근할 수 없는 주소 공간을 가리킴 디바이스 드라이버를 읽기 / 쓰기 위치를 관리하는 형태로 작성해야 한다면 f_pos 매개변수를 처리해야 함. read() 함수의 구현 (9) ssize_t xxx_read(strcuct file *filp, const ssize_t rdwr_read(strcuct file *filp, char *buf, size_t count, loff_t *f_pos) { char *buf, size_t count, loff_t *f_pos) { if(!(준비된 데이터가 있는가?)) { unsigned char status; if(!(filp->f_flags & O_NOBLOCK)) { int loop; // 블록 모드로 열렸다면 // 프로세스를 재운다. // 하드웨어에서 데이터를 읽는다. } for(loop = 0; loop < count; loop++) { } status = inb(RDWR_READ_ADDR); // 하드웨어에서 데이터를 읽는다. // 사용자 공간에 데이터를 전달한다. // inb(), outb(),…., read(), write() 함수사용 put_user(status, (char *) &buf[loop]; // 또는 버퍼를 읽는다. } // 처리된 데이터 개수 // 사용자 공간에 데이터를 전달한다. return count; // copy_to_user, put_user } return 처리된 데이터 개수; } write() 함수의 구현 (1) 데이터 전달 함수 copy_from_user(to, from, n) get_user(x, ptr) fd = open( const char *pathname, int flags); ret = write(int fd, void *buf, size_t count); 응용프로그램에서 전달한 버퍼의 주소 ssize_t xxx_write(struct file *filp, char *buf, size_t count, loff_t *f_pos) { return ret; 응용프로그램에서 } 요청한 데이터의 크기 write() 함수의 구현 (2) 디바이스 드라이버의 write() 함수가 반환하는 값은 0 이상인 경우와 0 미만인 경우 반환값은 응용프로그램에서 호출한 write()함수에 그대로 전 달되므로 응용 프로그램은 반환값을 보고 write()함수 결과처 리 0 이상은 write() 함수가 정상적으로 수행됨 이 값은 디바이스 드라이버의 write()함수가 응용프로그램에 전달한 데이터의 개수(count)가 됨 write() 함수가 수행되는 시점에 하드웨어에서 발생한 데이터 의 개수가 count 보다 적을 경우 count 값이 만족될 때까지 기다릴것인지 아니면 발생된 데이 터만 처리하고 종료할 것인지는 응용프로그램에서 디바이스 파일을 열었을 때 어떤 옵션을 주었는가에 따라 달라짐. write()함수의 매개변수중에서 struct file *filp를 참조하여 판단 write() 함수의 구현 (3) 반환값이 요구된 값과 항상 일치하지 않음 O_NONBLOCK이나 O_NDELAY 옵션을 주고 파일을 열었 을 때 write() 함수의 반환값이 음수값일 때 EAGAIN : O_NONBLOCK으로 열렸지만 즉시 읽을 수 있 는 데이터가 없슴. EIO : I/O 에러가 발생 EFAULT : buf는 접근할 수 없는 주고 공간을 가리킴 ENOSPC : 데이터를 위한 공간이 없다. 디바이스 드라이버를 읽기 / 쓰기 위치를 관리하는 형태로 작성해야 한다면 f_pos 매개변수를 처리해야 함. struct file *filp •응용 프로그램에서 디바이스 파일을 open함수로 열었을 때 flags에 설정된 값. struct file { … unsigned int f_flags; •현재의 읽기/쓰기 위치를 담 loff_t f_pos; 음 void *private_data; struct file_operation *f_op; … }; •프로세스가 함수간에 메모리 를 공유할 목적이라면 적극 활 용 •부번호에 따라 다르게 동작하 는 디바이스 드라이버를 작성 할 수 있슴. I/O mapped I/O memory mapped I/O 하드웨어에서 데이터를 읽음. inb(), inw(), inl(), insb(), insw(), insl() 하드웨어에서 데이터를 읽음. 하드웨어에서 데이터를 씀. outb(), outw(), outl(), outb(), outw(), outsl() 하드웨어에서 데이터를 씀. • • • 사용하려면 #include <asm/io.h>를 소 스에 포함(대부분 매크로 함수) b(8bit), w(16bit), l(32bit)로 처리하는 시스템의 I/O 버스폭과 관련 스트림 I/O 명령들은 뒤에 s가 붙어 있 는것을 사용 readb(), readw(), readl(), memcpy_fromio() writeb(), writew(), writel(), memcpy_toio() • 사용하려면 #include <asm/io.h>를 소 스에 포함(대부분 매크로 함수) 사용자 메모리 공간 일반적인 동작 상태 의미 프로세스 #1 (사용자 모드) 커널 메모리 공간 프로세스 #1 (커널 모드) 시스템 콜 인터럽트 시스템 전체를 처리 하는 상태 의미 복귀 저수준 파일 입출력 함수(소프트웨어 인 터럽트를 이용) 프로세스 #2 (사용자 모드) 프로세스 #2 (커널 모드) 시스템 콜 인터럽트 커널 모드에서 사용자 메모리 공간 접근 복귀 프로세스 #3 (사용자 모드) verify_area(type, addr, size) 프로세스 #3 (커널 모드) 시스템 콜 인터럽트 복귀 copy_to_user(to, from, n) copy_from_user(to, from, n) get_user(x, ptr) put_user(x, ptr) #define RDWR_DEV_NAME #define RDWR_DEV_MAJOR "rdwrdev" 240 #define RDWR_WRITE_ADDR #define RDWR_READ_ADDR 0x0378 0x0379 int rdwr_open (struct inode *inode, struct file *filp) { return 0; } ssize_t rdwr_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { unsigned char status; int loop; for( loop = 0; loop < count; loop++ ) { status = inb( RDWR_READ_ADDR ); put_user( status, (char *) &buf[loop] ); } } return count; 13 4 ssize_t rdwr_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos) { unsigned char status; int loop; } for( loop = 0; loop < count; loop++ ) { get_user( status, (char *) buf ); outb( status , RDWR_WRITE_ADDR ); } return count; int rdwr_release (struct inode *inode, struct file *filp) { return 0; } struct file_operations rdwr_fops = { .owner = THIS_MODULE, .read = rdwr_read, .write = rdwr_write, .open = rdwr_open, .release = rdwr_release, }; 13 5 int rdwr_init(void) { int result; result = register_chrdev( RDWR_DEV_MAJOR, RDWR_DEV_NAME, &rdwr_fops); if (result < 0) return result; } return 0; void rdwr_exit(void) { unregister_chrdev( RDWR_DEV_MAJOR, RDWR_DEV_NAME ); } module_init(rdwr_init); module_exit(rdwr_exit); MODULE_LICENSE("Dual BSD/GPL"); 13 6 int main() { int dev; char buff[128]; int loop; dev = open( DEVICE_FILENAME, O_RDWR|O_NDELAY ); if( dev >= 0 ) { printf( "wait... input\n" ); } while(1) { if( read(dev,buff,1 ) == 1 ) { printf( "read data [%02X]\n", buff[0] & 0xFF ); if( !(buff[0] & 0x10) ) break; } } printf( "input ok...\n"); printf( "led flashing...\n"); for( loop=0; loop<5; loop++ ) { buff[0] = 0xFF; write(dev,buff,1 ); sleep(1); buff[0] = 0x00; write(dev,buff,1 ); sleep(1); } close(dev); 13 7 13 8 주 번호 리눅스 파일 - 응용 프로그램 #1 응용 프로그램 #2 디바이스 제어 요청 커널(Kernel) 디바이스 제어 함수 호출 디바이스 드라이버 디바이스 제어 디바이스 그림9-1] 리눅스에서의 디바이스 제어 구조 - 디렉토리 구조로 알려진 레이블의 계층구 조에 의해서 구성 레이블에 의해 참조되는 파일 정규파일(regular file) 디렉토리 파일(directory file) 특수파일(special file) - 커널 내부의 함수를 호출할 수 있는 정 보 제공 - 디바이스 파일(타입정보, 주 번호, 부 번호) - mknod /dev/xxx c M m 응용프로그램 OPEN() READ() 부 번호 주 번호 디바이스 드라이버 타입정보 chrdevs[주번호] blkdevs[주번호] 파일 입출력 함수 정보 file_operation xxx_open() xxx_read() 제어하려는 디바이스를 구분하기 위한 디바이스의 ID Inode(/include/linux/fs.h) struct inode { … unsigned long atomic_t umode_t unsigned int uid_t gid_t dev_t }; i_ino; i_count; i_mode; i_nlink; i_uid; i_gid; - 파일의 유형:정규파일, 디렉토리, 특수 등 허가권 소유권과 그룹id 하드링크 계수 마지막 수정과 마지막 접근 시간 정규/디렉토리 파일 : 디스크 블록의 위치 특수 파일 : 주/보조 장치 번호 심볼릭 링크 : 심볼릭 링크의 값 … i_rdev; loff_t struct timespec struct timespec struct timespec unsigned int unsigned long unsigned long unsigned long unsigned short unsigned char … i_size; i_atime; i_mtime; i_ctime; i_blkbits; i_blksize; i_version; i_blocks; i_bytes; i_sock; /2 bin3 ls5 usr4 cp6 test.c7 label inode # . 2 .. 2 bin 3 usr 4 부 번호 ◦ 디바이스의 구분 COM1, COM2 구분 ◦ 용도에 따른 디바이스의 구분(misc 계열 디바이스) misc_register(&xxx_miscdev) misc_deregister(&xxx_miscdev) ◦ 블록 디바이스의 파티션 구분 struct miscdevice{ int minor; const char *name; struct file_operation *fops; struct miscdevice *next, *prev; devfs_handle_t devfs_handle; } 디바이스 타입 ◦ kdev_t (2.4) “include/linux/kdev_t.h” typedef unsigned short kdev_t; #define MINORBITS #define MINORMASK #define MAJOR(dev) #define MINOR(dev) #define HASHDEV(dev) #define MKDEV(ma,mi) 8bits 8 ((1U << MINORBITS) - 1) ((unsigned int) ((dev) >> MINORBITS)) ((unsigned int) ((dev) & MINORMASK)) ((unsigned int) (dev)) (((ma) << MINORBITS) | (mi)) 8bits Major Minor ◦ dev_t (2.6) “include/linux/coda.h” typedef unsigned long dev_t; #define MINORBITS 12bits 20bits Major Minor 20 struct file_operations minor0_fops = { // 부 번호가 1일 경우 처리하는 파일 오퍼레이션 …. }; struct file_operations minor1_fops = { // 부 번호가 2일 경우 처리하는 파일 오퍼레이션 ….}; Int minor_open(struct inode *inode, struct file *filp) { switch(MINOR(inode->i_rdev)) { case 1: filp->f_op = &minor0_fops; break; case 2: filp->f_op = &minor1_fops; break; … default : return –ENXIO; } } if(filp->f_op && filp->f_op->open) return filp->f_op->open(inode, filp); retun 0; struct file_operations master_fops= { .open = minor_open, }; Int xxx_init(void) { int result; result = register_chrdev(MINOR_DEV_MAJOR, MINOR_DEV_NAME, &master_fops); } #include <> Int minor0_open(){…} Ssize_t minor0_write(){…} Int minor0_release(){…} Int minor1_open(){…} Ssize_t minor1_read(){…} Int minor1_release(){…} Struct file_operations minor0_fops = {…} Struct file_operations minor1_fops = {…} Int minor_open() { switch(MINOR(inode->i_rdev)) { case 1: filp->f_op = &minor0_fops; break; case 2: filp->f_op = &minor1_fops; break; default : return –ENXIO; } #include <> #define READ_DEVICE_FILENAME “/dev/minor_read” #define WRITE_DEVICE_FILENAME “/dev/minor_write” Int main() {… read_dev = open(READ_DEVICE_FILENAME, O_RDWR|O_NDELAY); write_dev = open(WRITE_DEVICE_FILENAME, O_RDWR|O_NDELAY); while(1){ if(read(read_dev,buff,1) == 1) // 클립 접촉여부 확 인 { printf(“ read data…”); if(!(buff[0] & 0x10)) break; } for(loop=0;loop<5;loop++) { … // LED ON wirte(write_dev,buff,1); … } close(read_dev); close(write_dev); if(filp->f_op && filp->f_op->open) return filp->f_op->open(inode, filp); retun 0; } Int init_module() Int cleanup_module() return 0; } ◦ 실행방법 [root @#] mknod /dev/minor_write c 240 1 [root @#] mknod /dev/minor_write c 240 2 [root @#] make 2.4 [root @#] insmod minor_dev.o 2.6 [root @#] insmod minor_dev.ko 참고) lsmod cat /proc/ksyms | grep <모듈이름> [root @#] ./minor.app .. .. READ DATA [7F] READ DATA [7F] READ DATA [7F] READ DATA [6F] Input ok… led flashing… [root @#] [root @#] rmmod minor_dev [root @#] dmesg Document/devices.txt 테스트나 특정 플랫폼용으로 할당된 주 번호와 부 번호 ◦ 주 번호 60~63 120~127 240~254 ◦ 부 번호 주 번호 10번의 부 번호 240~255 #define #define #define #define MINOR_DEV_NAME MINOR_DEV_MAJOR MINOR_WRITE_ADDR MINOR_READ_ADDR "minordev" 240 0x0378 0x0379 int minor0_open (struct inode *inode, struct file *filp) { printk( "call minor0_open\n" ); return 0; } ssize_t minor0_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos) { unsigned char status; int loop; } for( loop = 0; loop < count; loop++ ) { get_user( status, (char *) buf ); outb( status , MINOR_WRITE_ADDR ); } return count; int minor0_release (struct inode *inode, struct file *filp) { printk( "call minor0_release\n" ); return 0; } int minor1_open (struct inode *inode, struct file *filp) { printk( "call minor1_open\n" ); return 0; } ssize_t minor1_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { unsigned char status; int loop; for( loop = 0; loop < count; loop++ ) { status = inb( MINOR_READ_ADDR ); put_user( status, (char *) &buf[loop] ); } } return count; int minor1_release (struct inode *inode, struct file *filp) { printk( "call minor1_release\n" ); return 0; } struct file_operations minor0_fops = { .owner = THIS_MODULE, .write = minor0_write, .open = minor0_open, .release = minor0_release, }; 14 8 struct file_operations minor1_fops = { .owner = THIS_MODULE, .read = minor1_read, .open = minor1_open, .release = minor1_release, }; int minor_open (struct inode *inode, struct file *filp) { printk( "call minor_open\n" ); switch (MINOR(inode->i_rdev)) { case 1: filp->f_op = &minor0_fops; break; case 2: filp->f_op = &minor1_fops; break; default : return -ENXIO; } if (filp->f_op && filp->f_op->open) return filp->f_op->open(inode,filp); } int minor_init(void) { int result; result = register_chrdev( MINOR_DEV_MAJOR, MINOR_DEV_NAME, &minor_fops); if (result < 0) return result; } return 0; void minor_exit(void) { unregister_chrdev( MINOR_DEV_MAJOR, MINOR_DEV_NAME ); } module_init(minor_init); module_exit(minor_exit); MODULE_LICENSE("Dual BSD/GPL"); return 0; struct file_operations minor_fops = { .owner = THIS_MODULE, .open = minor_open, }; 14 9 #define READ_DEVICE_FILENAME "/dev/minor_read" #define WRITE_DEVICE_FILENAME "/dev/minor_write" int main() { int read_dev; int write_dev; char buff[128]; int loop; printf( "wait... input\n" ); while(1) { if( read(read_dev,buff,1 ) == 1 ) { printf( "read data [%02X]\n", buff[0] & 0xFF ); if( !(buff[0] & 0x10) ) break; } } printf( "input ok...\n"); printf( "led flashing...\n"); for( loop=0; loop<5; loop++ ) { buff[0] = 0xFF; write(write_dev,buff,1 ); sleep(1); buff[0] = 0x00; write(write_dev,buff,1 ); sleep(1); } read_dev = open( READ_DEVICE_FILENAME, O_RDWR|O_NDELAY ); if( read_dev < 0 ) { printf( READ_DEVICE_FILENAME "open error\n" ); exit(1); } write_dev = open( WRITE_DEVICE_FILENAME, O_RDWR|O_NDELAY ); if( write_dev < 0 ) { printf( WRITE_DEVICE_FILENAME "open error\n" ); close( read_dev ); exit(1); } close(read_dev); close(write_dev); } return 0; 15 0 15 1 01. 디바이스 제어 ◦ ◦ ◦ ◦ 디바이스 제어 ioctl()함수의 역할 ioctl()함수의 일반적 형태 ioctl()함수의 매개변수 cmd관련 MACRO함수들 02. ioctl() 함수 사용 예 디바이스 제어 ioctl() 함수의 역할 ◦ ◦ ◦ ◦ ◦ Input / Output Control device driver와 application간에 범용적인 대화 통로 할당 받은 인터럽트 변경 점유하고 있는 물리주소 변경 그외 여러가지… ioctl() 함수의 매개변수 int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); linux/fs.h struct file_operations inode : file inode structure file : device file 자신 cmd = ioctl 명령 번호 arg = arguement cmd관련 MACRO 함수 31 15 0 2 14 8 8 direction size type number ◦ ◦ ◦ ◦ 32bits cmd의 구성 구분번호(ordinal / sequential number) : 2 bits 명령을 구분하는 명령어의 순차번호 매직번호(type) : 8 bits 다른 디바이스 드라이버의 ioctl명령과 구분하기 위한 값 데이터크기(size) : 14 bits 매개변수 arg를 통해 전달되는 메모리의 크기 읽기쓰기구분(direction) : 8 bits 일기, 쓰기를 위한 요구 명령을 구분하는 속성 asm/ioctl.h /* asm/ioctl.h */ #define _IOC_NRBITS #define _IOC_TYPEBITS #define _IOC_SIZEBITS #define _IOC_DIRBITS 8 8 14 2 /* /* /* /* Number field */ Type field */ Size field */ Direction field */ asm/ioctl.h /* Direction bits.*/ #define _IOC_NONE #define _IOC_WRITE #define _IOC_READ 0U 1U 2 /* Nothing */ /* Writing */ /* Reading */ /* 각 field들을 완전한 32 bits인 하나의 cmd 로 만듬*/ #define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ ((type) << _IOC_TYPESHIFT) | \ ((nr) << _IOC_NRSHIFT) | \ ((size) << _IOC_SIZESHIFT)) asm/ioctl.h (명령어 생성 매크로) /* used to create numbers */ #define _IO(type,nr) /* 의미 없는 cmd 로 … */ _IOC(_IOC_NONE,(type),(nr),0) #define _IOR(type,nr,size) /* 읽기용 cmd 로 …*/ _IOC(_IOC_READ,(type),(nr),sizeof(size)) #define _IOW(type,nr,size) /* 쓰기용 cmd 로 … */ _IOC(_IOC_WRITE,(type),(nr),sizeof(size)) #define _IOWR(type,nr,size) /* 읽기/쓰기용 cmd 로 */ _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size)) asm/ioctl.h (명령어 디코드 매크로) /* used to decode ioctl numbers.. */ #define _IOC_DIR(nr) /* Direction field 값 얻기 */ (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK) #define _IOC_TYPE(nr) /* Type field 값 얻기 */ (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK) #define _IOC_NR(nr) /* Number field 값 얻기 */ (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK) #define _IOC_SIZE(nr) /* Size field 값 얻기 */ (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK) #ifndef _IOCTLTEST_H_ #define _IOCTLTEST_H_ #define IOCTLTEST_MAGIC 't' typedef struct { unsigned long size; unsigned char buff[128]; } __attribute__ ((packed)) ioctl_test_info; #define IOCTLTEST_LEDOFF #define IOCTLTEST_LEDON #define IOCTLTEST_GETSTATE _IO( IOCTLTEST_MAGIC, 0 ) _IO( IOCTLTEST_MAGIC, 1 ) _IO( IOCTLTEST_MAGIC, 2 ) #define IOCTLTEST_READ _IOR( IOCTLTEST_MAGIC, 3 , ioctl_test_info ) #define IOCTLTEST_WRITE _IOW( IOCTLTEST_MAGIC, 4 , ioctl_test_info ) #define IOCTLTEST_WRITE_READ _IOWR( IOCTLTEST_MAGIC, 5 , ioctl_test_info ) #define IOCTLTEST_MAXNR 6 #endif // IOCTLTEST_H_ 16 1 #include "ioctl_test.h" #define IOCTLTEST_DEV_NAME #define IOCTLTEST_DEV_MAJOR "ioctldev" 240 #define IOCTLTEST_WRITE_ADDR #define IOCTLTEST_READ_ADDR 0x0378 0x0379 int ioctltest_open (struct inode *inode, struct file *filp) { return 0; } int ioctltest_release (struct inode *inode, struct file *filp) { return 0; } int ioctltest_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { ioctl_test_info ctrl_info; int err, size; int loop; if( _IOC_TYPE( cmd ) != IOCTLTEST_MAGIC ) return -EINVAL; if( _IOC_NR( cmd ) >= IOCTLTEST_MAXNR ) return -EINVAL; size = _IOC_SIZE( cmd ); 16 2 if( size ) { err = 0; if( _IOC_DIR( cmd ) & _IOC_READ ) err = verify_area( VERIFY_WRITE, (void *) arg, size ); else if( _IOC_DIR( cmd ) & _IOC_WRITE ) err = verify_area( VERIFY_READ , (void *) arg, size ); } if( err ) return err; switch( cmd ) { case IOCTLTEST_LEDOFF : outb( 0x00 , IOCTLTEST_WRITE_ADDR ); break; case IOCTLTEST_LEDON : outb( 0xFF , IOCTLTEST_WRITE_ADDR ); break; case IOCTLTEST_GETSTATE : return inb( IOCTLTEST_READ_ADDR ); case IOCTLTEST_READ : ctrl_info.buff[0] = inb( IOCTLTEST_READ_ADDR ); ctrl_info.size = 1; copy_to_user ( (void *) arg, (const void *) &ctrl_info, (unsigned long ) size ); break; case IOCTLTEST_WRITE : copy_from_user ( (void *)&ctrl_info, (const void *) arg, size ); for( loop = 0; loop < ctrl_info.size; loop++ ) outb( ctrl_info.buff[loop] , IOCTLTEST_WRITE_ADDR ); break; case IOCTLTEST_WRITE_READ : copy_from_user ( (void *)&ctrl_info, (const void *) arg, size ); for( loop = 0; loop < ctrl_info.size; loop++ ) outb( ctrl_info.buff[loop] , IOCTLTEST_WRITE_ADDR ); } } return 0; ctrl_info.buff[0] = inb( IOCTLTEST_READ_ADDR ); ctrl_info.size = 1; copy_to_user ( (void *) arg, (const void *) &ctrl_info, (unsigned long ) size ); break; 16 3 struct file_operations ioctltest_fops = { .owner = THIS_MODULE, .ioctl = ioctltest_ioctl, .open = ioctltest_open, .release = ioctltest_release, }; int ioctltest_init(void) { int result; result = register_chrdev( IOCTLTEST_DEV_MAJOR, IOCTLTEST_DEV_NAME, &ioctltest_fops); if (result < 0) return result; } return 0; void ioctltest_exit(void) { unregister_chrdev( IOCTLTEST_DEV_MAJOR, IOCTLTEST_DEV_NAME ); } module_init(ioctltest_init); module_exit(ioctltest_exit); MODULE_LICENSE("Dual BSD/GPL"); 16 4 int main() { ioctl_test_info info; int dev; int state; int cnt; dev = open( DEVICE_FILENAME, O_RDWR|O_NDELAY ); if( dev >= 0 ) { printf( "wait... input\n" ); ioctl(dev, IOCTLTEST_LEDON ); while(1) { state = ioctl(dev, IOCTLTEST_GETSTATE ); if( !(state & 0x10) ) break; } sleep(1); ioctl(dev, IOCTLTEST_LEDOFF ); 16 5 printf( "wait... input\n" ); while(1) { info.size = 0; ioctl(dev, IOCTLTEST_READ, &info ); if( info.size > 0 ) { if( !(info.buff[0] & 0x10) ) break; } } info.size = 1; info.buff[0] = 0xFF; for( cnt=0; cnt<10; cnt++ ) { ioctl(dev, IOCTLTEST_WRITE, &info ); info.buff[0] = ~info.buff[0]; usleep( 500000 ); } printf( "wait... input\n" ); cnt = 0; state = 0xFF; 16 6 while(1) { info.size = 1; info.buff[0] = state; ioctl(dev, IOCTLTEST_WRITE_READ, &info ); if( info.size > 0 ) { if( !(info.buff[0] & 0x10) ) break; } cnt++; if( cnt >= 2 ) { cnt = 0; state = ~state; } usleep( 100000 ); } ioctl(dev, IOCTLTEST_LEDOFF ); close(dev); } return 0; } 16 7 # mknod /dev/ioctldev c 240 0 # insmod ioctl_dev.ko # ./ioctl_app wait… input wait… input wait… input # rmmod ioctl_dev 16 8 16 9 하드웨어 (x86) RTC(Real Time Clock) TSC(Time Stamp Counter) PIT(Programmable Interval Timer) – 8254 호환칩 APIC(Advanced Programmable Interrupt Controller) 내의 타이머 HZ : 1초당 발생하는 인터럽트 횟수 USER_HZ : HZ 값을 보정하는 수 * jiffies: 2.4에서 tick마다 증가하는 전역 변수 wall_jiffies : 가장 최근의 wall time 갱신 때의 jiffies jiffies_64: 2.6에서 tick 마다 증가하는 전역 변수 * get_jiffies_64() : jiffies_64 값을 참조하기 위한 함수 * * : kernel 2.6 do_IRQ(0) timer_interrupt do_timer_interrupt do_timer_interrupt_hook do_timer jiffies_64++ update_times() 2.4 #define DIFF_TIME(3*HZ/10) u32 aftertime; aftertime = jiffies + DIFF_TIME; #define DIFF_TIME (30) u32 aftertime; aftertime = jiffies + DIFF_TIME; 2.6 #define DIFF_TIME (30) u64 aftertime; aftertime = get_jiffies_64() + DIFF_TIME*(HZ/USER_HZ); #define DIFF_TIME (3*HZ/10) u64 aftertime; aftertime = get_jiffies_64() + DIFF_TIME; 짧은 지연 mdelay() – 밀리초단위(5 ms 이내) udelay() – 마이크로초 단위 지연 ndelay() – 나노초 단위 지연. 시스템 클럭이 1GHz 이상일 때 가능 긴 지연 2.4 #define DELAY_TIME_MSEC (3*HZ/10) unsigned long endtime = jiffies + DELAY_TIME_MSEC; while(jiffies < endtime) schedule(); 2.6 #define DELAY_TIME_MSEC (3*HZ/10) u64 endtime = get_jiffies_64() + DELAY_TIME_MSEC; while(jiffies<endtime); struct timeval { time_t suseconds }; tv_Sec; tv_usec; // sec // microsec struct timespec { time_t long }; tv_sec; tv_nsec; // sec // nano sec do_gettimeofday() : 시스템 시간을 초로 얻어온다 do_settimeofday() : 초로 환산된 시스템 시간을 설정한다 mktime() : 날짜와 시간을 초로 바꾼다 커널 타이머 목록 : 수행할 함수와 함수가 수행되어야 하는 시간에 대한 정보를 담 고 있는 연결리스트 struct timer_list : 커널 타이머 구조체 struct timer_list { struct list_head list; unsigned long expires; unsigned long data; void (*function)(unsigned long); }; // // 만료 시간 참조될 데이타의 주소 // 수행 함수 init_timer() : 커널 타이머 구조체를 초기화한다 add_timer() : 커널 타이머에 수행될 함수를 등록한다 del_timer() : 커널 타이머 목록에서 등록된 것을 제거한다 struct timer_list timer 초기화 init_timer(&timer); timer.expires = get_jiffies_64()+(3*HZ/10); timer.data = (unsigned long)&mng_data[0]; timer.function = handler_function; 등록 void add_timer(&timer); 제거 int del_timer(&timer); 타이머 장치 1/Hz 간격으로 인터럽트 발생 디바이스 드라이버 커널 struct timer_list timer 타이머 인터럽트 처리 1/Hz 초 간격으로 호출 __run_timers 1) 초기화 timer_list 2) 등록 4)자동제거 등록된 timer_list 에서 timer.expires >= jiffies_64 검사 timer.function 호출 후 제거 data 3) 호출 4) 제거 init_timer(&timer); timer.expires = jiffies_64 + 호출시간; timer.data = (unsigned long)data addr; timer.function = handler; add_timer(&timer); void handler (unsigned long arg) { … } del_timer(&timer); #define #define KERNELTIMER_WRITE_ADDR TIME_STEP 0x0378 (2*HZ/10) // 0.2 sec typedef struct { struct timer_list timer; unsigned long led; }__attribute__ ((packed)) KERNEL_TIMER_MANAGER; static KERNEL_TIMER_MANAGER*ptrmng = NULL; void kerneltimer_timeover(unsigned long arg); void kerneltimer_registertimer(KERNEL_TIMER_MANAGER *pdata, unsigned long timeover) { } init_timer(&(*pdata->timer)); pdata->timer.expires = get_jiffies_64()+timeover; pdata->timer.data = (unsigned long) pdata; pdata->timer.function = kerneltimer_timeover; // // // // 구조체 초기화 실행 시간 설정 전달인자 주소값 수행될 함수 add_timer(&(pdata->timer)); // 타이머 구조체 등록 void kerneltimer_timeover(unsigned long arg) { KERNEL_TIMER_MANAGER *pdata = NULL; if(arg) { pdata = (KERNEL_TIMER_MANAGER*)arg; } } outb((unsigned char) (pdata->led & 0xFF), KERNELTIMER_WRITE_ADDR); pdata->led = ~(pdata->led); kerneltimer_registertimer(pdata, TIME_STEP); int kerneltimer_init(void) { ptrmng = kmalloc(sizeof(KERNEL_TIMER_MANAGER), GFP_KERNEL); if(ptrmng == NULL) return -ENOMEM; memset(ptrmng, 0, sizeof(KERNEL_TIMER_MANAGER)); ptrmng->led = 0; kerneltimer_registertimer(ptrmng, TIME_STEP); } return 0; void kerneltimer_exit(void) { if(ptrmng != NULL) { del_timer(&(ptrmng->timer)); kfree(ptrmng); } } outb(0x00, KERNELTIMER_WRITE_ADDR); module_init(kerneltimer_init); module_exit(kerneltimer_exit); MODULE_LISENCE("Dual BSD/GPL"); 18 2 어떤 프로세스가 수행되는 도중에 다른 서비스 처리 루틴이 끼어들 어 프로세스의 수행을 방해하는 것. 인터럽트가 발생하면 수행 중인 프로세스의 상태를 저장하고 수행을 중단한 다음 ISR (Interrupt Service Routine)을 수행. ISR처리가 끝나면 중단된 프로세스를 다시 수행. 인터럽트의 종류 ◦ 오류 인터럽트와 같은 내부 인터럽트 ◦ 외부 인터럽트 (IRQ요청 사용) ◦ IRQ – Interrupt Request Line Interrupts : Asynchronous ◦ H/W device는 cpu clock에 비동기적 으로 interrupt를 발생시키 므로 kernel은 언제든지 interrupt에 의해 방해 받을 수 있음 Exception : Synchronous ◦ CPU clock에 동기화되어 발생 ◦ CPU가 명령을 실행하는 도중이나 프로그래밍 에러등에 의해 발생 Interrupt나 exception에 의한 code는 process에 의한 실행code가 아님 Architecture에 따라 처리하는 방법이 다르므로 Linux Kernel에서는 do_IRQ()함수를 통해 IRQ Interrupt를 처리. Interrupt가 발생한 IRQ 번호에 대해 등록된 서비스 함수가 없는 경우 해당 Interrupt는 무시된다. Hareware IRQ interrupt 발생 Kernel Device driver 아키텍쳐별 IRQ 처리루틴 do_IRQ(n) irq_desc[irqs] struct xxx data; 1) 등록 request_irq(irq, int_handler, flag, “xxx”, &data); irqreturn_t int_handler(int irq, void *dev_id, struct ptr_regs *regs) { 2) 호출 handle a interrupt... 등록된 irq_desc에서 발생된 irq ISR 호출 } 3) 제거 free_irq(irq, &data) Kernel 2.4 void int_interrupt( int irq, void *dev_id, struct pt_regs *regs) { } Kernel 2.6 irq_return_t int_interrupt( int irq, void *dev_id, struct pt_regs *regs) { return IRQ_HANDLED; } Interrupt number Interrupt ID or Interrupt 함수가 사용 가능한 메모리 주소 Interrupt 발생시 레지스터 값 irq re tu rn _ t in t_ in te rru p t( in t irq , vo id *d e v _ id , stru ct p t_ re g s *re g s ) { ch a r *d a ta ; … d a ta = km a llo c( 1 2 , G F P _ A T O M IC ); if ( d a ta != N U L L ) { … kfre e (d a ta ); } … re tu rn IR Q _ H A N D L E D ; } Memory 할당 시 kmalloc() / kfree() 사용. vmalloc() / vfree() / ioremap() 은 process휴면 가능성이 존재하기 때문 에 사용하기 곤란 kmalloc()도 GFP_ATOMIC인자를 사용해 process휴면 가능성을 제거 해야 함 ISR이전에 할당한 Memory는 제약없이 사용가능. ISR은 request_irq()를 이용해 kernel에 등록된 후 사용 가능하다. int request_irq( unsigned int irq, void (*handler)(int, void *, struct pt_regs *), unsigned long frags, const char *device, void *dev_id ); Kernel 2.6 int request_irq ( unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *device, void * dev_id ); Flags request_irq()는 sleep할 수 있으므로 interrupt context 나 중단돼서는 안 되는 상황에 서 호출할 수 없다. 등록이 성공하면 /proc/irq에 해당 interrupt항목 생성. Kernel 2.4 SA_INTERRUPT : 다른 interrupt를 허가하지 않음 SA_SHIRQ : interrupt 번호를 공유 SA_SAMPLE_RANDOM : Random 값 처리에 영향을 줌 irqreturn_t xxx_interrupt ( int irq, void *dev_id, struct pt_regs *regs ) { … return IRQ_HANDLED; } int xxx_open ( struct inode *inode, struct file *filp ) { if ( !retuest_irq(XXX_IRQ, xxx_interrupt, SA_INTERRUPT, “xxx”, NULL) ) { //정상등록 } return 0; } Return value Success : 0 ◦ Arguments K e rn e l 2 .4 in t re tu e s t_ irq ( u n s ig n e d in t irq , v o id (*h a n d le r)(in t, v o id *, s tru c t p t_ re g s *), u n s ig n e d lo n g fla g s , c o n s t c h a r *d e v ic e , v o id * d e v _ id ) K e rn e l 2 .6 in t re q u e s t_ irq ( u n s ig n e d in t irq , irq re tu rn _ t (*h a n d le r)(in t, v o id *, s tru c t p t_ re g s *), u n s ig n e d lo n g fla g s , c o n s t c h a r *d e v ic e , v o id *d e v _ id ) 해 당 d e v ic e 유일하게 식별하기 할 당 할 in te rru p t A S C II 이 름 위 한 ID 번호 In te rru p t h a n d le r O p tio n fla g s 1.5 인터럽트 함수 해제 void free_irq( unsigned int irq, void *dev_id ) 반드시 process context에서 호출 Global Variable 사용 ◦ Interrupt 하나에 interrupt service 함수 하나가 동작하는 경우 ◦ 하나의 interrupt service함수로 여러 device를 제어하는 경 우에는 사용할 수 없음 Device의 정보를 전달하는 가장 보편적인 방법 ( 다중 프로 세스 환경에 적합 ) in t in t_ o p e n ( s tru c t in o d e *in o d e , s tru c t file *filp ) { R _ IN T _ IN F O *p trIn fo ; p trIn fo = k m a llo c (s iz e o f(R _ IN T _ IN F O ), G F P _ K E R N E L ); filp -> p riv a te _ d a ta = p trIn fo ; … if ( !re q u e s t_ irq (X X X _ IR Q , c o u n t_ in te rru p t, S A _ IN T E R R U P T , “ te s t”, p trIn fo ) ) { e n a b le _ h a rd w a re _ in t(p trIn fo ); } re tu rn 0 ; } in t in t_ re le a s e ( s tru c t in o d e *in o d e , s tru c t file *filp ) { R _ IN T _ IN F O *p trIn fo = (R _ IN T _ IN F O *) filp -> p riv a te _ d a ta ; d is a b le _ h a rd w a re _ in t(p trIn fo ); fre e _ irq ( X X X _ IR Q , p trIn fo ); k fre e ( p trIn fo ); re tu rn 0 ; } PC와 같은 범용 System ◦ open() / close() 에서 등록 / 해제 특정 목적의 System ◦ 모듈의 등록 / 해제 시점에 맞추어 등록 / 해제 request_irq() 함수를 사용할 때 flags와 dev_id를 변경 ◦ flags : SA_SHIRQ가 포함되어야 함. ◦ dev_id : 0이 아닌 값을 사용 dev_id값을 사용해 인터럽트를 공유하는 device들을 구 분한다. Kernel2.6에서는 같은 인터럽트에 대해 여러 ISR이 동작 하는 것을 방지하기 위해 ISR의 return value를 확인하 여 처리. ISR이 동작 중에 다른 interrupt가 발생하지 못하도록 함. ◦ ISR을 수행하는 도중에 상위 interrupt가 발생해 현재 ISR이 중단되면 안 되는 경우 일반함수 수행 중 데이터 처리를 보호하기 위해 interrupt 차단 ◦ Interrupt와 관련된 data처리나 data입력을 Queue / Linked List로 하 는 경우 ISR를 등록하는 request_irq() flag변수에 SA_INTERRUPT를 포함시 키면 ISR도중에 다른 interrupt를 disable한다. Interrupt 두 개가 동시에 발생한 경우 flag에 SA_INTERRUPT를 포 함한 ISR을 먼저 수행 특정 처리 구간에서 interrupt를 disable하는 경우 ◦ void disable_irq ( int irq ) : interrupt disable ◦ void enable_irq ( int irq ) : interrupt enable ◦ asm/irq.h header를 포함해야 함 Processor전체 interrupt en/disable ( asm/system.h ) ◦ Kernel 2.4 cli(void) sti(void) save_flag(unsigned long frags) restore_flags(unsigned long frags) ◦ Kernel 2.6 local_irq_disable(void) local_irq_enable(void) local_save_flags(unsigned long frags) local_irq_resotre(unsigned long frags) Device driver가 사용하는 변수와 ISR이 사용하는 변수가 전역변수로 서 로 같으면 동기를 맞추기 위해 interrupt en/disable을 사용 – interrupt disable period가 길거나 빠른 처리를 요구할 경우 interrupt 처리에 문 제가 생길 수 있음. Kernal 2.6에서는 보다 가벼운 seqlock_t를 사용해서 변수간 동기를 맞 춘다. 제어용 변수 선언 seqlock_t the_lock = S E Q LO C K_U N LO C K E D ; unsigned int seq ; 데이터 변경 루틴 데이터 취득 루틴 w rite_seqlock(& the_lock); do{ seq = read_seqbegin (& the_lock); … … 데이터 변경 데이터 처리 … w rite_sequnlock(& the_lock); … } w hile read_seqretry(&the_lock,seq); 인터럽트와 난수 발생 처리 ◦ 예측 불가능한 값 ( 난수 )가 필요한 경우 srand()함수를 사용 /proc/sys/kernel/random/uuid을 매개 변수로 사용 Kernel interrupt 함수와 random device driver에 의해 생 성 ◦ 일반적인 Device driver도 난수 발생에 영향을 줄 수 있 음 request_irq()에 SA_SAMPLE_RANDOM을 포함 인터럽트 발생 횟수 확인 ◦ /proc/interrupt file_operation structure ◦ ◦ ◦ ◦ ◦ read -> int_read write -> int_write open -> int_open release -> int_release owner -> THIS_MODULE (v2.6) Interrupt registeration ◦ Request_irq(PRINT_IRQ, int_interrupt, SA_INTERRUPT, INT_DEV_NAME, NULL) #define INT_DEV_NAME "intdev" #define INT_DEV_MAJOR 240 #define INT_WRITE_ADDR 0x0378 #define INT_READ_ADDR 0x0379 #define INT_CTRL_ADDR 0x037A #define PRINT_IRQ 7 #define PRINT_IRQ_ENABLE_MASK 0x10 #define INT_BUFF_MAX 64 typedef struct { unsigned long time; } __attribute__ ((packed)) R_INT_INFO; R_INT_INFO intbuffer[INT_BUFF_MAX]; int intcount = 0; 19 9 void int_clear( void ) { int lp; for( lp = 0; lp < INT_BUFF_MAX; lp++ ) { intbuffer[lp].time = 0; } } intcount = 0; irqreturn_t int_interrupt(int irq, void *dev_id, struct pt_regs *regs) { if( intcount < INT_BUFF_MAX ) { intbuffer[intcount].time = get_jiffies_64(); intcount++; } return IRQ_HANDLED; } int int_open (struct inode *inode, struct file *filp) { if( !request_irq( PRINT_IRQ , int_interrupt, SA_INTERRUPT, INT_DEV_NAME, NULL) ) { outb( PRINT_IRQ_ENABLE_MASK, INT_CTRL_ADDR ); } int_clear(); } return 0; 20 0 ssize_t int_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { int readcount; char *ptrdata; int loop; readcount = count / sizeof( R_INT_INFO ); if( readcount > intcount ) readcount = intcount; ptrdata = (char * ) &intbuffer[0]; for( loop = 0; loop < readcount * sizeof(R_INT_INFO); loop++ ) { put_user( ptrdata[loop], (char *) &buf[loop] ); } } return readcount * sizeof( R_INT_INFO ); ssize_t int_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos) { unsigned char status; int loop; int_clear(); for( loop = 0; loop < count; loop++ ) { get_user( status, (char *) buf ); outb( status , INT_WRITE_ADDR ); } } return count; 20 1 int int_release (struct inode *inode, struct file *filp) { outb( 0x00, INT_CTRL_ADDR ); free_irq( PRINT_IRQ , NULL ); return 0; } struct file_operations int_fops = { .owner = THIS_MODULE, .read = int_read, .write = int_write, .open = int_open, .release = int_release, }; int int_init(void) { int result; result = register_chrdev( INT_DEV_MAJOR, INT_DEV_NAME, &int_fops); if (result < 0) return result; } return 0; void int_exit(void) { unregister_chrdev( INT_DEV_MAJOR, INT_DEV_NAME ); } module_init(int_init); module_exit(int_exit); MODULE_LICENSE("Dual BSD/GPL"); 20 2 #define DEVICE_FILENAME "/dev/intdev" typedef struct { unsigned long time; } __attribute__ ((packed)) R_INT_INFO; #define INT_BUFF_MAX 64 int main() { int dev; R_INT_INFO intbuffer[INT_BUFF_MAX]; int intcount; char buff[128]; int loop; dev = open( DEVICE_FILENAME, O_RDWR|O_NDELAY ); if( dev >= 0 ) { printf( "start...\n" ); buff[0] = 0xFF; write(dev,buff,1 ); 20 3 printf( "wait... input\n" ); while(1) { memset( intbuffer, 0, sizeof( intbuffer ) ); intcount = read(dev,(char *) &intbuffer[0],sizeof(R_INT_INFO) ) / sizeof(R_INT_INFO) ; if( intcount ) break; } printf( "input ok...\n"); sleep(1); memset( intbuffer, 0, sizeof( intbuffer ) ); printf( "read interrupt times\n" ); intcount = read(dev,(char *) intbuffer,sizeof(intbuffer) ) / sizeof(R_INT_INFO) ; for( loop =0; loop < intcount; loop++ ) { printf( "index = %d time = %ld\n", loop, intbuffer[loop].time ); } } } printf( "led flashing...\n"); for( loop=0; loop<5; loop++ ) { buff[0] = 0xFF; write(dev,buff,1 ); sleep(1); buff[0] = 0x00; write(dev,buff,1 ); sleep(1); } close(dev); return 0; 20 4 다중 프로세스와 시분할 처리 프로세스의 Sleep과 시스템의 효율성 프로세스가 잠든 상태 사건과 프로세스 스케줄링 206 프로세스가 하드웨어의 외부 입력상태 변화를 기다리기위 해 Sleep 하게 되는 것 Example: Serial App int dev; char buff[128]; int readsize; dev = open(“/dev/ttyS0”, O_RDWR); … readsize = read(dev, buff, 16); … 207 Interrupt Handler read 호출 프로세스 #1 실제로 수행됨 처리 처리 할당된 시간 프로세스 #2 처리 처리 스케줄 전환 스케줄 전환 처리 스케줄 전환 스케줄 전환 208 __add_wait_queue() adds task to a wait queue, sets the task’s state to TASK_INTERRUPTIBLE, and calls schedule(). schedule() calls deactivate_task() which removes the task from the runqueue (task is runnable) TASK_RUNNING (task is not runnable) TASK_INTERRUPTIBLE Event occurs, and try_to_wake_up() sets the task to TASK_RUNNING, calls activate_task() to add the task to a runqueue, and calls schedule(). __remove_wait_queue() removes the task from the wait queue 209 헤더파일: include/linux/wait.h 변수 선언 및 초기화 ◦ wait_queue_head_t WaitQueue; ◦ init_waitqueue_head(&WaitQueue); ◦ DECLARE_WAIT_QUEUE_HEAD(WaitQueue); 프로세스 관련 ◦ interruptible_sleep_on(&WaitQueue) ◦ interruptible_sleep_on_timeout(&WaitQueue, 10) ◦ wake_up_interruptible(&WaitQueue) ◦ wait_event(wq, condition) ◦ wait_event_interruptible(wq, condition) ◦ wait_event_interruptible_timeout(wq, condition, timeout) 210 블록킹 I/O mode로 열기 wait_queue_head_t 구조체 ◦ open() flag 옵션으로 O_NDELAY를 주지 않으면 됨 ◦ 프로세스를 관리하기 위한 대기큐 필요 ◦ blocking 조건마다 각각 대기큐 필요 ◦ 대기큐 변수 생성 방법 wait_queue_head_t WaitQueue; init_waitqueue_head(&WaitQueue); DECLARE_WAIT_QUEUE_HEAD(WaitQueue); 211 재우기 DECLARE_WAIT_QUEUE_HEAD(WaitQueue_Read); … if(IsWait()) { if(!(filp->f_flags & O_NONBLOCK) interruptible_sleep_on(&WaitQueue_Read); // or // interruptible_sleep_on_timeout(&WaitQueue_Read, HZ); else return –EAGAIN; } 깨우기 wake_up_interruptible(&WaitQueue_Read) 212 헤더 파일 ◦ 2.4 : linux/sched.h ◦ 2.6 : linux/wait.h type : wait_event_interruptible(wq, condition) example if(!intcount) { } if(!(filp->f_flags & O_NONBLOCK)) { interruptible_sleep_on(&WaitQ) } else { return –EAGAIN; } int return; if((!intcount) && (filp->f_flags & O_NONBLOCK)) return –EAGAIN; ret = wait_event_interruptible(WaitQ, intcount); if(ret) return ret; 213 typedef struct { unsigned long time; } __attribute__ ((packed)) R_INT_INFO; #define BLOCKIO_BUFF_MAX 64 int main() { int dev; R_INT_INFO intbuffer[BLOCKIO_BUFF_MAX]; int intcount; char buff[128]; int loop; dev = open( DEVICE_FILENAME, O_RDWR ); if( dev >= 0 ) { printf( "start...\n" ); buff[0] = 0xFF; write(dev,buff,1 ); 214 printf( "wait... input\n" ); intcount = read(dev,(char *) &intbuffer[0],sizeof(R_INT_INFO) ); printf( "input ok...\n"); sleep(1); memset( intbuffer, 0, sizeof( intbuffer ) ); printf( "read interrupt times\n" ); intcount = read(dev,(char *) intbuffer,sizeof(intbuffer) ) / sizeof(R_INT_INFO) ; for( loop =0; loop < intcount; loop++ ) { printf( "index = %d time = %ld\n", loop, intbuffer[loop].time ); } } } printf( "led flashing...\n"); for( loop=0; loop<5; loop++ ) { buff[0] = 0xFF; write(dev,buff,1 ); sleep(1); buff[0] = 0x00; write(dev,buff,1 ); sleep(1); } close(dev); return 0; 215 #define BLOCKIO_DEV_NAME #define BLOCKIO_DEV_MAJOR #define BLOCKIO_WRITE_ADDR #define BLOCKIO_READ_ADDR #define BLOCKIO_CTRL_ADDR "blockiodev" 240 0x0378 0x0379 0x037A #define BLOCKIO_IRQ 7 #define BLOCKIO_IRQ_ENABLE_MASK #define BLOCKIO_BUFF_MAX 0x10 64 typedef struct { unsigned long time; } __attribute__ ((packed)) R_BLOCKIO_INFO; R_BLOCKIO_INFO intbuffer[BLOCKIO_BUFF_MAX]; int intcount = 0; DECLARE_WAIT_QUEUE_HEAD( WaitQueue_Read ); // 읽기에 대한 블럭 모드 구현을 위한 대기 큐 변수 216 void blockio_clear( void ) { int lp; for( lp = 0; lp < BLOCKIO_BUFF_MAX; lp++ ) intbuffer[lp].time = 0; } intcount = 0; void blockio_interrupt(int irq, void *dev_id, struct pt_regs *regs) { if( intcount < BLOCKIO_BUFF_MAX ) { intbuffer[intcount].time = jiffies; intcount++; } wake_up_interruptible( &WaitQueue_Read ); } int blockio_open (struct inode *inode, struct file *filp) { if( !request_irq( BLOCKIO_IRQ , blockio_interrupt, SA_INTERRUPT, BLOCKIO_DEV_NAME, NULL) ) outb( BLOCKIO_IRQ_ENABLE_MASK, BLOCKIO_CTRL_ADDR ); blockio_clear(); } return 0; 217 ssize_t blockio_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { int readcount; char *ptrdata; int loop; if( !intcount ) { if( !(filp->f_flags & O_NONBLOCK) ) { interruptible_sleep_on( &WaitQueue_Read ); } else { return -EAGAIN; } } readcount = count / sizeof( R_BLOCKIO_INFO ); if( readcount > intcount ) readcount = intcount; ptrdata = (char * ) &intbuffer[0]; for( loop = 0; loop < readcount * sizeof(R_BLOCKIO_INFO); loop++ ) { put_user( ptrdata[loop], (char *) &buf[loop] ); } } return readcount * sizeof( R_BLOCKIO_INFO ); 218 ssize_t blockio_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos) { unsigned char status; int loop; blockio_clear(); for( loop = 0; loop < count; loop++ ) { get_user( status, (char *) buf ); outb( status , BLOCKIO_WRITE_ADDR ); } return count; } int blockio_release (struct inode *inode, struct file *filp) { outb( 0x00, BLOCKIO_CTRL_ADDR ); free_irq( BLOCKIO_IRQ , NULL ); return 0; } 219 struct file_operations blockio_fops = { read : blockio_read, write : blockio_write, open : blockio_open, release : blockio_release, }; int init_module(void) { int result; result = register_chrdev( BLOCKIO_DEV_MAJOR, BLOCKIO_DEV_NAME, &blockio_fops); if (result < 0) return result; return 0; } void cleanup_module(void) { unregister_chrdev( BLOCKIO_DEV_MAJOR, BLOCKIO_DEV_NAME ); } 22 0 입출력 다중화 개요 입출력 다중화 system call ◦ Select ◦ Poll 입출력 다중화 내부 구현 – driver – 예제 (프린터 포트) 특정 장치 및 파일과 같은 resource들에 대한 입출 력 처리를 event 중심으로 비 동기적으로 처리 관련 user system call ◦ Select ◦ Poll 암수 원형 #include <sys/select.h> int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *time-out); 입력 패러미터 ◦ ◦ ◦ ◦ ◦ n : readfds, writefds, exceptfds 의 파일 디스크립터의 최대값 +1 Readfds : read event 를 listening할 fd 저장한 fd_set Write fds : write event를 listening 할 fd 저장한 fd_set Exceptfds : error event를 listening 할 fd 저장한 fd_set Timeout : 이벤트가 발생하지 않았을 시 select함수가 리턴 할 timeout 리턴값 ◦ -1 : error, 0 : timeout, 양의 정수 : 이벤트와 관련한 fd개수 설명 ◦ Readfds, writefds, exceptfds에 각각 관심있는 event에 등록된 장치의 해당 event 수신 시 리턴한다. 기타 ◦ FD_ZERO : fd_set 구조체 변수 초기화 ◦ FD_SET : fd_set에 fd등록 ◦ FD_ISSET: fd_set내의 특정 fd의 이벤트 발생 여부 확인하는 매크로 #include <string.h> #include <fcntl.h> #include <termios.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int main( int argc, char **argv ) { int sfd1, sfd2, sfd3; tv.tv_sec = 5; // 5초에 대한 시간 tv.tv_usec = 0; // 사건이 생기기 전에 대기 한다. retval = select(FD_SETSIZE, &rfds, NULL, &errorfds, &tv); if( retval < 0 ) { perror ("select"); exit (EXIT_FAILURE); } if( retval == 0 ) { printf("5 초안에 아무 데이타도 없었다.\n"); } // 수신된 데이타가 있는가를 검사한다. for (i = 0; i < FD_SETSIZE; ++i) { if (FD_ISSET (i, &read_fd_set)) { readcnt = read( i, buff, 256 ); write( i, buff, readcnt ); } } // 에러에 대한 검사를 수행한다. for (i = 0; i < FD_SETSIZE; ++i) { if (FD_ISSET (i, &errorfds)) { printf("장치 에러 발생.\n"); exit (EXIT_FAILURE); } } fd_set rfds; fd_set errorfds; struct timeval tv; int retval; char buff[256]; int readcnt; sfd1 = open( "/dev/ttyS1", O_RDWR | O_NOCTTY ); sfd2 = open( "/dev/ttyS2", O_RDWR | O_NOCTTY ); sfd3 = open( "/dev/ttyS3", O_RDWR | O_NOCTTY ); : 각각의 시리얼 환경 설정 루틴들 while(1) { // 읽기 이벤트 대상이 되는 것들 FD_ZERO(&rfds); FD_SET(sfd1, &rfds); FD_SET(sfd2, &rfds); FD_SET(sfd3, &rfds); // 에러 이벤트 대상이 되는 것들 FD_ZERO(&errorfds); FD_SET(sfd1, &errorfds); FD_SET(sfd2, &errorfds); FD_SET(sfd3, &errorfds); } close( sfd1 ); close( sfd2 ); close( sfd3 ); } 암수 원형 #include <sys/poll.h> int poll(struct pollfd *ufds, unsigned int nfds, int timeout); 입력 패러미터 ◦ ufds : 대상 fd와 관심있는 event 그리고 return된 event를 담은 자료형 struct pollfd { }; int fd; short events; short revents; /* file descriptor */ /* requested events */ /* returned events */ ◦ nfds : 등록된 event 개수 ◦ Timeout : 이벤트 발생이 안되어 poll함수 리턴 할 timeout (ms) 설명 ◦ Select와 같이 특정 fd에 대해 등록된 event 발생 시 return하여 관련 event에 대한 처리를 할 수 있도록 한다. Event 확인 ◦ Poll() 리턴 시, 등록한 pollfd 자료형의 revents 필드를 event macro상수 와 AND 연산으로 확인 Event macro ◦ POLLIN fd 로부터 데이터 읽기 가능한 상태 ◦ POLLPRI (network 수신) fd로부터 긴급한 데이터 읽기 가능한 상태 ◦ POLLOUT fd 로부터 데이터 쓰기 가능한 상태 ◦ POLLERR ◦ POLLHUP ◦ POLLNVAL fd가 유효하지 않은 값이 된 상태 예) socket fd의 무효화 여 부 #include #include #include #include #include #include #include #include <stdio.h> <string.h> <fcntl.h> <termios.h> <sys/time.h> <sys/types.h> <unistd.h> <sys/poll.h> while(1) { // 사건이 생기기 전에 대기 한다. retval = poll( (struct pollfd *)&Events, 3, 5000 ); < 0 ) if( retval < 0 ) { perror("poll error : " ); exit (EXIT_FAILURE); } if( retval == 0 ) { printf("5 초안에 아무 데이타도 없었다.\n"); continue; } int main( int argc, char **argv ) { int sfd1, sfd2, sfd3; struct pollfd Events[3]; int retval; char buff[256]; int readcnt; for (i = 0; i < 3; i++) { // 에러에 대한 검사를 수행한다. if( Events[i].revents & POLLERR ) { printf("장치 에러 발생.\n"); exit (EXIT_FAILURE); } sfd1 = open( "/dev/ttyS1", O_RDWR | O_NOCTTY ); sfd2 = open( "/dev/ttyS2", O_RDWR | O_NOCTTY ); sfd3 = open( "/dev/ttyS3", O_RDWR | O_NOCTTY ); : 각각의 시리얼 환경 설정 루틴들 // 수신된 데이타가 있는가를 검사한다. if( Events[i].revents & POLLIN ) { readcnt = read( i, buff, 256 ); write( i, buff, readcnt ); } memset( Events, 0, sizeof( Events ) ); Events[0].fd = sfd1; Events[0].events = POLLIN | POLLERR; // 수신 이벤트 // 에러 이벤트 } Events[1].fd = sfd2; Events[1].events = POLLIN | POLLERR; } // 수신 이벤트 // 에러 이벤트 Events[2].fd = sfd3; Events[2].events = POLLIN | POLLERR; // 수신 이벤트 // 에러 이벤트 close( sfd1 ); close( sfd2 ); close( sfd3 ); } 드라이버의 blocking I/O를 구현하기 위해 file operation 구조체의 poll필드 구현 해야 함 file operation 구조체의 poll필드 함수 원형 ◦ unsigned int xxx_poll( struct file *file, poll_table *wait) Poll 함수 기능 ◦ 커널 poll_table에 polling 대상이 되는 사건에 대한 wait queue등록 ◦ Select/poll함수를 통해 입출력 다중화를 할 수 있도록 관련 mask값 반환 Poll 함수의 전형적인 구현 예 /* read와 write에 대한 wait queue 선언 및 초기화 */ DECLARE_WAIT_QUEUE_HEAD(waitq_read); DECLARE_WAIT_QUEUE_HEAD(waitq_read); Unsigned int xxx_poll( struct file *file, poll_table *wait){ Int mask = 0; /* read/write wait queue를 커널에서 관리하는 poll_table에 등록 */ poll_wait(file, &waitq_read, wait); poll_wait(file, &waitq_write, wait); /* 반환값 처리 */ If (처리가능한 데이터 있음) mask |= (POLLIN | POLLRDNORM); If(출력 가능)mask |= (POLLOUT | POLLWRNORM); Return mask; int main() { int dev; struct pollfd Events[1]; int retval; char int int int buff[128]; readcnt; loop; flashing; printf( "poll Program Start\n" ); dev = open("/dev/polldev", O_RDWR ); if( dev < 0 ) { printf( "[/dev/polldev] Open fail\n"); exit(-1); } flashing = 0; printf( "wait poll \n" ); buff[0] = 0xff; write(dev,buff,1 ); 231 while(1) { memset( Events, 0, sizeof( Events ) ); Events[0].fd = dev; Events[0].events = POLLIN; retval = poll( (struct pollfd *)&Events, 1, 1000 ); if( retval < 0 ) { perror("poll error : " ); exit (EXIT_FAILURE); } if( retval == 0 ) { flashing = !flashing; if( flashing ) buff[0] = 0x00; else buff[0] = 0xff; write(dev,buff,1 ); continue; } 23 2 if( Events[0].revents & POLLERR ) { printf("Device Error\n"); exit(EXIT_FAILURE); } if( Events[0].revents & POLLIN ) { readcnt = read( dev, buff, sizeof( buff ) ); printf( "READ DATA COUNT [%d]\n", readcnt ); for( loop = 0; loop < readcnt; loop++ ) { printf( "READ DATA [%02X]\n", buff[loop] ); } } } buff[0] = 0x00; write(dev,buff,1 ); close(dev); return 0; } 23 3 #define POLL_DEV_NAME #define POLL_DEV_MAJOR #define POLL_WRITE_ADDR #define POLL_READ_ADDR #define POLL_CTRL_ADDR "polldev" 240 0x0378 0x0379 0x037A #define POLL_IRQ 7 #define POLL_IRQ_ENABLE_MASK #define poll_BUFF_MAX 0x10 64 DECLARE_WAIT_QUEUE_HEAD( WaitQueue_Read ); #define MAX_QUEUE_CNT 128 static unsigned char ReadQ[MAX_QUEUE_CNT]; static unsigned long ReadQCount = 0; static unsigned long ReadQHead = 0; static unsigned long ReadQTail = 0; 234 void poll_interrupt(int irq, void *dev_id, struct pt_regs *regs) { unsigned long flags; save_flags(flags); cli(); if( ReadQCount < MAX_QUEUE_CNT ) { ReadQ[ReadQHead] = (unsigned long) inb( POLL_READ_ADDR ); ReadQHead = ( ReadQHead + 1 ) % MAX_QUEUE_CNT; ReadQCount++; } restore_flags(flags); } wake_up_interruptible( &WaitQueue_Read ); int poll_open (struct inode *inode, struct file *filp) { if( !request_irq( POLL_IRQ , poll_interrupt, SA_INTERRUPT, POLL_DEV_NAME, NULL) ) { outb( POLL_IRQ_ENABLE_MASK, POLL_CTRL_ADDR ); } return 0; } 23 5 ssize_t poll_read(struct file *filp, char *buf, size_t count, loff_t *f_pos) { unsigned long flags; int realmax; int loop; int retstate; if( (!ReadQCount) && (filp->f_flags & O_NONBLOCK) ) return -EAGAIN; retstate = wait_event_interruptible( WaitQueue_Read, ReadQCount ); if( retstate ) return retstate; save_flags(flags); cli(); realmax = 0; if( ReadQCount > 0 ) { if( ReadQCount <= count ) realmax = ReadQCount; else realmax = count; for( loop = 0; loop < realmax; loop++ ) { put_user( ReadQ[ReadQTail], (char *) &buf[loop] ); ReadQTail = ( ReadQTail + 1 ) % MAX_QUEUE_CNT; ReadQCount--; } } restore_flags(flags); } return realmax; 23 6 ssize_t poll_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos) { unsigned char status; int loop; for( loop = 0; loop < count; loop++ ) { get_user( status, (char *) buf ); outb( status , POLL_WRITE_ADDR ); } return count; } unsigned int poll_poll( struct file *filp, poll_table *wait ) { unsigned int mask = 0; poll_wait( filp, &WaitQueue_Read, wait ); if( ReadQCount > 0 ) mask |= POLLIN | POLLRDNORM; return mask; } 23 7 int poll_release (struct inode *inode, struct file *filp) { outb( 0x00, POLL_CTRL_ADDR ); free_irq( POLL_IRQ , NULL ); return 0; } struct file_operations poll_fops = { read : poll_read, write : poll_write, poll : poll_poll, open : poll_open, release : poll_release, }; int init_module(void) { int result; result = register_chrdev( POLL_DEV_MAJOR, POLL_DEV_NAME, &poll_fops); if (result < 0) return result; } return 0; void cleanup_module(void) { unregister_chrdev( POLL_DEV_MAJOR, POLL_DEV_NAME ); } 23 8 23 9 01. 02. 03. 04. 태스크 큐와 워크 큐의 필요성 태스크 큐 워크 큐 선점형 커널 240 특정 I/O에 대한 지속적인 감시가 필요한 경우 ◦ 디바이스 드라이버의 주기적인 I/O 감시 방법 타이머 인터럽트 이용 => 여분의 타이머 인터럽트 필요, overhead 큼 커널 타이머 이용 => 정확한 주기로 동작, 1/HZ 이하의 주기에 선 사용 불가능 태스크 큐와 워크 큐 이용 => 동작 시점 예측 힘듬, 커널 타이머 보다 자주 혹은 빠르게 호출될 가 능성이 있음, 워크큐의 경우 유연 한 처리 가능 bottomhalf 이용(ver 2.4 이하) => 제한적인 방법 241 인터럽트 핸들러 함수에서의 신속한 처리가 힘든 경우 ◦ Tophalf/Bottomhalf 모델에서 Bottomhalf로 쓰임 ex) 이더넷 디바이스, 사운드 디바이스 인터럽트 서비스 함수(ISR)가 가지는 제약 이외의 처리를 해 야할 경우 ◦ 인터럽트 서비스 함수(ISR)는 제약이 많음 ISR내의 긴 지연은 시스템 전체 성능 저하를 유발함 vmalloc이나 파일 입출력과 같이 sleep 상태를 유발할 수 있는 함 수는 사용이 금지됨 242 태스크 큐의 초기 설계 목표 ◦ 인터럽트를 허용한 실행(Bottomhalf) ◦ 사용자 프로세스와 같은 태스크 특성(sleep 가능 등) 보유 (구현 실패) 워크 큐로 대치됨 243 tq_struct struct struct tq_struct { struct list_head unsigned long void void list; sync; (*routine)(void *); *data } ◦ routine : 나중에 커널이 수행해야할 함수 ◦ data : routine 함수가 사용할 data 244 태스크 큐 작업의 등록 int queue_task(struct tq_struct *bh_pointer, task_queue *bh_list); ◦ bh_list : 태스크 큐 관리용 list tq_timer : 타이머 인터럽트와 연동 tq_immediate : 소프트웨어 인터럽트와 연동, Bottomhalf와 관련 소프트웨어 인터럽트는 system call에 의해 발생되거나 kernel이 강제로 발생(ex. mark_bh, softirqd)시킨다. tq_disk : 블록형 디바이스 드라이버에서 사용 ◦ bh_pointer : 등록될 tq_struct struct 245 태스크 큐 작업의 실행 ◦ 다른 인터럽트를 허용함 ◦ 느린 인터럽트 핸들러와 유사 => sleep 해선 안됨 태스크 큐 작업의 종료 ◦ 실행된 태스크 큐는 태스크 큐 관리 list에서 자동 제거됨 ◦ 태스크 큐 struct는 디바이스 드라이버 내에서 수동으로 제 거해야 함 246 태스크 큐의 특별한 사용법 ◦ 전용의 태스크 큐 사용 가능 ◦ void run_task_queue(task_queue *list); 전용의 태스크 큐 작업 강제 실행 tq_timer, tq_immediate 를 대상으로 호출하면 안됨 247 워크 큐의 특징 ◦ 태스크 큐와 기본 설계 이념 동일 ◦ 태스크 큐의 문제점 보완 ◦ 우선 순위가 높은 전용의 kernel thread를 이용하여 태스크 특성 부여 (선점형 커널 도입으로 성능이 보장되었음) ◦ 2.6에서 태스크 큐를 대체함 248 work_struct struct struct work_struct { unsigned long struct list_head void void void struct timer_list pending; entry; (*func)(void *); *data; *wq_data; timer; } ◦ func : 수행할 함수 ◦ data : func 함수가 사용할 data 249 스케줄 워크 큐 ◦ keventd 스레드가 관리하는 워크 큐 ◦ DECLARE_WORK(name, void (*function)(void *), void *data); 워크 큐에 등록될 작업 구조체를 선언하고 초기화하는 함수 ◦ INIT_WORK(struct work_struct *work, void (*function)(void *), void *data); 이미 선언된 워크 큐 작업 구조체를 초기화하는 함수 ◦ int schedule_work(struct work_struct *work); keventd가 소모하는 워크 큐에 워크 큐 작업을 등록하는 함수 ◦ int schedule_delayed_work(struct work_struct *work, unsigned long delay); delay(1/HZ 단위) 후, schedule_work과 같은 작업을 수행하는 함 수 250 스케줄 워크 큐 ◦ void flush_scheduled_work(void); keventd가 소모하는 워크 큐의 작업들이 모두 처리될 수 있게 스케 줄링 전환을 수행하며 대기하는 함수 디바이스 드라이버 제거시나 커널 종료시에만 호출되어야 함 ◦ keventd thread -10의 우선 순위 값으로 시작함 보통 땐 sleep함 schedule_work(), schedule_delayed_work()함수에 의해 깨 어남 ◦ 모듈의 라이센스가 GPL이 아니어도 사용 가능 251 디바이스 드라이버 고유의 워크 큐 ◦ 다른 디바이스 드라이버가 스케줄 워크 큐를 통해 처리 시간이 긴 작 업을 수행해서 스케줄 워크 큐 사용이 힘들어 질 때 필요함 ◦ 모듈의 라이센스가 GPL이나 BSD일 때 사용 가능 ◦ struct workqueue_struct * create_workqueue(const char * name); name을 이름으로 갖는 워크 큐 처리 thread와 워크 큐를 생성하는 함수 ◦ void destroy_workqueue(struct workqueue_struct *queue); 워크 큐에서 수행 중인 작업이나 대기 중인 작업의 완료를 기다린 후, 워크 큐를 안전하게 제거하고 스레드를 종료시키는 함수 252 디바이스 드라이버 고유의 워크 큐 ◦ void flush_workqueue(struct workqueue_struct *queue); 스케줄 워크 큐의 flush_scheduled_work()과 유사 ◦ int queue_work(struct workqueue_struct *queue, struct work_struct *work); 스케줄 워크 큐의 schedule_work()과 유사 ◦ int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay); 스케줄 워크 큐의 schedule_delayed_work()과 유사 253 비선점형 커널 ◦ 실행 흐름이 커널 안에 있을 경우, 다른 태스크로 선점되는 것이 불가 능 ◦ 커널 코드 종료나 정지 시에만 다른 태스크로 스케줄링 가능 2.6 커널 ◦ nested ISR에 schedule 함수 추가됨 ◦ 커널 안에서 수행되는 태스크가 lock을 획득하고 있지 않다면 선점 가능(preempt_count로 관리) 254 커널 선점이 가능한 경우 ◦ ◦ ◦ ◦ 인터럽트 핸들러로부터 커널 공간으로 리턴할 경우 커널 코드가 다시 선점 가능해질 경우 커널 안의 태스크가 명시적으로 schedule()을 호출하는 경우 커널 안의 태스크가 중단되는 경우 255 25 6 시스템 내부의 상황 ◦ ◦ ◦ ◦ ◦ ◦ 인터럽트 할당 PCI 사용정보 프로세스 동작 상황 메모리 상태 커널 내부 상태 값 (실시간 시스템 정보) etc .. (표 17-1 참조) proc 디렉토리 마운트 ◦ 부팅 스크립트에서 다음 명령을 실행 mount –t proc proc /proc 디바이스 드라이버를 디버깅 하거나 셀에서 디바이스 드라이버의 상 태를 쉽게 관찰하거나 제어할 때 유용 시스템 정보를 파일처럼 볼 수 있게 해주는 램 파일 시스템 커널과 디바이스 드라이버에서 제공하는 정보를 응용 프로그램에서 읽거나 데이터를 써 넣을 수 있다. 응용 프로그램이 /proc 디렉토리 하부에 디렉토리나 파일을 새로 만 들 수 없고, 삭제나 이름 변경 불가 (커널이나 디바이스 드라이버에 서만 가능) 디바이스 드라이버를 위해 반드시 proc 파일 시스템을 제공할 필요 없음 (기본 인터페이스가 구현 되어있어서 따로 proc 파일 시스템을 만들 필요는 없다.) 파일이 특성은 일반 파일과 같다 (open(), close(), read(), write()등 을 쓸 수 있다.) <-> 파일을 새로 만들 수 없고 데이터는 보존 되지 않는다. • struct proc_dir_entry :디렉토리 표현을 위한 변수 구조체 • proc_mkdir : 디렉토리 생성 함수 • create_proc_entry : 파일 생성 함수 • remove_proc_entry : 디렉토리 또는 파일 제거 함수 struct proc_dir_entry { unsigned short low_ino; unsigned short namelen; const char *name; mode_t mode; nlink_t nlink; uid_t uid; gid_t gid; unsigned long size; struct inode_operations * proc_iops; struct file_operations * proc_fops; get_info_t *get_info; struct module *owner; struct proc_dir_entry *next, *parent, *subdir; void *data; read_proc_t *read_proc; write_proc_t *write_proc; atomic_t count; /* use count */ int deleted; /* delete flag */ kdev_t rdev; }; /proc 디렉토리에 하부 디렉토리를 만들때 struct proc_dir_entry *root_proc_dir = NULL; root_proc_dir = proc_mkdir(“testdir”,0); proc 파일 시스템 만들기 struct proc_dir_entry * root_proc_file = NULL; root_proc_file = create_proc_entry( “testfile", S_IFREG | S_IRWXU, root_proc_dir ); proc 파일 시스템 읽기 int read_proc_test( char *page, char **start, off_t off, int count, int *eof, void *data_unused ) { page에 써 넣는다. *eof = 1; /* 스트림에 해당하는데 일반적인 경우가 아님 */ return 써 넣어진 테이터 수; } proc 파일 시스템 쓰기 int write_proc_test( struct file *file, const char *buffer, unsigned long count, void *data) { 사용자 공간의 메모리인 buffer의 내용을 커널 공간에 써 넣는다 . return 처리된 데이터 수 } proc 파일 시스템 삭제 remove_proc_entry( “testfile" , root_proc_dir ); remove_proc_entry( “testdir" , 0 ); struct struct struct struct proc_dir_entry proc_dir_entry proc_dir_entry proc_dir_entry *sumproc_root_fp *sumproc_val1_fp *sumproc_val2_fp *sumproc_result_fp = = = = NULL; NULL; NULL; NULL; char sumproc_str1[PAGE_SIZE-80] = { 0, }; char sumproc_str2[PAGE_SIZE-80] = { 0, }; int read_sumproc_val( char *page, char **start, off_t off, int count, int *eof, void *data_unused ) { char *buf; char *realdata; realdata = (char *)data_unused; buf = page; buf += sprintf( buf, "Value = [%s]\n", realdata ); *eof = 1; return buf - page; } 26 2 int write_sumproc_val( struct file *file, const char *buffer, unsigned long count, void *data) { int len; char *realdata = (char *)data; if (copy_from_user(realdata, buffer, count)) return -EFAULT; realdata[count] = '\0'; len = strlen(realdata); if (realdata[len-1] == '\n') realdata[--len] = 0; return count; } int read_sumproc_result( char *page, char **start, off_t off,int count,int *eof, void *data_unused ) { char *buf = page; int a, b, sum; a b sum buf = simple_strtoul( sumproc_str1, NULL, 10 ); = simple_strtoul( sumproc_str2, NULL, 10 ); = a + b; += sprintf( buf, "Result [%d + %d = %d]\n", a,b, sum ); *eof = 1; return buf - page; } 26 3 int init_module(void) { sumproc_root_fp = proc_mkdir( "sumproc", 0 ); sumproc_val1_fp = create_proc_entry("val1", S_IFREG|S_IRWXU, sumproc_root_fp); if( sumproc_val1_fp ) { sumproc_val1_fp->data = sumproc_str1; sumproc_val1_fp->read_proc = read_sumproc_val; sumproc_val1_fp->write_proc = write_sumproc_val; } sumproc_val2_fp = create_proc_entry("val2", S_IFREG|S_IRWXU , sumproc_root_fp); if( sumproc_val2_fp ) { sumproc_val2_fp->data = sumproc_str2; sumproc_val2_fp->read_proc = read_sumproc_val; sumproc_val2_fp->write_proc = write_sumproc_val; } sumproc_result_fp = create_proc_entry("result",S_IFREG|S_IRUSR,sumproc_root_fp); if( sumproc_result_fp ) sumproc_result_fp->read_proc = read_sumproc_result; return 0; } 26 4 void cleanup_module(void) { remove_proc_entry( "result" remove_proc_entry( "val2" remove_proc_entry( "val1" remove_proc_entry( "sumproc" } , , , , sumproc_root_fp ); sumproc_root_fp ); sumproc_root_fp ); 0 ); 26 5 26 6 267 MMUless system의 메모리 공 간을 플랫 메모리 공간이라 함 프로세스 별로 주소 공간이 나 눠져야 함 컴파일시 프로세스가 수행될 위치를 미리 지정하거나 상대 적인 주소로 프로세스가 컴파 일 되어야 함 H/W적인 메모리 보호 장치 없 음 메모리 단편화 문제 발생 26 8 플랫 메모리 공간의 문제점 해결 다중 프로세스 지원 메모리 공간 보호 물리적인 메모리 제약에서 벗어나 프로그래밍 가능 하드디스크와 같은 보조 기억 장치를 메모리처럼 사용할 수 있게 함(swap device) 269 모든 가상 주소와 물리 주소를 1대1로 매핑 => 메모리 소모가 큼 => 페이지 단위로 처리함 (리눅스에선 보통 4KB) 페이지 테이블은 주소 변환 정보 뿐만 아니라, 메모리 접근 속성도 관 리함 => 허가되지 않은 접근 => exception 발생(H/W) => exception 처리(커널) 270 271 프로세스마다 독자적인 메모리 공간을 보유함 ◦ 단, 커널 주소 공간은 공유함 1개의 커널 MMU 테이블과 다수의 프로세스 MMU 테이블 존재 mmap 등의 API를 이용하여 I/O device의 물리 주소를 프 로세스 주소 공간으로 동적 매핑 가능 272 273 3단계 페이징 모델 ◦ 실제로 보통 2단계만 사용됨 PGD : Page Directory PMD : Page Mid level Directory PTE : Page Table Entry 274 디바이스 드라이버에서 H/W를 제어 하기 위해 물리 주소를 커널 주소 공간의 가상 주소로 변경해야 함 void *ioremap (unsigned long offset, unsigned long size); ◦ offset : 매핑하길 원하는 물리 주소 공간의 시작 주소 ◦ size : 매핑하길 원하는 물리 주소 공간의 크기, MMU 관리 단위인 PAGE_SIZE 단위여야 함 ◦ 반환값: 성공 시 매핑된 가상 주소값, 실패 시 NULL값 반환 void * ioremap_nocache (unsigned long offset, unsigned long size); ◦ ioremap과 유사하나, PCI device의 non-prefetchable 영역같은 noncachable 영역에 사용 void iounmap (void *addr); ◦ 매핑 해제 함수 275 리눅스 커널은 부팅 단계에서 시스템을 제어하기 위한 모든 I/O 제어 물리 주소나 램 영역의 물리 주소를 MMU 테이블로 미리 작성함 => 고 정 매핑된 예약 영역 고정 매핑된 영역은 PAGE_OFFSET과 같은 특정 offset을 통해 물리 주 소와 가상 주소 간의 변환이 가능하다 unsigned long virt_to_phys (volatile void * address), void * phys_to_virt (unsigned long address) ◦ 램이나 비디오 램과 같은 메모리 관련 주소 공간에 사용 unsigned long virt_to_bus (volatile void * address), void * bus_to_virt (unsigned long address) ◦ DMA등과 관련된 주소 공간에 사용(호환성을 위해 권장함) 276 mmap ◦ 대용량의 데이터를 처리하는 하드웨어 디바이스 드라이버를 작성할 때 효율적임 ◦ 파일이나 디바이스 드라이버가 제공하는 물리 주소 영역을 프로세스 가 직접 사용할 수 있게 해줌 메모리 복사 없이 access 가능 => 효율적임 277 278 279 void * mmap (void *start, size_t length, int prot, int flags, int fd, off_t offset); ◦ start : 프로세스 주소 공간에 매핑하기를 원하는 주소 => 의미 없음, 보통 0 사용 ◦ length : PAGE_SIZE 단위의 매핑 영역 크기 ◦ prot : PROT_READ, PROT_WRITE ◦ flags : MAP_SHARED => 동일한 장치를 여러 프로세스가 열 수 있음, MAP_PRIVATE => 하나의 프로세스만 해당 영역에 접근 허용 ◦ offset : 디바이스 드라이버에 따라 다르게 해석됨 ex) 시작 물리 주소, offset 주소 등 ◦ 반환값 : 성공 시 매핑된 프로세스 주소 공간 주소, 실패 시 NULL값 int munmap (void *start, size_t length); => 매핑 해제 함수 280 int mmap (struct file *filp, struct vm_area_struct *vma); ◦ 응용 프로그램에서 mmap을 호출하면 커널은 적절한 vm_area_struct를 생 성 및 초기화하여 전달한다 ◦ struct vm_area_struct unsigned long vm_start : 매핑될 가상 주소의 선두 번지 unsigned long vm_end : 매핑될 가상 주소의 마지막 번지 unsigned long vm_flags : 응용 프로그램 mmap()의 flags 내용이 적용된 값, 디바이스 드라이버가 추가로 조작함 VM_READ : 읽기 허용 VM_WRITE : 쓰기 허용 VM_EXEC : 실행 허용 VM_SHARED : 프로세스간 공유 허용 VM_IO : 메모리 맵드 I/O 주소 공간 VM_RESERVED : 스왑 아웃되지 않도록 예약 281 unsigned long vm_pgoff : 응용 프로그램 mmap()의 offset값이 PAGE_SHIFT만큼 오른쪽으로 이동되어 전달됨 ◦ 간단한 예제 int mmap(struct file *filp, struct vm_area_struct *vma) { unsigned long physical = vma->vm_pgoff << PAGE_SHIFT; unsigned long size = vma->vm_end – vma->start; if(size > MAX_MAPPING_SIZE) return –EINVAL; vma->vm_flags |= (VM_IO | VM_RESERVED); if(remap_page_range(vma, vma->vm_start, physical, size, vma->vm_page_prot)) return –EAGAIN; } return 0; 282 vma->vm_flags |= (VM_IO | VM_RESERVED)의 의미 VM_IO : 매핑된 물리 주소 공간이 I/O 영역임을 표시, 해당 영역 접근 실패 시, 프로세서가 코어 덤프하며 죽 는 것을 방지한다 VM_RESERVED : 스왑 아웃 대상에서 제외 시킨다 283 284 28 5 수행중 커널에 모듈 추가 -> 링크 과정(심볼릭에 주소 부여) -> 심볼릭 테이블 등록 -> 외부 참조 가능 커널 프로그램 외부참조심볼선언 5 1 모듈코드 외부참조심볼선언 심볼 심볼 테이블 심볼 주소 3 2 /proc/ksyms 4 286 EXPORT_SYMBOL(심볼명) : 2.4, 2.6 EXPORT_SYMBOL_GPL (심볼명) : 2.6추가 EXPORT_SYMBOL_NOVERS (심볼명) : 2.4 int out_data; EXPORT_SYMBOL(out_data); void check_test(int a){ } EXPORT_SYMBOL(check_test); 287 - 커널내에 심볼이 위치한 주소 심볼의 타입정보 : 2.6 심볼명 커널버전과 CRC 합친 값 : 2.4 커널 동작후 모듈로 포함되었을때 해당 심볼을 보유한 모듈명 Ver 2.4 d48132f0 usb_epnum_to_ep_desc_Rc2f07138[usbcore] Ver 2.6 d2875c60 T parport_proc_unregister[parport] A absolute B bss segment symbol C common symbol D data segment symbol R read-only data segment symbol T text segment symbol U undefined I indirect reference (alias to other symbol) If lowercase, the symbol is local; if uppercase, the symbol is global (external). 288 int func_var1 = 0; int func_var2 = 0; int func_sum ( int var3 { printk( "func_var1 printk( "func_var2 printk( "var3 return func_var1 + } ) = %d\n", func_var1 ); = %d\n", func_var2 ); = %d\n", var3 ); func_var2 + var3 ; int init_module(void) { return 0; } void cleanup_module(void) { } EXPORT_SYMBOL_NOVERS(func_var1); EXPORT_SYMBOL_NOVERS(func_var2); EXPORT_SYMBOL_NOVERS(func_sum); 28 9 extern int func_var1; extern int func_var2; extern int func_sum ( int sub3 ); int init_module(void) { int result; func_var1 = 3; func_var2 = 4; printk( "%d + %d + 5 = %d\n", func_var1, func_var2, func_sum(5) ); return 0; } void cleanup_module(void) { } 29 0