Kernel Objects (kobject)

Linux 커널 오브젝트 모델의 핵심: kobject, kref 참조 카운팅, kset, ktype, sysfs 통합을 심층 분석합니다.

전제 조건: 디바이스 드라이버시스템 콜 문서를 먼저 읽으세요. kobject는 커널 객체 생명주기와 sysfs 노출 규약의 중심이므로, 참조 카운트와 계층 모델을 먼저 이해해야 합니다.
일상 비유: 이 주제는 자산 태그와 관리 대장과 비슷합니다. 자산마다 식별자와 소속 경로가 필요하듯이, kobject도 객체 식별·부모관계·해제 순서를 엄격히 관리합니다.

핵심 요약

  • 참조 카운팅kobject_get()/kobject_put()으로 오브젝트 수명을 안전하게 관리합니다.
  • release 콜백 — 참조 카운트가 0이 되면 kobj_type.release가 호출되어 리소스를 해제합니다.
  • sysfs 매핑 — 각 kobject는 /sys 아래 디렉토리로 나타나 사용자 공간에서 접근 가능합니다.
  • kset 계층 구조 — kobject를 kset에 등록하여 부모-자식 트리와 uevent 알림을 구성합니다.
  • 임베디드 패턴container_of()로 kobject를 포함하는 상위 구조체(device, driver 등)를 역참조합니다.

단계별 이해

  1. kobject 생명주기 이해
    kobject_init_and_add()로 초기화·등록하고, kobject_put()으로 해제하는 기본 흐름을 파악합니다. kfree를 직접 호출하면 안 되며 반드시 release 콜백에서 처리해야 합니다.
  2. sysfs 속성 노출
    kobj_typesysfs_opsdefault_attrs/default_groups를 정의하여 /sys 파일로 노출하는 과정을 학습합니다.
  3. kset과 uevent 연결
    kset_create_and_add()로 kobject 그룹을 만들고, uevent_ops를 통해 udev에 핫플러그 이벤트를 전달하는 구조를 이해합니다.
  4. 디바이스 모델과의 관계
    struct device, struct device_driver, struct bus_type 내부에 kobject가 어떻게 임베딩되어 커널 디바이스 모델을 형성하는지 확인합니다.
예제 읽기 가이드: 이 문서는 개념 설명용 의사코드를 중심으로 구성하되, sysfs/모듈 경로는 실습 가능한 예제를 함께 제공합니다. 코드 주석의 개념 예시는 구조 파악용, 실습 예제는 빌드/로딩/검증 흐름 점검용입니다.
관련 표준: (커널 내부 OOP 패턴, 외부 표준 없음) Linux 커널 디바이스 모델의 핵심 자료구조입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

개요 (Overview)

Linux 커널은 수천 개의 디바이스, 드라이버, 버스, 모듈 등 다양한 커널 오브젝트를 관리해야 합니다. kobject는 이 모든 오브젝트의 공통 기반 클래스 역할을 합니다. C 언어에는 클래스 상속이 없으므로, 커널은 임베디드 구조체 패턴을 사용하여 이를 구현합니다.

kobject 서브시스템이 제공하는 핵심 기능:

ℹ️

Greg Kroah-Hartman의 "Everything you never wanted to know about kobjects, ksets, and ktypes"(LWN, 2007)는 kobject 이해의 필수 문서입니다. 커널 소스의 Documentation/core-api/kobject.rst도 참고하세요.

struct kobject 구조체

struct kobject<linux/kobject.h>에 정의됩니다:

/* 개념 예시: include/linux/kobject.h 핵심 구조체 */
/* include/linux/kobject.h */
struct kobject {
    const char          *name;       /* sysfs 디렉토리 이름 */
    struct list_head     entry;       /* kset의 리스트에 연결 */
    struct kobject      *parent;     /* 부모 kobject (sysfs 계층) */
    struct kset         *kset;       /* 소속된 kset */
    const struct kobj_type *ktype; /* 타입 디스크립터 */
    struct kernfs_node  *sd;         /* sysfs 디렉토리 노드 */
    struct kref          kref;       /* 참조 카운터 */
    unsigned int state_initialized:1; /* 초기화 완료 */
    unsigned int state_in_sysfs:1;    /* sysfs에 등록됨 */
    unsigned int state_add_uevent_sent:1;
    unsigned int state_remove_uevent_sent:1;
};

kref (참조 카운팅)

krefrefcount_t 기반의 참조 카운터입니다. kobject의 수명을 안전하게 관리합니다:

struct kref {
    refcount_t refcount;  /* atomic 참조 카운터 */
};

/* 참조 획득 */
struct kobject *kobject_get(struct kobject *kobj);  /* kref_get 내부 호출 */

/* 참조 해제 - 카운트가 0이 되면 release 콜백 호출 */
void kobject_put(struct kobject *kobj);  /* kref_put 내부 호출 */
⚠️

kobject_put() 이후에 해당 kobject 포인터를 사용하면 use-after-free 버그가 발생합니다. kobject_put() 호출 후에는 즉시 포인터를 NULL로 설정하는 것이 좋습니다.

kobj_type (타입 디스크립터)

kobj_type은 kobject의 동작을 정의합니다:

struct kobj_type {
    void (*release)(struct kobject *kobj);       /* 참조 카운트 0 시 호출 */
    const struct sysfs_ops *sysfs_ops;             /* show/store 콜백 */
    const struct attribute_group **default_groups; /* 기본 sysfs 속성 */
    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 *, kuid_t *, kgid_t *);
};

/* sysfs read/write 콜백 */
struct sysfs_ops {
    ssize_t (*show)(struct kobject *, struct attribute *, char *buf);
    ssize_t (*store)(struct kobject *, struct attribute *, const char *buf, size_t count);
};

kset (kobject 집합)

kset은 kobject들의 집합이며, 자체도 kobject를 포함합니다:

struct kset {
    struct list_head list;               /* 소속 kobject들의 리스트 */
    spinlock_t list_lock;                 /* 리스트 보호 lock */
    struct kobject kobj;                  /* kset 자체의 kobject */
    const struct kset_uevent_ops *uevent_ops; /* uevent 콜백 */
};

/* kset uevent 콜백 */
struct kset_uevent_ops {
    int (*filter)(const struct kobject *kobj);           /* uevent 필터링 */
    const char *(*name)(const struct kobject *kobj);    /* 서브시스템 이름 */
    int (*uevent)(const struct kobject *kobj, struct kobj_uevent_env *env);
};

kobject/kset/ktype 관계도

kobject, kset, ktype 관계 kset list_head kobj (embedded) uevent_ops struct device dev_name, ... kobject kobj struct device dev_name, ... kobject kobj kobj_type release() sysfs_ops default_groups entry ktype /sys/devices/... (sysfs 디렉토리 계층) sysfs 매핑
kset은 kobject들의 집합. 모든 kobject는 kobj_type을 공유하며 sysfs에 매핑됨

kobject 생명주기

kobject의 생명주기는 다음 단계를 따릅니다:

/* 1. 초기화 */
kobject_init(&kobj, &my_ktype);  /* kref를 1로 설정, ktype 연결 */

/* 2. sysfs에 등록 (이름과 부모 지정) */
kobject_add(&kobj, parent, "my_object");  /* /sys/...parent.../my_object 생성 */

/* 또는 1+2를 한번에: */
kobject_init_and_add(&kobj, &my_ktype, parent, "%s", name);

/* 3. uevent 전송 (udev에 알림) */
kobject_uevent(&kobj, KOBJ_ADD);

/* 4. 사용 중 참조 카운팅 */
struct kobject *ref = kobject_get(&kobj);  /* refcount++ */
/* ... 사용 ... */
kobject_put(ref);  /* refcount-- */

/* 5. 제거 */
kobject_del(&kobj);  /* sysfs에서 제거 */
kobject_put(&kobj);  /* 최종 참조 해제 → release() 콜백 호출 */

sysfs 통합

kobject에 sysfs attribute를 추가하면 사용자 공간에서 cat/echo로 값을 읽고 쓸 수 있습니다:

/* attribute 정의 */
struct my_obj {
    struct kobject kobj;
    int value;
    char name[32];
};

struct my_attr {
    struct attribute attr;
    ssize_t (*show)(struct my_obj *, struct my_attr *, char *);
    ssize_t (*store)(struct my_obj *, struct my_attr *, const char *, size_t);
};

/* show 콜백: cat /sys/.../value 시 호출 */
static ssize_t value_show(struct my_obj *obj, struct my_attr *attr, char *buf)
{
    return sysfs_emit(buf, "%d\\n", obj->value);
}

/* store 콜백: echo 42 > /sys/.../value 시 호출 */
static ssize_t value_store(struct my_obj *obj, struct my_attr *attr,
                          const char *buf, size_t count)
{
    int ret = kstrtoint(buf, 10, &obj->value);
    return ret ? ret : count;
}

sysfs_emit() vs sprintf()

커널 5.2+에서는 sysfs show 콜백에서 반드시 sysfs_emit()를 사용해야 합니다:

함수안전성비고
sprintf(buf, ...)위험PAGE_SIZE 초과 시 버퍼 오버플로우. 사용 금지
scnprintf(buf, PAGE_SIZE, ...)안전5.2 이전 커널에서의 올바른 방법
sysfs_emit(buf, ...)최적buf가 page-aligned인지 검증, PAGE_SIZE 자동 제한
sysfs_emit_at(buf, offset, ...)최적여러 값을 순차적으로 출력할 때
/* 여러 값을 순차 출력하는 패턴 */
static ssize_t status_show(struct my_obj *obj, struct my_attr *attr, char *buf)
{
    int len = 0;
    len += sysfs_emit_at(buf, len, "value: %d\\n", obj->value);
    len += sysfs_emit_at(buf, len, "name: %s\\n", obj->name);
    return len;
}

attribute_group과 ATTRIBUTE_GROUPS 매크로

여러 attribute를 그룹으로 묶어 관리하는 것이 권장 패턴입니다. kobj_typedefault_groups에 연결하면 kobject 등록 시 자동으로 sysfs에 추가됩니다:

/* 개별 attribute 정의 */
static struct my_attr value_attr = __ATTR(value, 0664, value_show, value_store);
static struct my_attr name_attr = __ATTR(name, 0444, name_show, NULL);

/* attribute 배열 → attribute_group */
static struct attribute *my_attrs[] = {
    &value_attr.attr,
    &name_attr.attr,
    NULL,  /* 반드시 NULL 종료 */
};
ATTRIBUTE_GROUPS(my);  /* my_groups[] 자동 생성 */

/* kobj_type에 연결 → kobject_add() 시 자동 등록 */
static const struct kobj_type my_ktype = {
    .release        = my_release,
    .sysfs_ops      = &my_sysfs_ops,
    .default_groups = my_groups,  /* ATTRIBUTE_GROUPS가 생성한 배열 */
};
💡

default_groups를 사용하면 sysfs_create_group()을 수동으로 호출할 필요가 없습니다. kobject가 sysfs에 추가될 때 자동으로 attribute들이 생성되고, 제거될 때 자동으로 삭제됩니다.

바이너리 속성 (Binary Attributes)

텍스트가 아닌 바이너리 데이터를 sysfs로 노출할 때는 struct bin_attribute를 사용합니다. 펌웨어 덤프, EEPROM 데이터, 레지스터 맵 등에 활용됩니다:

/* 바이너리 attribute: read/write가 버퍼 단위로 동작 */
static ssize_t firmware_read(struct file *filp, struct kobject *kobj,
                            struct bin_attribute *attr,
                            char *buf, loff_t off, size_t count)
{
    struct my_device *dev = to_my_device(kobj);
    if (off >= dev->fw_size)
        return 0;
    if (off + count > dev->fw_size)
        count = dev->fw_size - off;
    memcpy(buf, dev->fw_data + off, count);
    return count;
}

/* BIN_ATTR 매크로로 선언 */
static BIN_ATTR_RO(firmware, FW_MAX_SIZE);  /* 읽기 전용 */
/* static BIN_ATTR_WO(firmware, FW_MAX_SIZE); — 쓰기 전용 */
/* static BIN_ATTR_RW(firmware, FW_MAX_SIZE); — 읽기/쓰기 */

/* 바이너리 attribute 그룹 */
static struct bin_attribute *my_bin_attrs[] = {
    &bin_attr_firmware,
    NULL,
};

static const struct attribute_group my_group = {
    .attrs      = my_attrs,      /* 텍스트 속성들 */
    .bin_attrs  = my_bin_attrs,  /* 바이너리 속성들 */
};

sysfs_notify()로 poll/select 지원

사용자 공간에서 sysfs 파일의 변경을 poll/select로 대기할 수 있습니다:

/* 커널: 값 변경 시 통지 */
static void my_update_value(struct my_obj *obj, int new_val)
{
    obj->value = new_val;
    sysfs_notify(&obj->kobj, NULL, "value");
    /* NULL = attribute가 kobject 디렉토리 바로 아래에 있음 */
    /* 하위 그룹에 있으면 그룹 이름 전달: sysfs_notify(&kobj, "group", "attr") */
}
/* 사용자 공간: poll()로 변경 대기 */
int fd = open("/sys/kernel/my_obj/value", O_RDONLY);
struct pollfd pfd = { .fd = fd, .events = POLLPRI | POLLERR };

/* 최초 한 번 읽어서 현재 값 소비 */
read(fd, buf, sizeof(buf));

while (1) {
    poll(&pfd, 1, -1);  /* 변경까지 블록 */
    lseek(fd, 0, SEEK_SET);
    ssize_t n = read(fd, buf, sizeof(buf));
    printf("new value: %.*s", (int)n, buf);
}

uevent 메커니즘

kobject의 uevent는 사용자 공간의 udev에게 디바이스 추가/제거를 알립니다:

uevent 타입상수설명
추가KOBJ_ADD새 오브젝트가 sysfs에 등록됨
제거KOBJ_REMOVE오브젝트가 sysfs에서 제거됨
변경KOBJ_CHANGE오브젝트 상태 변경
이동KOBJ_MOVE오브젝트가 다른 위치로 이동
온라인KOBJ_ONLINE오브젝트 활성화
오프라인KOBJ_OFFLINE오브젝트 비활성화
바인드KOBJ_BIND드라이버-디바이스 바인딩
언바인드KOBJ_UNBIND드라이버-디바이스 언바인딩

kset uevent 필터링과 환경변수

kset의 uevent_ops를 구현하여 uevent를 필터링하거나 사용자 정의 환경변수를 추가할 수 있습니다:

/* uevent 필터: 특정 조건의 kobject만 uevent 발생 */
static int my_filter(const struct kobject *kobj)
{
    const struct kobj_type *ktype = get_ktype(kobj);
    /* 우리 타입의 kobject만 uevent 허용 */
    if (ktype == &my_ktype)
        return 1;  /* uevent 발생 */
    return 0;  /* uevent 억제 */
}

/* uevent 서브시스템 이름 */
static const char *my_name(const struct kobject *kobj)
{
    return "my_subsystem";  /* SUBSYSTEM=my_subsystem */
}

/* uevent 환경변수 추가 */
static int my_uevent(const struct kobject *kobj,
                     struct kobj_uevent_env *env)
{
    const struct my_device *dev = to_my_device(kobj);
    /* udev 규칙에서 사용할 환경변수 추가 */
    add_uevent_var(env, "MY_SERIAL=%d", dev->serial);
    add_uevent_var(env, "MY_TYPE=%s", dev->type_name);
    return 0;
}

static const struct kset_uevent_ops my_uevent_ops = {
    .filter = my_filter,
    .name   = my_name,
    .uevent = my_uevent,
};

/* kset 생성 시 uevent_ops 연결 */
struct kset *my_kset = kset_create_and_add("my_kset", &my_uevent_ops, kernel_kobj);
# udevadm으로 uevent 모니터링
$ udevadm monitor --environment --subsystem-match=my_subsystem
UDEV  [1234.567890] add  /kernel/my_kset/my_device (my_subsystem)
ACTION=add
DEVPATH=/kernel/my_kset/my_device
SUBSYSTEM=my_subsystem
MY_SERIAL=42
MY_TYPE=sensor

# udev 규칙 예시: /etc/udev/rules.d/99-my.rules
SUBSYSTEM=="my_subsystem", ENV{MY_TYPE}=="sensor", RUN+="/usr/bin/sensor_init.sh"

container_of 패턴

container_of 매크로는 임베디드 kobject에서 부모 구조체를 복원합니다. 이는 커널 오브젝트 모델의 핵심 패턴입니다:

struct my_device {
    int serial;
    struct kobject kobj;  /* 임베디드 kobject */
};

/* kobject 포인터에서 my_device 복원 */
static inline struct my_device *to_my_device(struct kobject *kobj)
{
    return container_of(kobj, struct my_device, kobj);
}

/* release 콜백에서 사용 */
static void my_release(struct kobject *kobj)
{
    struct my_device *dev = to_my_device(kobj);
    pr_info("releasing device serial %d\\n", dev->serial);
    kfree(dev);
}

실전 예제: 커스텀 kobject 모듈

기본 예제 (kobject_create_and_add 사용)

/* 실습 예제: kobject_create_and_add 기반 sysfs 모듈 */
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>

static int my_value = 0;

static ssize_t my_show(struct kobject *kobj,
                       struct kobj_attribute *attr, char *buf)
{
    return sysfs_emit(buf, "%d\\n", my_value);
}

static ssize_t my_store(struct kobject *kobj,
                        struct kobj_attribute *attr,
                        const char *buf, size_t count)
{
    int ret = kstrtoint(buf, 10, &my_value);
    return ret ? ret : count;
}

static struct kobj_attribute my_attr =
    __ATTR(my_value, 0664, my_show, my_store);

static struct kobject *my_kobj;

static int __init my_init(void)
{
    int ret;
    my_kobj = kobject_create_and_add("my_kobject", kernel_kobj);
    if (!my_kobj)
        return -ENOMEM;
    ret = sysfs_create_file(my_kobj, &my_attr.attr);
    if (ret)
        kobject_put(my_kobj);
    return ret;
}

static void __exit my_exit(void)
{
    kobject_put(my_kobj);
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
# 모듈 로드 후 sysfs 접근
$ cat /sys/kernel/my_kobject/my_value
0
$ echo 42 > /sys/kernel/my_kobject/my_value
$ cat /sys/kernel/my_kobject/my_value
42

확장 예제: attribute_group + 커스텀 ktype + 에러 처리

실제 커널 드라이버에 가까운 완전한 예제입니다. 커스텀 kobj_typeattribute_group을 사용하고, 적절한 에러 처리를 포함합니다:

#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>

struct my_device {
    struct kobject kobj;
    int value;
    char name[32];
    bool enabled;
};
#define to_my_device(x) container_of(x, struct my_device, kobj)

/* --- sysfs attribute 콜백 --- */
struct my_sysfs_attr {
    struct attribute attr;
    ssize_t (*show)(struct my_device *, char *);
    ssize_t (*store)(struct my_device *, const char *, size_t);
};
#define to_my_sysfs_attr(x) container_of(x, struct my_sysfs_attr, attr)

static ssize_t my_sysfs_show(struct kobject *kobj,
                              struct attribute *attr, char *buf)
{
    struct my_device *dev = to_my_device(kobj);
    struct my_sysfs_attr *a = to_my_sysfs_attr(attr);
    if (!a->show)
        return -EIO;
    return a->show(dev, buf);
}

static ssize_t my_sysfs_store(struct kobject *kobj,
                               struct attribute *attr,
                               const char *buf, size_t count)
{
    struct my_device *dev = to_my_device(kobj);
    struct my_sysfs_attr *a = to_my_sysfs_attr(attr);
    if (!a->store)
        return -EIO;
    return a->store(dev, buf, count);
}

static const struct sysfs_ops my_sysfs_ops = {
    .show  = my_sysfs_show,
    .store = my_sysfs_store,
};

/* --- 개별 attribute 구현 --- */
static ssize_t value_show(struct my_device *dev, char *buf)
{
    return sysfs_emit(buf, "%d\\n", dev->value);
}

static ssize_t value_store(struct my_device *dev,
                          const char *buf, size_t count)
{
    return kstrtoint(buf, 10, &dev->value) ?: count;
}

static ssize_t name_show(struct my_device *dev, char *buf)
{
    return sysfs_emit(buf, "%s\\n", dev->name);
}

static ssize_t enabled_show(struct my_device *dev, char *buf)
{
    return sysfs_emit(buf, "%d\\n", dev->enabled);
}

static ssize_t enabled_store(struct my_device *dev,
                             const char *buf, size_t count)
{
    return kstrtobool(buf, &dev->enabled) ?: count;
}

static struct my_sysfs_attr attr_value =
    { .attr = { .name = "value", .mode = 0664 },
      .show = value_show, .store = value_store };
static struct my_sysfs_attr attr_name =
    { .attr = { .name = "name", .mode = 0444 },
      .show = name_show };
static struct my_sysfs_attr attr_enabled =
    { .attr = { .name = "enabled", .mode = 0664 },
      .show = enabled_show, .store = enabled_store };

static struct attribute *my_dev_attrs[] = {
    &attr_value.attr,
    &attr_name.attr,
    &attr_enabled.attr,
    NULL,
};
ATTRIBUTE_GROUPS(my_dev);

/* --- release 콜백 --- */
static void my_device_release(struct kobject *kobj)
{
    struct my_device *dev = to_my_device(kobj);
    pr_info("my_device: releasing '%s'\\n", dev->name);
    kfree(dev);
}

static const struct kobj_type my_device_ktype = {
    .release        = my_device_release,
    .sysfs_ops      = &my_sysfs_ops,
    .default_groups = my_dev_groups,
};

/* --- 모듈 init/exit --- */
static struct my_device *my_dev;

static int __init my_init(void)
{
    int ret;

    my_dev = kzalloc(sizeof(*my_dev), GFP_KERNEL);
    if (!my_dev)
        return -ENOMEM;

    strscpy(my_dev->name, "example", sizeof(my_dev->name));
    my_dev->value = 0;
    my_dev->enabled = true;

    /* kobject_init_and_add: ktype의 default_groups가 자동 등록됨 */
    ret = kobject_init_and_add(&my_dev->kobj, &my_device_ktype,
                              kernel_kobj, "%s", "my_device");
    if (ret) {
        kobject_put(&my_dev->kobj); /* release 콜백이 kfree 수행 */
        return ret;
    }

    kobject_uevent(&my_dev->kobj, KOBJ_ADD);
    pr_info("my_device: created at /sys/kernel/my_device\\n");
    return 0;
}

static void __exit my_exit(void)
{
    kobject_put(&my_dev->kobj);
    /* kobject_put → refcount 0 → my_device_release → kfree */
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
# 사용 예
$ insmod my_module.ko
$ ls /sys/kernel/my_device/
enabled  name  value
$ cat /sys/kernel/my_device/name
example
$ echo 100 > /sys/kernel/my_device/value
$ echo 0 > /sys/kernel/my_device/enabled
$ rmmod my_module

디바이스 모델에서의 kobject

실제 커널에서 kobject는 struct device, struct bus_type, struct device_driver 등에 임베디드됩니다. /sys/devices/ 아래의 전체 디렉토리 트리가 kobject 계층으로 형성됩니다:

sysfs 디바이스 트리 — kobject/kset 계층 구조 /sys/ bus/ (kset) class/ (kset) devices/ (kset) pci/ — pci_bus_type usb/ — usb_bus_type platform/ — platform_bus net/ — kset (net_class) eth0/ — net_device.dev.kobj block/ — kset (block_class) platform/ (kobject) pci0000:00/ (kobject) virtual/ (kobject) kobject / kset 역할 비교 kset (컨테이너 집합) • kobject들의 그룹 (연결 리스트 관리) • uevent 필터링/전송 주체 • 예: bus_type.p→drivers_kset, devices_kset • struct kset { struct kobject kobj; ... } kobject (개별 커널 오브젝트) • sysfs 디렉토리 한 개에 1:1 대응 • 참조 카운트(kref)로 수명 관리 • 예: net_device.dev.kobj, disk.dev.kobj • struct kobject { const char *name; ... }

커널 내 실제 사용 사례

커널의 주요 서브시스템에서 kobject가 어떻게 사용되는지 살펴봅니다:

구조체 kobject 필드 sysfs 경로 예시 역할
struct device kobj /sys/devices/... 모든 디바이스의 기반. sysfs에 디바이스 트리 형성
struct bus_type p->subsys.kobj /sys/bus/pci/ 버스 타입별 디렉토리. devices/drivers 하위 디렉토리 관리
struct device_driver p->kobj /sys/bus/pci/drivers/e1000/ 드라이버별 sysfs 엔트리. bind/unbind 인터페이스
struct module mkobj.kobj /sys/module/my_mod/ 모듈 파라미터, 참조 카운트, 섹션 정보 노출
struct block_device bd_device.kobj /sys/block/sda/ 블록 디바이스 속성, 파티션 정보, I/O 스케줄러
struct net_device dev.kobj /sys/class/net/eth0/ 네트워크 인터페이스 속성 (MTU, MAC, 통계 등)
struct class p->subsys.kobj /sys/class/input/ 디바이스 클래스 그룹화. 같은 종류의 디바이스 모음
/* struct device 내부의 kobject 사용 */
struct device {
    struct kobject kobj;           /* /sys/devices/... 디렉토리 */
    struct device *parent;        /* → kobj.parent 설정 */
    const struct device_type *type;
    struct bus_type *bus;
    struct device_driver *driver;
    /* ... */
};

/* device_add() 내부에서 kobject 등록 과정:
 * 1. kobject_add(&dev->kobj, parent, ...)  → sysfs 디렉토리 생성
 * 2. kobject_uevent(&dev->kobj, KOBJ_ADD)  → udev 알림
 * 3. bus_add_device(dev)                   → /sys/bus/.../devices/에 symlink
 * 4. device_create_file(dev, ...)          → 추가 attribute 생성
 */

/* struct module의 kobject: /sys/module/ 아래 */
struct module_kobject {
    struct kobject kobj;
    struct module *mod;
    struct kobject *drivers_dir; /* /sys/module/xxx/drivers/ */
};
ℹ️

/sys 디렉토리의 모든 항목은 궁극적으로 kobject에 의해 형성됩니다. ls -la /sys/의 각 디렉토리는 하나의 kobject입니다. 커널 부팅 시 수천 개의 kobject가 생성되며, lsmod, lspci, ip link 등의 명령은 내부적으로 sysfs (= kobject 트리)를 읽습니다.

주의사항과 함정 (Common Mistakes)

1. release 콜백 미구현

/* 잘못된 코드: release 콜백이 없는 kobj_type */
static const struct kobj_type bad_ktype = {
    .sysfs_ops = &my_ops,
    /* .release 누락! */
};
/* kobject_put() 시 커널 경고 발생:
 * "kobject: 'xxx' does not have a release() function,
 *  it is broken and must be fixed."
 * 메모리 leak 발생 */

/* 올바른 코드: 반드시 release 구현 */
static void my_release(struct kobject *kobj)
{
    struct my_obj *obj = container_of(kobj, struct my_obj, kobj);
    kfree(obj);
}

static const struct kobj_type good_ktype = {
    .release   = my_release,
    .sysfs_ops = &my_ops,
};

2. kobject_put() 후 포인터 접근

/* 잘못된 코드: put 후 접근 → use-after-free */
kobject_put(&my_dev->kobj);
pr_info("device name: %s\\n", my_dev->name); /* BUG! kfree 이미 호출됨 */

/* 올바른 코드: put 전에 필요한 정보 추출 */
pr_info("device name: %s\\n", my_dev->name);
kobject_put(&my_dev->kobj);
my_dev = NULL;  /* dangling pointer 방지 */

3. kobject 초기화 누락

/* 잘못된 코드: init 없이 add 호출 */
struct my_obj *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
kobject_add(&obj->kobj, parent, "name");
/* BUG! state_initialized가 설정되지 않아 경고 발생 */

/* 올바른 코드: init_and_add 또는 init 후 add */
struct my_obj *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
kobject_init_and_add(&obj->kobj, &my_ktype, parent, "name");

4. sysfs 퍼미션 오류

/* 잘못된 코드: world-writable sysfs 파일 */
static struct kobj_attribute bad_attr =
    __ATTR(secret, 0666, my_show, my_store);
/* 보안 위험! 비특권 사용자가 커널 상태를 변경 가능 */

/* 올바른 퍼미션 패턴 */
__ATTR(value, 0664, show, store);  /* root RW, group RW, other RO */
__ATTR_RO(info);                   /* 0444: 모두 읽기 전용 */
__ATTR_WO(command);                /* 0200: root만 쓰기 */
__ATTR_RW(config);                 /* 0644: root RW, 나머지 RO */
⚠️

sysfs 퍼미션에서 0222(쓰기 전용)나 0666(모두 읽기/쓰기)은 커널 빌드 시 VERIFY_OCTAL_PERMISSIONS 매크로에 의해 컴파일 에러가 발생합니다. world-writable은 보안 검토에서 거부됩니다.

5. kobject_init_and_add 실패 시 메모리 관리

/* 잘못된 코드: 실패 시 kfree로 직접 해제 */
ret = kobject_init_and_add(&obj->kobj, &my_ktype, NULL, "name");
if (ret) {
    kfree(obj);  /* BUG! kobject_init이 이미 호출됨 → kobject_put 필요 */
    return ret;
}

/* 올바른 코드: 실패해도 kobject_put으로 정리 */
ret = kobject_init_and_add(&obj->kobj, &my_ktype, NULL, "name");
if (ret) {
    kobject_put(&obj->kobj);  /* release 콜백이 kfree 수행 */
    return ret;
}
/* kobject_init()이 호출된 이후에는 반드시 kobject_put()으로 정리해야 함.
 * kobject_init_and_add()는 내부적으로 kobject_init()을 먼저 호출하므로,
 * add가 실패해도 init은 이미 완료된 상태. */

디버깅과 내부 검사

sysfs를 통한 kobject 탐색

# kobject 계층 구조 확인
$ find /sys -maxdepth 3 -type d | head -30

# 특정 디바이스의 kobject 정보
$ ls -la /sys/devices/pci0000:00/0000:00:1f.0/
# 각 파일/디렉토리가 attribute 또는 하위 kobject

# kobject의 uevent 내용 확인
$ cat /sys/devices/pci0000:00/0000:00:1f.0/uevent
DRIVER=lpc_ich
PCI_CLASS=60100
PCI_ID=8086:A141
SUBSYSTEM=pci

# uevent 실시간 모니터링
$ udevadm monitor --kernel --property

디버깅 커널 옵션

옵션기능
CONFIG_DEBUG_KOBJECTkobject 생성/삭제/참조 변경 시 디버그 메시지 출력
CONFIG_DEBUG_KOBJECT_RELEASEkobject release 시 지연을 추가하여 use-after-free 탐지
CONFIG_KASANuse-after-free, 범위 초과 접근 등 메모리 오류 탐지
CONFIG_PROVE_LOCKINGkobject 관련 lock 순서 검증 (lockdep)

ftrace로 kobject 추적

# kobject 관련 함수 추적
$ echo 'kobject_add' > /sys/kernel/debug/tracing/set_ftrace_filter
$ echo 'kobject_del' >> /sys/kernel/debug/tracing/set_ftrace_filter
$ echo 'kobject_put' >> /sys/kernel/debug/tracing/set_ftrace_filter
$ echo function > /sys/kernel/debug/tracing/current_tracer
$ echo 1 > /sys/kernel/debug/tracing/tracing_on

# 특정 동작 수행 후 추적 결과 확인
$ cat /sys/kernel/debug/tracing/trace
# tracer: function
# insmod-1234 [001] .... 1234.567: kobject_add <-device_add
# insmod-1234 [001] .... 1234.568: kobject_put <-module_add_driver

# CONFIG_DEBUG_KOBJECT 활성화 시 dmesg 출력
$ dmesg | grep kobject
[  12.345] kobject: 'my_device' (ffffff80123456): kobject_add_internal: parent: 'kernel'
[  12.346] kobject: 'my_device' (ffffff80123456): fill_kobj_path: path = '/kernel/my_device'

커널 버전별 변경사항

버전변경 내용
2.6.0kobject, kset, ktype 도입 (통합 디바이스 모델)
2.6.25kobject_create_and_add() 편의 함수 추가
3.14default_attrsdefault_groups 마이그레이션 시작
4.10kernfs 기반 sysfs 리팩토링 완료
5.2sysfs_emit() 도입 (snprintf 대체, 버퍼 오버플로우 방지)
5.18default_attrs 완전 제거, default_groups만 사용
6.2const 정확성 개선 (kobj_type 내 콜백 매개변수)
💡

참고 자료: LWN: The zen of kobjects, 커널 소스 Documentation/core-api/kobject.rst, samples/kobject/

일반적인 실수와 올바른 패턴

kobject를 사용할 때 자주 발생하는 실수와 올바른 접근 방법을 비교합니다.

❌ 실수 1: kobject 초기화 전 memset 사용

/* 잘못된 예: kobject를 포함한 구조체를 memset으로 초기화 */
struct my_device {
    struct kobject kobj;
    int value;
};

struct my_device *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
memset(dev, 0, sizeof(*dev));  /* ❌ kobject 내부 상태 손상 */
kobject_init(&dev->kobj, &my_ktype);

/* 올바른 예: kzalloc 후 kobject_init만 호출 */
struct my_device *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
/* memset 불필요 - kzalloc이 이미 0으로 초기화 */
kobject_init(&dev->kobj, &my_ktype);  /* ✓ 내부 spinlock 등 올바르게 초기화 */

❌ 실수 2: 에러 경로에서 이중 kobject_put

/* 잘못된 예: kobject_init_and_add 실패 시 잘못된 정리 */
struct my_device *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
int ret = kobject_init_and_add(&dev->kobj, &my_ktype, parent, "mydev");
if (ret) {
    kobject_put(&dev->kobj);  /* ✓ 올바름 */
    kfree(dev);              /* ❌ 이중 해제! release에서 이미 kfree됨 */
    return ret;
}

/* 올바른 예: kobject_put만 호출 (release가 kfree 처리) */
struct my_device *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
int ret = kobject_init_and_add(&dev->kobj, &my_ktype, parent, "mydev");
if (ret) {
    kobject_put(&dev->kobj);  /* ✓ release 콜백에서 kfree 처리 */
    return ret;
}

❌ 실수 3: sysfs show에서 sprintf 사용

/* 잘못된 예: sprintf로 버퍼 오버플로우 위험 */
static ssize_t value_show(struct kobject *kobj,
                          struct kobj_attribute *attr, char *buf)
{
    struct my_device *dev = container_of(kobj, struct my_device, kobj);
    return sprintf(buf, "%d\n", dev->value);  /* ❌ PAGE_SIZE 검증 없음 */
}

/* 올바른 예: sysfs_emit 사용 (커널 5.2+) */
static ssize_t value_show(struct kobject *kobj,
                          struct kobj_attribute *attr, char *buf)
{
    struct my_device *dev = container_of(kobj, struct my_device, kobj);
    return sysfs_emit(buf, "%d\n", dev->value);  /* ✓ 자동 버퍼 검증 */
}

❌ 실수 4: parent kobject 참조 카운트 누락

/* 잘못된 예: parent가 먼저 해제될 수 있음 */
void create_child(struct kobject *parent) {
    struct kobject *child = kobject_create_and_add("child", parent);
    /* parent에 대한 참조 없음 - parent가 먼저 해제되면 dangling pointer */
}

/* 올바른 예: parent 참조 카운트 증가 (kobject_init_and_add가 자동 처리) */
void create_child(struct kobject *parent) {
    struct kobject *child = kobject_create_and_add("child", parent);
    /* ✓ kobject_add 내부에서 parent에 대해 kobject_get 호출 */
    /* child가 존재하는 동안 parent도 유지됨 */
}

❌ 실수 5: kset_register 전 kobject 초기화 누락

/* 잘못된 예: kset 내부 kobject 미초기화 */
struct kset *my_kset = kset_create_and_add("mykset", NULL, NULL);
/* 하지만 kset_create_and_add가 내부적으로 처리하므로 OK */

/* 수동으로 kset 생성 시 주의 필요 */
struct kset *my_kset = kzalloc(sizeof(*my_kset), GFP_KERNEL);
kset_register(my_kset);  /* ❌ kobject 초기화 안 됨 */

/* 올바른 예: kset 수동 초기화 */
struct kset *my_kset = kzalloc(sizeof(*my_kset), GFP_KERNEL);
my_kset->kobj.kset = NULL;
my_kset->kobj.ktype = &kset_ktype;
kobject_set_name(&my_kset->kobj, "mykset");
kset_register(my_kset);  /* ✓ 올바르게 등록 */

✅ 모범 사례 체크리스트

항목설명검증 방법
release 필수 구현모든 kobj_type에 release 콜백 필요없으면 커널 경고 발생
kobject_put 후 NULL 설정use-after-free 방지kobj = NULL;
sysfs_emit 사용sprintf 대신 sysfs_emit (5.2+)버퍼 오버플로우 자동 방지
ATTRIBUTE_GROUPS 매크로수동 NULL 종료 배열 대신 매크로 사용컴파일 타임 체크
parent 수명 주의child보다 parent가 먼저 해제되지 않게kobject_add가 자동 참조 관리
에러 경로 정리kobject_init_and_add 실패 시 kobject_put만 호출kfree는 release에서

성능 최적화 가이드

kobject와 sysfs는 성능에 민감한 경로는 아니지만, 고빈도 접근 시 최적화가 필요할 수 있습니다.

참조 카운팅 최적화

/* ❌ 비효율적: 루프 내 반복 참조 획득/해제 */
void process_devices(struct kset *kset) {
    struct kobject *k;
    list_for_each_entry(k, &kset->list, entry) {
        kobject_get(k);  /* atomic 연산 비용 */
        do_something(k);
        kobject_put(k);  /* atomic 연산 비용 */
    }
}

/* ✅ 효율적: kset lock으로 보호하며 참조 없이 순회 */
void process_devices(struct kset *kset) {
    struct kobject *k;
    spin_lock(&kset->list_lock);
    list_for_each_entry(k, &kset->list, entry) {
        do_something(k);  /* lock 보호 하에 직접 접근 */
    }
    spin_unlock(&kset->list_lock);
}

sysfs 속성 캐싱

/* ❌ 비효율적: 매번 하드웨어 레지스터 읽기 */
static ssize_t status_show(struct kobject *kobj,
                           struct kobj_attribute *attr, char *buf)
{
    struct my_device *dev = container_of(kobj, struct my_device, kobj);
    u32 status = readl(dev->base + STATUS_REG);  /* 느린 I/O */
    return sysfs_emit(buf, "0x%x\n", status);
}

/* ✅ 효율적: 값 캐싱 + 변경 시에만 업데이트 */
struct my_device {
    struct kobject kobj;
    u32 cached_status;
    unsigned long last_update;
};

static ssize_t status_show(struct kobject *kobj,
                           struct kobj_attribute *attr, char *buf)
{
    struct my_device *dev = container_of(kobj, struct my_device, kobj);
    unsigned long now = jiffies;

    /* 1초 이상 지났으면 갱신 */
    if (time_after(now, dev->last_update + HZ)) {
        dev->cached_status = readl(dev->base + STATUS_REG);
        dev->last_update = now;
    }
    return sysfs_emit(buf, "0x%x\n", dev->cached_status);
}

kobject 조회 최적화

/* ❌ 비효율적: 이름으로 반복 조회 */
struct kobject *find_device_by_name(const char *name) {
    struct kobject *k;
    list_for_each_entry(k, &devices_kset->list, entry) {
        if (strcmp(k->name, name) == 0)
            return k;
    }
    return NULL;
}

/* ✅ 효율적: hash table로 빠른 조회 */
static DEFINE_HASHTABLE(device_hash, 8);  /* 256 buckets */

struct my_device {
    struct kobject kobj;
    struct hlist_node hash_node;
};

void add_device(struct my_device *dev) {
    kobject_init_and_add(&dev->kobj, ...);
    hash_add(device_hash, &dev->hash_node, hash_string(dev->kobj.name));
}

struct my_device *find_device_by_name(const char *name) {
    struct my_device *dev;
    hash_for_each_possible(device_hash, dev, hash_node, hash_string(name)) {
        if (strcmp(dev->kobj.name, name) == 0)
            return dev;
    }
    return NULL;
}

sysfs poll 최적화

/* sysfs_notify() 호출 빈도 최소화 */
struct my_device {
    struct kobject kobj;
    int value;
    int last_notified_value;
};

void update_value(struct my_device *dev, int new_value) {
    dev->value = new_value;

    /* 값이 실제로 변경되었을 때만 notify */
    if (dev->value != dev->last_notified_value) {
        sysfs_notify(&dev->kobj, NULL, "value");
        dev->last_notified_value = new_value;
    }
}
💡

성능 측정: perf로 kobject 관련 hot path 확인: perf record -e probe:kobject_get,probe:kobject_put -a sleep 10

실전 케이스 스터디

실제 드라이버에서 kobject를 활용하는 패턴들을 단계별로 살펴봅니다.

케이스 1: 커스텀 버스 타입 구현

시나리오: 가상 버스 타입을 만들어 여러 디바이스를 관리

/* 1단계: 버스 타입 정의 */
struct bus_type my_bus_type = {
    .name = "mybus",
    .match = my_bus_match,
    .probe = my_bus_probe,
};

/* 2단계: 버스용 kset 생성 */
static struct kset *my_bus_kset;

static int __init my_bus_init(void) {
    int ret;

    /* 버스 등록 */
    ret = bus_register(&my_bus_type);
    if (ret)
        return ret;

    /* /sys/bus/mybus 생성 */
    my_bus_kset = kset_create_and_add("mybus", NULL, &bus_kset->kobj);
    if (!my_bus_kset) {
        bus_unregister(&my_bus_type);
        return -ENOMEM;
    }

    return 0;
}

/* 3단계: 디바이스를 버스에 추가 */
struct my_device {
    struct device dev;  /* 내부에 kobject 포함 */
    int id;
};

void my_device_register(struct my_device *mydev) {
    mydev->dev.bus = &my_bus_type;
    mydev->dev.parent = NULL;
    dev_set_name(&mydev->dev, "mydev%d", mydev->id);
    device_register(&mydev->dev);  /* kobject 자동 관리 */
}

케이스 2: 커스텀 uevent 환경변수

시나리오: 디바이스 추가 시 udev에 커스텀 정보 전달

static int my_uevent(const struct kobject *kobj, struct kobj_uevent_env *env)
{
    struct my_device *dev = container_of(kobj, struct my_device, kobj);

    /* 커스텀 환경변수 추가 */
    add_uevent_var(env, "MYDEV_ID=%d", dev->id);
    add_uevent_var(env, "MYDEV_TYPE=%s", dev->type);
    add_uevent_var(env, "MYDEV_VERSION=%s", dev->version);

    return 0;
}

static const struct kset_uevent_ops my_uevent_ops = {
    .uevent = my_uevent,
};

/* kset 생성 시 uevent_ops 연결 */
struct kset *kset = kset_create_and_add("mydevices", &my_uevent_ops, NULL);

/* udev 룰 예제 (/etc/udev/rules.d/99-mydev.rules) */
# ACTION=="add", ENV{MYDEV_TYPE}=="sensor", RUN+="/usr/local/bin/setup-sensor.sh"

케이스 3: 핫플러그 디바이스 동적 관리

시나리오: USB처럼 런타임에 디바이스 추가/제거

/* 디바이스 핫플러그 추가 */
struct my_device *my_device_add(int id) {
    struct my_device *dev;
    int ret;

    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return NULL;

    dev->id = id;
    dev->kobj.kset = my_devices_kset;

    ret = kobject_init_and_add(&dev->kobj, &my_ktype, NULL, "device%d", id);
    if (ret) {
        kobject_put(&dev->kobj);  /* release에서 kfree */
        return NULL;
    }

    /* uevent 전송 (udev 알림) */
    kobject_uevent(&dev->kobj, KOBJ_ADD);

    return dev;
}

/* 디바이스 핫플러그 제거 */
void my_device_remove(struct my_device *dev) {
    /* uevent 전송 (udev 알림) */
    kobject_uevent(&dev->kobj, KOBJ_REMOVE);

    /* sysfs에서 제거 + 참조 카운트 감소 */
    kobject_del(&dev->kobj);
    kobject_put(&dev->kobj);  /* release 콜백 호출 */
}

케이스 4: 계층적 디바이스 구조

시나리오: 컨트롤러 → 포트 → 디바이스 계층 구조

/* /sys/devices/controller0/port1/device0 구조 생성 */

struct kobject *controller_kobj;
struct kobject *port_kobj;
struct kobject *device_kobj;

/* 1. 컨트롤러 생성 */
controller_kobj = kobject_create_and_add("controller0", &devices_kset->kobj);

/* 2. 포트 생성 (컨트롤러의 자식) */
port_kobj = kobject_create_and_add("port1", controller_kobj);

/* 3. 디바이스 생성 (포트의 자식) */
device_kobj = kobject_create_and_add("device0", port_kobj);

/* 정리 시 역순으로 제거 (자식 → 부모) */
kobject_put(device_kobj);   /* device0 제거 */
kobject_put(port_kobj);      /* port1 제거 */
kobject_put(controller_kobj); /* controller0 제거 */

문제 해결 FAQ

kobject 사용 시 자주 발생하는 문제와 해결 방법입니다.

Q1: "kobject: '...' does not have a release() function" 경고 발생

증상: dmesg에 경고 메시지 출력

kobject: 'mydevice' (ffff888100000000): does not have a release() function, it is broken and must be fixed.

원인: kobj_typerelease 콜백이 없음

해결:

/* release 콜백 추가 */
static void my_device_release(struct kobject *kobj)
{
    struct my_device *dev = container_of(kobj, struct my_device, kobj);
    kfree(dev);
}

static const struct kobj_type my_ktype = {
    .release = my_device_release,  /* 필수! */
    .sysfs_ops = &my_sysfs_ops,
};

Q2: kobject_add_internal failed: -EEXIST

증상: kobject_init_and_add()-EEXIST 반환

원인: 같은 이름의 kobject가 이미 존재

해결:

/* 중복 이름 확인 */
struct kobject *existing = kset_find_obj(my_kset, "mydevice");
if (existing) {
    kobject_put(existing);  /* kset_find_obj가 참조 증가시킴 */
    pr_err("Device already exists\n");
    return -EEXIST;
}

/* 또는 고유 이름 생성 */
static atomic_t device_counter = ATOMIC_INIT(0);
int id = atomic_inc_return(&device_counter);
kobject_init_and_add(&dev->kobj, &my_ktype, NULL, "device%d", id);

Q3: sysfs에 디렉토리가 생성되지 않음

증상: kobject_init_and_add() 성공했지만 /sys에 디렉토리 없음

원인: parent가 NULL이고 kset도 설정 안 됨

해결:

/* 방법 1: parent 지정 */
kobject_init_and_add(&dev->kobj, &my_ktype,
                      &devices_kset->kobj,  /* parent */
                      "mydevice");

/* 방법 2: kset 소속 */
dev->kobj.kset = my_kset;
kobject_init_and_add(&dev->kobj, &my_ktype, NULL, "mydevice");
/* → /sys/kernel/mydevices/mydevice/ 생성 */

Q4: udev가 uevent를 받지 못함

디버깅:

# uevent 모니터링
udevadm monitor --environment

# 커널에서 uevent 수동 트리거
echo add > /sys/devices/.../uevent

# uevent 전송 확인 (커널 코드)
ret = kobject_uevent(&dev->kobj, KOBJ_ADD);
if (ret)
    pr_err("Failed to send uevent: %d\n", ret);

Q5: kobject 메모리 누수

증상: kmemleak에서 kobject 관련 누수 보고

원인: kobject_put() 호출 누락 또는 참조 카운트 불균형

디버깅:

# kmemleak 활성화
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak

# kobject 참조 카운트 추적 (ftrace)
echo 1 > /sys/kernel/tracing/events/kobject/kobject_init/enable
echo 1 > /sys/kernel/tracing/events/kobject/kobject_cleanup/enable
cat /sys/kernel/tracing/trace

해결:

/* kobject_get/put 균형 확인 */
struct kobject *kobj = kobject_get(obj);  /* +1 */
do_something(kobj);
kobject_put(kobj);  /* -1, 필수! */

/* kset_find_obj는 참조 증가시킴 */
struct kobject *found = kset_find_obj(my_kset, "name");
if (found) {
    do_something(found);
    kobject_put(found);  /* 반드시 put! */
}

Q6: sysfs 속성 읽기/쓰기 권한 오류

증상: cat /sys/.../attr에서 "Permission denied"

원인: attribute 퍼미션과 show/store 콜백 불일치

/* ❌ 잘못된 예: 0444 (read-only)인데 store 구현 */
static struct kobj_attribute value_attr =
    __ATTR(value, 0444, value_show, value_store);  /* store 무시됨 */

/* ✓ 올바른 예: 퍼미션과 콜백 일치 */
static struct kobj_attribute value_attr =
    __ATTR(value, 0644, value_show, value_store);  /* rw-r--r-- */

/* read-only */
static struct kobj_attribute readonly_attr =
    __ATTR_RO(readonly);  /* 0444, show만 */

/* write-only */
static struct kobj_attribute writeonly_attr =
    __ATTR_WO(writeonly);  /* 0200, store만 */
💡

디버깅 도구: CONFIG_DEBUG_KOBJECT=y 설정 시 kobject 생명주기 디버깅 메시지 활성화

kobject 수명주기 감사 체크리스트

kobject 문제는 대부분 참조 카운트 불균형과 해제 타이밍 오류에서 발생합니다. sysfs와 uevent까지 연결되므로 한 번의 누락이 장기 누수나 use-after-free로 이어질 수 있습니다.

  1. 생성 경로: kobject_init_and_add 성공/실패 분기 정리 확인
  2. 참조 균형: kobject_getkobject_put 짝 확인
  3. release 보장: ktype의 release 콜백 존재/실행 확인
  4. sysfs 정리: remove 시 attribute 그룹 해제 순서 점검
증상원인대응
release 미호출put 누락참조 증가 지점 전수 점검
double free중복 put/수동 free 혼용해제 책임을 release 콜백으로 단일화
sysfs 잔존 파일remove 순서 불일치attribute 제거 → kobject put 순서 고정

Kernel Objects와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.