커널 오브젝트 (kobject)

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

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

핵심 요약

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

단계별 이해

  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에 핫플러그(Hotplug) 이벤트를 전달하는 구조를 이해합니다.
  4. 디바이스 모델과의 관계
    struct device, struct device_driver, struct bus_type 내부에 kobject가 어떻게 임베딩되어 커널 디바이스 모델을 형성하는지 확인합니다.
예제 읽기 가이드: 이 문서는 개념 설명용 의사코드를 중심으로 구성하되, sysfs/모듈 경로는 실습 가능한 예제를 함께 제공합니다. 코드 주석의 개념 예시는 구조 파악용, 실습 예제는 빌드/로딩/검증 흐름 점검용입니다.
관련 표준: (커널 내부 OOP 패턴, 외부 표준 없음) Linux 커널 디바이스 모델의 핵심 자료구조입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

개요 (Overview)

Linux 커널은 수천 개의 디바이스, 드라이버, 버스(Bus), 모듈 등 다양한 커널 오브젝트를 관리해야 합니다. 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로 설정하는 것이 좋습니다.

kref 내부 메커니즘 상세

krefrefcount_t(atomic 카운터)를 래핑한 구조체입니다. kobject_get()/kobject_put()은 내부적으로 kref_get()/kref_put()을 호출합니다. 카운터가 0에 도달하면 release 콜백이 호출됩니다:

kref 참조 카운팅 메커니즘 kobject_init() refcount = 1 kobject_get() refcount++ kobject_put() refcount-- ref == 0? ktype->release() container_of → kfree return (계속 사용) Yes No kobject_put() 내부 호출 체인 1. kobject_put(kobj) 2. └─ kref_put(&kobj->kref, kobject_release) 3. └─ refcount_dec_and_test(&kref->refcount) 4. └─ (0이면) kobject_release(kref) 5. └─ kobject_cleanup(kobj) 6. ├─ sysfs_remove_groups() 7. ├─ sysfs_remove_dir() 8. ├─ kobj_type->release(kobj) 9. └─ kobject_name 해제 refcount_t: REFCOUNT_SATURATED 도달 시 WARN_ONCE 발생
kref_put()이 refcount를 원자적(Atomic)으로 감소시키고, 0이 되면 cleanup 체인 실행. release() 콜백에서 실제 메모리 해제
/* kref API 직접 사용 예 (kobject 외부에서) */
struct my_buffer {
    struct kref refcount;
    void *data;
    size_t size;
};

static void my_buffer_release(struct kref *ref)
{
    struct my_buffer *buf = container_of(ref, struct my_buffer, refcount);
    kfree(buf->data);
    kfree(buf);
}

/* 참조 획득 */
kref_get(&buf->refcount);

/* 참조 해제 - 마지막 참조 시 release 호출 */
kref_put(&buf->refcount, my_buffer_release);

/* kref_put_mutex: mutex를 잡고 release 호출 (동시성 보호) */
kref_put_mutex(&buf->refcount, my_buffer_release, &my_mutex);

/* kref_get_unless_zero: 이미 0이면 실패 (trylock 패턴) */
if (kref_get_unless_zero(&buf->refcount)) {
    /* 참조 획득 성공 */
} else {
    /* 이미 해제 중 - 사용 불가 */
}

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() 콜백 호출 */

생명주기 상태 다이어그램

kobject는 명확한 상태 전이를 따릅니다. 각 상태에서 허용되는 동작이 제한되며, 잘못된 순서로 호출하면 커널 경고가 발생합니다:

kobject 생명주기 상태 전이 Allocated kzalloc() Initialized state_initialized=1 Active (sysfs) state_in_sysfs=1 Uevent Sent add_uevent_sent=1 In Use refcount > 1 Removed kobject_del() 호출 Released release() → kfree() init() add() uevent() get() del() put() put() (ref=0) init 후 add 실패 시 put() 상태 플래그 (struct kobject 비트필드) state_initialized : kobject_init() 완료 state_in_sysfs : kobject_add() 완료, sysfs 디렉토리 존재 state_add_uevent_sent : KOBJ_ADD uevent 전송됨 state_remove_uevent_sent : KOBJ_REMOVE uevent 전송됨
kobject 생명주기: 할당 → 초기화 → sysfs 등록 → uevent → 사용 → 제거 → 해제. 에러 경로에서도 반드시 kobject_put()으로 정리

kobject_create_and_add() vs kobject_init()+kobject_add() 비교

kobject를 생성하는 두 가지 방법이 있습니다. 용도에 따라 적절한 API를 선택해야 합니다:

kobject 생성 API 비교 kobject_create_and_add() 단순 sysfs 디렉토리 생성용 1. kzalloc(sizeof(kobject)) 2. kobject_init(kobj, &dynamic_kobj_ktype) 3. kobject_add(kobj, parent, name) 특징: - ktype 고정 (dynamic_kobj_ktype) - release에서 kfree(kobj) 자동 수행 - 커스텀 attribute는 sysfs_create_file()로 추가 - 용도: 단순 sysfs 디렉토리, 모듈 파라미터 그룹 kobject_init() + kobject_add() 커스텀 ktype이 필요한 경우 1. kzalloc(sizeof(my_device)) 2. kobject_init(&dev->kobj, &my_ktype) 3. kobject_add(&dev->kobj, parent, name) 특징: - 커스텀 ktype (release, sysfs_ops, groups) - release에서 container_of → kfree 필요 - default_groups로 attribute 자동 등록 - 용도: 드라이버, 디바이스, 서브시스템
왼쪽: 간편 API (단순 sysfs 노드), 오른쪽: 전체 제어 API (커스텀 ktype 필요 시)
항목kobject_create_and_add()kobject_init() + kobject_add()
할당내부에서 kzalloc호출자가 직접 할당 (임베디드 패턴)
ktypedynamic_kobj_ktype (고정)커스텀 ktype 지정 가능
release자동 (kfree(kobj))커스텀 release 콜백 필수
attributesysfs_create_file() 수동 추가default_groups로 자동 등록
에러 처리NULL 반환 시 정리 불필요실패 시 kobject_put() 필수
대표 사용처kernel_kobj, firmware_kobjstruct device, struct module
/* kobject_init_and_add: 위 두 단계를 한 번에 수행하는 편의 함수 */
int kobject_init_and_add(struct kobject *kobj,
                        const struct kobj_type *ktype,
                        struct kobject *parent,
                        const char *fmt, ...);

/* 내부적으로:
 * 1. kobject_init(kobj, ktype)  → kref = 1, state_initialized = 1
 * 2. kobject_add_varg(kobj, parent, fmt, args)
 *    → kobject_set_name_vargs()  → kobj->name 설정
 *    → kobject_add_internal()    → parent 연결, sysfs 디렉토리 생성
 *    → state_in_sysfs = 1
 */

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 초과 시 버퍼 오버플로(Buffer Overflow)우. 사용 금지
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 매크로(Macro)

여러 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를 사용합니다. 펌웨어(Firmware) 덤프(Dump), EEPROM 데이터, 레지스터(Register) 맵 등에 활용됩니다:

/* 바이너리 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 파일 생성/삭제 내부 동작

kobject가 sysfs에 추가될 때 내부적으로 kernfs 노드가 생성됩니다. 각 attribute는 kernfs 파일 노드로 매핑되고, kobject 자체는 디렉토리 노드입니다:

sysfs 파일 생성 내부 동작 kobject_add() sysfs_create_dir_ns(kobj) kernfs_create_dir_ns() populate_dir(kobj) ktype->default_groups[] 각 group의 attrs[] 순회 sysfs_add_file_mode_ns() kernfs_create_file_ns() 결과: sysfs 트리 /sys/kernel/my_device/ ├── value (rw) ├── name (ro) ├── enabled (rw) ├── uevent (rw) └── power/ (subdir) 삭제 흐름 (kobject_del/put) 1. sysfs_remove_groups(kobj, groups) 2. sysfs_remove_dir(kobj) 3. └─ kernfs_remove(kobj->sd) 4. kobject_put(parent) [부모 참조 해제] 5. ktype->release(kobj) [메모리 해제] kernfs 노드 타입 KERNFS_DIR : 디렉토리 KERNFS_FILE : 일반 파일 KERNFS_LINK : 심볼릭 링크 kobj->sd → kernfs_node
kobject_add() 호출 시 kernfs 기반 sysfs 디렉토리와 attribute 파일이 생성되는 내부 흐름

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드라이버-디바이스 언바인딩

uevent 전달 경로

kobject에서 발생한 uevent는 두 가지 경로로 사용자 공간에 전달됩니다: netlink 소켓(Socket)과 uevent_helper(레거시). 현대 시스템에서는 netlink를 통해 udevd가 이벤트를 수신합니다:

kset_uevent_ops와 uevent 전달 경로 커널 공간 kobject_uevent() kset_uevent_ops filter(kobj) → 0이면 억제 name(kobj) → SUBSYSTEM 설정 uevent(kobj, env) → 환경변수 추가 add_uevent_var(env, "KEY=val") kobj_uevent_env ACTION=add DEVPATH=/devices/... SUBSYSTEM=pci NETLINK_KOBJECT_UEVENT netlink_broadcast() uevent_helper (레거시) call_usermodehelper() 사용자 공간 systemd-udevd netlink 소켓 수신 udev 규칙 처리 /etc/udev/rules.d/*.rules /dev 노드 생성 mknod, chmod, chown udevadm monitor 디버깅/모니터링 현대 (netlink) 레거시 (helper)
uevent는 kset_uevent_ops를 거쳐 netlink로 udevd에 전달. 레거시 uevent_helper는 현대 시스템에서 비활성

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에 디바이스 트리(Device Tree) 형성
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 스케줄러(Scheduler)
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 실패 시 메모리 관리(Memory Management)

/* 잘못된 코드: 실패 시 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은 이미 완료된 상태. */

디버깅(Debugging)과 내부 검사

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 시 지연(Latency)을 추가하여 use-after-free 탐지
CONFIG_KASANuse-after-free, 범위 초과 접근 등 메모리 오류 탐지
CONFIG_PROVE_LOCKINGkobject 관련 lock 순서 검증 (lockdep)

kobject 릭 추적 기법

kobject 참조 카운트 누수는 메모리 릭과 sysfs 잔존 노드의 주요 원인입니다. 체계적인 추적 방법을 살펴봅니다:

kobject 릭 진단 흐름 1. 증상 감지 - kmemleak 보고 - rmmod 후 sysfs 잔존 - "kobject leak" dmesg 경고 - 메모리 사용량 지속 증가 2. 원인 식별 - CONFIG_DEBUG_KOBJECT=y - ftrace: kobject_get/put - KASAN: use-after-free 탐지 - kmemleak: backtrace 분석 3. 수정 - get/put 짝 맞추기 - 에러 경로에 put 추가 - release 콜백 구현 - kobject_del() 순서 확인 진단 도구 체계 kmemleak CONFIG_DEBUG_KMEMLEAK 할당 후 미참조 메모리 탐지 /sys/kernel/debug/kmemleak KASAN CONFIG_KASAN=y use-after-free 실시간 감지 메모리 접근 범위 검증 ftrace kobject 트레이스포인트 get/put 호출 스택 기록 참조 카운트 변화 추적 KOBJECT_RELEASE CONFIG_DEBUG_KOBJECT_RELEASE release 지연 실행 use-after-free 타이밍 확대 자동화 검증 스크립트 insmod my_module.ko && sleep 1 && rmmod my_module dmesg | grep -E "kobject|leak|WARNING" && cat /sys/kernel/debug/kmemleak | grep my_
kobject 릭 진단: 증상 감지 → 원인 식별 → 수정. 4가지 진단 도구를 조합하여 사용
# 종합 kobject 디버깅 절차

# 1. 커널 빌드 시 디버그 옵션 활성화
CONFIG_DEBUG_KOBJECT=y
CONFIG_DEBUG_KOBJECT_RELEASE=y
CONFIG_KASAN=y
CONFIG_DEBUG_KMEMLEAK=y

# 2. 모듈 로드/언로드 전후 비교
$ echo clear > /sys/kernel/debug/kmemleak
$ insmod my_module.ko
$ # ... 테스트 동작 수행 ...
$ rmmod my_module

# 3. 릭 확인
$ echo scan > /sys/kernel/debug/kmemleak
$ cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff888100000000 (size 128):
  comm "insmod", pid 1234, jiffies 4294967296
  backtrace:
    kzalloc+0x1a/0x20
    my_device_create+0x35/0xb0 [my_module]
    my_init+0x42/0x70 [my_module]

# 4. sysfs 잔존 노드 확인
$ ls /sys/kernel/my_device/ 2>/dev/null && echo "LEAK: sysfs node remains"

# 5. kobject 관련 dmesg 확인
$ dmesg | grep -E "kobject.*leak|does not have a release|WARNING.*kobject"

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 내 콜백 매개변수)
6.4kobj_type에서 get_ownership() 콜백으로 sysfs 파일 소유자 동적 설정
6.6kobject_uevent_env() 환경변수 크기 제한 강화, 오버플로우 방지
6.8kernfs RCU 기반 순회 최적화, sysfs 읽기 경로 성능 개선
6.12sysfs_emit()__printf 속성 추가, 포맷 문자열 컴파일 타임 검증

주요 마이그레이션 가이드

default_attrs → default_groups 마이그레이션 (5.18+)

/* 5.18 이전: default_attrs 사용 */
static struct attribute *old_attrs[] = {
    &attr_value.attr,
    &attr_name.attr,
    NULL,
};

static const struct kobj_type old_ktype = {
    .release       = my_release,
    .sysfs_ops     = &my_ops,
    .default_attrs = old_attrs,  /* 5.18에서 제거됨! */
};

/* 5.18+: default_groups 사용 */
static struct attribute *new_attrs[] = {
    &attr_value.attr,
    &attr_name.attr,
    NULL,
};
ATTRIBUTE_GROUPS(new);  /* new_group, new_groups[] 자동 생성 */

static const struct kobj_type new_ktype = {
    .release        = my_release,
    .sysfs_ops      = &my_ops,
    .default_groups = new_groups,  /* ATTRIBUTE_GROUPS 매크로 결과 */
};

sprintf → sysfs_emit 마이그레이션 (5.2+)

# Coccinelle 스크립트로 자동 변환 (커널 소스 포함)
$ spatch --sp-file scripts/coccinelle/api/device_attr_show.cocci \
         --in-place drivers/my_driver/

# 수동 변환 패턴:
# sprintf(buf, fmt, ...)  →  sysfs_emit(buf, fmt, ...)
# snprintf(buf, PAGE_SIZE, fmt, ...)  →  sysfs_emit(buf, fmt, ...)
# scnprintf(buf, PAGE_SIZE, fmt, ...)  →  sysfs_emit(buf, fmt, ...)
💡

참고 자료: 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 생명주기 디버깅 메시지 활성화

네임스페이스(Namespace) 지원 (kobj_ns_type_operations)

Linux 네트워크 네임스페이스에서는 같은 이름의 인터페이스(eth0)가 여러 네임스페이스에 존재할 수 있습니다. sysfs는 kobj_ns_type_operations를 통해 네임스페이스별로 kobject를 분리합니다:

sysfs 네임스페이스 분리 구조 /sys/class/net/ (kset) 초기 네트워크 네임스페이스 (init_net, ns_tag=0xffff0001) eth0/ (kobject) lo/ (kobject) docker0/ (kobject) nsenter --net=/proc/1/ns/net ls /sys/class/net/ → eth0, lo, docker0 컨테이너 NS (pid 1234) (ns_tag=0xffff0002) eth0/ (kobject) lo/ (kobject) nsenter --net=/proc/1234/ns/net ls /sys/class/net/ → eth0, lo kobj_ns_type_operations type: KOBJ_NS_TYPE_NET current_may_mount() grab_current_ns() netlink_ns() initial_ns() kobj_type에서 연결: child_ns_type() namespace()
같은 /sys/class/net/ 아래에서 네임스페이스별로 다른 kobject 집합이 보임. kernfs가 ns 태그로 필터링
/* kobj_ns_type_operations 구조체 (net 네임스페이스 예시) */
const struct kobj_ns_type_operations net_ns_type_operations = {
    .type            = KOBJ_NS_TYPE_NET,
    .current_may_mount = net_current_may_mount,
    .grab_current_ns   = net_grab_current_ns,
    .netlink_ns        = net_netlink_ns,
    .initial_ns        = net_initial_ns,
    .drop_ns           = net_drop_ns,
};

/* kobj_type에서 네임스페이스 콜백 연결 */
static const struct kobj_type net_class_ktype = {
    .release        = net_class_release,
    .sysfs_ops      = &kobj_sysfs_ops,
    .child_ns_type  = net_child_ns_type,  /* 자식 kobject의 NS 타입 결정 */
    .namespace      = net_namespace,       /* 현재 kobject의 NS 태그 반환 */
};

/* kernfs가 sysfs 디렉토리 열거 시 ns 태그로 필터링:
 * - 프로세스의 현재 네임스페이스와 일치하는 kobject만 표시
 * - 다른 네임스페이스의 kobject는 보이지 않음
 * - KOBJ_NS_TYPE_NET: 네트워크 네임스페이스
 * - KOBJ_NS_TYPE_NONE: 네임스페이스 미사용 (기본)
 */
ℹ️

네임스페이스 지원은 주로 네트워크 서브시스템(/sys/class/net/)에서 사용됩니다. 컨테이너(Docker, Kubernetes)에서 각 네임스페이스가 독립적인 sysfs 뷰를 제공하는 핵심 메커니즘입니다.

커널 모듈(Kernel Module)에서의 kobject 사용 패턴

커널 모듈이 kobject를 사용하는 대표적인 패턴들을 정리합니다:

패턴 1: 단순 sysfs 인터페이스

/* 모듈 파라미터 + sysfs를 통한 런타임 제어 */
static int debug_level = 0;
module_param(debug_level, int, 0644);
/* → /sys/module/my_module/parameters/debug_level 자동 생성 */

/* 추가 sysfs 인터페이스가 필요하면 kobject_create_and_add 사용 */
static struct kobject *my_kobj;

static int __init my_init(void)
{
    /* /sys/kernel/my_subsystem/ 디렉토리 생성 */
    my_kobj = kobject_create_and_add("my_subsystem", kernel_kobj);
    if (!my_kobj)
        return -ENOMEM;

    /* attribute 파일 추가 */
    if (sysfs_create_group(my_kobj, &my_attr_group)) {
        kobject_put(my_kobj);
        return -ENOMEM;
    }
    return 0;
}

static void __exit my_exit(void)
{
    kobject_put(my_kobj);
    /* sysfs_remove_group 불필요: kobject_put → release에서 자동 정리 */
}

패턴 2: kset으로 동적 객체 관리

/* 런타임에 여러 객체를 동적으로 추가/제거하는 패턴 */
static struct kset *my_kset;

struct my_obj {
    struct kobject kobj;
    int id;
    struct list_head node;  /* 별도 관리 리스트 */
};

static LIST_HEAD(obj_list);
static DEFINE_MUTEX(obj_mutex);

/* 새 객체 생성: /sys/kernel/my_objects/obj_N/ */
struct my_obj *my_obj_create(int id)
{
    struct my_obj *obj;
    int ret;

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

    obj->id = id;
    obj->kobj.kset = my_kset;  /* kset 소속 설정 */

    ret = kobject_init_and_add(&obj->kobj, &my_obj_ktype,
                              NULL, "obj_%d", id);
    if (ret) {
        kobject_put(&obj->kobj);
        return ERR_PTR(ret);
    }

    kobject_uevent(&obj->kobj, KOBJ_ADD);

    mutex_lock(&obj_mutex);
    list_add_tail(&obj->node, &obj_list);
    mutex_unlock(&obj_mutex);

    return obj;
}

/* 모듈 init에서 kset 생성 */
static int __init my_init(void)
{
    my_kset = kset_create_and_add("my_objects", &my_uevent_ops, kernel_kobj);
    if (!my_kset)
        return -ENOMEM;
    return 0;
}

패턴 3: struct device 기반 (권장)

/* 대부분의 드라이버는 kobject를 직접 사용하지 않고
 * struct device를 통해 간접적으로 사용합니다 */
struct my_hw_device {
    struct device dev;  /* 내부에 kobject 포함 */
    void __iomem *base;
    int irq;
};

/* device 기반 attribute (DEVICE_ATTR 매크로) */
static ssize_t status_show(struct device *dev,
                           struct device_attribute *attr, char *buf)
{
    struct my_hw_device *hw = dev_get_drvdata(dev);
    return sysfs_emit(buf, "%d\n", readl(hw->base + STATUS_REG));
}
static DEVICE_ATTR_RO(status);

/* device_create_file() 또는 dev_groups로 자동 등록 */
static struct attribute *my_hw_attrs[] = {
    &dev_attr_status.attr,
    NULL,
};
ATTRIBUTE_GROUPS(my_hw);

/* 드라이버에서 dev_groups 지정 → probe 시 자동 attribute 생성 */
static struct platform_driver my_driver = {
    .driver = {
        .name = "my_hw",
        .dev_groups = my_hw_groups,  /* 자동 sysfs attribute */
    },
    .probe  = my_probe,
    .remove = my_remove,
};
💡

kobject vs device 선택 기준: 하드웨어 디바이스 → struct device 사용, 순수 커널 내부 객체(스케줄러 정책, 메모리 풀 등) → kobject 직접 사용. 대부분의 경우 struct device가 적절합니다.

디바이스 모델 통합 심층 분석

struct device 내부의 kobject가 커널 디바이스 모델 전체를 어떻게 형성하는지 상세히 살펴봅니다:

struct device 내부의 kobject 통합 struct device struct kobject kobj struct device *parent struct bus_type *bus struct device_driver *driver const struct device_type *type struct class *class void *driver_data dev_t devt struct device_node *of_node sysfs 디렉토리 kobj.sd → kernfs_node 참조 카운팅 get_device()/put_device() 부모-자식 계층 kobj.parent = &parent->kobj device_add() 내부 흐름 1. dev = get_device(dev) 2. kobject_add(&dev->kobj, parent, dev_name(dev)) 3. device_create_file(dev, &dev_attr_uevent) 4. device_add_groups(dev, dev->groups) 5. bus_add_device(dev) → /sys/bus/.../devices/ 심링크 6. bus_probe_device(dev) → 드라이버 매칭 시도 7. kobject_uevent(&dev->kobj, KOBJ_ADD) 8. device_pm_add(dev) → 전원 관리 등록 device_del() → device_unregister() 1. device_pm_remove(dev) 2. bus_remove_device(dev) 3. device_remove_attrs(dev) 4. kobject_del(&dev->kobj) 5. put_device(dev) → refcount 0이면 release
struct device의 kobject가 sysfs 디렉토리, 참조 카운팅, 계층 구조의 핵심. device_add()에서 kobject 관련 모든 설정이 수행됨
/* device_add() 중 주요 kobject 관련 코드 경로 */

/* 1. parent 설정: device->parent가 있으면 kobject parent로 설정 */
if (dev->parent)
    dev->kobj.parent = &dev->parent->kobj;
else if (dev->bus && dev->bus->dev_root)
    dev->kobj.parent = &dev->bus->dev_root->kobj;

/* 2. kset 설정: class가 있으면 class의 kset */
if (dev->class)
    dev->kobj.kset = dev->class->p->glue_dirs;

/* 3. kobject 등록 → sysfs 디렉토리 생성 */
kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev_name(dev));

/* 4. bus의 devices kset에 심볼릭 링크 생성 */
sysfs_create_link(&dev->bus->p->devices_kset->kobj,
                  &dev->kobj, dev_name(dev));

/* get_device/put_device는 kobject_get/put의 래퍼 */
static inline struct device *get_device(struct device *dev) {
    return dev ? to_dev(kobject_get(&dev->kobj)) : NULL;
}
static inline void put_device(struct device *dev) {
    if (dev)
        kobject_put(&dev->kobj);
}

kobject 내부 아키텍처

kobject, kset, ktype, kernfs가 어떻게 연결되어 전체 시스템을 형성하는지 종합적으로 살펴봅니다:

kobject 종합 아키텍처: 커널 ↔ sysfs ↔ 사용자 공간 사용자 공간 cat /sys/.../attr echo val > attr poll/select udevadm monitor systemd-udevd VFS → kernfs 계층 kernfs_fop_read kernfs_fop_write kernfs_fop_poll Netlink 전송 NETLINK_KOBJECT_UEVENT broadcast sysfs 콜백 계층 sysfs_ops .show(kobj, attr, buf) sysfs_ops .store(kobj, attr, buf, n) sysfs_notify() kernfs_notify() kobject_uevent() uevent_ops→uevent() kobject / ktype / kset 계층 struct kobject struct kobj_type struct kset struct kref 실제 커널 오브젝트 (kobject 임베디드) struct device struct device_driver struct bus_type struct module struct class
4계층 아키텍처: 사용자 공간 → VFS/kernfs → sysfs 콜백 → kobject/ktype/kset → 실제 커널 객체

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 순서 고정

참조 카운팅 — kref와 kobject_get/put

kobject의 수명을 안전하게 관리하는 핵심은 kref 기반 참조 카운팅(Reference Counting)입니다. 올바른 패턴과 실수를 방지하는 규칙을 다룹니다.

kref 내부 구조

/* include/linux/kref.h */
struct kref {
    refcount_t refcount;  /* atomic_t 래퍼, 0 감지 시 경고 */
};

/* kobject 내부의 kref */
struct kobject {
    /* ... */
    struct kref    kref;    /* 참조 카운터 */
    /* ... */
};

/* kobject_get: kref 증가 */
struct kobject *kobject_get(struct kobject *kobj)
{
    if (kobj) {
        if (!kref_get_unless_zero(&kobj->kref))
            return NULL;  /* 이미 해제 중 */
    }
    return kobj;
}

/* kobject_put: kref 감소 → 0이면 release 호출 */
void kobject_put(struct kobject *kobj)
{
    if (kobj)
        kref_put(&kobj->kref, kobject_release);
}

참조 카운팅 규칙

규칙설명위반 시 결과
소유권 규칙 참조를 얻은 코드 경로가 반드시 put해야 함 메모리 누수 또는 use-after-free
get 전 유효성 kobject_get() 호출 전 kobj가 유효해야 함 NULL 역참조, 커널 OOPS
release에서만 해제 kfree()를 직접 호출하지 말고 release 콜백에서 double free, sysfs 잔존 파일
init은 1로 시작 kobject_init()이 kref를 1로 설정
에러 경로 kobject_init()kobject_add() 실패 시에도 kobject_put() 필수 release 콜백 미호출 → 메모리 누수

올바른 참조 카운팅 패턴

/* 패턴 1: 오브젝트를 다른 컨텍스트에 전달 */
void pass_to_workqueue(struct my_obj *obj)
{
    /* 워크큐에 전달하기 전 참조 증가 */
    kobject_get(&obj->kobj);
    queue_work(wq, &obj->work);
}

static void my_work_handler(struct work_struct *work)
{
    struct my_obj *obj = container_of(work, struct my_obj, work);
    /* ... 작업 수행 ... */
    kobject_put(&obj->kobj);  /* 완료 후 참조 해제 */
}

/* 패턴 2: 리스트 순회 중 안전한 참조 */
void iterate_children(struct kobject *parent)
{
    struct kobject *child;
    spin_lock(&parent->kset->list_lock);
    list_for_each_entry(child, &parent->kset->list, entry) {
        if (kobject_get(child)) {
            spin_unlock(&parent->kset->list_lock);
            /* child 사용 (슬립 가능한 작업도 OK) */
            process_child(child);
            kobject_put(child);
            spin_lock(&parent->kset->list_lock);
        }
    }
    spin_unlock(&parent->kset->list_lock);
}

/* 패턴 3: 에러 경로에서의 올바른 정리 */
static int my_obj_create(struct kobject *parent)
{
    struct my_obj *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
    int ret;

    if (!obj)
        return -ENOMEM;

    kobject_init(&obj->kobj, &my_ktype);  /* refcount = 1 */

    ret = kobject_add(&obj->kobj, parent, "my_object");
    if (ret) {
        /* ✓ kobject_init 후 실패 → kobject_put으로 정리
         *   put이 release를 호출하여 kfree 수행 */
        kobject_put(&obj->kobj);
        return ret;
    }

    kobject_uevent(&obj->kobj, KOBJ_ADD);
    return 0;
}
kobject 참조 카운팅 상태 전이 미초기화 kzalloc() init 초기화됨 refcount = 1 add 등록됨 (sysfs) refcount = 1+ get 활성 (다중 참조) refcount = N get/put 반복 last put 해제 중 refcount = 0 release() sysfs 제거 kobject_del → kfree 해제 완료 add 실패 → put
kobject의 참조 카운트 상태 전이. kobject_init()에서 refcount=1로 시작하여, 마지막 kobject_put()이 release 콜백을 호출합니다.

고급 sysfs 속성 패턴

실전에서 자주 사용되는 sysfs 속성 패턴들을 정리합니다.

DEVICE_ATTR 매크로

struct device에 속성을 추가할 때는 kobject 레벨보다 높은 추상화인 DEVICE_ATTR 매크로(Macro)가 더 편리합니다:

/* DEVICE_ATTR 사용 — show/store에 struct device* 직접 전달 */
static ssize_t status_show(struct device *dev,
                          struct device_attribute *attr,
                          char *buf)
{
    struct my_device *mydev = dev_get_drvdata(dev);
    return sysfs_emit(buf, "%s\n",
                       mydev->running ? "running" : "stopped");
}

static ssize_t status_store(struct device *dev,
                           struct device_attribute *attr,
                           const char *buf, size_t count)
{
    struct my_device *mydev = dev_get_drvdata(dev);

    if (sysfs_streq(buf, "start"))
        mydev->running = 1;
    else if (sysfs_streq(buf, "stop"))
        mydev->running = 0;
    else
        return -EINVAL;

    return count;
}

/* DEVICE_ATTR_RW(name) → 자동으로 name_show, name_store 연결 */
static DEVICE_ATTR_RW(status);

/* 읽기 전용 */
static ssize_t version_show(struct device *dev,
                           struct device_attribute *attr,
                           char *buf)
{
    return sysfs_emit(buf, "%d.%d\n", MAJOR_VER, MINOR_VER);
}
static DEVICE_ATTR_RO(version);

/* 쓰기 전용 */
static ssize_t reset_store(struct device *dev,
                          struct device_attribute *attr,
                          const char *buf, size_t count)
{
    my_hw_reset(dev_get_drvdata(dev));
    return count;
}
static DEVICE_ATTR_WO(reset);

/* attribute_group으로 묶기 */
static struct attribute *my_dev_attrs[] = {
    &dev_attr_status.attr,
    &dev_attr_version.attr,
    &dev_attr_reset.attr,
    NULL,
};
ATTRIBUTE_GROUPS(my_dev);

조건부 속성 가시성 (is_visible)

/* 하드웨어 기능에 따라 속성을 동적으로 숨기기 */
static umode_t my_attrs_visible(struct kobject *kobj,
                               struct attribute *attr, int n)
{
    struct device *dev = kobj_to_dev(kobj);
    struct my_device *mydev = dev_get_drvdata(dev);

    /* temperature 속성은 센서가 있는 경우에만 표시 */
    if (attr == &dev_attr_temperature.attr &&
        !mydev->has_temp_sensor)
        return 0;  /* 0 반환 → 속성 생성하지 않음 */

    return attr->mode;  /* 기본 모드 유지 */
}

static const struct attribute_group my_attr_group = {
    .attrs     = my_dev_attrs,
    .is_visible = my_attrs_visible,
};

sysfs_notify() — poll/select 지원

/* 커널 측: 값 변경 시 알림 */
void my_irq_handler(struct my_device *dev)
{
    dev->temperature = read_hw_temp(dev);
    /* sysfs 파일을 감시 중인 사용자 프로세스에 알림 */
    sysfs_notify(&dev->dev.kobj, NULL, "temperature");
}

/* 사용자 공간: poll()로 변경 감지 */
/* fd = open("/sys/devices/.../temperature", O_RDONLY);
 * pfd.fd = fd;
 * pfd.events = POLLPRI | POLLERR;
 * read(fd, buf, sizeof(buf));  // 초기 값 읽기
 * while (1) {
 *     poll(&pfd, 1, -1);        // 변경 대기
 *     lseek(fd, 0, SEEK_SET);   // 되감기
 *     read(fd, buf, sizeof(buf));
 * }
 */

uevent 환경변수 커스터마이징

kset의 uevent_ops를 통해 uevent에 커스텀 환경변수를 추가하여 udev 규칙에서 활용할 수 있습니다.

/* uevent 콜백: 커스텀 환경변수 추가 */
static int my_uevent(const struct kobject *kobj,
                     struct kobj_uevent_env *env)
{
    struct my_obj *obj = to_my_obj(kobj);

    /* udev 규칙에서 ENV{MY_TYPE}=="sensor"로 매칭 가능 */
    add_uevent_var(env, "MY_TYPE=%s", obj->type_name);
    add_uevent_var(env, "MY_ID=%d", obj->id);
    add_uevent_var(env, "MY_VERSION=%u", obj->hw_version);

    return 0;
}

/* uevent 필터: 특정 조건에서만 이벤트 전달 */
static int my_filter(const struct kobject *kobj)
{
    struct my_obj *obj = to_my_obj(kobj);
    /* 내부 전용 오브젝트는 uevent 억제 */
    return !obj->internal_only;
}

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

대응하는 udev 규칙 예시:

# /etc/udev/rules.d/99-mydevice.rules
# MY_TYPE이 "sensor"인 디바이스만 매칭
ACTION=="add", ENV{MY_TYPE}=="sensor", \
    SYMLINK+="my_sensor_%E{MY_ID}", \
    MODE="0666"

# uevent 수동 발생으로 규칙 테스트
# echo add > /sys/devices/.../uevent

실전: 완전한 kobject + sysfs 모듈

kset, kobject, sysfs 속성, uevent를 모두 포함하는 완전한 커널 모듈 예제입니다.

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

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("kobject/kset/sysfs 완전한 예제");

struct my_sensor {
    struct kobject kobj;
    int temperature;    /* 밀리섭씨 */
    int threshold;      /* 경고 임계값 */
    bool enabled;
};
#define to_sensor(x) container_of(x, struct my_sensor, kobj)

/* --- sysfs 속성 콜백 --- */
struct sensor_attr {
    struct attribute attr;
    ssize_t (*show)(struct my_sensor *, char *);
    ssize_t (*store)(struct my_sensor *, const char *, size_t);
};

static ssize_t temp_show(struct my_sensor *s, char *buf)
{
    return sysfs_emit(buf, "%d\n", s->temperature);
}

static ssize_t threshold_show(struct my_sensor *s, char *buf)
{
    return sysfs_emit(buf, "%d\n", s->threshold);
}

static ssize_t threshold_store(struct my_sensor *s,
                               const char *buf, size_t count)
{
    int ret = kstrtoint(buf, 10, &s->threshold);
    return ret ? ret : count;
}

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

static ssize_t enabled_store(struct my_sensor *s,
                             const char *buf, size_t count)
{
    int ret = kstrtobool(buf, &s->enabled);
    return ret ? ret : count;
}

static struct sensor_attr temp_attr =
    { .attr = { .name = "temperature", .mode = 0444 }, .show = temp_show };
static struct sensor_attr thresh_attr =
    { .attr = { .name = "threshold", .mode = 0664 },
      .show = threshold_show, .store = threshold_store };
static struct sensor_attr enabled_attr =
    { .attr = { .name = "enabled", .mode = 0664 },
      .show = enabled_show, .store = enabled_store };

static struct attribute *sensor_attrs[] = {
    &temp_attr.attr, &thresh_attr.attr, &enabled_attr.attr, NULL,
};
ATTRIBUTE_GROUPS(sensor);

/* --- sysfs_ops --- */
static ssize_t sensor_show(struct kobject *kobj,
                           struct attribute *attr, char *buf)
{
    struct sensor_attr *sa = container_of(attr, struct sensor_attr, attr);
    struct my_sensor *s = to_sensor(kobj);
    return sa->show ? sa->show(s, buf) : -EIO;
}

static ssize_t sensor_store(struct kobject *kobj,
                            struct attribute *attr,
                            const char *buf, size_t count)
{
    struct sensor_attr *sa = container_of(attr, struct sensor_attr, attr);
    struct my_sensor *s = to_sensor(kobj);
    return sa->store ? sa->store(s, buf, count) : -EIO;
}

static const struct sysfs_ops sensor_sysfs_ops = {
    .show  = sensor_show,
    .store = sensor_store,
};

/* --- release 콜백 --- */
static void sensor_release(struct kobject *kobj)
{
    struct my_sensor *s = to_sensor(kobj);
    pr_info("sensor release: %s\n", kobject_name(kobj));
    kfree(s);
}

static const struct kobj_type sensor_ktype = {
    .release        = sensor_release,
    .sysfs_ops      = &sensor_sysfs_ops,
    .default_groups = sensor_groups,
};

/* --- kset --- */
static struct kset *sensor_kset;

static struct my_sensor *create_sensor(const char *name, int temp)
{
    struct my_sensor *s;
    int ret;

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

    s->temperature = temp;
    s->threshold = 80000;  /* 80°C */
    s->enabled = 1;

    s->kobj.kset = sensor_kset;  /* kset에 소속 */
    ret = kobject_init_and_add(&s->kobj, &sensor_ktype, NULL, "%s", name);
    if (ret) {
        kobject_put(&s->kobj);
        return NULL;
    }

    kobject_uevent(&s->kobj, KOBJ_ADD);
    return s;
}

/* --- 모듈 초기화/정리 --- */
static struct my_sensor *cpu_sensor, *gpu_sensor;

static int __init sensor_init(void)
{
    sensor_kset = kset_create_and_add("my_sensors", NULL, kernel_kobj);
    if (!sensor_kset)
        return -ENOMEM;

    cpu_sensor = create_sensor("cpu_temp", 45000);
    gpu_sensor = create_sensor("gpu_temp", 52000);

    if (!cpu_sensor || !gpu_sensor) {
        if (cpu_sensor) kobject_put(&cpu_sensor->kobj);
        if (gpu_sensor) kobject_put(&gpu_sensor->kobj);
        kset_unregister(sensor_kset);
        return -ENOMEM;
    }

    pr_info("sensor module loaded\n");
    return 0;
}

static void __exit sensor_exit(void)
{
    kobject_put(&gpu_sensor->kobj);
    kobject_put(&cpu_sensor->kobj);
    kset_unregister(sensor_kset);
    pr_info("sensor module unloaded\n");
}

module_init(sensor_init);
module_exit(sensor_exit);
모듈 검증 방법
# 빌드 및 로드
make -C /lib/modules/$(uname -r)/build M=$PWD modules
sudo insmod sensor.ko

# sysfs 확인
ls /sys/kernel/my_sensors/
# cpu_temp/  gpu_temp/

cat /sys/kernel/my_sensors/cpu_temp/temperature
# 45000

echo 75000 > /sys/kernel/my_sensors/cpu_temp/threshold
cat /sys/kernel/my_sensors/cpu_temp/threshold
# 75000

# uevent 확인
udevadm monitor --kernel --subsystem-match=my_sensors

# 정리
sudo rmmod sensor

kobject API 빠른 참조

API용도주의사항
kobject_init(kobj, ktype) kobject 초기화 (refcount=1) add 전 반드시 호출. ktype 필수
kobject_add(kobj, parent, fmt, ...) sysfs에 등록 실패 시에도 put 필요
kobject_init_and_add(kobj, ktype, parent, fmt, ...) init + add 통합 실패 시 put 필요
kobject_create_and_add(name, parent) 동적 할당 + init + add 간단한 디렉토리 생성용. ktype 지정 불가
kobject_get(kobj) 참조 카운트 증가 NULL 가능. 반환값 확인 필수
kobject_put(kobj) 참조 카운트 감소 0 도달 시 release 자동 호출
kobject_del(kobj) sysfs에서 제거 (refcount 미변경) put 전에 호출하여 sysfs 접근 차단
kobject_uevent(kobj, action) uevent 전송 add 후 호출. udev에 알림
kobject_rename(kobj, new_name) sysfs 이름 변경 KOBJ_MOVE uevent 자동 발생
kset_create_and_add(name, ops, parent) kset 생성 + sysfs 등록 해제: kset_unregister()
kobject_name(kobj) kobject 이름 반환 읽기 전용

참고자료

커널 공식 문서

커널 소스 코드

외부 자료

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