커널 오브젝트 (kobject)
Linux 커널 오브젝트 모델의 핵심: kobject, kref 참조 카운팅, kset, ktype, sysfs 통합을 심층 분석합니다.
핵심 요약
- 참조 카운팅 —
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)합니다.
단계별 이해
- kobject 생명주기 이해
kobject_init_and_add()로 초기화·등록하고,kobject_put()으로 해제하는 기본 흐름을 파악합니다.kfree를 직접 호출하면 안 되며 반드시 release 콜백에서 처리해야 합니다. - sysfs 속성 노출
kobj_type에sysfs_ops와default_attrs/default_groups를 정의하여/sys파일로 노출하는 과정을 학습합니다. - kset과 uevent 연결
kset_create_and_add()로 kobject 그룹을 만들고,uevent_ops를 통해 udev에 핫플러그(Hotplug) 이벤트를 전달하는 구조를 이해합니다. - 디바이스 모델과의 관계
struct device,struct device_driver,struct bus_type내부에 kobject가 어떻게 임베딩되어 커널 디바이스 모델을 형성하는지 확인합니다.
개념 예시는 구조 파악용, 실습 예제는 빌드/로딩/검증 흐름 점검용입니다.
개요 (Overview)
Linux 커널은 수천 개의 디바이스, 드라이버, 버스(Bus), 모듈 등 다양한 커널 오브젝트를 관리해야 합니다. kobject는 이 모든 오브젝트의 공통 기반 클래스 역할을 합니다. C 언어에는 클래스 상속이 없으므로, 커널은 임베디드 구조체 패턴을 사용하여 이를 구현합니다.
kobject 서브시스템이 제공하는 핵심 기능:
- 참조 카운팅 (Reference Counting) —
kref를 통한 안전한 오브젝트 수명 관리 - sysfs 표현 — 각 kobject는
/sys아래에 디렉토리로 나타남 - uevent 알림 — 오브젝트 추가/제거 시 사용자 공간(udev)에 이벤트 전달
- 계층 구조 — parent-child 관계를 통한 트리 형성
- 타입 시스템 —
kobj_type으로 sysfs attribute와 release 콜백 정의
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 (참조 카운팅)
kref는 refcount_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 내부 메커니즘 상세
kref는 refcount_t(atomic 카운터)를 래핑한 구조체입니다. kobject_get()/kobject_put()은 내부적으로 kref_get()/kref_put()을 호출합니다. 카운터가 0에 도달하면 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 생명주기
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_create_and_add() vs kobject_init()+kobject_add() 비교
kobject를 생성하는 두 가지 방법이 있습니다. 용도에 따라 적절한 API를 선택해야 합니다:
| 항목 | kobject_create_and_add() | kobject_init() + kobject_add() |
|---|---|---|
| 할당 | 내부에서 kzalloc | 호출자가 직접 할당 (임베디드 패턴) |
| ktype | dynamic_kobj_ktype (고정) | 커스텀 ktype 지정 가능 |
| release | 자동 (kfree(kobj)) | 커스텀 release 콜백 필수 |
| attribute | sysfs_create_file() 수동 추가 | default_groups로 자동 등록 |
| 에러 처리 | NULL 반환 시 정리 불필요 | 실패 시 kobject_put() 필수 |
| 대표 사용처 | kernel_kobj, firmware_kobj | struct 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_type의 default_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_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 필터링과 환경변수
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_type과 attribute_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 계층으로 형성됩니다:
커널 내 실제 사용 사례
커널의 주요 서브시스템에서 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_KOBJECT | kobject 생성/삭제/참조 변경 시 디버그 메시지 출력 |
CONFIG_DEBUG_KOBJECT_RELEASE | kobject release 시 지연(Latency)을 추가하여 use-after-free 탐지 |
CONFIG_KASAN | use-after-free, 범위 초과 접근 등 메모리 오류 탐지 |
CONFIG_PROVE_LOCKING | kobject 관련 lock 순서 검증 (lockdep) |
kobject 릭 추적 기법
kobject 참조 카운트 누수는 메모리 릭과 sysfs 잔존 노드의 주요 원인입니다. 체계적인 추적 방법을 살펴봅니다:
# 종합 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.0 | kobject, kset, ktype 도입 (통합 디바이스 모델) |
| 2.6.25 | kobject_create_and_add() 편의 함수 추가 |
| 3.14 | default_attrs → default_groups 마이그레이션 시작 |
| 4.10 | kernfs 기반 sysfs 리팩토링 완료 |
| 5.2 | sysfs_emit() 도입 (snprintf 대체, 버퍼 오버플로우 방지) |
| 5.18 | default_attrs 완전 제거, default_groups만 사용 |
| 6.2 | const 정확성 개선 (kobj_type 내 콜백 매개변수) |
| 6.4 | kobj_type에서 get_ownership() 콜백으로 sysfs 파일 소유자 동적 설정 |
| 6.6 | kobject_uevent_env() 환경변수 크기 제한 강화, 오버플로우 방지 |
| 6.8 | kernfs RCU 기반 순회 최적화, sysfs 읽기 경로 성능 개선 |
| 6.12 | sysfs_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_type에 release 콜백이 없음
해결:
/* 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를 분리합니다:
/* 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가 커널 디바이스 모델 전체를 어떻게 형성하는지 상세히 살펴봅니다:
/* 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 수명주기 감사 체크리스트
kobject 문제는 대부분 참조 카운트 불균형과 해제 타이밍 오류에서 발생합니다. sysfs와 uevent까지 연결되므로 한 번의 누락이 장기 누수나 use-after-free로 이어질 수 있습니다.
- 생성 경로:
kobject_init_and_add성공/실패 분기 정리 확인 - 참조 균형:
kobject_get와kobject_put짝 확인 - release 보장: ktype의
release콜백 존재/실행 확인 - 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_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 이름 반환 | 읽기 전용 |
참고자료
커널 공식 문서
- Everything you never wanted to know about kobjects — kobject, kset, ktype 공식 문서
- Driver Model — 드라이버 모델 개요 (kobject 기반 계층 구조)
- sysfs — The filesystem for exporting kernel objects — sysfs 파일시스템 문서
- Adding reference counters (krefs) — kref 참조 카운터 API
커널 소스 코드
lib/kobject.c— kobject 코어 구현 (kobject_init, kobject_add, kobject_put)lib/kobject_uevent.c— uevent 전송 구현include/linux/kobject.h— kobject, kset, kobj_type 구조체 정의include/linux/sysfs.h— sysfs attribute 정의include/linux/kref.h— kref 참조 카운터 정의fs/sysfs/— sysfs 파일시스템 구현 디렉토리drivers/base/core.c— device kobject 관리 (device_add에서 kobject_add 호출)
외부 자료
- The zen of kobjects — kobject 설계 철학과 사용법 해설
- Driver porting: Device model overview — 디바이스 모델의 kobject 기반 구조 설명
- LDD3 Chapter 14: The Linux Device Model — kobject/kset/subsystem 상세 해설
관련 문서
Kernel Objects와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.