Kernel Objects (kobject)
Linux 커널 오브젝트 모델의 핵심: kobject, kref 참조 카운팅, kset, ktype, sysfs 통합을 심층 분석합니다.
핵심 요약
- 참조 카운팅 —
kobject_get()/kobject_put()으로 오브젝트 수명을 안전하게 관리합니다. - release 콜백 — 참조 카운트가 0이 되면
kobj_type.release가 호출되어 리소스를 해제합니다. - sysfs 매핑 — 각 kobject는
/sys아래 디렉토리로 나타나 사용자 공간에서 접근 가능합니다. - kset 계층 구조 — kobject를 kset에 등록하여 부모-자식 트리와 uevent 알림을 구성합니다.
- 임베디드 패턴 —
container_of()로 kobject를 포함하는 상위 구조체(device, driver 등)를 역참조합니다.
단계별 이해
- 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에 핫플러그 이벤트를 전달하는 구조를 이해합니다. - 디바이스 모델과의 관계
struct device,struct device_driver,struct bus_type내부에 kobject가 어떻게 임베딩되어 커널 디바이스 모델을 형성하는지 확인합니다.
개념 예시는 구조 파악용, 실습 예제는 빌드/로딩/검증 흐름 점검용입니다.
개요 (Overview)
Linux 커널은 수천 개의 디바이스, 드라이버, 버스, 모듈 등 다양한 커널 오브젝트를 관리해야 합니다. 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로 설정하는 것이 좋습니다.
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() 콜백 호출 */
sysfs 통합
kobject에 sysfs attribute를 추가하면 사용자 공간에서 cat/echo로 값을 읽고 쓸 수 있습니다:
/* attribute 정의 */
struct my_obj {
struct kobject kobj;
int value;
char name[32];
};
struct my_attr {
struct attribute attr;
ssize_t (*show)(struct my_obj *, struct my_attr *, char *);
ssize_t (*store)(struct my_obj *, struct my_attr *, const char *, size_t);
};
/* show 콜백: cat /sys/.../value 시 호출 */
static ssize_t value_show(struct my_obj *obj, struct my_attr *attr, char *buf)
{
return sysfs_emit(buf, "%d\\n", obj->value);
}
/* store 콜백: echo 42 > /sys/.../value 시 호출 */
static ssize_t value_store(struct my_obj *obj, struct my_attr *attr,
const char *buf, size_t count)
{
int ret = kstrtoint(buf, 10, &obj->value);
return ret ? ret : count;
}
sysfs_emit() vs sprintf()
커널 5.2+에서는 sysfs show 콜백에서 반드시 sysfs_emit()를 사용해야 합니다:
| 함수 | 안전성 | 비고 |
|---|---|---|
sprintf(buf, ...) | 위험 | PAGE_SIZE 초과 시 버퍼 오버플로우. 사용 금지 |
scnprintf(buf, PAGE_SIZE, ...) | 안전 | 5.2 이전 커널에서의 올바른 방법 |
sysfs_emit(buf, ...) | 최적 | buf가 page-aligned인지 검증, PAGE_SIZE 자동 제한 |
sysfs_emit_at(buf, offset, ...) | 최적 | 여러 값을 순차적으로 출력할 때 |
/* 여러 값을 순차 출력하는 패턴 */
static ssize_t status_show(struct my_obj *obj, struct my_attr *attr, char *buf)
{
int len = 0;
len += sysfs_emit_at(buf, len, "value: %d\\n", obj->value);
len += sysfs_emit_at(buf, len, "name: %s\\n", obj->name);
return len;
}
attribute_group과 ATTRIBUTE_GROUPS 매크로
여러 attribute를 그룹으로 묶어 관리하는 것이 권장 패턴입니다. kobj_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를 사용합니다. 펌웨어 덤프, EEPROM 데이터, 레지스터 맵 등에 활용됩니다:
/* 바이너리 attribute: read/write가 버퍼 단위로 동작 */
static ssize_t firmware_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct my_device *dev = to_my_device(kobj);
if (off >= dev->fw_size)
return 0;
if (off + count > dev->fw_size)
count = dev->fw_size - off;
memcpy(buf, dev->fw_data + off, count);
return count;
}
/* BIN_ATTR 매크로로 선언 */
static BIN_ATTR_RO(firmware, FW_MAX_SIZE); /* 읽기 전용 */
/* static BIN_ATTR_WO(firmware, FW_MAX_SIZE); — 쓰기 전용 */
/* static BIN_ATTR_RW(firmware, FW_MAX_SIZE); — 읽기/쓰기 */
/* 바이너리 attribute 그룹 */
static struct bin_attribute *my_bin_attrs[] = {
&bin_attr_firmware,
NULL,
};
static const struct attribute_group my_group = {
.attrs = my_attrs, /* 텍스트 속성들 */
.bin_attrs = my_bin_attrs, /* 바이너리 속성들 */
};
sysfs_notify()로 poll/select 지원
사용자 공간에서 sysfs 파일의 변경을 poll/select로 대기할 수 있습니다:
/* 커널: 값 변경 시 통지 */
static void my_update_value(struct my_obj *obj, int new_val)
{
obj->value = new_val;
sysfs_notify(&obj->kobj, NULL, "value");
/* NULL = attribute가 kobject 디렉토리 바로 아래에 있음 */
/* 하위 그룹에 있으면 그룹 이름 전달: sysfs_notify(&kobj, "group", "attr") */
}
/* 사용자 공간: poll()로 변경 대기 */
int fd = open("/sys/kernel/my_obj/value", O_RDONLY);
struct pollfd pfd = { .fd = fd, .events = POLLPRI | POLLERR };
/* 최초 한 번 읽어서 현재 값 소비 */
read(fd, buf, sizeof(buf));
while (1) {
poll(&pfd, 1, -1); /* 변경까지 블록 */
lseek(fd, 0, SEEK_SET);
ssize_t n = read(fd, buf, sizeof(buf));
printf("new value: %.*s", (int)n, buf);
}
uevent 메커니즘
kobject의 uevent는 사용자 공간의 udev에게 디바이스 추가/제거를 알립니다:
| uevent 타입 | 상수 | 설명 |
|---|---|---|
| 추가 | KOBJ_ADD | 새 오브젝트가 sysfs에 등록됨 |
| 제거 | KOBJ_REMOVE | 오브젝트가 sysfs에서 제거됨 |
| 변경 | KOBJ_CHANGE | 오브젝트 상태 변경 |
| 이동 | KOBJ_MOVE | 오브젝트가 다른 위치로 이동 |
| 온라인 | KOBJ_ONLINE | 오브젝트 활성화 |
| 오프라인 | KOBJ_OFFLINE | 오브젝트 비활성화 |
| 바인드 | KOBJ_BIND | 드라이버-디바이스 바인딩 |
| 언바인드 | KOBJ_UNBIND | 드라이버-디바이스 언바인딩 |
kset uevent 필터링과 환경변수
kset의 uevent_ops를 구현하여 uevent를 필터링하거나 사용자 정의 환경변수를 추가할 수 있습니다:
/* uevent 필터: 특정 조건의 kobject만 uevent 발생 */
static int my_filter(const struct kobject *kobj)
{
const struct kobj_type *ktype = get_ktype(kobj);
/* 우리 타입의 kobject만 uevent 허용 */
if (ktype == &my_ktype)
return 1; /* uevent 발생 */
return 0; /* uevent 억제 */
}
/* uevent 서브시스템 이름 */
static const char *my_name(const struct kobject *kobj)
{
return "my_subsystem"; /* SUBSYSTEM=my_subsystem */
}
/* uevent 환경변수 추가 */
static int my_uevent(const struct kobject *kobj,
struct kobj_uevent_env *env)
{
const struct my_device *dev = to_my_device(kobj);
/* udev 규칙에서 사용할 환경변수 추가 */
add_uevent_var(env, "MY_SERIAL=%d", dev->serial);
add_uevent_var(env, "MY_TYPE=%s", dev->type_name);
return 0;
}
static const struct kset_uevent_ops my_uevent_ops = {
.filter = my_filter,
.name = my_name,
.uevent = my_uevent,
};
/* kset 생성 시 uevent_ops 연결 */
struct kset *my_kset = kset_create_and_add("my_kset", &my_uevent_ops, kernel_kobj);
# udevadm으로 uevent 모니터링
$ udevadm monitor --environment --subsystem-match=my_subsystem
UDEV [1234.567890] add /kernel/my_kset/my_device (my_subsystem)
ACTION=add
DEVPATH=/kernel/my_kset/my_device
SUBSYSTEM=my_subsystem
MY_SERIAL=42
MY_TYPE=sensor
# udev 규칙 예시: /etc/udev/rules.d/99-my.rules
SUBSYSTEM=="my_subsystem", ENV{MY_TYPE}=="sensor", RUN+="/usr/bin/sensor_init.sh"
container_of 패턴
container_of 매크로는 임베디드 kobject에서 부모 구조체를 복원합니다. 이는 커널 오브젝트 모델의 핵심 패턴입니다:
struct my_device {
int serial;
struct kobject kobj; /* 임베디드 kobject */
};
/* kobject 포인터에서 my_device 복원 */
static inline struct my_device *to_my_device(struct kobject *kobj)
{
return container_of(kobj, struct my_device, kobj);
}
/* release 콜백에서 사용 */
static void my_release(struct kobject *kobj)
{
struct my_device *dev = to_my_device(kobj);
pr_info("releasing device serial %d\\n", dev->serial);
kfree(dev);
}
실전 예제: 커스텀 kobject 모듈
기본 예제 (kobject_create_and_add 사용)
/* 실습 예제: kobject_create_and_add 기반 sysfs 모듈 */
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
static int my_value = 0;
static ssize_t my_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sysfs_emit(buf, "%d\\n", my_value);
}
static ssize_t my_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int ret = kstrtoint(buf, 10, &my_value);
return ret ? ret : count;
}
static struct kobj_attribute my_attr =
__ATTR(my_value, 0664, my_show, my_store);
static struct kobject *my_kobj;
static int __init my_init(void)
{
int ret;
my_kobj = kobject_create_and_add("my_kobject", kernel_kobj);
if (!my_kobj)
return -ENOMEM;
ret = sysfs_create_file(my_kobj, &my_attr.attr);
if (ret)
kobject_put(my_kobj);
return ret;
}
static void __exit my_exit(void)
{
kobject_put(my_kobj);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
# 모듈 로드 후 sysfs 접근
$ cat /sys/kernel/my_kobject/my_value
0
$ echo 42 > /sys/kernel/my_kobject/my_value
$ cat /sys/kernel/my_kobject/my_value
42
확장 예제: attribute_group + 커스텀 ktype + 에러 처리
실제 커널 드라이버에 가까운 완전한 예제입니다. 커스텀 kobj_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에 디바이스 트리 형성 |
struct bus_type |
p->subsys.kobj |
/sys/bus/pci/ |
버스 타입별 디렉토리. devices/drivers 하위 디렉토리 관리 |
struct device_driver |
p->kobj |
/sys/bus/pci/drivers/e1000/ |
드라이버별 sysfs 엔트리. bind/unbind 인터페이스 |
struct module |
mkobj.kobj |
/sys/module/my_mod/ |
모듈 파라미터, 참조 카운트, 섹션 정보 노출 |
struct block_device |
bd_device.kobj |
/sys/block/sda/ |
블록 디바이스 속성, 파티션 정보, I/O 스케줄러 |
struct net_device |
dev.kobj |
/sys/class/net/eth0/ |
네트워크 인터페이스 속성 (MTU, MAC, 통계 등) |
struct class |
p->subsys.kobj |
/sys/class/input/ |
디바이스 클래스 그룹화. 같은 종류의 디바이스 모음 |
/* struct device 내부의 kobject 사용 */
struct device {
struct kobject kobj; /* /sys/devices/... 디렉토리 */
struct device *parent; /* → kobj.parent 설정 */
const struct device_type *type;
struct bus_type *bus;
struct device_driver *driver;
/* ... */
};
/* device_add() 내부에서 kobject 등록 과정:
* 1. kobject_add(&dev->kobj, parent, ...) → sysfs 디렉토리 생성
* 2. kobject_uevent(&dev->kobj, KOBJ_ADD) → udev 알림
* 3. bus_add_device(dev) → /sys/bus/.../devices/에 symlink
* 4. device_create_file(dev, ...) → 추가 attribute 생성
*/
/* struct module의 kobject: /sys/module/ 아래 */
struct module_kobject {
struct kobject kobj;
struct module *mod;
struct kobject *drivers_dir; /* /sys/module/xxx/drivers/ */
};
/sys 디렉토리의 모든 항목은 궁극적으로 kobject에 의해 형성됩니다. ls -la /sys/의 각 디렉토리는 하나의 kobject입니다. 커널 부팅 시 수천 개의 kobject가 생성되며, lsmod, lspci, ip link 등의 명령은 내부적으로 sysfs (= kobject 트리)를 읽습니다.
주의사항과 함정 (Common Mistakes)
1. release 콜백 미구현
/* 잘못된 코드: release 콜백이 없는 kobj_type */
static const struct kobj_type bad_ktype = {
.sysfs_ops = &my_ops,
/* .release 누락! */
};
/* kobject_put() 시 커널 경고 발생:
* "kobject: 'xxx' does not have a release() function,
* it is broken and must be fixed."
* 메모리 leak 발생 */
/* 올바른 코드: 반드시 release 구현 */
static void my_release(struct kobject *kobj)
{
struct my_obj *obj = container_of(kobj, struct my_obj, kobj);
kfree(obj);
}
static const struct kobj_type good_ktype = {
.release = my_release,
.sysfs_ops = &my_ops,
};
2. kobject_put() 후 포인터 접근
/* 잘못된 코드: put 후 접근 → use-after-free */
kobject_put(&my_dev->kobj);
pr_info("device name: %s\\n", my_dev->name); /* BUG! kfree 이미 호출됨 */
/* 올바른 코드: put 전에 필요한 정보 추출 */
pr_info("device name: %s\\n", my_dev->name);
kobject_put(&my_dev->kobj);
my_dev = NULL; /* dangling pointer 방지 */
3. kobject 초기화 누락
/* 잘못된 코드: init 없이 add 호출 */
struct my_obj *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
kobject_add(&obj->kobj, parent, "name");
/* BUG! state_initialized가 설정되지 않아 경고 발생 */
/* 올바른 코드: init_and_add 또는 init 후 add */
struct my_obj *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
kobject_init_and_add(&obj->kobj, &my_ktype, parent, "name");
4. sysfs 퍼미션 오류
/* 잘못된 코드: world-writable sysfs 파일 */
static struct kobj_attribute bad_attr =
__ATTR(secret, 0666, my_show, my_store);
/* 보안 위험! 비특권 사용자가 커널 상태를 변경 가능 */
/* 올바른 퍼미션 패턴 */
__ATTR(value, 0664, show, store); /* root RW, group RW, other RO */
__ATTR_RO(info); /* 0444: 모두 읽기 전용 */
__ATTR_WO(command); /* 0200: root만 쓰기 */
__ATTR_RW(config); /* 0644: root RW, 나머지 RO */
sysfs 퍼미션에서 0222(쓰기 전용)나 0666(모두 읽기/쓰기)은 커널 빌드 시 VERIFY_OCTAL_PERMISSIONS 매크로에 의해 컴파일 에러가 발생합니다. world-writable은 보안 검토에서 거부됩니다.
5. kobject_init_and_add 실패 시 메모리 관리
/* 잘못된 코드: 실패 시 kfree로 직접 해제 */
ret = kobject_init_and_add(&obj->kobj, &my_ktype, NULL, "name");
if (ret) {
kfree(obj); /* BUG! kobject_init이 이미 호출됨 → kobject_put 필요 */
return ret;
}
/* 올바른 코드: 실패해도 kobject_put으로 정리 */
ret = kobject_init_and_add(&obj->kobj, &my_ktype, NULL, "name");
if (ret) {
kobject_put(&obj->kobj); /* release 콜백이 kfree 수행 */
return ret;
}
/* kobject_init()이 호출된 이후에는 반드시 kobject_put()으로 정리해야 함.
* kobject_init_and_add()는 내부적으로 kobject_init()을 먼저 호출하므로,
* add가 실패해도 init은 이미 완료된 상태. */
디버깅과 내부 검사
sysfs를 통한 kobject 탐색
# kobject 계층 구조 확인
$ find /sys -maxdepth 3 -type d | head -30
# 특정 디바이스의 kobject 정보
$ ls -la /sys/devices/pci0000:00/0000:00:1f.0/
# 각 파일/디렉토리가 attribute 또는 하위 kobject
# kobject의 uevent 내용 확인
$ cat /sys/devices/pci0000:00/0000:00:1f.0/uevent
DRIVER=lpc_ich
PCI_CLASS=60100
PCI_ID=8086:A141
SUBSYSTEM=pci
# uevent 실시간 모니터링
$ udevadm monitor --kernel --property
디버깅 커널 옵션
| 옵션 | 기능 |
|---|---|
CONFIG_DEBUG_KOBJECT | kobject 생성/삭제/참조 변경 시 디버그 메시지 출력 |
CONFIG_DEBUG_KOBJECT_RELEASE | kobject release 시 지연을 추가하여 use-after-free 탐지 |
CONFIG_KASAN | use-after-free, 범위 초과 접근 등 메모리 오류 탐지 |
CONFIG_PROVE_LOCKING | kobject 관련 lock 순서 검증 (lockdep) |
ftrace로 kobject 추적
# kobject 관련 함수 추적
$ echo 'kobject_add' > /sys/kernel/debug/tracing/set_ftrace_filter
$ echo 'kobject_del' >> /sys/kernel/debug/tracing/set_ftrace_filter
$ echo 'kobject_put' >> /sys/kernel/debug/tracing/set_ftrace_filter
$ echo function > /sys/kernel/debug/tracing/current_tracer
$ echo 1 > /sys/kernel/debug/tracing/tracing_on
# 특정 동작 수행 후 추적 결과 확인
$ cat /sys/kernel/debug/tracing/trace
# tracer: function
# insmod-1234 [001] .... 1234.567: kobject_add <-device_add
# insmod-1234 [001] .... 1234.568: kobject_put <-module_add_driver
# CONFIG_DEBUG_KOBJECT 활성화 시 dmesg 출력
$ dmesg | grep kobject
[ 12.345] kobject: 'my_device' (ffffff80123456): kobject_add_internal: parent: 'kernel'
[ 12.346] kobject: 'my_device' (ffffff80123456): fill_kobj_path: path = '/kernel/my_device'
커널 버전별 변경사항
| 버전 | 변경 내용 |
|---|---|
| 2.6.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 내 콜백 매개변수) |
참고 자료: 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 생명주기 디버깅 메시지 활성화
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 순서 고정 |
관련 문서
Kernel Objects와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.