ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스 커널] LDM - kobject, kset, ktype
    Linux/kernel 2023. 9. 22. 01:08

    글의 참고

    - https://docs.kernel.org/core-api/kobject.html

    - https://www.cnblogs.com/schips/p/linux_device_model.html

    - http://www.wowotech.net/device_model/kobject.html

    - https://blog.csdn.net/lizuobin2/article/details/51523693

    - http://books.gigatux.nl/mirror/kerneldevelopment/0672327201/ch17lev1sec1.html

    - https://embeddedbuddies555.wordpress.com/2020/04/12/kset-example-in-device-driver-module/


    글의 전제

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

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


    글의 내용

    - Overview

    " `kobject`는 리눅스 커널 디바이스 모델의 가장 베이스 자료 구조다. `kobj`는 크게 3가지 기능을 제공해준다.

    1. 리눅스 커널 디바이스 모델의 계층 구조를 형성해준다.
    2. `ref count`를 통해서 객체가 사용되지 않을 경우, 자동으로 메모리에서 제거되게 해준다.
    3. sysfs에 `virtual filesystem`인 `sysfs`를 도입시킴으로써, `user space`와 `kernel space`가 통신할 수 있도록 해준다.

     

     

    " 이 글에서는 `1, 2`의 내용을 다룰 것이고, `3` 번은 다른 글에서 다루도록 한다.

    // include/linux/kobject.h - v6.5
    ....
    
    struct kobject {
    	const char		*name;
    	struct list_head	entry;
    	struct kobject		*parent;
    	struct kset		*kset;
    	const struct kobj_type	*ktype;
    	struct kernfs_node	*sd; /* sysfs directory entry */
    	struct kref		kref;
    #ifdef CONFIG_DEBUG_KOBJECT_RELEASE
    	struct delayed_work	release;
    #endif
    	unsigned int state_initialized:1;
    	unsigned int state_in_sysfs:1;
    	unsigned int state_add_uevent_sent:1;
    	unsigned int state_remove_uevent_sent:1;
    	unsigned int uevent_suppress:1;
    };
    ....

     

     

    " `kobject` 필드에 대한 설명은 다음과 같다. 각 필드의 자세한 내용들은 뒤에서 다시 자세히 다룬다.

    - name : sysfs에 나타내는 이름을 나타낸다.

    - entry : `kset`에 연결되는 연결 리스트 노드를 나타낸다.

    - parent : 이 필드를 속성해서 `kobj`가 계층 구조를 형성하게 된다. 뒤에서 자새히 다룬다.

    - kset : 자신이 포함되는 `kset`을 가리킨다.

    - ktype : kobj release 및 sysfs read/wrtie 콜백 함수를 포함하는 필드

    - kref : `refcount` 를 의미한다.

    - release : 일반적으로 `refcount`가 0이면, 즉각적으로 `kobject_cleanup`이 호출된다. 그런데,
    `CONFIG_DEBUG_KOBJECT_RELEASE` 컨피그가 설정되어 있으면, 딜레이 워크큐를 사용해서 `refcount`가 `0`이더라도,
    일정 시간뒤에 `kobject_cleanup` 함수가 호출된다.

    - state_initialized : `kobj` 가 초기화가 되면, 이 갑은 `1`로 설정된다. `kobj` 초기화는 `kobject_init_internal` 함수에서 진행한다.

    - state_in_sysfs : 이 값이 `1` 이면, sysfs에 해당 폴더가 만들어졌다는 뜻

    - state_add_uevent_sent : 이 값은 새로운 kobj 가 시스템에 등록됬다는 것을 KOBJ_ADD 이벤트를 통해서 유저 스페이스에게 통보되면, SET 된다. CLEAR 는 kobj 를 초기화할 때를 제외하곤 없다. 즉, 한 번 SET 되면, 초기화되기 전까지는 계속 SET 되어있게된다.

    - state_remove_uevent_sent : kobject_uevent_env(kobj, KOBJ_REMOVE, ...) 함수가 호출될 때, 마다 이 플래그 SET 된다. 즉, 디바이스가 제거되었음을 user space 에게 알릴 때, 이 플래그도 함께 SET 된다.

    - uevent_suppress : 이 플래그 SET 되면, 해당 kobj 의 uevent 는 report 되지 않는다.

     

     

    " kset 은 서로 관련있는 kobject 를 모아놓는 컨테이너와 같다. 기본적으로, 3가지 기능을 제공한다.

    1. 서로 연관이 있는 객체들을 묶는 컨테이너 역할을 한다. 예를 들어, `all block devices` 혹은 `all PCI device drivers` 와 같이 말이다.

    2. `kset`은 sysfs에서 `sub-directory` 역할을 하며, 자신도 하나의 `kobject`를 하나 가지고 있다. 이 말은 `kset`이 `kobject`의 특별한 종류라는 것을 알 수 있다. 그리고, 자신이 포함하고 있는 `kobject`들의 부모가 될 수 있다(`kobj.parent = kset.kobj`).

    3. `kset`은 `hot-plugging` 기능을 지원한다. 즉, `kset`에 새로운 `kobject`가 add/remove 될 때 `uevent`가 user space로 전달된다.

     

     

    " 객체 지향적으로 말하자면, kset 은 여러 kobjects 들을 포함하는 top-level container 라고 볼 수 있다. kset 은 내부적으로 연결 리스트(`kset.list`) 를 통해서 자식(`kobject`) 들을 유지한다. 이건 kobject 도 마찬가지다. `kobject` 또한 내부적으로 `kobj.kset`을 통해서 자신이 어떤 `kset`에 포함되어 있는지 알 수 있다. 그리고, 일반적으로 특정 `kset`에 포함된 `kobject`은 자신의 부모로 자신을 감싸고 있는 `kset`을 부모로 지정한다(`kset.kobj`). 그렇다면, `kset`의 부모는 어떻게 표현할까? `kset`도 특별한 `kobject`라고 했다. 그러므로, `kset.kobj->parent`가 `kset`의 부모가 된다.

    // include/linux/kobject.h - v6.5
    ....
    
    /**
     * struct kset - a set of kobjects of a specific type, belonging to a specific subsystem.
     *
     * A kset defines a group of kobjects.  They can be individually
     * different "types" but overall these kobjects all want to be grouped
     * together and operated on in the same manner.  ksets are used to
     * define the attribute callbacks and other common events that happen to
     * a kobject.
     *
     * @list: the list of all kobjects for this kset
     * @list_lock: a lock for iterating over the kobjects
     * @kobj: the embedded kobject for this kset (recursion, isn't it fun...)
     * @uevent_ops: the set of uevent operations for this kset.  These are
     * called whenever a kobject has something happen to it so that the kset
     * can add new environment variables, or filter out the uevents if so
     * desired.
     */
    struct kset {
    	struct list_head list;
    	spinlock_t list_lock;
    	struct kobject kobj;
    	const struct kset_uevent_ops *uevent_ops;
    } __randomize_layout;
    ....

     

    " `kset`과 `kobject`의 관계는 부모-자식 관계는 아니다. 그렇기 때문에, LDM hierarchy structure 에서 핵심은 `kobj.parent` 라고 보는 것이 맞다. 부모-자식 관계를 형성하기 위해서 아래와 같은 방법들이 존재한다. 그러나, 아래의 코드들을 수동으로 입력해서 부모 노드를 강제로 설정하는 것은 추천되지 않는다. 부모 노드를 설정하는 것은 kobject subsystem 에서 제공하는 API 를 통해서 커널이 원하는 방향으로 형성되어야 한다.

     

    1. kobj.parent = kobj
    2. kobj.parent = kset.kobj
    3. kset.kobj.parent = kset.kobj
    ...

     

    " 그렇다면, `refcount`의 관계는 어떻게 될까? 예를 들어, `A-kobj`가 `B-kset`에 포함되고 특정 `C-kobj`를 부모 노드라고 가정하자. 이 때, `A-kobj`가 누군가에 의해 참조될 경우 `A-kobj`의 `refcount`가 증가할 것이다. 이 때, `B-kset` & `C-kobj`는 어떻게 될까? `B-kset`의 `refcount`와 `C-kobj`의 `refcount`가 각각 증가한다. 심지어 `C-kobj`와 `B-kset.kobj`가 동일하다면, `ref count`가 2번 증가한다. 새로운 `kobj`가 추가될 때도 마찬가지다.

     


    https://embeddedbuddies555.wordpress.com/2020/04/12/kset-example-in-device-driver-module/

     

    : `kset`과 `kobject`의 관계는 기본적으로 2가지 원칙이 존재한다.

     

    1. 특정 `kset`에 속한 모든 `kobject`들은 자신을 감싸고 있는 `kset.kobj`를 부모로 선택한다(`kobj.parent  = kset.kobj`).
    2. 특정 `kset`에 속한 모든 `kobject`들은 자신을 감싸고 있는 `kset`의 연결 리스트에 연결된다(`kset.list`).

     

    " 위에서 말한 관계{`kset`, `kobj`} 말고도 {`kset`, `kset`}, {`kobj`, `kobj`} 와의 관계도 존재한다. 그러나, 가장 기본은 {`kset`, `kobj`} 의 관계다. 이 내용은 뒤에서 다시 다룬다. 그리고, `kobj`가 부모를 설정할 때, 3가지 원칙이 있다.

     

    1. 특정 `kobj`를 부모로 설장할 수 있다(`parent` 인자에 특정 `kobj`를 전달해야 한다).
    2. 만약 부모 `kobj`를 지정하지 않았다면(`parent` 인자를 전달하지 않을 경우), 소속된 `kset.kobj`를 부모로 설정한다.
    3. 만약 부모 `kobj`를 지정하지 않고 소속된 `kset`도 없다면, 최상위 `kobj`를 부모 노드로 선택하게 된다. 이 말은 `/sys` 바로 아래에 `kobj` 디렉토리가 생성된다는 뜻이다.

     

    " 위에 내용은 `kobject_add_internal` 함수에서 다시 다룬다. 아래 그림은 `block sub-system` 구조를 `kobj` 관점에서 그려본 것이다. 최상위에 `/sys/block/`을 `kset`으로 표현하고, 그 아래 서브 디렉토리에 2개의 `kobj`가 존재한다(`/sys/block/hda`, `/sys/block/hdb`). `/sys/block/hda` 같은 경우는 3개의 파일을 가지고 있다. 이 친구들은 `kobj`로 표현되지는 않는다. 왜냐면, 디렉토리가 아닌 파일이기 때문이다. 파일은 sysfs의 `attribute`로 표현이 되는데, 이 내용은 이 글을 참고하자.

     


    https://embeddedbuddies555.wordpress.com/2020/04/12/kset-example-in-device-driver-module/

     

    " 그렇다면, `kset`과 `kobj` 둘다 sysfs에 디렉토리로 나타나는데, 2개의 차이는 뭘까? `kset`은 관리에 특화된 `kobj` 이기 때문에, 새로운 `kobj`가 `kset`에 추가 및 제거 될 때마다 `uevent`를 생성해서 유저 스페이스에 알린다.

     

    " 몇몇 `kobj`는 아마 비슷한 동작을 수행하는 경우가 있을 것이다. 예를 들어, `A-kobj`와 `B-kobj`의 release 동작이 동일한 프로세스일 수 있다. 이럴 때, 각 `kobj`가 자신만의 release 함수를 정의하는 대신에 `ktype`에 공통 동작을 정의해놓고, 각 `kobj`가 공통 동작(`ktype`)을 공유해서 사용할 수 있다. 지금 말한 것처럼 `ktype` 에는 비슷한 혹은 유사한 공통 동작을 정의해놓는 구조체다.

     

    // include/linux/kobject.h - v6.5
    ....
    
    struct kobj_type {
    	void (*release)(struct kobject *kobj);
    	const struct sysfs_ops *sysfs_ops;
    	const struct attribute_group **default_groups;
    	const struct kobj_ns_type_operations *(*child_ns_type)(const struct kobject *kobj);
    	const void *(*namespace)(const struct kobject *kobj);
    	void (*get_ownership)(const struct kobject *kobj, kuid_t *uid, kgid_t *gid);
    };
    ....

     

    " `ktype`에 대해 정리하자면, 여러 `kobj` 들이 공통적으로 가지고 있는 특징을 모아놓은 구조체라고 볼 수 있다.

     

     

     

    - Hierarchy structure

    " kobjects 들이 어떻게 hierarchy structure 를 형성하는지 알아보기 전에 사용 시나리오를 먼저 살펴보자.

    1. struct kset 을 먼저 정의한다.
     
    2. struct test 구조체를 만든다. 이 때, 반드시 struct kobject 를 포함시킨다(핵심).

    3. struct test 구조체에 struct kobject 를 포인터 타입(동적)으로 선언할 것인지, 일반 데이터 타입(정적) 으로 선언할 것인지를 결정한다. kobject 가 선언한 타입에 맞게 커널에 등록한다.

    4. struct test 구조체에서 kobject 를 어떻게 release 할지를 정의할 struct ktype 을 정의한다. 이 구조체를 struct test_ktype 이라고 정의한다. 예를 들어, character device 같은 경우는 동적으로 release 할 때와 정적으로 release 함수를 별도로 두고 있다. kobject 가 동적일 경우에만 `cdev_dynamic_relaese()` 함수를 사용하고, 정적인 경우에는 `cdev_default_release()` 함수를 이용한다.
    // linux/fs/char_dev.c - v6.5
    static void cdev_default_release(struct kobject *kobj)
    {
    	struct cdev *p = container_of(kobj, struct cdev, kobj);
    	struct kobject *parent = kobj->parent;
    
    	cdev_purge(p);
    	kobject_put(parent);
    }
    
    static void cdev_dynamic_release(struct kobject *kobj)
    {
    	struct cdev *p = container_of(kobj, struct cdev, kobj);
    	struct kobject *parent = kobj->parent;
    
    	cdev_purge(p);
    	kfree(p);
    	kobject_put(parent);
    }
    
    static struct kobj_type ktype_cdev_default = {
    	.release	= cdev_default_release,
    };
    
    static struct kobj_type ktype_cdev_dynamic = {
    	.release	= cdev_dynamic_release,
    };​


    5. 이 구조체를 참조할 때 마다 reference count 가 증가한다. 그리고, 사용이 끝나면 reference count 를 감소시킨다. 

    6. reference count 가 0 이 되면, struct test_ktype 의 `ktype->release` 함수를 호출해서 kobject 선언 방식에 따라 어떻게 release 할지를 결정하게 된다.

     

     

    " 위에서 볼 수 있다시피, `계층 구조`의 핵심은 새로 정의한 구조체에 `kobject`를 추가하는 것이다. 그리고, 커널에 등록하는 시점에 해당 구조체는 커널이 관리하는 sysfs 트리 구조에 포함되게 된다. 그럼, `kobject`가 어떻게 커널 sysfs 트리에 어떻게 등록될까? `kobject` 관련 함수 종류는 총 4가지로 나눌 수 있다.

    1. 생성 : kobject_create_* | kset_create_*
    2. 초기화 : kobject_init_* | kset_init
    3. 등록 : kobject_add_* | kset_register
    4. 라이프 사이클 : kobject_[get | put]

     

     

    " `kobject_create_*` / `kobject_init_*` 함수의 차이는 `kobject`를 생성해주냐 아니냐에 차이다. 즉, `create`는 kobject를 새로 생성해준다. 그리고, 초기화를 진행한다. 그러나, `init`은 이미 메모리를 할당받은 `kobject`를 대상으로 초기화를 진행한다. 초기화가 완료된 `kobj`는 `add` 함수를 통해서 커널 sysfs 트리에 등록된다. `kset`이 어떻게 생성되는지 부터 살펴보자. 함수 호출 플로우는 다음과 같다.

    kset_create_and_add
      kset_create
        kobject_set_name
      kset_register
        kset_init
        kobject_add_internal
          kobj_kset_join
          create_dir
            sysfs_create_dir_ns
       kobject_uevent

     

     

    " `kset_create_and_add` 함수는 `kset`을 생성하는 동시에 커널 sysfs 트리에 등록해주는 함수다. 이 함수는 `kset`을 생성하는 `create` 함수와 커널 sysfs 트리에 등록하는 `register` 함수로 나뉘어져 있다.

    // lib/kobject.c - v6.5
    struct kset *kset_create_and_add(const char *name,
    				 const struct kset_uevent_ops *uevent_ops,
    				 struct kobject *parent_kobj)
    {
    	struct kset *kset;
    	int error;
    
    	kset = kset_create(name, uevent_ops, parent_kobj);
    	....
    	
        	error = kset_register(kset);
    	....
        
    	return kset;
    }

     

     

    " `kset_create` 함수는 `kset` 을 새로 생성하면서 기본적인 필드(이름, 부모 등)들을 초기화한다. 디폴트로 `kset_ktype` 을 사용하고 있다.

    // lib/kobject.c - v6.5
    static struct kset *kset_create(const char *name,
    				const struct kset_uevent_ops *uevent_ops,
    				struct kobject *parent_kobj)
    {
    	struct kset *kset;
    	int retval;
    
    	kset = kzalloc(sizeof(*kset), GFP_KERNEL);
    	....
        
    	retval = kobject_set_name(&kset->kobj, "%s", name);
    	....
        
    	kset->uevent_ops = uevent_ops;
    	kset->kobj.parent = parent_kobj;
    	
        	....
    	
        	kset->kobj.ktype = &kset_ktype;
    	kset->kobj.kset = NULL;
    
    	return kset;
    }

     

     

    " `kset_register` 함수는 2가지 역할을 수행한다.

    1. `kset` / `kset->kobj` 필드 초기화.
    2. `kset`을 커널 sysfs 트리에 추가.

     

    " `kset_init -> kobject_init_internal` 함수를 통해서 `kset`이 먼저 초기화되고, 이후에 `kset->kobj`가 초기화된다.

    // lib/kobject.c - v6.5
    ....
    
    static void kobject_init_internal(struct kobject *kobj)
    {
    	if (!kobj)
    		return;
    	kref_init(&kobj->kref);
    	INIT_LIST_HEAD(&kobj->entry);
    	kobj->state_in_sysfs = 0;
    	kobj->state_add_uevent_sent = 0;
    	kobj->state_remove_uevent_sent = 0;
    	kobj->state_initialized = 1;
    }
    ....
    
    void kset_init(struct kset *k)
    {
    	kobject_init_internal(&k->kobj);
    	INIT_LIST_HEAD(&k->list);
    	spin_lock_init(&k->list_lock);
    }
    ....
    
    int kset_register(struct kset *k)
    {
    	int err;
    
    	....
        
    	kset_init(k);
    	err = kobject_add_internal(&k->kobj);
    	
        	....
        
    	kobject_uevent(&k->kobj, KOBJ_ADD);
    	return 0;
    }

     

     

    " `kobject_add_internal` 함수는 인자로 전달딘 `kobj`의 부모 노드를 결정하는 함수다. 여기서 부모 노드를 결정하는데 2가지 비교가 수행된다.

    1. 인자로 전달된 부모 노드가 있을 경우, 부모 노드는 인자로 전달되 노드가 된다(`parent = kobject_get(kobj->parent)`).
    2. 인자로 전달된 부모 노드가 없을 경우, 새로 생성된 `kobj->kset->kobj`를 부모 노드로 사용한다(`parent = kobject_get(&kobj->kset->kobj)`).

     

     

    " 그리고 `create_dir(kobj)` 함수를 통해 sysfs에 폴더를 만든다. `1`번 항목에 걸리면, 인자로 전달된 부모 노드 아래에 폴더가 생성된다. `2`번 항목에 걸리면, `kset` 폴더에 아래에 생성된다.

     

    // lib/kobject.c - v6.5
    ....
    
    static int kobject_add_internal(struct kobject *kobj)
    {
    	int error = 0;
    	struct kobject *parent;
    	....
        
    	parent = kobject_get(kobj->parent);
    
    	/* join kset if set, use it as parent if we do not already have one */
    	if (kobj->kset) {
    		if (!parent)
    			parent = kobject_get(&kobj->kset->kobj);
    		kobj_kset_join(kobj);
    		kobj->parent = parent;
    	}
    	
        	....
        
    	error = create_dir(kobj);
    	if (error) {
    		kobj_kset_leave(kobj);
    		kobject_put(parent);
    		kobj->parent = NULL;
    		....
            
    	} else
    		kobj->state_in_sysfs = 1;
    
    	return error;
    }
    ....

     

     

    " 그런데, `kobj`가 생성될 때, `kset` 무조건 포함되어야 하는거 아닐까? 그렇지 않다. `kest`에 포함되지 않는 `kobj`가 존재할 수 있다. 왜냐면, `kset`은 부모-자식 관계를 형성하는데 핵심적인 역할을 하는 것이 아니기 때문이다. 계층 구조의 핵심은 `kobj.parent` 필드다. 새로 생성된 `kobj`를 `kset` 연결 리스트에 연결시켜주는 함수가 `kobj_kset_join` 다. 이 때, 전제는 인자로 전달된 `kobj`의 `kset` 필드가 NULL이 아니여야 한다. 그러나, `kset` 필드가 NULL이면, 부모는 존재하지만, `kset`에는 포함되지 않는 `kobj`가 된다.

    // lib/kobject.c - v6.5
    static void kobj_kset_join(struct kobject *kobj)
    {
    	....
    
    	kset_get(kobj->kset);
    	spin_lock(&kobj->kset->list_lock);
    	list_add_tail(&kobj->entry, &kobj->kset->list);
    	spin_unlock(&kobj->kset->list_lock);
    }

     

     

    " 그런데, `kobj`를 생성하는 코드에서 `kobj.kset`이 설정되는 부분을 본적이 있는가? `create_and_register` 계열의 함수들, 즉, `생성후에 곧 바로 커널 sysfs 트리에 등록` 함수들은 인자로 `parent` 만 받고, `kset`은 받지 않는다. 결국, 이런 함수들로는 `kobj`가 만들어 질 때, `kset`을 추가할 수 가 없다. `kset`을 추가하는 방법은 `create`와 `register`를 별도로 사용하고, 그 사이에 `kobj.kset = kset` 과정을 수동으로 입력해야 한다.

    1. kobj create & init
    2. kobj.kset = kset
    3. kobj register

     

     

    " 아래 예시는 `/sys/firmware/devicetree/` 폴더에 디바이스 트리 노드들이 추가되는 코드다. 구체적인 내용은 생략하면, `np->kobj`는 이미 생성 및 초기화가 완료된 상태다. 여기에 `np->kobj.kset = of_kset(/sys/firmware/devicetree)`를 통해 `kset`을 설정한 뒤에 `kobject_add` 함수가 호출되는 것을 볼 수 있다. `kset`은 이렇게 `create`와 `register` 사이에서 추가된다.

    // drivers/of/kobj.c - v6.5
    int __of_attach_node_sysfs(struct device_node *np)
    {
    	const char *name;
    	struct kobject *parent;
    	struct property *pp;
    	int rc;
    
    	....
        
    	np->kobj.kset = of_kset;
    	if (!np->parent) {
    		/* Nodes without parents are new top level trees */
    		name = safe_name(&of_kset->kobj, "base");
    		parent = NULL;
    	} else {
    		name = safe_name(&np->parent->kobj, kbasename(np->full_name));
    		parent = &np->parent->kobj;
    	}
    	
        	....
    	rc = kobject_add(&np->kobj, parent, "%s", name);
    	....
    }
    ....
    
    // drivers/of/base.c
    void __init of_core_init(void)
    {
    	struct device_node *np;
    
    	of_kset = kset_create_and_add("devicetree", NULL, firmware_kobj);
    	....	
    }

     

     

    " 부모 노드가 전달되지 않고, `kobj`에 설정된 `kset`도 없을 수 도 있다. 이 때, `kobj`의 폴더는 어디에 생성될까? 실제 sysfs에 폴더가 만들어지는 함수 호출 스택은 `create_dir -> sysfs_create_dir_ns` 다. `sysfs_create_dir_ns` 함수에서 만약, 인자로 전달된 `kobj`의 parent 여부에 따라 2가지 행동을 취한다.

    1. 설정된 부모 노드가 있는 경우, 해당 부모 노드 아래에 폴더 생성 
    2. 설정된 부모 노드가 없는 경우, `/sys(sysfs_root_kn)` 아래에 폴더 생성

     

    // fs/sysfs/sysfs.h - v6.5
    ....
    
    /*
     * mount.c
     */
    extern struct kernfs_node *sysfs_root_kn;
    
    // fs/sysfs/dir.c - v6.5
    ....
    
    /**
     * sysfs_create_dir_ns - create a directory for an object with a namespace tag
     * @kobj: object we're creating directory for
     * @ns: the namespace tag to use
     */
    int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
    {
    	struct kernfs_node *parent, *kn;
    	kuid_t uid;
    	kgid_t gid;
    	....
        
    	if (kobj->parent)
    		parent = kobj->parent->sd;
    	else
    		parent = sysfs_root_kn;
    
    	....
    	kn = kernfs_create_dir_ns(parent, kobject_name(kobj), 0755, uid, gid,
    				  kobj, ns);
    	.....
        
    	kobj->sd = kn;
    	return 0;
    }
    ....

     

     

    " 위와 같은 현상은 `kset_create_and_add` / `kobject_create_and_add` / `kobject_init_and_add` 함수에 두 번째(parent) 인자에 NULL을 전달하는 경우에 발생한다. 아래 코드를 보면 확실히 알 수 있다. 아래 코드에서 앞 에 두 개의 함수는 두 번째 인자(parent)에 NULL이 들어가기 때문에 `/sys/devices`, `/sys/dev` 폴더가 생성된다. 뒤에 2개 함수는 두 번째 인자에 `dev_kobj(/sys/dev)`를 받기 때문에, `/sys/dev/block`, `/sys/dev/char` 폴더가 생성된다.

    // drivers/base/core.c - v6.5
    int __init devices_init(void)
    {
    	devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
    	....
        
    	dev_kobj = kobject_create_and_add("dev", NULL);
    	....
        
    	sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
    	....
        
    	sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
    	....
    }

     

     

    " kset 의 생성 과정을 알아봤으니, 이번에는 kobj 의 생성 과정을 알아 보자. kobj 를 생성할 때, 가장 쉽게 사용할 수 있는 함수가 kobject_create_and_add 함수다. 이 함수는 kset_create_and_add 함수와 동일한 기능을 하기 때문에, 더 이해하기가 쉬울 것이다.

    kobject_create_and_add
      kobject_create
        kobject_init
        kobject_init_internal
      kobject_add
        kobject_add_varg
        kobject_add_internal
          kobj_kset_join
          create_dir
            sysfs_create_dir_ns

     

     

    " `kobject_create_and_add()` 함수는 kobj 를 동적으로 생성한 뒤 초기화하는 함수다.

    1. kobj 동적 생성 : kobject_create
    2. kobj 커널 sysfs 트리에 추가 : kobject_add
    // lib/kobject.c - v6.5
    ....
    
    static struct kobject *kobject_create(void)
    {
    	struct kobject *kobj;
    
    	kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
    	....
    
    	kobject_init(kobj, &dynamic_kobj_ktype);
    	return kobj;
    }
    
    struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
    {
    	struct kobject *kobj;
    	int retval;
    
    	kobj = kobject_create();
    	....
    
    	retval = kobject_add(kobj, parent, "%s", name);
    	....
        
    	return kobj;
    }
    ....

     

     

    " `kobject_init_*` 함수들은 `kobj` 가 생성된 이후에 필드들을 초기화하는 함수다. 초기화시에 특별한 기능이 포함된게 아니기 때문에, 구체적인 설명은 생략한다.

    // lib/kobject.c - v6.5
    void kobject_init(struct kobject *kobj, const struct kobj_type *ktype)
    {
    	char *err_str;
    
    	....
    
    	kobject_init_internal(kobj);
    	kobj->ktype = ktype;
    	return;
        
    	....
    }
    // lib/kobject.c - v6.5
    static void kobject_init_internal(struct kobject *kobj)
    {
    	if (!kobj)
    		return;
    	kref_init(&kobj->kref);
    	INIT_LIST_HEAD(&kobj->entry);
    	kobj->state_in_sysfs = 0;
    	kobj->state_add_uevent_sent = 0;
    	kobj->state_remove_uevent_sent = 0;
    	kobj->state_initialized = 1;
    }

     

     

    " `kobject_add()` 함수는 오류 처리 함수라고 보면 된다(기능적으로 딱히 하는 일은 없다). `kobject_add_varg()` 함수에서 중요한 부분은 `kobj->parent = parent` 부분이다. 즉, 새로 생성된 `kobj` 의 부모로 인자로 전달된 `parent` 를 저장한다. 이 값이 NULL 인지 아닌지는 여기서는 중요하지 않다. 해당 부분은 `kobject_add_internal` 함수에서 판단한다. `kobject_add_internal` 함수는 위에서 설명했다시피, kobj 를 커널 sysfs 트리에 추가한다. 이 때, parent 와 kset 이 없다면, kobj 는 `/sys/` 바로 밑에 생성된다. 자세한 내용은 위에 내용을 참고하자.

     // lib/kobject.c - v6.5
    ....
    
    static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
    					   struct kobject *parent,
    					   const char *fmt, va_list vargs)
    {
    	int retval;
    
    	retval = kobject_set_name_vargs(kobj, fmt, vargs);
    	if (retval) {
    		pr_err("can not set name properly!\n");
    		return retval;
    	}
    	kobj->parent = parent;
    	return kobject_add_internal(kobj);
    }
    ....
    
    int kobject_add(struct kobject *kobj, struct kobject *parent,
    		const char *fmt, ...)
    {
    	va_list args;
    	int retval;
    
    	....
    
    	if (!kobj->state_initialized) {
    		....
    	}
    	va_start(args, fmt);
    	retval = kobject_add_varg(kobj, parent, fmt, args);
    	va_end(args);
    
    	return retval;
    }
    ....

     

     

    " 다음 주제로 넘어가기전에 내용을 정리해보자. 아래 그림은 리눅스 커널 `버스` 구조를 `kobj` 관점에서 바라본 것이다. 먼저 `/sys/bus/`를 표현하는 `kset`이 있다. 그리고, `/sys/bus/` 안에 `/sys/bus/i2c/`, `/sys/bus/platform/`, `/sys/bus/spi/`가 포함되어 있다. 이들은 모두 `kobj`로 표현된다. `/sys/bus/i2c/` 안에는 2개의 `kset`을 포함한다. `uevent`, `drivers_probe` 와 같은 파일들은 모두 `attribute`로 표현된다.  


    https://blog.csdn.net/yangjizhen1533/article/details/111155659

     

     

     

     

    - Life cycle

    " kobject 에서 제일 중요한 부분은 `reference count 를 어떻게 관리할 것이냐` 이다. kobject 의 refcount 가 0 이 되면 메모리에서 제거되야 한다. 그렇다면, 이 말은 kobject 는 정적 선언이 아닌, 반드시 동적 할당으로 메모리를 할당받아야 한다는 뜻일까? 그럴듯 하지만, 현실은 그렇지 않다. 대부분의 struct kobject 는 다른 구조체안에서 선언될 때, 포인터 타입이 아닌, 데이터 타입으로 선언된다.

    // include/linux/device.h - v6.5
    struct device {
    	struct kobject kobj;
        ....
    };
    // include/linux/cdev.h - v6.5
    struct cdev {
    	struct kobject kobj;
    	...
    } __randomize_layout;
    // drivers/base/base.h - v6.5
    struct driver_private {
    	struct kobject kobj;
        ...
    };

     

     

    " 왜냐면, container_of() 매크로를 사용하기 위해서다. 즉, 자신에게 속해있는 kobj 이 주소를 전달함으로써 자신의 구조체(위에서 struct device, struct cdev, struct driver_private) 를 반환받기 위해서 사용된다. 그렇다면, 굳이 kobject 를 사용할 필요가 있을까? 정적으로 kobject 를 사용하는 대다수의 케이스는 sysfs 에 export 시키기 위해서다. 그렇다면, sysfs 에 export 되기 위해서는 반드시 kobject 가 필요할까? 그렇지 않다. sysfs 에 export 되는 것은 kobject 뿐만 아니라, attribute 도 가능하다. 이 내용은 kernfs 글에서 다루도록 한다.

     

    " kobject 가 정적으로 선언한 구조체들의 또 다른 특징은 자체 release 방식이 있다는 것이다. 즉, 자신만의 struct ktype 을 별도로 제공한다. 공통점을 파악해보면, kobj 를 free 하기보다는 kobj 를 포함하는 구조체(struct device, struct device_private) 를 kfree() 함수에 전달한다. 그렇다면, 이 구조체들의 release 함수들은 언제 호출될까? 이 건 kobj 와 동일하다. 즉, kobj.kref 가 0 이 되면 각 release() 함수가 호출된다고 보면 된다.

    // drivers/base/core.c - v6.5
    /**
     * device_release - free device structure.
     * @kobj: device's kobject.
     *
     * This is called once the reference count for the object
     * reaches 0. We forward the call to the device's release
     * method, which should handle actually freeing the structure.
     */
    static void device_release(struct kobject *kobj)
    {
    	struct device *dev = kobj_to_dev(kobj);
    	struct device_private *p = dev->p;
    
    	/*
    	 * Some platform devices are driven without driver attached
    	 * and managed resources may have been acquired.  Make sure
    	 * all resources are released.
    	 *
    	 * Drivers still can add resources into device after device
    	 * is deleted but alive, so release devres here to avoid
    	 * possible memory leak.
    	 */
    	devres_release_all(dev);
    
    	kfree(dev->dma_range_map);
    
    	if (dev->release)
    		dev->release(dev);
    	else if (dev->type && dev->type->release)
    		dev->type->release(dev);
    	else if (dev->class && dev->class->dev_release)
    		dev->class->dev_release(dev);
    	else
    		WARN(1, KERN_ERR "Device '%s' does not have a release() function, it is broken and must be fixed. See Documentation/core-api/kobject.rst.\n",
    			dev_name(dev));
    	kfree(p);
    }
    // drivers/base/bus.c - v6.5
    static void driver_release(struct kobject *kobj)
    {
    	struct driver_private *drv_priv = to_driver(kobj);
    
    	pr_debug("driver: '%s': %s\n", kobject_name(kobj), __func__);
    	kfree(drv_priv);
    }
    
    static const struct kobj_type driver_ktype = {
    	.sysfs_ops	= &driver_sysfs_ops,
    	.release	= driver_release,
    };

     

    " 참고로, 새로운 구조체를 만들 때, 절대로 struct kobject 구조체를 2개 이상 선언해서는 안된다. 왜냐면, ref count 가 겹칠 수 있기 때문이다. 새로운 구조체를 만드는데 kobject 구조체를 포함되어야 한다면, 반드시 한 개만 선언해야 한다.

     

    " 그렇다면, kobj 를 하나 생성할 때, 최초의 kref 는 몇으로 설정될까? kref_init() 함수가 호출되면서 ref count 가 1로 초기화된다.

    // include/linux/kref.h - v6.5
    ....
    
    struct kref {
    	atomic_t refcount;
    };
    
    /**
     * kref_init - initialize object.
     * @kref: object in question.
     */
    static inline void kref_init(struct kref *kref)
    {
    	atomic_set(&kref->refcount, 1);
    }
    ....

     

     

    " 일반적으로 `kobj`의 `ref count`를 증가시키는 방법은 `kobject_get` 함수를 사용하는 것이다. 내부적으로 `kref_get` 함수를 호출해서 `atomic context`에서 `kref->refcount`를 증가시킨다. 그런데, `kref_get` 함수가 호출되는 시점에 이미 `kref->refcount`가 0인 경우가 있을 수 있다. 즉, `kobj`를 freeing 중에 선점당해서 `refcount`를 증가시키려고 할 때, 이런 현상이 발생할 수 있다. 이 때 `refcount`를 증가시킨 뒤, 복귀하는 코드에서 문제가 발생할 수 있다. 왜냐면, 값을 감소시킨 뒤, free 해야 하는데 `refcount`가 갑자기 증가한 것이다. 이런 문제를 대비해서 `kref_get_unless_zero` 함수를 사용해서 `refcount`가 0이 아닌 경우에만 증가시키는 방법을 사용해야 한다.

    // include/linux/kref.h - v6.5
    ....
    
    /**
     * kref_get - increment refcount for object.
     * @kref: object.
     */
    static inline void kref_get(struct kref *kref)
    {
    	WARN_ON_ONCE(atomic_inc_return(&kref->refcount) < 2);
    }
    
    // lib/kobject.c - v6.5
    ....
    
    /**
     * kobject_get() - Increment refcount for object.
     * @kobj: object.
     */
    struct kobject *kobject_get(struct kobject *kobj)
    {
    	if (kobj) {
    		if (!kobj->state_initialized)
    			....
                
    		kref_get(&kobj->kref);
    	}
    	return kobj;
    }
    EXPORT_SYMBOL(kobject_get);
    ....

     

     

    " `kobj` 를 제거할 때, 함수 호출 스택은 아래와 같다. 제거 루틴에서 중요한 부분은 `kobject_put` 함수가 호출될 때, `kref_put`에 두 번째로 전달되는 콜백 함수(`kobject_release`)가 중요하다. `kref_sub` 함수에서 `refcount`가 0이 되면, `release` 함수가 호출되는데, 이 함수가 `kobject_release` 함수다.

     kobject_put
      kref_put
        kref_sub
          kobject_release
            kobject_cleanup
              kobj->ktype->release

     

     

    " `kobject_release` 함수는 인자로 전달받은 `kref`에서 `container_of` 매크로를 통해 `kobj`를 추출해 낸다. 그리고, `kobject_cleanup` 함수에 인자로 `kobj` 를 전달해서 `kobj->ktype->release` 함수를 호출한다. 여기서 `ktype->release` 함수에 대해 알 필요가 있다.

     

    // include/linux/kref.h - v6.5
    ....
    
    static inline int kref_sub(struct kref *kref, unsigned int count,
    	     void (*release)(struct kref *kref))
    {
    	....
    
    	if (atomic_sub_and_test((int) count, &kref->refcount)) {
    		release(kref);
    		return 1;
    	}
    	return 0;
    }
    
    static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
    {
    	return kref_sub(kref, 1, release);
    }
    ....
    
    // lib/kobject.c - v6.5
    ....
    
    static void kobject_cleanup(struct kobject *kobj)
    {
    	struct kobject *parent = kobj->parent;
    	const struct kobj_type *t = get_ktype(kobj);
    	....
        
    	if (t && t->release) {
        		....
    		t->release(kobj);
    	}
    	....
    
    	kobject_put(parent);
    }
    
    #ifdef CONFIG_DEBUG_KOBJECT_RELEASE
    static void kobject_delayed_cleanup(struct work_struct *work)
    {
    	kobject_cleanup(container_of(to_delayed_work(work),
    				     struct kobject, release));
    }
    #endif
    
    static void kobject_release(struct kref *kref)
    {
    	struct kobject *kobj = container_of(kref, struct kobject, kref);
    #ifdef CONFIG_DEBUG_KOBJECT_RELEASE
    	unsigned long delay = HZ + HZ * get_random_u32_below(4);
    	....
        
    	INIT_DELAYED_WORK(&kobj->release, kobject_delayed_cleanup);
    
    	schedule_delayed_work(&kobj->release, delay);
    #else
    	kobject_cleanup(kobj);
    #endif
    }
    ....
    
    void kobject_put(struct kobject *kobj)
    {
    	if (kobj) {
    		if (!kobj->state_initialized)
    			....
                
    		kref_put(&kobj->kref, kobject_release);
    	}
    }
    EXPORT_SYMBOL(kobject_put);
    ....

     

    " kobject 는 대개 자체적인 의미보다는 다른 구조체안에 들어가서 더 큰 의미를 지니게 된다. 예를 들어, 아래와 같이 `struct cdev` 구조체는 kobject 를 포함으로써 cdev 끼리의 계층 구조를 만들 수 있게 된다. 왜냐면, 위에 kobj->parent 필드를 통해 부모 객체를 표현하고, kset 을 통해 자신과 비슷한 동일 계층의 객체들을 표현하고 있기 때문이다.

     

    " 그런데, kobject 가 어떤 구조체에 포함된다는 것은 kobj 의 모든 속성을 상속받게 된다는 뜻이 된다. 즉, struct cdev 는 kobj->kref->refcount 까지도 물려받게 된다는 뜻이다. 즉, cdev 의 lifecycle 은 kobj 가 책임진다는 뜻이된다. 왜냐면, cdev 가 kobj 를 상속받는 순간 cdev 도 refcount 가 0 이 되면, 메모리에서 제거되어야 하기 때문이다. 그런데, 문제가 있다. kobj->kref->refcount가 0 이 되는 시점에 kobj 를 상속받는 구조체가 release 되야 하는 건 알겠다. 그런데, kobj 를 상속받는 구조체는 굉장히 많을텐데, 이 구조체들은 모두 release 하는 방식이 동일할까? 예를 들어, `struct cdev`, `struct device_node`, `struct mdev_type` 등 많은 구조체들이 release 하는 방식이 동일할까?

     

    " 이 때, `ktype`이 등장한다. 우리는 `struct ktype.release` 콜백 함수가 선언되어 있는 것을 알고 있다. 이 함수의 용도는 `kobject. kref.refcount 가 0 이 되는 시점에 호출되는 함수다. 이 함수는 누가 구현해야 할까? `kobject`를 포함하고 있는 상위 객체에서 구현해야 한다. 왜 그럴까? 정리해야 할 리소스는 자기 자신이 가장 잘 알기 때문이다. 예를 들어, struct cdev 를 해제하는 방법은 cdev 에 포함되어 있는 kobject 가 아닌 cdev 자기 자신이 가장 잘 알고 있다. 그래서, ktype.release 콜백 함수의 구현은 kobject를 품고있는 각 상위 객체들이 제공해야 한다. 이 때, 어떻게 kobject 로 상위 객체를 특정할 수 있을까? container_of 를 사용하면 된다. 아래 코드는 cdev 구조체에서 kobject 를 포함하고 있는 것을 확인할 수 있다. 그러므로, 이 cdev 구조체에서 kobj 는 반드시 정적으로 선언되어야 하고, ktype 을 제공해서 자신을 release 할 수 있는 방법을 커널에 제공해야 한다. 그래서 cdev 는 동적 할당 때와 정적을 선언되서 초기화될 때, 설정되는 ktype 이 다르다.

    1. cdev 정적 할당 release - cdev_default_release
    2. cdev 동적 할당 release - cdev_dynamic_release
    // 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;
    ....
    
    //fs/char_dev.c - v6.5
    ....
    
    static void cdev_default_release(struct kobject *kobj)
    {
    	struct cdev *p = container_of(kobj, struct cdev, kobj);
    	struct kobject *parent = kobj->parent;
    
    	cdev_purge(p);
    	kobject_put(parent);
    }
    
    static void cdev_dynamic_release(struct kobject *kobj)
    {
    	struct cdev *p = container_of(kobj, struct cdev, kobj);
    	struct kobject *parent = kobj->parent;
    
    	cdev_purge(p);
    	kfree(p);
    	kobject_put(parent);
    }
    
    static struct kobj_type ktype_cdev_default = {
    	.release	= cdev_default_release,
    };
    
    static struct kobj_type ktype_cdev_dynamic = {
    	.release	= cdev_dynamic_release,
    };
    
    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;
    }
    
    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;
    }
    ....

     

     

     

     

    - Relationship between kobject and kset

    " 위에서 언급했었지만, `kobj`와 `kset`은 상당히 복잡한 관계로 얽혀있다. 그래서 이 관계를 2가지 기준으로 나눠서 정리할 필요가 있다.

    1. 모든 kobj 는 반드시 parent 가 존재한다.
    1. 수동으로 kobj 를 생성하지 않는 이상, 커널에서 제공하는 함수를 사용한다면 모든 kobj 는 반드시 부모가 존재한다. 

    2. parent 설정은 add 및 register 과정에서 이루어진다. 


    3. add 및 register 함수에 parent 인자를 전달할 경우, 전달한 kobj 가 parent 가 된다. 그러나, parent 를 전달하지 않으면, kset.kobj 가 부모가 된다. 그런데, parent 도 전달하지 않고, kset.kobj 도 없다면, 루트 노드가 부모가 된다.
     

    2. 모든 kobj 가 반드시 kset 에 포함되어야 하는 것은 아니다.
    1. kset 을 설정해주는 함수는 존재하지 않는다. 즉, kset 을 설정하려면 수동으로 kset 을 설정하는 코드를 작성해야 한다. 그리고, add() 및 register() 함수를 호출하면 kset 이 설정된다.

    2. 새로 만든  kobj 를 특정 kset.list 에 포함시키기 위해서 kset.kobj 를 kobject_add_internal() 함수 인자로 전달해도 소용없다. 왜냐면, kobject_add_internal() 함수 내용을 보면 알겠지만, kobj.kset 이 이미 설정되어 있어야 kset.list 에 kobj 가 추가될 수 있다.

     

     

    " 위에 2가지 내용을 기본으로 아래 블락 다이어그램을 해석해보자. {1, 4} 관계를 만들려면, 코드를 어떻게 작성해야 할까? `kobj`가 커널에 등록될 때, `kset.list`에 포함되고, 부모 노드로 `kset.kobj`를 가리키고 있는 걸로 봐서는 수동으로 `kset`을 설정한 경우다. 즉, {`kobj create` -> 수동으로 `kobj.kset = kset` -> `kobj add | register`} 과정을 거친것이다.


    https://blog.csdn.net/lizuobin2/article/details/51523693

     

    " {4, 6} 관계는 어떨까? {1, 4} 관계와 거의 동일하다. 단지 주체가 달라졌다. {`kset create` -> 수동으로 `kset.kobj.kset = kset` -> `kset.kobj add | register`} 과정을 거친것이다.

     

    " 그렇다면, {4, 2} 관계는 어떨까? 여기서 {4, 6}과의 차이는 `kset.list`에 포함되지 않는다는 것이다. 즉, 수동으로 `kset`을 설정하는 부분이 사라졌으므로, {`kset create` -> `kset.kobj add | register`} 과정을 거친것이다. 일반적으로 이런 경우는 함수를 따로 호출하지않고, create와 add를 하나로 합친 `kset_create_and_add` 함수를 호출한다.

     

     " {3 , 5} 관계는 어떨까? `kset.list`에 포함되어 있지 않다. 즉, 수동으로 `kset`을 설정한 부분이 없다는 뜻이다. 이런 경우는 create와 add를 하나로 합친 `kobject_create_and_add` 함수를 호출한다. 주의점은 여기서 케이스가 2가지로 나뉠 수 있다.

    1. `kobject_create_and_add` 함수에 부모가 전달된 경우 : 전달된 부모 노드를 부모 노드로 인식
    2. `kobject_create_and_add` 함수에 부모가 전달되지 않은 경우 : 루트 노드를 부모 노드로 인식

     

    " 즉, 위 그림에서 5번 노드는 루트 노드일 수 도 있고, 일반 노드일 수 도 있다.

     

    " {4, 5} 관계도 위에 관계들과 크게 다르지 않으므로, 설명은 생략한다. 

Designed by Tistory.