Kernel Objects (kobject, kset, ktype)
Linux 커널 오브젝트 모델의 핵심: kobject, kref 참조 카운팅, kset, ktype, sysfs 통합을 심층 분석합니다.
개요 (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 */
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는 atomic_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 사용)
#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 계층으로 형성됩니다:
# sysfs 디바이스 트리 예시
/sys/
├── bus/
│ ├── pci/ ← kset: pci_bus_type.p->drivers_kset
│ │ ├── devices/
│ │ └── drivers/
│ └── usb/
├── class/
│ ├── net/ ← kset: net_class.p->glue_dirs
│ │ ├── eth0/ ← kobject: net_device.dev.kobj
│ │ └── lo/
│ └── block/
├── devices/ ← kset: devices_kset
│ ├── platform/
│ └── pci0000:00/
└── kernel/ ← kset: kernel_kobj
커널 내 실제 사용 사례
커널의 주요 서브시스템에서 kobject가 어떻게 사용되는지 살펴봅니다:
| 구조체 | kobject 필드 | sysfs 경로 예시 | 역할 |
|---|---|---|---|
struct device |
kobj |
/sys/devices/... |
모든 디바이스의 기반. sysfs에 디바이스 트리 형성 |
struct bus_type |
p->subsys.kobj |
/sys/bus/pci/ |
버스 타입별 디렉토리. devices/drivers 하위 디렉토리 관리 |
struct device_driver |
p->kobj |
/sys/bus/pci/drivers/e1000/ |
드라이버별 sysfs 엔트리. bind/unbind 인터페이스 |
struct module |
mkobj.kobj |
/sys/module/my_mod/ |
모듈 파라미터, 참조 카운트, 섹션 정보 노출 |
struct block_device |
bd_device.kobj |
/sys/block/sda/ |
블록 디바이스 속성, 파티션 정보, I/O 스케줄러 |
struct net_device |
dev.kobj |
/sys/class/net/eth0/ |
네트워크 인터페이스 속성 (MTU, MAC, 통계 등) |
struct class |
p->subsys.kobj |
/sys/class/input/ |
디바이스 클래스 그룹화. 같은 종류의 디바이스 모음 |
/* struct device 내부의 kobject 사용 */
struct device {
struct kobject kobj; /* /sys/devices/... 디렉토리 */
struct device *parent; /* → kobj.parent 설정 */
const struct device_type *type;
struct bus_type *bus;
struct device_driver *driver;
/* ... */
};
/* device_add() 내부에서 kobject 등록 과정:
* 1. kobject_add(&dev->kobj, parent, ...) → sysfs 디렉토리 생성
* 2. kobject_uevent(&dev->kobj, KOBJ_ADD) → udev 알림
* 3. bus_add_device(dev) → /sys/bus/.../devices/에 symlink
* 4. device_create_file(dev, ...) → 추가 attribute 생성
*/
/* struct module의 kobject: /sys/module/ 아래 */
struct module_kobject {
struct kobject kobj;
struct module *mod;
struct kobject *drivers_dir; /* /sys/module/xxx/drivers/ */
};
/sys 디렉토리의 모든 항목은 궁극적으로 kobject에 의해 형성됩니다. ls -la /sys/의 각 디렉토리는 하나의 kobject입니다. 커널 부팅 시 수천 개의 kobject가 생성되며, lsmod, lspci, ip link 등의 명령은 내부적으로 sysfs (= kobject 트리)를 읽습니다.
주의사항과 함정 (Common Mistakes)
1. release 콜백 미구현
/* 잘못된 코드: release 콜백이 없는 kobj_type */
static const struct kobj_type bad_ktype = {
.sysfs_ops = &my_ops,
/* .release 누락! */
};
/* kobject_put() 시 커널 경고 발생:
* "kobject: 'xxx' does not have a release() function,
* it is broken and must be fixed."
* 메모리 leak 발생 */
/* 올바른 코드: 반드시 release 구현 */
static void my_release(struct kobject *kobj)
{
struct my_obj *obj = container_of(kobj, struct my_obj, kobj);
kfree(obj);
}
static const struct kobj_type good_ktype = {
.release = my_release,
.sysfs_ops = &my_ops,
};
2. kobject_put() 후 포인터 접근
/* 잘못된 코드: put 후 접근 → use-after-free */
kobject_put(&my_dev->kobj);
pr_info("device name: %s\n", my_dev->name); /* BUG! kfree 이미 호출됨 */
/* 올바른 코드: put 전에 필요한 정보 추출 */
pr_info("device name: %s\n", my_dev->name);
kobject_put(&my_dev->kobj);
my_dev = NULL; /* dangling pointer 방지 */
3. kobject 초기화 누락
/* 잘못된 코드: init 없이 add 호출 */
struct my_obj *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
kobject_add(&obj->kobj, parent, "name");
/* BUG! state_initialized가 설정되지 않아 경고 발생 */
/* 올바른 코드: init_and_add 또는 init 후 add */
struct my_obj *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
kobject_init_and_add(&obj->kobj, &my_ktype, parent, "name");
4. sysfs 퍼미션 오류
/* 잘못된 코드: world-writable sysfs 파일 */
static struct kobj_attribute bad_attr =
__ATTR(secret, 0666, my_show, my_store);
/* 보안 위험! 비특권 사용자가 커널 상태를 변경 가능 */
/* 올바른 퍼미션 패턴 */
__ATTR(value, 0664, show, store); /* root RW, group RW, other RO */
__ATTR_RO(info); /* 0444: 모두 읽기 전용 */
__ATTR_WO(command); /* 0200: root만 쓰기 */
__ATTR_RW(config); /* 0644: root RW, 나머지 RO */
sysfs 퍼미션에서 0222(쓰기 전용)나 0666(모두 읽기/쓰기)은 커널 빌드 시 VERIFY_OCTAL_PERMISSIONS 매크로에 의해 컴파일 에러가 발생합니다. world-writable은 보안 검토에서 거부됩니다.
5. kobject_init_and_add 실패 시 메모리 관리
/* 잘못된 코드: 실패 시 kfree로 직접 해제 */
ret = kobject_init_and_add(&obj->kobj, &my_ktype, NULL, "name");
if (ret) {
kfree(obj); /* BUG! kobject_init이 이미 호출됨 → kobject_put 필요 */
return ret;
}
/* 올바른 코드: 실패해도 kobject_put으로 정리 */
ret = kobject_init_and_add(&obj->kobj, &my_ktype, NULL, "name");
if (ret) {
kobject_put(&obj->kobj); /* release 콜백이 kfree 수행 */
return ret;
}
/* kobject_init()이 호출된 이후에는 반드시 kobject_put()으로 정리해야 함.
* kobject_init_and_add()는 내부적으로 kobject_init()을 먼저 호출하므로,
* add가 실패해도 init은 이미 완료된 상태. */
디버깅과 내부 검사
sysfs를 통한 kobject 탐색
# kobject 계층 구조 확인
$ find /sys -maxdepth 3 -type d | head -30
# 특정 디바이스의 kobject 정보
$ ls -la /sys/devices/pci0000:00/0000:00:1f.0/
# 각 파일/디렉토리가 attribute 또는 하위 kobject
# kobject의 uevent 내용 확인
$ cat /sys/devices/pci0000:00/0000:00:1f.0/uevent
DRIVER=lpc_ich
PCI_CLASS=60100
PCI_ID=8086:A141
SUBSYSTEM=pci
# uevent 실시간 모니터링
$ udevadm monitor --kernel --property
디버깅 커널 옵션
| 옵션 | 기능 |
|---|---|
CONFIG_DEBUG_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
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# TASK-PID | | SOFTIRQ TIMESTAMP FUNCTION
# insmod-1234 [001] .... 1234.567: kobject_add <-device_add
# insmod-1234 [001] .... 1234.568: kobject_put <-module_add_driver
# CONFIG_DEBUG_KOBJECT 활성화 시 dmesg 출력
$ dmesg | grep kobject
[ 12.345] kobject: 'my_device' (ffffff80123456): kobject_add_internal: parent: 'kernel'
[ 12.346] kobject: 'my_device' (ffffff80123456): fill_kobj_path: path = '/kernel/my_device'
커널 버전별 변경사항
| 버전 | 변경 내용 |
|---|---|
| 2.6.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/