티스토리 뷰

System/Linux

Linux Device Driver 정리 (1)

Tribal 2017. 5. 28. 04:21

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 사용

자체적으로 데이터를 관리하기 위한 기능을 가진
응용 프로그램 사용(DBMS....)


참고 : 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 등록
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      // 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() : 커널에 블록 디바이스 드라이버를 해제
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      int 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번 과정은 커널에서 모듈 제거

  1. 모듈이 insmod 명령어로 로드되면 모듈의 Entry Function인 58번째 줄의 init_module()이 호출됨
  2. 68번째 줄에서 블럭 디바이스의 전체 크기(즉, 장치의 용량)만큼 메모리 할당
  3. 76번째 줄에서 register_blkdev()가 호출되어, 커널에 Block Device의 이름과 드라이버의 동작과 관련된 작업에 사용되는 operations 구조체 주소를 등록
  4. 84번째 줄에서 디바이스 입출력 요청을 스케쥴링하기 위한 큐와 요청 처리를 위해 모듈이 사용할 함수 등록
  5. 87~89번째 줄에서 블럭 디바이스 드라이버에서 사용하는 블럭의 크기와 실제 블럭(섹터)의 크기 등록
  6. 장치를 사용하면 SampleBDD_dbops의 함수 포인터를 확인하여 행위에 맞는 연산처리 실시
    ex) 장치를 열면(mount) open, 장치를 닫으면(umount) release, 장치 제어와 관련되면 ioctl
  7. 커널은 장치로부터 입출력 등의 요청을 받으면 일단 큐에 넣음(디바이스 입출력 스케쥴링)
  8. 커널은 모듈에 입출력에 대한 요청 내용을 전달하고, 모듈은 이를 처리할 request() 함수를 호출해서 처리
    (system buffer에 내용이 없으므로 장치로부터 내용을 요청, 모듈은 request() 함수로 system buffer에 내용 채워 넣음)
  9. 커널과 모듈은 큐가 텅 빌 때 까지 7~8번 과정 반복
  10. 모듈이 rmmod  명령어로 해제하면 모듈의 Exit Function인 94번째 줄의 cleanup_module()이 호출됨
  11. 103번째 줄에서 블럭 디바이스의 크기만큼 할당된 메모리 해제
  12. 106번째 줄에서 2번 과정의 커널에 등록한 드라이버 제거
  13. 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 작업을 수행한다.


참고 자료 모음

07_old.pdf

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
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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