Kernel Objects (kobject, kset, ktype)

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

개요 (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 */
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 (참조 카운팅)

krefatomic_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 사용)

#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 디바이스 트리 예시
/sys/
├── bus/
│   ├── pci/       ← kset: pci_bus_type.p->drivers_kset
│   │   ├── devices/
│   │   └── drivers/
│   └── usb/
├── class/
│   ├── net/       ← kset: net_class.p->glue_dirs
│   │   ├── eth0/  ← kobject: net_device.dev.kobj
│   │   └── lo/
│   └── block/
├── devices/       ← kset: devices_kset
│   ├── platform/
│   └── pci0000:00/
└── kernel/        ← kset: kernel_kobj

커널 내 실제 사용 사례

커널의 주요 서브시스템에서 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
#              _-----=> irqs-off
#             / _----=> need-resched
#            | / _---=> hardirq/softirq
# TASK-PID   | | SOFTIRQ  TIMESTAMP  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/