Linux Tutorial #17 객체를 관리하는 객체 (kobject)

문연수·2021년 6월 2일
1

Linux Tutorial

목록 보기
18/25

드라이버 모델과 이것 위에 설계되어진 kobject 추상성을 이해하는 어려움 중 하나는 명백한 시작 지점이 없다는 것이다. kobject 를 다루기 위해선 서로를 참조하는 몇 가지 자료형에 대한 이해를 필요로 한다. 이를 쉽게 만들기 위해 애매모호한 표현으로 시작하여 세부 사항을 추가하는 다각도 접근 방식을 취할 것이다.

1. 개요

이를 위해, 여기에는 우리가 다루게 될 몇가지 용어에 대한 몇가지 짧은 정의가 있다:

kobject

  1. kobjectstruct kobject 자료형의 객체이다. kobject 는 이름과 참조 카운터를 가진다. kobject 는 또한 (객체가 계층적으로 배치되는 것을 허용하는) 부모 포인터와 특정한 자료형 가지며, 보통 sysfs 가상 파일 시스템 으로 표현되어진다.
  2. kobject 는 일반적으로 그것 자체는 중요하지 않고 대신, 일반적으로 이들은 실제로 중요한 코드 포함하는 또 다른 구조체에 내장된다.
  3. 그 어떤 구조체도 절대 그 안에 하나 이상의 kobject 를 내장할 수 없다. 만일 하나 이상을 가진다면 객체에 대한 참조 카운터는 확실하게 엉망이 될 것이고 불일치할 것이다. 이는 분명 너의 코드에 버그가 있는 것이다. 그러니 절대 이런 짓을 하지 마라.

ktype

ktypekobject 를 내장한 자료형의 객체이다. kobject 를 내장한 모든 구조체는 대응하는 ktype 을 필요로 한다. ktypekobject 가 생성되고 삭제될때 어떤 일이 벌어졌는지 제어한다.

kset

ksetkobject 의 집합이다. 이러한 kobject 는 같은 타입일 수도 있고 다른 타입에 속할 수도 있다. ksetkobject 집합에 대한 기본적인 컨테이너다. kset 은 자신에 대한 kobject 를 포함하지만, kset 중심 코드가 이 kobject 를 자동으로 처리함으로 너는 구현 세부사항을 안전하게 무시할 수 있다.

2. kobject 연결 다이어그램

kobject 그와 관련된 데이터들은 위와 같은 형태로 연결되어 있다.

ktype

가장 간단한 ktype 에 대해 먼저 설명하겠다. ktype 은 보는 것처럼 아주 단순한 세 개의 멤버 변수를 가진다.

  • release() 함수: 이 함수는 해당 구조체를 가지는 kobject 의 생명 주기가 끝났을 때 호출되는 함수이다. ktypekobject 의 멤버 변수이다.
  • default_attrs 구조체 배열: 앞서 말했듯이 kobjectsysfs 으로 표현 된다고 했다. 이는 default_attrs 의 값에 의해 실제 파일이 만들어지며 name 은 파일에 이름, owner 는 파일의 소유자, mode 는 읽기, 쓰기 모드를 가진다.
  • sysfs_opssysfs 파일 시스템에서 해당 파일에 대한 읽기 혹은 쓰기 연산이 수행되었을 때 호출되는 콜백 함수를 등록할 수 있다. 파일이 쓰였을 때에는 store() 함수가 호출되고, 파일을 읽었을 때는 show() 함수가 호출된다.

kset

ksetkobject 의 컨테이너이다. 이것 자체가 kobject 이며, 이는 kset 의 생명 주기를 관리한다.

kobject

kobject 는 이름을 가진다. 이는 또한 kset 에 속해 있다. kobject 는 하나 이상의 kset 에 속할 수 없다. 이는 부모 kobject 를 가질 수 있으며, 특정한 ktype 일 수 있다. 만일 부모를 가지지 않는다면, kobjectsysfs 에 추가될 때 이는 ksetkobject 로 대입된다. 만일 ktype 을 가지지 않는다면 kset 을 상속 받는다.

3. kobject 연결 다이어그램

ksetkobject 연결 리스트의 head 가 되며 kset 이 가지고 있는 kobject 는 하위 kobject 의 부모가 된다.

4. 코어 함수 분석

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 함수는 새롭게 생성된 kobjectkset 에 연결 시킨다.

sysfs_create_group()

sysfs_create_group() 함수는 인자로 전달된 kobject 의 경로에 attributes 그룹의 파일을 생성한다. 앞선 kobject 의 계층적 구조(부모와 자식의 관계)는 디렉터리 경로로 생성되고, 각각의 kobject 가 가진 attributes 는 폴더로 생성되어 진다. 이후에 샘플 코드 분석에서 더 자세하게 드러난다.

5. 샘플 코드 분석

리눅스 커널은 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, &notes_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_examplektypestruct 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_examplekobj_typedefault_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 인자를 통해 어떤 이름의 파일인지 식별해서 결과를 만들어낸다.

6. 모듈 삽입 및 결과 확인

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/

profile
2000.11.30

0개의 댓글