티스토리 뷰
Device의 종류
- Block Device : Block 단위로 입출력을 하는 Device, Block은 File System의 섹터를 의미
- Character Device : Character 단위, 즉 바이트 단위로 입출력을 하는 Device, 데이터 관리 기능을 가진 응용 프로그램
- Network Device : 네트워크 층과 연결되어 있음(루프백 장치, 랜카드와 같은...)
|
Block Device |
Character Device |
데이터 전송 단위 |
Block(File System의 섹터) |
Character(문자, Byte) |
전송 버퍼 처리 |
System Buffer 사용 |
응용 프로그램의 Buffer 처리 |
대표 장치 |
하드디스크 플로피디스크 등...(디스크 종류) |
프린트, 단말기 등... |
주요 특징 |
File System 사용 |
자체적으로 데이터를 관리하기 위한 기능을 가진 |
참고 : http://linuxism.tistory.com/170
Device Driver 접근 구조
여기서 중요한 것은 Block Device는 시스템 버퍼 캐시를 사용하고, Character Device는 사용하지 않는다는 점이다. 프로그램이 파일 입출력을 사용하는 것은 단순하게 느껴지지만 내부적으로는 복잡한 과정이 이루어지는 것을 볼 수 있다.
바탕 지식
- major number : 디바이스를 처리하기 위한 Device Driver 식별 번호, 최대 255 이다.
- minor number : Device Driver가 처리하는 특정 Device를 식별하기 위한 번호
블록 디바이스(Block Device) 요청 처리 방식
리눅스 커널은 블록 디바이스를 효율적으로 처리하기 위해 처리할 내용을 큐에 모아두었다가 일정 시간이 지나거나 임계치에 도달하면 블록 디바이스 드라이버에 처리를 요구, 블록 디바이스 드라이버에 요구하는 처리 함수는 두 가지 형식으로 존재(커널 처리에 있어서 두 가지가 동시에 일어나는 경우는 없음)
- 요구 사항이 발생할 때마다 요구 큐의 내용 정리를 요청하는 경우
- 실제 데이터의 처리를 요구
블록 디바이스 관련 함수 정리
- 블록 디바이스 드라이버 등록·해제 함수
- register_blkdev() : 커널에 블록 디바이스 드라이버를 등록, 이를 처리하기 위한 기본 함수인 operation 등록1234567891011121314151617181920// EBUSY => 디바이스 또는 자원이 Busy(처리 중 또는 lock) 상태// EINVAL => 잘못된 인자int register_blkdev(unsigned int major, const char *name,struct block_device_operations *bdops);{ if (major == 0){for (major = MAX_BLKDEV-1; major > 0; major--) {if (blkdevs[major].bdops == NULL) {blkdevs[major].name = name;blkdevs[major].bdops = bdops;return major;}}return –EBUSY;}if (major >= MAX_BLKDEV) /* MAX_BLKDEV = 255 */return –EINVAL;if (blkdevs[major].bdops && blkdevs[major].bdops != bdops)return –EBUSY;blkdevs[major].name = name;blkdevs[major].bdops = bdops;return 0; // 정상 등록 완료}
cs - unregister_blkdev() : 커널에 블록 디바이스 드라이버를 해제123456789101112int unregister_blkdev(unsigned int major, const char *name);{if (major >= MAX_BLKDEV)return –EINVAL;if (!blkdevs[major].bdops)return –EINVAL;if ( strcmp(blkdevs[major].name, name))return –EINVAL;blkdevs[major].name = NULL;blkdevs[major].bdops = NULL;return 0; // 정상 해제 성공}
cs - 요청 처리 함수
- make_request() : 커널은 처리해야 할 요구가 발생하면 블록 디바이스 드라이버에게 요구 큐의 정리를 요청, 드라이버가 이를 처리하는데 사용하는 함수, RAM과 같은 헤더, 실린더 개념이 없는 곳에 사용
- request() : 커널은 데이터를 처리하기 위해 블록 디바이스 드라이버에 정의되어 있는 함수인 이걸 호출, 하드 디스크같은 곳에 사용, CURRENT 매크로로 커널에서 전달된 요구 내용 검사(실제 처리할 내용)
- exit_request() : 요청 처리에 대한 결과를 전달하는 함수
- 요청 처리 등록 함수
- blk_queue_make_request() : make_request() 함수를 등록하는 함수
- blk_init_queue() : request() 함수를 등록하는 함수
- 파티션 등록·해제 함수
- add_gendisk() : 파티션을 처리하기 위한 구조체 gendisk 추가
- del_gendisk() : 파티션을 처리하기 위한 구조체 gendisk
- 기타 함수
- block_read(), bread() : System Buffer에 있는 내용을 요청하는 함수
- ll_rw_blk() : Buffer에 요청한 내용이 없으므로 디바이스의 큐에 요청 내용 생성 -> 블럭 디바이스는 이를 제대로 처리하면 끝
- vmalloc(), vfree() : 연속된 가상 메모리 영역 공간 할당·해제
- printk() : 커널 메시지 출력
블록 디바이스 드라이버 예제 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | #include <linux/kernel.h> #include <linux/module.h> #ifdef CONFIG_MODVERSIONS #define CONFIG_MODVERSIONS #include <linux/modversions.h> #endif #include <linux/fs.h> #include <linux/vmalloc.h> #include <asm/uaccess.h> #define SampleBDD_MAJOR 0 #define DEVICE_NAME "SampleBDD" #define DEVICE_NUM 2 #define SampleBDD_DEF_SIZE 1024 #define SampleBDD_BLOCKSIZE 1024 #define SampleBDD_SECTSIZE 512 extern int s_nMajor; #define MAJOR_NR (s_nMajor) #define DEVICE_REQUEST SampleBDD_request #define DEVICE_NR(device) (MINOR(device)) #define DEVICE_NO_RANDOM #include <linux/blk.h> // Global Variables static int s_nMajor = 0; // Major Number static int SampleBDD_size = SampleBDD_DEF_SIZE; static int s_kbsize[DEVICE_NUM]; // 1024 바이트 블록 크기 static int s_blocksize[DEVICE_NUM]; // 디바이스 드라이버에서 사용할 블록 크기 static int s_hardsect[DEVICE_NUM]; // 실제 디스크 섹터의 크기 static int s_length[DEVICE_NUM]; // 블럭 디바이스 크기(장치의 용량) static char *s_data[DEVICE_NUM]; // 블럭 디바이스 데이터 저장 공간 MODULE_PARM(SampleBDD_size, "i"); MODULE_PARM_DESC(SampleBDD_size, "RAM Disk size in KB"); // Function Prototypes static int SampleBDD_open(struct inode *inodep, struct file *filp); static int SampleBDD_release(struct inode *inodep, struct file *filp); static int SampleBDD_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg); static void SampleBDD_request(request_queue_t *p); // Device Operations static struct block_device_operations SampleBDD_bdops = { open : SampleBDD_open, /* 장치 열기 */ release : SampleBDD_release, /* 장치 해제 */ ioctl : SampleBDD_ioctl, /* 디바이스 제어 연산 */ }; // Entry Function int init_module(void) { int i, k; for(i = 0; i < DEVICE_NUM; ++i) { s_kbsize[i] = SampleBDD_size; s_blocksize[i] = SampleBDD_BLOCKSIZE; s_hardsect[i] = SampleBDD_SECTSIZE; s_length[i] = (SampleBDD_size << BLOCK_SIZE_BITS); // 장치의 저장 공간 확보 if((s_data[i] = vmalloc(s_length[i])) == NULL) { for(j = 0; j < i; ++j) vfree(s_data[j]); return -ENOMEM; } } // 커널에 블럭 디바이스 등록 if((s_nMajor = register_blkdev(SampleBDD_MAJOR, DEVICE_NAME, &SampleBDD_bdops)) < 0) { printk(DEVICE_NAME " : Device registration failed (%d)\n", s_nMajor); return s_nMajor; } printk(DEVICE_NAME " : Device registered with Major Number = %d\n", MAJOR_NR); // 요청을 저장할 큐와 요청 처리 함수 등록 blk_init_queue(BLK_DEFAULT_QUEUE(s_nMajor), SampleBDD_request); // 블럭 디바이스에서 사용하는 블럭 크기와 실제 블럭(섹터) 크기 등록 blk_size[MAJOR_NR] = s_kbsize; blksize_size[MAJOR_NR] = s_blocksize; hardsect_size[MAJOR_NR] = s_hardsect; return 0; } // Exit Function void cleanup_module(void) { int i; for(i = 0; i < DEVICE_NUM; ++i) destroy_buffers(MKDEV(MAJOR_NR, i)); // 장치 저장 공간 해제 for(i = 0; i < DEVICE_NUM; ++i) vfree(s_data[i]); // 블럭 디바이스 해제 unregister_blkdev(MAJOR_NR, DEVICE_NAME); // 큐 정리 blk_cleanup_queue(BLK_DEFAULT_QUEUE(s_nMajor)); blk_size[MAJOR_NR] = NULL; blksize_size[MAJOR_NR] = NULL; hardsect_size[MAJOR_NR] = NULL; } // Device Operations int SampleBDD_open(struct inode *inodep, struct file *filp) { printk("Open Function.\n"); if(DEVICE_NR(inodep->i_rdev) >= DEVICE_NUM) return -ENXIO; MOD_INC_USE_COUNT; return 0; } int SampleBDD_release(struct inode *inodep, struct file *filp) { printk("Release Function.\n"); MOD_DEC_USE_COUNT; return 0; } int SampleBDD_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg) { int minor = DEVICE_NR(inodep->i_rdev); long devsize; switch(cmd) { case BLKGETSIZE: devsize = s_length[minor] return put_user(devsize, (long *)arg); case BLKSSZGET: return put_user(s_blocksize[minor], (int *)arg); case BLKFLSBUF: if(!capable(CAP_SYS_ADMIN)) return -EACCES; destroy_buffers(inodep->i_rdev); break; default: return -EINVAL; } return 0; } // Request Processing void SampleBDD_request(request_queue_t *p) { char *ptr; int size, minor; while(1) { INIT_REQUEST; if((minor = DEVICE_NR(CURRENT_DEV)) > DEVICE_NUM) { printk(DEVICE_NAME" : Unknown Minor Device\n"); end_request(0); continue; } ptr = s_data[minor] + CURRENT->sector * s_hardsect[minor]; size = CURRENT->current_nr_sectors * s_hardsect[minor]; if(ptr + size > s_data[minor] + s_length[minor]) { printk(DEVICE_NAME" : Request past end of device\n"); end_requset(0); continue; } switch(CURRENT->cmd) { case READ: memcpy(CURRENT->buffer, ptr, size); break; case WRITE: memcpy(ptr, CURRENT->buffer, size); break; default: end_request(0); continue; } end_request(1); //정상 } } | cs |
1~5번 과정은 커널에 모듈 등록, 6~9번 과정은 디바이스 처리, 10~13번 과정은 커널에서 모듈 제거
- 모듈이 insmod 명령어로 로드되면 모듈의 Entry Function인 58번째 줄의 init_module()이 호출됨
- 68번째 줄에서 블럭 디바이스의 전체 크기(즉, 장치의 용량)만큼 메모리 할당
- 76번째 줄에서 register_blkdev()가 호출되어, 커널에 Block Device의 이름과 드라이버의 동작과 관련된 작업에 사용되는 operations 구조체 주소를 등록
- 84번째 줄에서 디바이스 입출력 요청을 스케쥴링하기 위한 큐와 요청 처리를 위해 모듈이 사용할 함수 등록
- 87~89번째 줄에서 블럭 디바이스 드라이버에서 사용하는 블럭의 크기와 실제 블럭(섹터)의 크기 등록
- 장치를 사용하면 SampleBDD_dbops의 함수 포인터를 확인하여 행위에 맞는 연산처리 실시
ex) 장치를 열면(mount) open, 장치를 닫으면(umount) release, 장치 제어와 관련되면 ioctl - 커널은 장치로부터 입출력 등의 요청을 받으면 일단 큐에 넣음(디바이스 입출력 스케쥴링)
- 커널은 모듈에 입출력에 대한 요청 내용을 전달하고, 모듈은 이를 처리할 request() 함수를 호출해서 처리
(system buffer에 내용이 없으므로 장치로부터 내용을 요청, 모듈은 request() 함수로 system buffer에 내용 채워 넣음) - 커널과 모듈은 큐가 텅 빌 때 까지 7~8번 과정 반복
- 모듈이 rmmod 명령어로 해제하면 모듈의 Exit Function인 94번째 줄의 cleanup_module()이 호출됨
- 103번째 줄에서 블럭 디바이스의 크기만큼 할당된 메모리 해제
- 106번째 줄에서 2번 과정의 커널에 등록한 드라이버 제거
- 108번째 줄에서 큐와 함수를 제거
※ operations에는 버퍼 캐시와 관련된 내용이 없다. 그래서 버퍼캐시와 관련된 blk_dev_struct와 이 구조체로 이루어진 배열 blk_dev[]에 request queue를 두고 사용한다. request() 함수의 처리를 확인해보면 알 수 있지만, 버퍼캐시에 읽고 쓰는 것을 볼 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | struct request_queue; typedef struct request_queue request_queue_t; struct request_queue { struct list_head request_freelist[2]; struct list_head queue_head; elevator_t elevator; request_fn_proc * request_fn; merge_request_fn * back_merge_fn; merge_request_fn * front_merge_fn; merge_requests_fn * merge_requests_fn; make_request_fn * make_request_fn; plug_device_fn * plug_device_fn; void * queuedata; struct tq_struct plug_tq; char plugged; char head_active; spinlock_t request_lock; wait_queue_head_t wait_for_request; }; struct blk_dev_struct { request_queue_t request_queue; queue_proc *queue; void *data; }; extern struct blk_dev_struct blk_dev[MAX_BLKDEV]; | cs |
블록 디바이스 요청과 관련된 세부 내용
파일 시스템은 디스크 버퍼를 buffer_head(bh) 구조체로 관리하며, bh의 정보로 bio 구조체를 할당하여 사용한다. bio 구조체는 VFS에서 요청한 블럭 I/O 연산 정보에 대해서 저장하고 있고, 여기에는 기본적으로 I/O를 수행할 디스크 영역의 정보와 I/O를 수행할 데이터를 저장하기 위한 메모리 영역의 정보를 포함한다(하나의 bio는 디스크 상 연속된 영역 만을 나타내고 있어, 데이터가 나눠져 있다면 나눠진 부분들에 대한 bio 구조체가 별도 존재). bio 구조체에 저장되는 I/O 연산 종류는 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #define RW_MASK REQ_WRITE #define RWA_MASK REQ_RAHEAD #define READ 0 #define WRITE RW_MASK #define READA RWA_MASK #define READ_SYNC (READ | REQ_SYNC) #define READ_META (READ | REQ_META) #define WRITE_SYNC (WRITE | REQ_SYNC | REQ_NOIDLE) #define WRITE_ODIRECT (WRITE | REQ_SYNC) #define WRITE_META (WRITE | REQ_META) #define WRITE_FLUSH (WRITE | REQ_SYNC | REQ_NOIDLE | REQ_FLUSH) #define WRITE_FUA (WRITE | REQ_SYNC | REQ_NOIDLE | REQ_FUA) #define WRITE_FLUSH_FUA (WRITE | REQ_SYNC | REQ_NOIDLE | REQ_FLUSH | REQ_FUA) | cs |
bio 구조체에 VFS에서 요청한 블럭 I/O 연산 정보가 저장되고 나면, generic_make_request() 함수와 같은 request를 생성하는 함수를 호출한다. 그래서 해당 bio를 장치 드라이버에 제공하여 request를 생성한다. bio에는 어떤 연산을 해야 할지에 대한 정보를 저장되었지만, request에는 실제로 장치 드라이버에서 장치와 실제 I/O 작업을 수행하는 것에 필요한 정보가 저장된다. 이후 장치 드라이버는 request() 함수를 실행하여, request queue에서 실제 처리 작업 정보가 저장된 request를 꺼내 디스크 버퍼와 I/O 작업을 수행한다.
참고 자료 모음
https://wiki.kldp.org/Translations/html/The_Linux_Kernel-KLDP/tlk8.html
http://forum.falinux.com/zbxe/index.php?mid=device_driver&page=4
역시 내용만 보면 이해가 어려우니깐 예제 소스 코드를 보고 이해를 시작하는게 빠른 것 같다.
'System > Linux' 카테고리의 다른 글
Linux TLS(Thread Local Storage) 정리 (0) | 2017.06.05 |
---|---|
Linux Device Driver 정리 (2) (0) | 2017.06.01 |
Linux 커널 및 모듈 공부 기초 (0) | 2017.05.27 |
ptmalloc2 Exploit 기법 정리 (0) | 2017.04.26 |
Pwnable을 위한 Tools 정리 (4) | 2017.04.14 |