-
[리눅스 커널] LDM - kobject, kset, ktypeLinux/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} 관계도 위에 관계들과 크게 다르지 않으므로, 설명은 생략한다.
'Linux > kernel' 카테고리의 다른 글
[리눅스 커널] Timer - clock event device (0) 2023.09.25 [리눅스 커널] LDM - kobj_type & sysfs & kernfs (0) 2023.09.23 [리눅스 커널] PM - Platform-dependent power management (0) 2023.09.18 [리눅스 커널] PM - restart & shutdown & halt (0) 2023.09.18 [리눅스 커널] Timer - Broadcast timer (0) 2023.09.17