ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스 커널] DD - character device
    Linux/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

    - https://itecnote.com/tecnote/what-does-actually-cdev_add-do-in-terms-of-registering-a-device-to-the-kernel/


    글의 전제

    - 밑줄로 작성된 글은 강조 표시를 의미한다.

    - 그림 출처는 항시 그림 아래에 표시했다.


    글의 내용

    - 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 번 자리가 비어있을 경우.
    " 만약, prev 가 NULL 이 아니라면, 새로 생성된 major number 에게 빈 자리를 할당하지 않고, 기존 노드의 다음 자리로 연결되거나 기존 노드와 자리 바꿈을 의미한다. 

     

     

     

    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 관계를 이해하길 바란다.

Designed by Tistory.