드라이버 모델과 이것 위에 설계되어진 kobject
추상성을 이해하는 어려움 중 하나는 명백한 시작 지점이 없다는 것이다. kobject
를 다루기 위해선 서로를 참조하는 몇 가지 자료형에 대한 이해를 필요로 한다. 이를 쉽게 만들기 위해 애매모호한 표현으로 시작하여 세부 사항을 추가하는 다각도 접근 방식을 취할 것이다.
이를 위해, 여기에는 우리가 다루게 될 몇가지 용어에 대한 몇가지 짧은 정의가 있다:
kobject
kobject
는 struct kobject
자료형의 객체이다. kobject
는 이름과 참조 카운터를 가진다. kobject
는 또한 (객체가 계층적으로 배치되는 것을 허용하는) 부모 포인터와 특정한 자료형 가지며, 보통 sysfs 가상 파일 시스템
으로 표현되어진다.kobject
는 일반적으로 그것 자체는 중요하지 않고 대신, 일반적으로 이들은 실제로 중요한 코드 포함하는 또 다른 구조체에 내장된다.kobject
를 내장할 수 없다. 만일 하나 이상을 가진다면 객체에 대한 참조 카운터는 확실하게 엉망이 될 것이고 불일치할 것이다. 이는 분명 너의 코드에 버그가 있는 것이다. 그러니 절대 이런 짓을 하지 마라.ktype
ktype
은 kobject
를 내장한 자료형의 객체이다. kobject
를 내장한 모든 구조체는 대응하는 ktype
을 필요로 한다. ktype
은 kobject
가 생성되고 삭제될때 어떤 일이 벌어졌는지 제어한다.
kset
kset
은 kobject
의 집합이다. 이러한 kobject
는 같은 타입일 수도 있고 다른 타입에 속할 수도 있다. kset
은 kobject
집합에 대한 기본적인 컨테이너다. kset
은 자신에 대한 kobject
를 포함하지만, kset
중심 코드가 이 kobject
를 자동으로 처리함으로 너는 구현 세부사항을 안전하게 무시할 수 있다.
kobject
연결 다이어그램kobject
그와 관련된 데이터들은 위와 같은 형태로 연결되어 있다.
ktype
가장 간단한 ktype
에 대해 먼저 설명하겠다. ktype
은 보는 것처럼 아주 단순한 세 개의 멤버 변수를 가진다.
release()
함수: 이 함수는 해당 구조체를 가지는 kobject
의 생명 주기가 끝났을 때 호출되는 함수이다. ktype
은 kobject
의 멤버 변수이다.default_attrs
구조체 배열: 앞서 말했듯이 kobject
는 sysfs
으로 표현 된다고 했다. 이는 default_attrs
의 값에 의해 실제 파일이 만들어지며 name
은 파일에 이름, owner
는 파일의 소유자, mode
는 읽기, 쓰기 모드를 가진다.sysfs_ops
는 sysfs
파일 시스템에서 해당 파일에 대한 읽기 혹은 쓰기 연산이 수행되었을 때 호출되는 콜백 함수를 등록할 수 있다. 파일이 쓰였을 때에는 store()
함수가 호출되고, 파일을 읽었을 때는 show()
함수가 호출된다.kset
kset
은 kobject
의 컨테이너이다. 이것 자체가 kobject
이며, 이는 kset
의 생명 주기를 관리한다.
kobject
kobject
는 이름을 가진다. 이는 또한 kset
에 속해 있다. kobject
는 하나 이상의 kset
에 속할 수 없다. 이는 부모 kobject
를 가질 수 있으며, 특정한 ktype
일 수 있다. 만일 부모를 가지지 않는다면, kobject
가 sysfs
에 추가될 때 이는 kset
의 kobject
로 대입된다. 만일 ktype
을 가지지 않는다면 kset
을 상속 받는다.
kobject
연결 다이어그램
kset
은 kobject
연결 리스트의 head
가 되며 kset
이 가지고 있는 kobject
는 하위 kobject
의 부모가 된다.
kobject
를 위와 같이 연결하여 쓰려면 lib/kobject.c
소스파일에 있는 함수를 사용해야 한다.
kobject_create_and_add()
kobject
구조체를 생성 및 추가하는 kobject_create_and_add()
는 아래의 형태를 가진다:
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
struct kobject *kobj;
int retval;
kobj = kobject_create();
if (!kobj)
return NULL;
retval = kobject_add(kobj, parent, "%s", name);
if (retval) {
pr_warn("%s: kobject_add error: %d\n", __func__, retval);
kobject_put(kobj);
kobj = NULL;
}
return kobj;
}
kobject_create()
함수는 새로운 kobject
를 생성하고 kobject_add
함수는 새롭게 생성된 kobject
를 kset
에 연결 시킨다.
sysfs_create_group()
sysfs_create_group()
함수는 인자로 전달된 kobject
의 경로에 attributes
그룹의 파일을 생성한다. 앞선 kobject
의 계층적 구조(부모와 자식의 관계)는 디렉터리 경로로 생성되고, 각각의 kobject
가 가진 attributes
는 폴더로 생성되어 진다. 이후에 샘플 코드 분석에서 더 자세하게 드러난다.
리눅스 커널은 kobject
에 대한 샘플 코드를 제공한다. 그 내용은 samples/kobject/
에서 확인 가능하고 필자는 kobject-example.c
코드를 분석해볼 예정이다.
example_init()
진입부static int __init example_init(void)
{
int retval;
/*
* Create a simple kobject with the name of "kobject_example",
* located under /sys/kernel/
*
* As this is a simple directory, no uevent will be sent to
* userspace. That is why this function should not be used for
* any type of dynamic kobjects, where the name and number are
* not known ahead of time.
*/
example_kobj = kobject_create_and_add("kobject_example", kernel_kobj);
if (!example_kobj)
return -ENOMEM;
/* Create the files associated with this kobject */
retval = sysfs_create_group(example_kobj, &attr_group);
if (retval)
kobject_put(example_kobj);
return retval;
}
엔트리 함수인 example_init()
은 위와 같이 작성되어 있다. 가장 먼저 kobject_create_and_add()
함수를 호출해서 "kobject_example"
이라는 이름의 kobject
를 생성하고 이를 kernel_kobj
에 연결한다. 그럼 kernel_kobj
는 어디에 있을까?
이는 kernel/ksysfs.c
소스파일에 정의되어 있고 ksysfs_init()
함수가 아래와 같이 초기화한다.
static int __init ksysfs_init(void)
{
int error;
kernel_kobj = kobject_create_and_add("kernel", NULL);
if (!kernel_kobj) {
error = -ENOMEM;
goto exit;
}
error = sysfs_create_group(kernel_kobj, &kernel_attr_group);
if (error)
goto kset_exit;
if (notes_size > 0) {
notes_attr.size = notes_size;
error = sysfs_create_bin_file(kernel_kobj, ¬es_attr);
if (error)
goto group_exit;
}
return 0;
group_exit:
sysfs_remove_group(kernel_kobj, &kernel_attr_group);
kset_exit:
kobject_put(kernel_kobj);
exit:
return error;
}
이제 경로를 합쳐보면 최초의 부모 오브젝트인 kernel_kobj
는 "kernel"
이라는 이름을 가진다. 샘플 코드에서는 "kobject_example"
이라는 이름의 kobject
를 생성하고 이를 kernel_kobj
에 연결한다. 이는 디렉터리로 사용되어 /sys/kernel/kobject_example
경로를 완성한다. 이제 kobject_example
의 ktype
의 struct attribute_group
가 해당 경로에 생성되는 파일을 구성하게 된다.
struct attribute_group
/*
* Create a group of attributes so that we can create and destroy them all
* at once.
*/
static struct attribute *attrs[] = {
&foo_attribute.attr,
&baz_attribute.attr,
&bar_attribute.attr,
NULL, /* need to NULL terminate the list of attributes */
};
/*
* An unnamed attribute group will put all of the attributes directly in
* the kobject directory. If we specify a name, a subdirectory will be
* created for the attributes with the directory being the name of the
* attribute group.
*/
static struct attribute_group attr_group = {
.attrs = attrs,
};
kobject_example
의 kobj_type
의 default_groups
로 등록될 attr_group
의 정의이다. 총 3개의 속성을 등록했다. 각 속성은 아래와 같이 정의되어 있다.
struct kobj_attribute
static struct kobj_attribute foo_attribute =
__ATTR(foo, 0664, foo_show, foo_store);
static struct kobj_attribute baz_attribute =
__ATTR(baz, 0664, b_show, b_store);
static struct kobj_attribute bar_attribute =
__ATTR(bar, 0664, b_show, b_store);
앞서 말했듯이 struct default_attrs
구조체 배열은 이름
, 소유자
, 모드
의 정보, 여기에 더해 sysfs_ops
(show()
와 store()
콜백 함수) 를 가진다. 각각의 attributes
를 생성해서 등록하는 코드이다.
show()
, store()
콜백static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
pr_info("foo_show(): %d\n", foo);
return sprintf(buf, "%d\n", foo);
}
static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int ret;
ret = kstrtoint(buf, 10, &foo);
if (ret < 0)
return ret;
pr_info("foo_store(): %d\n", foo);
return count;
}
foo
에 해당하는 콜백 함수는 위와 같이 작성되어 있다. pr_info
는 필자가 실행 결과를 확인하기 위해 의도적으로 끼워 넣은 라인이다. 이후 모듈 삽입 시 결과를 확인하고 싶다면 다음과 같이 pr_info
구문을 작성하길 바란다.
static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
int var;
if (strcmp(attr->attr.name, "baz") == 0)
var = baz;
else
var = bar;
return sprintf(buf, "%d\n", var);
}
static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
int var, ret;
ret = kstrtoint(buf, 10, &var);
if (ret < 0)
return ret;
if (strcmp(attr->attr.name, "baz") == 0)
baz = var;
else
bar = var;
return count;
}
b_show()
와 b_store()
는 전달받은 attr
인자를 통해 어떤 이름의 파일인지 식별해서 결과를 만들어낸다.
Makefile
은 위와 같이 작성하길 바란다.
위와 같이 빌드하여 모듈을 삽입한다. 실행결과는 /var/log/syslog
를 확인하면 된다. 필자는 fedora
로 운영체제를 변경하여 journalctl
명령어를 통해 확인이 가능하다.
위와 같은 메세지가 나오면 성공이다. 앞서 등록한 콜백 함수의 결과를 확인하기 위해 sysfs
파일 시스템에 등록된 파일을 읽고 써보겠다.
경로는 예상했던 것처럼 /sys/kernel/kobject_examples
에 있다.
필자는 아래와 같이 root
권한으로 디렉터리에 접근하여 foo
파일을 읽고 써보았다.
journalctl
의 결과는 위와 같았다. 성공적으로 callback
함수가 호출된 것을 확인할 수 있었다.
[책] 리눅스 커널 소스 해설: 기초 입문 (정재준 저)
[사이트] https://www.kernel.org/doc/html/latest/core-api/kobject.html
[그림 1] http://marcocorvi.altervista.org/games/lkpe/kobj/devclass.htm
[그림 2] https://lwn.net/Articles/51437/