디바이스 드라이버 모델에서 디바이스들은 하이라키 토플로지 형태로 표현되고 있다. 이렇게 하이라키로 표현된 디바이스 드라이버들은 마운트된 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의 인터페이스 방법은 다음을 참고한다.
- 참고: Rules on how to access information in sysfs | kernel.org
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 len; } 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 len; } 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 len; } 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 <- 이 속성을 변경하는 순간 이벤트가 발생한다.
참고
- Device & Driver -1- (Device Driver Model) | 문c
- Device & Driver -2- (Bus & Class) | 문c
- Device & Driver -3- (Platform device) | 문c
- Kobject & kset | 문c – 현재 글
- driver_init() | 문c
- devtmpfs & kdevtmpfs 스레드 | 문c
- do_initcalls() | 문c
- Linux Device Model Part 1 | Sarah Diesburg – 다운로드 pdf
- Linux Device Model Part 2 | Sarah Diesburg – 다운로드 pdf
- Linux Device Drivers | OPERSYS – 다운로드 pdf
- Embedded data structures and lifetime management | Shuah Khan, Samsung Open Source Group – 다운로드 pdf
- The Sysfs Virtual Filesystem Exploring the Linux Device Model | Bill Gatliff – 다운로드 pdf
- Linux Device Drivers | Jie Liao – 다운로드 pdf
- The zen of kobjects | LWN.net
- Linux Device Drivers, Third Edition | LWN.net
- kobject/kset/ktype documentation and example code updated | LWN.net
- Documentation/driver-model/ | kernel.org
- Brief sysfs Tutorial | Ben Marks – 다운로드 pdf
- Rules on how to access information in sysfs | kernel.org
예제에서 1번과 2번의 차이가 먼가요?
안녕하세요?
하이라키로 관리되는 디바이스 드라이버 모델에서 kobject, kset 등이 가장 기본적인 정보인 디바이스 이름과 parent 디바이스 및 참조카운터 등을 다룹니다.
어떤 디바이스이든 사용하는 가장 밑바닥(base) 레벨입니다.
속성 파일을 만들기 위해 샘플을 3개 보여줬는데요. 1번 샘플은 base 레벨을 이해하기 위한 예제입니다. 서브시스템을 개발하는 커널 개발자들은 관련이 있지만, 커널 드라이버 개발자가 직접 사용할 일은 거의 없습니다. 오히려 디바이스 드라이버에서 속성 파일을 만들기 위해 사용하는 기본 모델은 2번입니다. 그리고 2번과 같은 레벨이 아니라 조금 더 윗단계의 레벨로 플랫폼 디바이스를 사용하는 경우에 속성 파일을 만들기 위한 예제입니다.
결국 1번은 foundamental을 이해하기 위한 부분이며, 실제 사용은 2)번과 3)번을 알아두면 유용합니다.
안녕하세요. 공부하다 잘 모르는 것이 있어 질문드립니다. sysfs구조 사진에서 platform 버스에 연결된 디바이스들이라고 써있는데 그럼 user application에서 실제 open으로 장치를 열 때는 /sys/bus/ 밑에 있는 device파일을 열면 되는 건가요?? 아니면 심볼링크로 연결 되어 있으니 /sys/dev에 있는 디바이스로도 열어도 되는건가요?? 아니면 디바이스마다 다른건가요?? 만약 버스 디바이스가 아닌 클래스 디바이스면 /sys/class 디렉토리 아래에 있는 device를 열어야 하나요?? 햇갈리네요.. /sys/devices 디렉토리에 있는 디바이스들은 또 어떤 의미인지 모르겠습니다.. user에서 어떤 디렉토리에 있는 디바이스를 열어야 하는지 알 수 있을까요?
안녕하세요?
디바이스 드라이버 모델은 기본적으로 sysfs 방식을 사용하잖아요?
sysfs 밑에 등록된 디바이스들이 속성 파일을 제공하는 경우 그 속성파일을 프로그래밍 없이 쉘 상태에서 읽고 쓰는 것으로 그 속성 기능을 사용할 수 있습니다.
예를 들어 네트워크 디바이스가 speed라는 속성 파일을 제공하는 경우 그 디바이스의 속도를 알기 위해 speed 파일을 cat 명령으로 보거나 echo 명령으로 기록하는 것으로 speed를 설정할 수 있습니다.
이렇게 쉘 상태에서 상태 보기와 설정이 자유롭습니다. 다만 프로그래머 입장에서는 속성 파일이 많아 코드가 길어지는 단점이 있습니다. 각 속성을 제어하려면 각각의 속성 기능을 사용하기 위해 접근하는 코드가 늘어나겠죠.
따라서 주요 드라이버 서브시스템들은 ioctl()을 제공하는 캐릭터 디바이스도 제공합니다. (gpio를 참고하시면 gpio는 클래스에 포함된 속성 파일을 통해 제어할 수도 있고, 캐릭터 디바이스를 통해 같은 기능을 제어할 수 있습니다.)
프로그래머 입장에서는 하이라키 구조로 구성된 각 디바이스의 속성 파일에 접근하는 것 보다 /dev 아래에 존재하는 캐릭터 디바이스 파일 하나를 열고 ioctl()로 제어하는 것이 더 낳은 선택일 수 있습니다.
sysfs 구조에서는 디바이스 및 드라이버가 자체적으로 제공하는 속성파일, 그리고 버스에 포함되는 경우 버스가 제공하는 속성 파일, 마지막으로 클래스에 포함되는 경우 클래스가 제공하는 속성 파일등에 접근하여 제어를 해야 합니다. 이러한 속성 사용법은 모든 버스나 클래스 디바이스마다 조금씩 다르므로 해당 버스 및 클래스 디바이스를 참고해서 사용해야 합니다.
수고하세요.
답변 감사합니다. 또 질문이 있는데요..어디서 도움을 얻을 곳이 없어..질문을 올립니다. 속성파일을 프로그래밍 없이 쉘 상태에서 읽고 쓸 수 있다고 하셨는데(echo cat 등) 이것은 device가 있는 디렉토리면 상관 없는 건가요? /sys/bus/디렉토리에 있는 device나 /sys/dev에 있는 device나 쉘 명령을 할 수 있나요?? 예제에서 쓰신 /sys/foo/foo는 어느 디렉토리인지 알 수 있을까요..?? 그리고 플랫폼 디바이스 글에 플랫폼 디바이스 드라이버 모델이 procfs와 캐릭터 디바이스 모델을 대체하는 것을 권고한다고 하셨는데 최신 리눅스에서는 디바이스 트리를 이용해 플랫폼 디바이스를 이용하는 것 같은데( sysfs) 답변해주신 내용은 캐릭터디바이스로 제어하는 것이 더 나은 선택이라고 하신거면 sysfs 드라이버 모델보다 예전 모델을 사용하는게 더 좋다는 말씀이신가요??아니면 아직 초보자에게는 sysfs구조로 제어하기에는 어려우니 캐릭터 디바이스 모델을 사용하는 것이 낫다는 말씀이신가요?? 그리고 마지막 질문이 쉘 명령으로 속성파일을 이용해 제어를 하는 방법으로 임베디드 시스템을 제어할 수 있는지 궁금합니다. 컴파일을 하고 시스템이 컴퓨터와 분리돼서 쉘 명령을 더이상 할 수 없는 상태에서는 이 방법을 못하는데 어떻게 해야하는건가요? 드라이버에 다 설정을 해놓아야 되는건가요??
감사합니다
안녕하세요?
답변을 하나 하나 다는 경우 더 혼동될까봐 포괄적인 설명으로 대체합니다.
먼저 디렉토리에 대해 분류를 해 보겠습니다.
1) /sys/devices
모든 디바이스가 하이라키로 관리되는 곳입니다.
그 외에 다음 디렉토리는 기능별로 추가 관리합니다.
2) /sys/bus
모든 버스가 한꺼번에 플랫하게 열거되고 이에 연결되는 디바이스와 드라이버가 그 하위 디렉토리에 포함됩니다.
3) /sys/class
모든 클래스가 한꺼번에 플랫하게 열거되고 이에 속한 클래스-디바이스가 그 하위 디렉토리에 포함됩니다.
4) /sys/dev/block과 /sys/dev/char
블럭 디바이스들과 캐릭터 디바이스들의 링크만 관리합니다.
5) /dev
legacy 블럭 디바이스와 캐릭터 디바이스 파일입니다. 이 파일을 열어 read/write/ioctl 등의 조작을 통해 드라이버와 대화합니다.
한개의 디바이스는 버스에 속할 수도 있고, 클래스에 속할 수도 있고, 캐릭터 디바이스나 블럭 디바이스에 속할 수 있으며 procfs 및 netlink 등 유저를 위한 커뮤니케이션 채널을 만들어 여러 방법으로 제공할 수 있습니다.
예를 들어보면 i2c에 연결되어 있는 gpio 장치를 생각해보시면 i2c 버스에도 연결되고 gpio 클래스에도 연결됩니다. 최근 커널(v4.8 이상)에서는 자동으로 캐릭터 디바이스도 생성됩니다. 이러한 경우 사용자 프로그램에서는 클래스 속성을 open 하여 제어해도 되고 캐릭터 디바이스를 open하여 제어할 수 있는 두 가지의 선택권이 주어집니다. (i2c 버스에 있는 속성은 기본 공통 속성 몇 가지만 있으므로 제외)
gpio 클래스 기능이 제공하는 속성과 gpio 캐릭터 디바이스가 제공하는 기능으로도 만족하지 못하면 개발자는 디바이스 또는 드라이버 자체에 custom 속성을 만들어 유저에게 해당 custom 속성을 제공할 수도 있고, netlink를 사용하여 socket을 통해 통신할 수 있는 기능을 제공할 수도 있습니다. 또한 legacy 방법인 procfs를 만들어 유저에게 기능을 제공할 수도 있는 다양한 선택을 할 수 있습니다. 처음에 잘 모를 때에는 각 방법으로 하나씩 디바이스 드라이버 모듈을 만들어 테스트해 보시면 금방 실력이 향상될 것입니다.
——
legacy 캐릭터 디바이스 스타일의 접근 보다는 최근에는 디바이스 드라이버 모델에 접근하는 방식을 권하지만, 특정(gpio 같은) 디바이스 들은 포트 별로 제어할 때 마다 많은 조작이 필요해서 프로그램 코드 사이즈가 커집니다. 이러한 경우에는 캐릭터 디바이스 방식을 쓰는 것이 더 편할것입니다.
——
유저 프로그램에서 디바이스의 속성 파일을 제어하는 것은 일반 파일을 액세스하는 것과 동일합니다. open 함수를 사용하여 open 하시고 read/write 등을 사용하시면 됩니다. 더 간단히 할 때에는 system(“echo 1 > abc”); 같이 아예 쉘 환경을 호출하여 사용할 수도 있습니다.
——
만일 디바이스 드라이버 구현에 대한 연습을 하신다면 다음 URL을 참고하시기 바랍니다.
https://github.com/jakeisname/moon_c
감사합니다.
echo 로 넣는 값을 세자리 int 보다 큰 수로 넣어보셨나요?
store 함수의 return 값이 sizeof(int)로 되어 있는데 별로 좋은 생각이 아닙니다.
저렇게 하시면 안되요.
좋은 지적에 감사드립니다.
copy & paste 신공에 의한 사고입니다. ^^;
return sizeof(int); -> return len; 으로 수정합니다.
시스템 타이머는 sysfs의 어디에서 볼 수 있을까요?
감사합니다.
안녕하세요?
카운터 및 타이머에 대해 실제 값을 보여주는 sysfs는 없지만 아래에서 조금 더 확인해볼 수 있습니다.
/sys/device/system/clockevents < - 클럭 이벤트에 사용된 드라이버를 알아볼 수 있습니다 (broadcast 포함) (rpi4: cpu별 arch_sys_timer)
/sys/device/system/clocksource < - 클럭 소스에 사용된 드라이버를 알아볼 수 있습니다. (rpi4: arch_sys_counter)
/sys/bus/platform/devices < - 사용하는 보드 구성에 따라 다릅니다. (rpi4: alarmtimer와 clocks)
/proc/timer_list < - kernel timer 및 hrtimer의 내역을 볼 수 있습니다.
감사합니다.