About kobject

Reference: https://lwn.net/Articles/54651/

Kernel Object

kobject는 kernel 내부에서 구조체를 관리하기 위한 도구이다.
이 도구는 독립적으로는 잘 쓰이지 않으며, 주로(99.9%) 구조체에 임베딩 되어 쓰인다.
자신을 임베딩한 구조체를 관리하기 위한 기능을 여러가지 내장하고 있는데 다음과 같은 기능을 내장하고 있다.

// kobject를 임베딩한 struct foo
struct foo {
    struct kobject kobj;
    ...
};
  1. 계층화
    • 부모를 표현하기 위한 kobject->parent
    • 내가 소속된 kset (optional)
  2. 참조 카운트 관리
    • kobject_get() - ref count 1 증가
    • kobject_put() - ref conut 1 감소
  3. kobject의 해제, 그리고 kobject 디바이스 파일 RW를 담당하는 ktype
    • 디바이스 파일은 /sys 하위항목에 생성된다.
  4. kobject를 그룹으로 관리하기 위한 kset 1

즉, kobject는 구조체를 /sys 하위에 노출 시키면서, 구조체를 계층적으로 관리할 수 있게 도와주는 도구라고 보면 된다.

kobject의 생성

kobjectkobject_init()으로 생성할 수 있다.

void kobject_init(struct kobject* kobj, struct kobj_type* ktype)

위에서 볼 수 있듯이, 임의의 kobj를 인자로 받고, ktype으로 kobj의 관리 정보들을 결정한다.
초기화된 kobject를 sysfs에 등록시키기 위해서는 kobject_add()를 이용한다.

void kobject_add(struct kobject* kobj, struct kobject* parent, const char* fmt, ...)

이 때, sysfs root에 등록시키려면 parent == NULL을 변수로 하여 호출한다.

ktype

ktypekobject의 해제, 그리고 kobject와 관련된 디바이스 파일 RW를 담당한다.

struct kobj_type {
    void (*release)(struct kobject* kobj);
    const struct sysfs_ops* sysfs_ops;
    struct attribute** default_attrs;
    ...
}

releasekobject가 임베딩 된 struct의 해제를 담당한다.

struct foo {
    struct kobject kobj;
    ...
};
void releasea_foo(struct kobject* kboj) {
    struct foo* p = container_of(kobj, struct foo, kobj);
    kfree(p)
}
static struct kobj_type foo_ktype {
    .release = release_foo,
    .default_attrs = attrs_foo,
    .sysfs_ops = sysfs_ops_foo,
};

위에서 알 수 있듯이, struct fookobject가 임베딩 되어 있고, release_foo()를 콜하여 struct foo를 메모리 해제할 수 있다.
release_foo()release에 펑션 포인터로 등록되어 있는데, kref의 ref count가 0이 되면 자동으로 호출된다.

default_attrsstruct attribute*를 배열로 가지고 있다.
kobjectkobject_add()에 의해 등록되면 내부에서 struct attribute* 배열 크기 만큼 sysfs_create_file()를 호출한다.
struct attribute 내부에 name과 access mode를 멤버변수가 있다. 이 멤버변수들을 이용해 sysfs_create_file()으로 디바이스 파일을 /sys/ 하위에 생성한다.

sysfs_opsstruct attribute에 의해 생성된 디바이스 파일을 접근(read/write)할 때, 동작할 핸들러를 지정한다.

struct sysfs_ops {
    ssize_t (*show)(...);
    ssize_t (*store)(...);
}

show()struct attribute에 의해 생성된 디바이스 파일들을 읽을 때, 호출되며 store()는 디바이스 파일을 쓸 때 호출된다.

서로 다른 디바이스 파일을 읽고 쓸 때, 똑같은 struct sysfs_ops가 호출된다.
하지만 상식적으로 당연히 다른 디바이스 파일을 읽으면 다른 struct sysfs_ops가 호출되어야 한다.
이 때문에 다음과 같이 트릭을 쓰게 된다.

struct custom_attr {
    struct attribute attr;
    ssize_t (*show)(...);
    ssize_t (*store)(...);
}
static ssize_t sysfs_show(struct kobject* kobj, struct attribute* attr, char* buf) {
    struct custom_attr* cattr = container_of(attr, struct custom_attr, attr)
    if (likely(cattr->show))
        return cattr->show(buf);
    return -EIO;
}

위에서 sysfs_show()struct sysfs_ops에 등록된 common operation이다.
attribute에 해당하는 custom operation을 호출하기 위해, attribute를 포함하는 구조체에 원하는 handler를 등록해놓고 container_of()를 통해 핸들러를 접근하면 된다.

kref

참조 카운트를 관리하는 변수이다.
만약 구조체의 스마트한 해제기능만을 사용하기 위해서는 kobject를 쓰지 말고, kref만을 포함하여 쓰는것이 맞다.
kobject_init()kobject를 만들 시, ref count를 1로 셋팅한다.
kobject_get()은 ref count를 1 올리며, kobject_put()은 ref count를 1 내린다.
만약 kobject의 ref count가 0이 되면 ktype->release()를 호출한다.

일반적으로 자식이 생기면 부모의 kobject의 ref count가 증가되며, 자식이 해제되면 ref count가 감소된다.

kset

kobject를 그룹으로 관리하기 위한 구조체이며, 자체적으로 kobject를 포함하고 있다.
계층 구조를 만드려면 kobject 내부에 있는 kset을 이용한다.
kobject->kset을 설정하고 kobject_add()를 호출하면 kset 리스트에 kobject가 등록되며, kobject->parentkset->kobj가 된다.

kobj->kset = kset_create(...);
kobject_add(kobj, ...); 
// kobject_add에 parent가 NULL이면, kobj->parent == kset->kobj가 되며, kset 내부 리스트에 kobj가 등록된다.
// 단, kobject_add()에 parent가 있다면 kobj->parent == parent이다. (kset 내부 리스트에는 kobj가 정상적으로 등록된다.)

  1. kobject->kset를 설정하고 kobject_add()를 호출하면 자동으로 현 kobject