-
[리눅스 커널] DD - character deviceLinux/kernel 2023. 11. 29. 20:10
글의 참고
- https://lwn.net/Articles/195805/
- https://blog.csdn.net/lxllinux/article/details/81777707
- https://sketch2sky.com/2017/09/29/linux-%E8%AE%BE%E5%A4%87%E5%8F%B7%E7%AE%A1%E7%90%86-ii/
- https://olegkutkov.me/2018/03/14/simple-linux-character-device-driver/
- https://blog.csdn.net/m0_50662680/article/details/129759120
- https://zhuanlan.zhihu.com/p/577974561
- https://blog.csdn.net/m0_74282605/article/details/131683103
- https://sysplay.in/blog/linux-device-drivers/2013/05/linux-character-drivers/
- https://www.win.tue.nl/~aeb/linux/lk/lk-13.html
글의 전제
- 밑줄로 작성된 글은 강조 표시를 의미한다.
- 그림 출처는 항시 그림 아래에 표시했다.
글의 내용
- Overview
" character device 는 1 byte 단위 통신이 가능한 디바이스를 의미한다. /dev/ 밑에 오는 모든 디바이스는 말 그대로 I/O 디바이스를 의미한다. 여기에는 block device, character device, network device 등이 있다.
- Data structure
" character device 를 크게 3 가지 자료 구조가 존재한다.
1. character device 그 자체를 나타내는 자료 구조 - struct cdev
2. character device 의 메타 데이터를 나타내는 자료 구조 - struct char_device_struct
3. character device 를 관리하는 자료 구조 - struct kobj_map" struct cdev 에서 가장 먼저 눈에 들어오는 부분은 kobject 가 정적으로 선언 되었다는 점이다. 이 부분에 대해서는 참 할말이 많아진다. 일단, kobject 가 특정 구조체안에 선언될 경우, 상위 구조체는 2 가지 특징을 부여받게 된다.
1. kobject 의 life-cycle 속성을 상위 구조체 또한 상속받게 된다. 즉, kobject 와 동일한 life-cycle 을 가질 수 있다.
2. kobject 가 sysfs 에 export 되기 때문에, user space 와 통신할 수 있는 객체가 될 수 있음.// include/linux/types.h - v6.5 typedef u32 __kernel_dev_t; .... typedef __kernel_dev_t dev_t;
// include/linux/cdev.h - v6.5 struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; } __randomize_layout;
" kobj 가 포인터 타입이 아닌, 데이터 타입으로 선언된 이유는 상위 구조체에게 release 방식을 전적으로 위임하기 위해서다. struct cdev 가 동적으로 메모리를 할당 받았다고 가정하자. 그리고, struct cdev 는 kobj 의 life-cycle 를 사용할 수 있다고 했다. 즉, struct cdev 안에 kobj.kref.refcount 가 0 이 되면, cdev 의 release 함수가 호출된다. cdev 의 release 함수는 당연히 동적으로 할당된 cdev 를 free 할 것이다. 그런데, 여기서 struct cdev 안에서 kobj 가 포인터 타입으로 선언 되었다면 어떨까? 거기다가 cdev 도 동적으로 할당되면 어떻게 될까? kobj->kref->refcount 가 0 이 되면, cdev 의 release 함수가 호출된다. cdev 의 release 함수는 당연히 동적으로 할당된 cdev 를 free 할 것이다. 동일한 것 같다. 그런데, 문제가 있다. cdev 를 free 할 때, kobj 도 포인터 타입이기 때문에 애도 free 를 해야한다. 즉, 2 번의 free 과정이 필요해진다. kobj 가 정적일 때는 cdev 만 free 하면 됬지만, kobj 가 동적일 때는 cdev 와 kobj 모두 free 를 해야한다. 그래서, kobj 는 다른 구조체에 선언될 때, 일반적으로 정적으로 선언된다. 대개 kobj 의 life-cycle 을 상속받기 위해서 사용된다고 보면된다.
" `struct char_device_struct` 자료 구조는 device number 를 관리하는데 사용된다. 여기서 중요한 건 major number 를 중심으로 관리된다는 것이다(뒤에서 다시 설명). major number 같은 경우 character device type 을 의미한다. minor number 같은 경우는 같은 타입(major number)의 디바이스를 구별해주는 역할을 한다. 참고로, `chrdevs` 변수는 일반 배열이 아닌, 포인터 배열인 점에 주의하자.
// fs/char_dev.c - v6.5 #define CHRDEV_MAJOR_HASH_SIZE 255
// fs/char_dev.c - v6.5 static struct char_device_struct { struct char_device_struct *next; unsigned int major; unsigned int baseminor; int minorct; char name[64]; struct cdev *cdev; /* will die */ } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
- major : struct char_device_struct 자료 구조는 하나의 major number 를 중심으로 나머지 자료들을 관리한다. 예를 들어, major 가 3 이면, 3 번 major number 는 몇 개의 minor number 를 할당할 수 있는지(minorct), minor number 할당은 몇 번 부터 할 것인지(baseminor) 등이 있다.
- baseminor : 위에서 언급했다싶이, 특정 major number 를 기준으로 첫 번째로 할당 가능한 minor number 를 나타낸다.
- minorct : 특정 major number 가 몇 개의 minor number 를 할당할 수 있는지를 나타낸다.
- cdev : 사용하지 않음." 여기서 진짜로 중요한 3 가지는 다음과 같다.
1. struct char_device_struct 자료 구조는 MAJOR NUMBER 를 기반으로 관리된다.
2. CHRDEV_MAJOR_HASH_SIZE 값은 255 다. 즉, 관리가 가능한 MAJOR NUMBER 를 255 개다.
3. character device major number 는 0 ~ 511 번 까지 할당이 가능하다.
// include/linux/fs.h - v6.5 /* fs/char_dev.c */ #define CHRDEV_MAJOR_MAX 512
" MAJOR NUMBER 를 관리할 수 있는 chrdevs 배열의 요소가 255 개만 존재하는데, 어떻게 255 번 이상의 character device major number 를 관리할 수 있을까? 사실, CHRDEV_MAJOR_HASH_SIZE 값을 CHRDEV_MAJOR_MAX 와 맞추면 그만이다. 그런데, 여러 가지 이유로 커널은 이러한 방식을 채택하지 않았다. 그렇다면, 어떻게 배열을 늘리지 않고, 이 문제를 해결할 수 있을까? 이 문제를 해결하려면 2 가지 조건이 갖춰지면 된다.
1. MAJOR NUMBER 에 HASH 알고리즘을 적용한다.
2. linked list 를 통해 연관된 MAJOR NUMBER 를 연결." 만약, 255 번 MAJOR NUMBER 를 요청이 들어오면, 어떻게 해야 할까? 요청 들어온 MAJOR NUMBER 가 CHRDEV_MAJOR_HASH_SIZE 과 크거나 같은 경우에는 HASH 값을 구해야 한다. HASH 알고리즘은 아래에서 볼 수 있다시피, 나머지 연산을 이용한다. 그래서, 255 가 전달되면 0 을 반환하고, 256 이 전달되면, 1 을 반환한다. 마치 원형 큐에 같다.
// fs/char_dev.c - v6.5 /* index in the above */ static inline int major_to_index(unsigned major) { return major % CHRDEV_MAJOR_HASH_SIZE; }
" HASH 값이 얻어지면, 아래와 같이 MAJOR NUMBER 가 관리된다. 즉, MAJOR NUMBER 0 ~ 254 가 list head 가 되고, HASH 값(MAJOR NUMBER % CHRDEV_MAJOR_HASH_SIZE) 이 동일한 chrdevs 배열 연결된다.
" `struct char_device_struct` 구조체는 major number 를 관리한다고 했다. 그래서, struct char_device_struct 에는 struct cdev 가 존재하지 않는다. 그렇다면, 커널에서 character devices 는 어떻게 관리할까? 커널에서 character devices 들을 관리하기 위해 만든 구조체가 struct kobj_map 이다.
// drivers/base/map.c - v6.5 struct kobj_map { struct probe { struct probe *next; dev_t dev; unsigned long range; struct module *owner; kobj_probe_t *get; int (*lock)(dev_t, void *); void *data; } *probes[255]; struct mutex *lock; };
" struct probe 구조체로 선언된 배열이 255 개 존재한다. 뭔가 구조가 struct char_device_struct 와 비슷하지 않나? 그래서, character device 에 대한 관리도 위와 같이 `dev_t dev(major number + minor number)` 필드를 HASH 값으로 변환하고, 해당 인덱스에 대응하는 character device 를 linked list(probes->next) 를 통해서 연결한다. `range` 는 몇 개의 minor numbers 를 관리할지를 나타낸다. dev_t dev[19:0](minor number) 를 start minor number 로 해서 range 까지 dev[31:20](major number) 를 갖는 character device(void *data) 가 관리한다. 결국, `kobj_probe_t *get` 은 character deivce 를 인자로 받아서, kobj 를 추출한다. 정리하면, struct probe 는 하나의 major number 에 mapping 되는 character deivce 를 하나를 관리하는 구조체라고 볼 수 있다. 그리고, struct kobj_map 은 여러 struct probe(character device) 를 관리하는 구조체라고 볼 수 있다. 리눅스 커널은 아래의 `cdev_map` 변수를 통해 시스템에 등록되는 모든 character devices 들을 관리한다.
// fs/char_dev.c - v6.5 static struct kobj_map *cdev_map;
" character device 에서 struct kobj_map 은 주로 file operation 시에 참조된다. 이 내용은 이 글 마지막 섹션에서 설명하도록 한다.
- Device number management
1. create static major number
" register_chrdev_region() 함수는 새로운 character device type 을 만들어 주는 함수다. 그런데, 한 가지 주의할 점은 새로운 character device type 을 만들 때, 자신이 할당하기 원하는 MAJOR NUMBER 가 있을 경우, register_chrdev_region() 함수를 호출하고, 새로운 character device type 을 만드는데 아무 MAJOR NUMBER 나 할당받아도 상관없다면, alloc_chrdev_region() 함수를 호출한다. 아래 그림을 보면, register_chrdev_region() 함수를 통해서 생성된 new character device type 은 MAJOR NUMBER 가 함수 호출시에 명시적으로 전달되지만, alloc_chrdev_region() 함수는 명시적으로 MAJOR NUMBER 를 전달하지 않는 것을 볼 수 있다.
" 아래 매크로는 character device 에서 자주 사용되는 3가지 매크로 함수(MAJOR, MINOR, MKDEV)다. MAJOR NUMBER 는 12-bit, MINOR NUMBER 는 20-bit 를 차지한다.
// include/linux/kdev_t.h - v6.5 #define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) // 0x000F_FFFF #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
// fs/char_dev.c - v6.5 /** * register_chrdev_region() - register a range of device numbers * @from: the first in the desired range of device numbers; must include * the major number. * @count: the number of consecutive device numbers required * @name: the name of the device or driver. * * Return value is zero on success, a negative error code on failure. */ int register_chrdev_region(dev_t from, unsigned count, const char *name) // --- 1 { struct char_device_struct *cd; dev_t to = from + count; // --- 2 dev_t n, next; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); // --- 2 if (next > to) // --- 2 next = to; cd = __register_chrdev_region(MAJOR(n), MINOR(n), // --- 3 next - n, name); if (IS_ERR(cd)) goto fail; } return 0; fail: to = n; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0); kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); } return PTR_ERR(cd); }
1. from 은 자신이 원하는 시작 major number 및 minor number 의 합쳐진 번호가 전달된다. count 는 몇 개의 minor number 를 할당받을 것인지를 나타낸다. for 문에 계산식이 복잡해서 그림으로 표현해봤다. next - n 은 major number 에서 사용가능한 minor number 의 개수를 의미한다(아래에서 `from = 0x00F0_0000` , `count = 3` 는 예시로 사용된 파라미터 값이다).
" 리눅스 커널에서 register_chrdev_region() 함수는 아래와 같이 사용된다. MKDEV() 첫 번째 인자는 major number , 두 번째는 minor number 를 의미한다. 아래 예시처럼, i2c device 와 input device 는 자신들이 명시적으로 지정한 character device major number 가 있기 때문에, register_chrdev_register() 함수를 호출한다.
// drivers/input/input.c - v6.5 static int __init input_init(void) { .... err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, "input"); .... }
// include/linux/i2c-dev.h - v6.5 #define I2C_MAJOR 89 /* Device major number */ .... // drivers/i2c/i2c-dev.c - v6.5 #define I2C_MINORS (MINORMASK + 1) // 1 << 20 .... static int __init i2c_dev_init(void) { .... res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c"); .... }
2. next 는 MAJOR NUMBER 를 의미한다. for 문을 돌릴 때 마다, next(MAJOR NUMBER)는 1씩 증가한다.
원하는 MAJOR NUMBER 를 명시적으로 지정했지만, 성공하지 못할 수 도 있다. 이러한 상황을 대비해서 MAJOR NUMBER 를 1 씩 증가시켜 가면서 탐색을 진행한다. 그렇다면, 언제까지 탐색해야 할까? 그 범위가 `count` 변수에 들어있다. 즉, 탐색 starting point(baseline) 는 `from` 이고, 범위는 `count` 이므로, 종료 조건은 `from+count(to)` 가 된다.
3. 실제 MAJOR NUMBER 가 이미 할당 되어있는지 확인하고, 그렇지 않다면, 할당하는 역할은 __register_chrdev_region() 함수가 맡는다." __register_chrdev_region() 함수는 한 개의 major number 를 생성 및 초기화한다. 그리고, chrdevs 배열에 연결되기 위해서 자리 선정 과정을 진행한다.
// fs/char_dev.c - v6.5 /* * Register a single major with a specified minor range. * * If major == 0 this function will dynamically allocate an unused major. * If major > 0 this function will attempt to reserve the range of minors * with given major. * */ static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) { struct char_device_struct *cd, *curr, *prev = NULL; int ret; int i; if (major >= CHRDEV_MAJOR_MAX) { // --- 1 pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n", name, major, CHRDEV_MAJOR_MAX-1); return ERR_PTR(-EINVAL); } if (minorct > MINORMASK + 1 - baseminor) { // --- 1 pr_err("CHRDEV \"%s\" minor range requested (%u-%u) is out of range of maximum range (%u-%u) for a single major\n", name, baseminor, baseminor + minorct - 1, 0, MINORMASK); return ERR_PTR(-EINVAL); } cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); .... mutex_lock(&chrdevs_lock); if (major == 0) { // --- 2 ret = find_dynamic_major(); .... major = ret; } ret = -EBUSY; i = major_to_index(major); // --- 3 for (curr = chrdevs[i]; curr; prev = curr, curr = curr->next) { if (curr->major < major) continue; if (curr->major > major) break; if (curr->baseminor + curr->minorct <= baseminor) continue; if (curr->baseminor >= baseminor + minorct) break; goto out; } cd->major = major; cd->baseminor = baseminor; cd->minorct = minorct; strscpy(cd->name, name, sizeof(cd->name)); if (!prev) { // --- 4 cd->next = curr; chrdevs[i] = cd; } else { cd->next = prev->next; prev->next = cd; } mutex_unlock(&chrdevs_lock); return cd; out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret); }
1. 위에서 마치 MAJOR NUMBER 를 무제한 할당할 수 있는 것처럼 얘기했지만, 실제로는 0 ~ 511 번 까지만 할당 가능하다. baseminor 는 MINOR NUMBER 는 어디서부터 탐색할 것인지를 의미한다. 예를 들어, baseminor 가 3 이면, MINOR NUMBER 의 할당은 `3 ~ 0xF_FFFF(1<<20)` 번 까지만 가능하다. 그런데, 약간 이상한 점을 느낄 수 가 있다. `MINORMASK + 1` 는 0x10_0000 인데, minorct 는 0x10_0000 이여도 괜찮은 걸까? minor number 를 0x10_0000 개 까지 할당해도 되는걸까? 괜찮다. 왜냐면, minorct 는 `번호` 가 아닌 `개수`를 의미하기 때문이다. minor number(`번호` 를 의미) 를 0x0 ~ 0xF_FFFF 번 까지만 할당 가능하지만, 실제 개수(minorct)는 0x10_0000 이기 때문이다.
2. 인자로 전달된 major 가 0 이라는 것은 alloc_chrdev_region() 함수를 호출해서 character device type 을 생성하려는 것을 의미한다. 즉, MAJOR NUMBER 를 동적으로 생성하려고 하는 것이다(`find_dynamic_major()`).
3. 자기 자리를 찾기 위한 과정이 상당히 복잡하다. 먼저, major number 비교부터 시작한다. major number 는 내림 차순으로 정렬되어 저장되기 때문에, 기존 major number(curr) 보다 새로 생성된 major number 가 작다면, 기존 노드에 앞에 새로 생성된 major number 를 삽입한다. 만약, major number(curr) 새로 생성된 major number 가 크다면, 아직 뒤에 더 큰 숫자들이 있을 수 있으므로, 자리를 찾기 위해 for 문을 다시 순회한다(`continue 명령어`). 만약, major number 가 같다면, 기존 노드와 자신 중에 누가 더 앞쪽에 저장해야 할지를 싸우게 된다.
" major number 가 같을 경우, 자리 싸움은 minor number 를 통해서 진행된다. minor number 또한 내림 차순으로 우선 순위가 높게 평가된다. 그래서, (4) 과정에서 보겠지만, minor number 가 작을 경우, major number 가 동일한 기존 노드와 자리를 바꾸게 된다.
4. prev 가 NULL 이라는 것은 2가지 의미가 있다.
1. chrdevs 배열의 첫 번째 요소가 비어있다는 것을 의미한다. 즉, 0 ~ 254 자리가 빈(empty) 것이다.
2. chrdevs 배열의 첫 번째 요소는 아니지만, 그 다음 자리가 빈 것을 의미한다. 예를 들어, 0 번 자리가 empty 는 아니지만, 255 번 자리가 비어있을 경우.2. create dynamic major number
" alloc_chrdev_region() 함수는 major number 를 명시적으로 지정할 필요가 없을 경우, 호출하는 함수다. 즉, 자동으로 major number 를 할당받는 소리다. 방법은 __register_chrdev_region() 함수의 첫 번째 인자에 `0` 을 전달하면 된다. 사실, 거의 대부분의 드라이버들은 register_chrdev_region() 함수보다는 이 함수를 더 많이 호출한다. 내용이 쉽기 때문에, 자세한 설명은 생략한다.
// fs/char_dev.c - v6.5 /** * alloc_chrdev_region() - register a range of char device numbers * @dev: output parameter for first assigned number * @baseminor: first of the requested range of minor numbers * @count: the number of minor numbers required * @name: the name of the associated device or driver * * Allocates a range of char device numbers. The major number will be * chosen dynamically, and returned (along with the first minor number) * in @dev. Returns zero or a negative error code. */ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) { struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0; }
" 그런데, major number 를 어떻게 동적으로 할당될까? empty major number 를 찾는 과정은 2 번의 탐색이 필요하다.
// include/linux/fs.h - v6.5 /* Marks the bottom of the first segment of free char majors */ #define CHRDEV_MAJOR_DYN_END 234 /* Marks the top and bottom of the second segment of free char majors */ #define CHRDEV_MAJOR_DYN_EXT_START 511 #define CHRDEV_MAJOR_DYN_EXT_END 384
// fs/char_dev.c - v6.5 static int find_dynamic_major(void) { int i; struct char_device_struct *cd; for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) { // --- 1 if (chrdevs[i] == NULL) return i; } for (i = CHRDEV_MAJOR_DYN_EXT_START; // --- 2 i >= CHRDEV_MAJOR_DYN_EXT_END; i--) { for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next) // --- 3 if (cd->major == i) // --- 3 break; if (cd == NULL) // --- 4 return i; } return -EBUSY; }
(1) 254(ARRAY_SIZE(chrdevs)-1) ~ 234(CHRDEV_MAJOR_DYN_END) 를 먼저 탐색한다. 여기서 사용중인 major number 가 없다면, 즉각적으로 반환한다.
(2) 254 ~ 234 에서 major number 를 찾지 못했다면, 511(CHRDEV_MAJOR_DYN_EXT_START) ~ 384(CHRDEV_MAJOR_DYN_EXT_END) 까지 탐색한다.
(3) 511 ~ 384 는 255 가 넘어가기 때문에, major_to_index() 함수를 통해서 511 ~ 384 를 1(511%255) ~ 129(384%255) 으로 Hashing 한다(chrdevs 의 길이가 255 다). 이 때, chrevs[hashing major number] == NULL 이면, major number 를 찾은게 된다. 그리고, 만약, chrevs[hashing major number] 가 NULL 이 아니면서, chrevs[hashing major number] 가 major number(i) 와 같다면(이미 major number 다른 character device 에 할당되어 있다면), 다른 major number 를 탐색한다. 왜냐면, character device 의 major number 는 512 개까지가 최대이기 때문이다. 예를 들어, 시스템에 이미 major number 129 와 384 가 할당되어 있다고 가정하자. 이 때, for 문에서 384 를 할당할 수 있는지를 검사한다고 가정하자. 그렇다면, 루틴은 다음과 같다.
1. chrdevs[(major_to_index(384)] 검사
- if(cd->major(129) == i(384)
2. chrdevs[(major_to_index(384)]->next 검사.
- if(chrdevs[(major_to_index(384)]->next.major(384) == i(384))" 안쪽 루프에서 2 번째 반복에서 `if(cd->major == i)` 가 true 가 된다. 만약, major number 에 대한 제한이 512 가 아니라면, 3 번째 반복을 해도 되지만, 512 로 제한되기 때문에 안쪽 루프를 탈출(break) 하고, 새로운 major number 를 탐색하는 것이다.
(4) cd 가 NULL 이라는 것은 chrdevs[(major_to_index(i)] 가 NULL 이라는 것을 의미한다. 즉, 커널에서 할당된 major number 가 없다는 뜻이므로, 할당 가능한 major number 라는 뜻이 된다.
- Character device default interface`s
1. cdev_alloc
" cdev_alloc() 함수는 character device 를 생성하는 역할을 한다. cdev 를 생성하면서 기본적인 초기화를 진행하는데, cdev->kobj_type 으로 ktype_cdev_dynamic 을 등록한다. ktype_cdev_dynamic 을 사용하는 이유는 cdev 가 동적으로 생성되기 때문이다. 만약, 정적으로 생성됬다면, `ktype_cdev_default` 가 등록이 된다.
// fs/char_dev.c - v6.5 /** * cdev_alloc() - allocate a cdev structure * * Allocates and returns a cdev structure, or NULL on failure. */ struct cdev *cdev_alloc(void) { struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p; }
2. cdev_init
" cdev_init() 함수는 이미 생성된 cdev 대상으로 초기화만 해주는 함수다. 이 때, struct file_operations 을 함께 전달할 수 있다. cdev 는 `/dev/` 에 생성되기 때문에, user application 과 VFS 를 통해서 직접적으로 통신이 가능하다. 그런데, cdev_init() 함수를 호출할 때, 주의할 점이 있다. 바로 `kobject_init(&cdev->kobj, &ktype_cdev_default)` 다.
// fs/char_dev.c - v6.5 /** * cdev_init() - initialize a cdev structure * @cdev: the structure to initialize * @fops: the file_operations for this device * * Initializes @cdev, remembering @fops, making it ready to add to the * system with cdev_add(). */ void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }
" cdev_alloc() 함수로 생성된 cdev 를 cdev_init() 함수로 초기화하면 안된다. 왜냐면, cdev_alloc() 함수로 생성된 cdev 는 동적 할당으로 생성되었기 때문이다. 동적 할당으로 생성된 cdev 는 반드시 ktype_cdev_dynamic 으로 release 해야한다. 아래 block diagram 을 보면, ktype_cdev_dynamic 과 ktype_cdev_default 의 차이를 알 수 있다.
3. cdev_del
" cdev_dev() 함수는 2 가지 역할을 한다.
1. global 하게 character devices 들을 관리하는 자료 구조인 cdev_map 에서 인자로 전달된 cdev 를 제거 - cdev_unmap()
2. cdev 의 kobj.kref.refcount 를 감소시켜, 자동으로 메모리를 해제 - kobject_put()static void cdev_unmap(dev_t dev, unsigned count) { kobj_unmap(cdev_map, dev, count); }
/** * cdev_del() - remove a cdev from the system * @p: the cdev structure to be removed * * cdev_del() removes @p from the system, possibly freeing the structure * itself. * * NOTE: This guarantees that cdev device will no longer be able to be * opened, however any cdevs already open will remain and their fops will * still be callable even after cdev_del returns. */ void cdev_del(struct cdev *p) { cdev_unmap(p->dev, p->count); kobject_put(&p->kobj); }
" cdev_del() 함수의 주석을 보면, 꾀나 무서운 내용이 언급되고 있다. 만약, cdev 가 이미 어딘가에 open API 를 통해서 사용중이라면, 중간에 cdev_del() 함수를 호출하더라도, ref 가 계속 남아있게 된다고 한다.
4. cdev_add
" 리눅스 커널에 익숙한 engineers 들이라면, 함수 이름에 `add` 가 주는 뉘앙스가 뭔지 감이 올 것이다. 그렇다. `cdev_add()` 함수는 character device 를 리눅스 커널에 등록하는 함수다.
// fs/char_dev.c - v6.5 /** * cdev_add() - add a char device to the system * @p: the cdev structure for the device * @dev: the first device number for which this device is responsible * @count: the number of consecutive minor numbers corresponding to this * device * * cdev_add() adds the device represented by @p to the system, making it * live immediately. A negative error code is returned on failure. */ int cdev_add(struct cdev *p, dev_t dev, unsigned count) { int error; p->dev = dev; p->count = count; if (WARN_ON(dev == WHITEOUT_DEV)) { error = -EBUSY; goto err; } error = kobj_map(cdev_map, dev, count, NULL, // --- 1 exact_match, exact_lock, p); .... kobject_get(p->kobj.parent); // --- 2 return 0; err: kfree_const(p->kobj.name); p->kobj.name = NULL; return error; }
(1) 실제 character device 를 커널에 등록하는 함수는 kobj_map() 함수다. character device(p) 를 커널에 등록해야 하니 당연히 character device(p) 를 전달하고, 해당 character device 에 mapping 되는 major number(dev[31:20]) 도 전달한다. 그리고, character device 가 관리하는 minor number 의 개수(dev[19:0], dev[19:0] + range) 도 전달한다. 그런데, 리눅스 커널은 character devices 들을 관리하기 위해 어떤 자료 구조를 사용할까? struct kobj_map 구조체를 사용한다. 이 구조체로 만들어진 커널 자료 구조가 `cdev_map` 이다.
(2) kobject_get(p->kobj.parent) 를 호출하는 이유는 폴더와 파일의 구조를 이해하면 쉽게 알 수 있다. 예를 들어, AAA/ 폴더안에 존재하는 폴더 및 파일들은 AAA/ 폴더안에 있으면서, 동시에 AAA/ 폴더를 사용한다고 볼 수 있다. 그렇기 때문에, 폴더 및 파일이 늘거나 감소할 경우, AAA/ 의 refcount 또한 증가 및 감소하게 된다. 아래 그림을 통해 kobjects 들간에 refcount 관계를 이해하길 바란다.
'Linux > kernel' 카테고리의 다른 글
[리눅스 커널] pinctrl - overview (1) 2023.12.04 [리눅스 커널] DMA - overview (0) 2023.12.01 [리눅스 커널] Scheduler - process address space switching (0) 2023.11.21 [리눅스 커널] Scheduler - process switching (0) 2023.11.21 [리눅스 커널] Synchronization - tranditional locking (0) 2023.11.17