Sysfs (kobject & kset)

디바이스 드라이버 모델에서 디바이스들은 하이라키 토플로지 형태로 표현되고 있다.  이렇게 하이라키로 표현된 디바이스 드라이버들은 마운트된 sysfs라는 파일 시스템을 통해 디렉토리 구조로 보여주고 관리된다. 이러한 디바이스 드라이버들의 하이라키 표현을 도와주기 위해 다음 구조체들을 사용한다.

  • kobject
  • kset
  • ktype
  • kref

 

kobject(kernel object)

디바이스들은 여러 가지 객체(구조체)로 표현되는데 이들이 기본적으로 가져야 할 이름과 참조 카운터 및 하이 라키 관계를 표현하기 위해 여러 개의 필드가 필요하다. 리눅스 커널은 이러한 정보 및 하이라키 구성관계를 쉽게 사용할 수 있도록 kobject 구조체를 사용한다.

  • kobject 구조체는 그 자체로는 사용되지 않고 다른 객체(구조체) 내부에 임베드되는 형태로 사용된다.
  • kobject는 이름(name)과 참조 카운터(kref)를 가진다.
  • kobject는 하이라키를 표현하기 위해 부모 kobject 포인터 및 ktype, kset 등을 가진다.
  • kobject는 sysfs 가상 파일 시스템에 디렉토리와 연동되므로 모든 디바이스의 구조를 디렉토리 구조로 표현하고 제어할 수 있다.
    • sysfs 구조에서 하나의 디렉토리는 kobject이다.
  • 관련 API
    • kobject_init()
    • kobject_init_and_add()
    • kobject_register()
    • kobject_add()
    • kobject_rename()
    • kobject_move()
    • kobject_get_path()
    • kobject_set_name()
    • kobject_del()
    • kobject_get()
    • kobject_get_unless_zero()
    • kobject_put()
    • kobject_create_and_add()

 

다음 그림의 좌측을 보면 3개의 객체가 하이라키 구조를 갖는 것을 표현하기 위해 각각 name, kref, parent 등의 멤버가 필요함을 알 수 있다. 이러한 정보를 쉽게 처리하기 위해 화살표 우측과 같이 kobject 구조체를 각 객체에 임베드하여 손쉽게 연동할 수 있음을 알 수 있다.

 

kset(kernel object set)

kobject를 여러개 담을 수 있는 컨테이너이다.

  • kset는 기본적으로 하나의 kobject를 포함한다.
  • kset의 컨테이너 구현은 kobject들이 연결된 리스트이다.
    • 이 리스트에 child kobject들이 연결된다.
  • sysfs에서 하나의 디렉토리 아래에 여러 개의 디렉토리가 필요한 경우 kset을 사용한다.
  • 관련 API
    • kset_register()
    • kset_unregister()
    • kset_find_obj()
    • kset_create_and_add()

 

다음 그림과 같은 디렉토리 구조를 같기 위해서는 디렉토리 밑에 2 개 이상의 child 디렉토리가 포함되어야 한다. 이러한 컨테이너를 구현하기 위해 kobject이외에도 list_lock, list, entry 등의 추가 필드들이 필요함을 알 수 있다. 화살표 우측과 같이 kobject를 포함한 컨테이너를 쉽게 구현할 수 있도록 kset 구조체가 사용됨을 알 수 있다.

 

ktype(kernel object type)

kobject가 제거될 때 호출될 함수가 지정된다. 또한 sysfs를 통해 kobject에 대한 정보를 보여주거나 수정될 때 처리할 오퍼레이션이 지정된다.

  • kobj_type 구조체를 사용한다.
  • kobject의 참조 카운터가 0이되어 더 이상 사용하지 않게될 경우 처리 방법이 지정된다.
    • 디폴트로 kfree() 함수가 호출되어 kobject가 제거된다.
  • sysfs에서 기본적으로 표현되고 수정될 방법이 지정된다.
    • 디폴트로 어트리뷰트에 지정된 show() 후크 함수 및 store() 함수가 지정된다.

 

다음 그림은 kobject가 생성될 때 디폴트 ktype으로 dynamic_kobj_ktype이 가리키는 kobj_type 구조체가 지정되는 것을 보여준다.

 

kref(kernel object reference)

참조 카운터가 kobject에 임베드되어 사용된다.  참조 카운터를 관리하는 표준 API인 kref_*() 함수들을 사용할 수 있다.

  • kobject_get() 함수 호출 시 참조 카운터를 1 증가시키는 kref_get() 함수가 호출된다.
  • kobject_put() 함수 호출 시 참조 카운터를 1 감소시키는 kref_put() 함수가 호출된다.
  • kobject_init() 함수 호출 시 참조 카운터를 1로 초기화시키는 kref_init() 함수가 호출된다.
  • 기타 함수
    • kref_read()
    • kref_get_unless_zero()
    • kref_put_mutex()
    • kref_put_lock()

 

다음 그림은 kobject가 처음 초기화되고 참조 카운터가 1이된 것을 보여준다. 그리고 kobject가 추가된 후 sysfs에 연결된 상태의 kobject 상태 값들을 보여준다.

 

Sysfs 파일시스템

sysfs 파일시스템은 유저가 커널 자료 구조와 인터페이스를 하기 위한 가상파일시스템이다. 디렉토리 및 파일들은 kset, kobject로 표현된다. sysfs는 보통 다음과 같이 마운트된다.

  • mount -t sysfs sysfs /sys

 

sysfs는 최근의 Device Driver Model을 따라 하이라키 관계를 사용하여 관리되는 방법이며 다음 방법들을 대체하는 것을 권고하고 있다.

  • procfs
    • 프로세스 정보를 유저 application과의 인터페이스를 위해 심플하게 readable text 출력 포맷으로 출력하게 고안된 파일 시스템이다.
    • 과도하게 이 디렉토리에 파일들이 모여있는 것을 피하기 위해 sysfs가 사용된다.
  • character 디바이스
    • ioctl()을 포함한 file operation 방식으로 유저 application과의 인터페이스를 수행한다.
    • major 및 minor 넘버링이 필요하다.

 

최신 버전 sysfs

  • sysfs는 앞으로 구조가 대폭 변경된다. sys 디렉토리에 직접 접근해야 하는 low-level 유저 application은 앞으로 몇 가지 rule을 따라야 한다. 앞으로 표준화될 새로운 sysfs의 인터페이스 방법은 다음을 참고한다.

 

sysfs 디렉토리 구조

sysfs의 구조는 대략 다음 그림과 같다.

 

Sysfs 관련 API들

디렉토리
  • sysfs_create_mount_point()
  • sysfs_remove_mount_point()
파일
  • sysfs_notify()
  • sysfs_create_file()
  • sysfs_create_files()
  • sysfs_create_file_ns()
  • sysfs_add_file()
  • sysfs_chmod_file()
  • sysfs_add_file_to_group()
  • sysfs_add_file_mode_ns()
  • sysfs_remove_file_ns()
  • sysfs_remove_files()
  • sysfs_remove_file_from_group()
  • sysfs_create_bin_file()
  • sysfs_remove_bin_file()
그룹
  • sysfs_create_group()
  • sysfs_create_groups()
  • sysfs_update_group()
  • sysfs_remove_group()
  • sysfs_remove_groups()
  • sysfs_merge_group()
  • sysfs_unmerge_group()
  • sysfs_add_link_to_group()
  • sysfs_remove_link_from_group()
  • __compat_only_sysfs_link_entry_to_kobj()
링크
  • sysfs_create_link()
  • sysfs_create_link_nowarn()
  • sysfs_create_link_sd()
  • sysfs_remove_link()
  • sysfs_rename_link_ns()

 

Sysfs 샘플

샘플을 통해 속성을 사용하는 방법과 유저 application으로 이벤트를 전달하는 방법을 알아본다.

 

속성 구조체와 매크로 함수

샘플들을 사용하면서 attribute 및 device_attribute 에 대한 2가지 속성들을 알아보기로한다. bus_attribute, class_attribute 및 driver_attribute 등의 샘플은 사용방법이 유사하므로 생략한다. 다음은 5 가지 속성에 대한 구조체와 관련 매크로 함수들을 보여준다.

  • attribute 구조체
    • __ATTR(_name, _mode, _show, _store)
    • __ATTR_RW(_name)
    • __ATTR_RO(_name)
    • __ATTR_WO(_name)
  • device_attribute 구조체
    • DEVICE_ATTR(_name, _mode, _show, _store)
    • DEVICE_ATTR_RW(_name)
    • DEVICE_ATTR_RO(_name)
    • DEVICE_ATTR_WO(_name)
  •  bus_attribute 구조체
    • BUS_ATTR(_name, _mode, _show, _store)
    • BUS_ATTR_RW(_name)
    • BUS_ATTR_RO(_name)
  • class_attribute 구조체
    • CLASS_ATTR_RW(_name)
    • CLASS_ATTR_RO(_name)
    • CLASS_ATTR_WO(_name)
  • driver_attribute 구조체
    • DRIVER_ATTR_RW(_name)
    • DRIVER_ATTR_RO(_name)
    • DRIVER_ATTR_WO(_name)

 

1) kobject_init() & kobject_add() 사용

Makefile

obj-m := foo1.o foo2.o foo3.o
KER_DIR := /lib/modules/$(shell uname -r)/build

all:
	make -C ${KER_DIR} M=$(PWD) modules
	gcc test.c -o test

clean:
	make -C ${KER_DIR} M=$(PWD) clean
	rm -f test.o
	rm -f test

 

foo.1 샘플은 attribute 구조체를 사용하고 kobject API를 사용하여 /sys/foo/foo_value 및 /sys/foo/foo_notify 속성을 만든다.

foo1.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/slab.h>

static struct kobject *foo_kobj;

struct foo_attr{
	struct attribute attr;
	int value;
};

static struct foo_attr foo_value = {
	.attr.name="foo_value",
	.attr.mode = 0644,
	.value = 0,
};

static struct foo_attr foo_notify = {
	.attr.name="foo_notify",
	.attr.mode = 0644,
	.value = 0,
};

static struct attribute * foo_attrs[] = {
	&foo_value.attr,
	&foo_notify.attr,
	NULL
};

static ssize_t foo_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);
	return scnprintf(buf, PAGE_SIZE, "%d\n", foo->value);
}

static ssize_t foo_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);

	sscanf(buf, "%d", &foo->value);
	foo_value.value = foo->value;
	sysfs_notify(foo_kobj, NULL, "foo_notify");
	return sizeof(int);
}

static struct sysfs_ops foo_ops = {
	.show = foo_show,
	.store = foo_store,
};

static struct kobj_type foo_type = {
	.sysfs_ops = &foo_ops,
	.default_attrs = foo_attrs,
};

static int __init foo_init(void)
{
	int ret = 0;

	printk("%s\n", __func__);

	foo_kobj = kzalloc(sizeof(*foo_kobj), GFP_KERNEL);
	if (!foo_kobj)
	{
		printk("%s: kzalloc() failed\n", __func__);
		return -1;
	}

	kobject_init(foo_kobj, &foo_type);
	ret = kobject_add(foo_kobj, NULL, "%s", "foo");
	if (ret) {
		printk("kobject_add() failed. ret=%d\n", ret);
		kobject_put(foo_kobj);
		foo_kobj = NULL;
	}

	return ret;	/* 0=success */
}

static void __exit foo_exit(void)
{
	if (foo_kobj) {
		kobject_put(foo_kobj);
	}

	printk("%s\n", __func__);
}

module_init(foo_init);
module_exit(foo_exit);
MODULE_LICENSE("GPL");

 

위의 샘플을 동작시키면 다음과 같은 속성 파일이 생성된다.

$ su -
$ insmod foo1.ko
$ cd /sys/foo
$ ls
foo_notify   foo_value
$ echo 123 > foo_value
$ cat foo_value
123
$ rmmod foo1

 

2) kobject_create_and_add() &  sysfs_create_group() 사용

 

foo2.c 샘플은 kobj_attribute 구조체를 사용하고 kobject 및 sysfs API를 사용하여 /sys/foo/foo_value 및 /sys/foo/foo_notify 속성을 만든다. foo2.c의 사용법은 foo1.c와 동일하다.

foo2.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/slab.h>

static struct kobject *foo_kobj;

struct foo_attr{
	struct kobj_attribute attr;
	int value;
};

static struct foo_attr foo_value;
static struct foo_attr foo_notify;

static struct attribute *foo_attrs[] = {
	&foo_value.attr.attr,
	&foo_notify.attr.attr,
	NULL
};

static struct attribute_group foo_group = {
	.attrs = foo_attrs,
};

static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr, 
		char *buf)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);
	return scnprintf(buf, PAGE_SIZE, "%d\n", foo->value);
}

static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, 
		const char *buf, size_t len)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);

	sscanf(buf, "%d", &foo->value);
	sysfs_notify(foo_kobj, NULL, "foo_notify");
	return sizeof(int);
}

static struct foo_attr foo_value = {
	.attr = __ATTR(foo_value, 0644,	foo_show, foo_store),
	.value = 0,
};

static struct foo_attr foo_notify = {
	.attr = __ATTR(foo_notify, 0644, foo_show, foo_store),
	.value = 0,
};

static int __init foo_init(void)
{
	int ret = 0;

	printk("%s\n", __func__);

	foo_kobj = kobject_create_and_add("foo", NULL);
	if (!foo_kobj) {
		printk("%s: kobject_create_and_add() failed\n", __func__);
		return -1;
	}

	ret = sysfs_create_group(foo_kobj, &foo_group);
	if (ret) 
		printk("%s: sysfs_create_group() failed. ret=%d\n", __func__, ret);

	return ret;	/* 0=success */
}

static void __exit foo_exit(void)
{
	if (foo_kobj) {
		kobject_put(foo_kobj);
	}

	printk("%s\n", __func__);
}

module_init(foo_init);
module_exit(foo_exit);
MODULE_LICENSE("GPL");

 

3) platform_device_register() 사용

 

foo3.c 샘플은 플랫폼 디바이스를 등록할 때 device_attribute 구조체를 사용한 디바이스 속성을 등록한다.

foo3.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kobject.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/platform_device.h>                                              

struct foo_attr{
	struct device_attribute attr;
	int value;
};

static struct foo_attr foo_value;
static struct foo_attr foo_notify;

static struct attribute *foo_attrs[] = {
	&foo_value.attr.attr,
	&foo_notify.attr.attr,
	NULL
};

ATTRIBUTE_GROUPS(foo);

static ssize_t foo_show(struct device *dev, struct device_attribute *attr, 
		char *buf)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);
	return scnprintf(buf, PAGE_SIZE, "%d\n", foo->value);
}

static ssize_t foo_store(struct device *dev, struct device_attribute *attr, 
		const char *buf, size_t len)
{
	struct foo_attr *foo = container_of(attr, struct foo_attr, attr);

	sscanf(buf, "%d", &foo->value);
	sysfs_notify(&dev->kobj, NULL, "foo_notify");
	return sizeof(int);
}

static struct foo_attr foo_value = {
	.attr = __ATTR(foo_value, 0644, foo_show, foo_store),
	.value = 0,
};

static struct foo_attr foo_notify = {
	.attr = __ATTR(foo_notify, 0644, foo_show, foo_store),
	.value = 0,
};

static struct platform_device foo_device = {                                   
	.name = "foo",
	.id = -1,
	.dev.groups = foo_groups,
};                                                                              

static int __init foo_init(void)
{
	int ret = 0;

	printk("%s\n", __func__);

	ret = platform_device_register(&foo_device);
	if (ret < 0) {
		printk("%s: platform_device_register() failed. ret=%d\n",
				__func__, ret);
		ret = -1;
	}

	return ret;	/* 0=success */
}

static void __exit foo_exit(void)
{
	platform_device_unregister(&foo_device);

	printk("%s\n", __func__);
}

module_init(foo_init);
module_exit(foo_exit);
MODULE_LICENSE("GPL");

 

위의 foo3.c 샘플을 동작시키면 다음과 같이 플랫폼 디비이스 디렉토리 뒤에 foo 플랫폼 디바이스가 만들어지고 그 안에 속성 파일이 생성된다.

$ su -
$ insmod foo3.ko
$ cd /sys/devices/platform/foo
$ ls
driver_override foo_notify foo_value modalias power subsystem uevent
$ echo 123 > foo_value
$ cat foo_value
123
$ rmmod foo3

 

이벤트 테스트를 위한 유저 application

위에서 작성한 3가지의 샘플 모듈을 사용하여 유저 application에서 poll() API를 사용하여 이벤트를 받는 방법을 보여주기 위한 샘플이다.

  • 참고로 커널 모듈에서 속성에 이벤트를 전달하는 방법은 sysfs_notify() 함수를 사용한다.
  • 주의: foo3.c 샘플과 연동하여 테스트하려면 아래 FOO_VALUE 및 FOO_NOTIFY의 디렉토리 경로를 플랫폼 디바이스 위치로 변경해야 한다.
    • #define FOO_VALUE “/sys/devices/platform/foo/foo_value”
    • #define FOO_NOTIFY “/sys/devices/platform/foo/foo_notify”

test.c

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h> 
#include <sys/stat.h> 
#include <poll.h>

#define FOO_VALUE "/sys/foo/foo_value"
#define FOO_NOTIFY "/sys/foo/foo_notify"

int main(int argc, char **argv)
{
	int len, value_fd, notify_fd, recv;
	char attr_data[100];
	struct pollfd fds[1];

	if ((value_fd = open("/sys/foo/foo_value", O_RDWR)) < 0)
	{
		printf("Unable to open %s\n", FOO_VALUE);
		exit(1);
	}

	if ((notify_fd = open("/sys/foo/foo_notify", O_RDWR)) < 0)
	{
		printf("Unable to open %s\n", FOO_NOTIFY);
		exit(1);
	}

	fds[0].fd = notify_fd;
	fds[0].events = POLLPRI | POLLERR;

	/* read garvage data once */
	len = read(notify_fd, attr_data, 100);

	fds[0].revents = 0;

	/* wait until 60 sec */
	recv = poll(fds, 1, 60000);
	if (recv < 0)
	{
		perror("poll error");
	}
	else if (recv == 0)
	{
		printf("Timeout!\n");
	}
	else
	{
		memset(attr_data, 0, 100);
		len = lseek(value_fd, 0, SEEK_SET);
		len = read(value_fd, attr_data, 100);
		printf( "foo_value=%s (len=%d)\n", attr_data, len);
	}

	close(notify_fd);
	close(value_fd);
}

 

다음과 같이 두 개의 터미널을 열고 각각에서 다음과 같이 동작시킨다.

터미널 1
$ su -                 <- 루트 계정으로 접근한다.
$ ./test3              <- 60초 동안 이벤트를 대기한다.
foo_value=456          <- 이벤트가 발생하는 순간 출력된다.
 (len=4)

 

터미널 2
$ su -                         <- 루트 계정으로 접근한다.
$ cd /sys/devices/platform/foo
$ echo 456 > foo_value         <- 이 속성을 변경하는 순간 이벤트가 발생한다.

 

참고

 

2 thoughts to “Sysfs (kobject & kset)”

  1. 안녕하세요?

    하이라키로 관리되는 디바이스 드라이버 모델에서 kobject, kset 등이 가장 기본적인 정보인 디바이스 이름과 parent 디바이스 및 참조카운터 등을 다룹니다.
    어떤 디바이스이든 사용하는 가장 밑바닥(base) 레벨입니다.

    속성 파일을 만들기 위해 샘플을 3개 보여줬는데요. 1번 샘플은 base 레벨을 이해하기 위한 예제입니다. 서브시스템을 개발하는 커널 개발자들은 관련이 있지만, 커널 드라이버 개발자가 직접 사용할 일은 거의 없습니다. 오히려 디바이스 드라이버에서 속성 파일을 만들기 위해 사용하는 기본 모델은 2번입니다. 그리고 2번과 같은 레벨이 아니라 조금 더 윗단계의 레벨로 플랫폼 디바이스를 사용하는 경우에 속성 파일을 만들기 위한 예제입니다.

    결국 1번은 foundamental을 이해하기 위한 부분이며, 실제 사용은 2)번과 3)번을 알아두면 유용합니다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다