Linux Driver Model
2005 KELP Open Serminar
@SIG
본 내용은 리눅스 커널 내에서 자주 사용되는 API를 중심으로 열거식으로 정리한 것이다. 유기적인 기능들에 대해서는 설명하지 않으므로 이러한 것을 원한다면 기존의 서적들이나 문서들을 참고하기 바란다.
또한 본 내용은 직접 사용하거나 이럴 거 같다는 유추 식으로 작성된 내용도 있으므로 이것이 정확하다고 말할 수는 없다. 그러나 나름대로는 오류가 없도록 할 것이며 잘못된 내용이 있다면 최대한 빨리 수정할 수 있도록 할 것이다.
커널 버전은 2.6.11 또는 그 이후 버전들을 기준으로 작성하였다. 커널의 API는 고정된 것이 아니라 버전별로 조금씩 변경될 수도 있다. 커널 API에 대한 최신의 정보는
http://lwn.net에서 확인할 수 있다.
- 2005.9.28 김도집 -
2 디바이스 등록 및 해제 #
리눅스 커널 2.6에서는 kobject라는 객체 기반으로 디바이스 및 드라이버의 계층적 구조를 갖고 있다. 예를 들어 등록된 버스가 있고 버스에 새로운 장치가 장착되면 버스 드라이버가 장치를 탐지하고 이에 맞는 드라이버를 찾아 등록하게 된다.
따라서 버스 상의 디바이스와 드라이버는 기본적으로 이름으로 찾게 된다. 따라서 버스 상에 등록된 디바이스의 이름과 드라이버의 이름이 서로 다르다면 정상적으로 드라이버가 동작하지 않는다.
다음은 platform_bus_type 라는 가상의 플랫폼 버스 상에 새로운 디바이스를 등록하는 방법이다.
platform_bus_type 라는 가상의 플랫폼 버스 상의 디바이스를 정의하는 구조체는 <linux/device.h>에 정의되어 있다. 그 구조체는 platform_device 이다.
구조체의 원형은 다음과 같다:
struct platform_device {
char *name;
u32 id;
struct device dev;
u32 num_resources;
struct resource *resource;
};
필드 | 설명 |
name | |
id | |
dev | |
num_resources | |
resource | |
간단한 사용 예는 다음과 같다:
static void xxx_relese_dev(struct device *dev)
{
}
static struct platform_device xxx_device = {
.name = "xxx_device",
.id = 0,
.dev = {
.release = xxx_release_dev,
},
.num_resources = 0,
};
2.2 platform_device_register #
플랫폼 디바이스를 등록한다.
함수 원형은 다음과 같다:
int platform_device_register(struct platform_device *pdev)
2.3 platform_device_unregister #
플랫폼 디바이스의 등록을 해제한다.
함수 원형은 다음과 같다:
void platform_device_unregister(struct platform_device *pdev)
driver_register/device_unregister 함수에서 사용하는 자료형은 <linux/device.h>에 선언되어 있다.
struct device_driver {
const char *name;
struct bus_type *bus;
struct completion unloaded;
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module *owner;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
int (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state, u32 level);
int (*resume) (struct device *dev, u32 level);
};
필드 | 설명 |
name | 디바이스 드라이버의 이름을 문자열 형으로 지정한다 (필수) |
bus | 보통은 &platform_bus_type으로 지정한다 (필수) |
unloaded | |
kobj | 내부적으로 드라이버 객체를 관리하기 위한 것으로 직접적으로 사용하지 않는다 (임의 지정하지 않는다) |
klist_devices | |
knode_bus | |
owner | |
probe() | 디바이스의 초기화 루틴이다 (필수) |
remove() | 디바이스가 제거될 때 호출되는 루틴이다 (필수) |
shutdown() | |
suspend() | 절전모드로 들어갈 때 호출된다 (필수) |
resume() | 절전모드를 빠져나올 때 호출된다 (필수) |
위 테이블에서 foo()와 같이 ()가 붙은 필드는 함수(포인터)를 의미한다.
3.2 driver_register #
버스를 갖는 드라이버를 등록할 때 사용하는 함수이다.
함수의 원형은 다음과 같다:
int driver_register(struct device_driver *drv);
관련함수: driver_unregister
static struct device_driver foo_driver = {
.name = "foo”,
.bus = &platform_bus_type,
.probe = foo_probe,
.remove = foo_remove,
.suspend = foo_suspend,
.resume = foo_resume,
};
static int __init foo_init(void) {
int err;
err = driver_register(&foo_driver);
return err;
}
3.3 driver_unregister #
driver_register()를 통해 등록된 드라이버를 해제할 때 사용한다.
함수의 원형은 다음과 같다:
void driver_unregister(struct device_driver *drv)
관련함수: driver_register
3.4 dev_set_drvdata #
사용자 데이터를 device 구조체의 driver_data 필드를 통해 참조할 수 있도록 설정하는 wrapper 함수이다.
함수 원형은 다음과 같다:
void dev_set_drvdata(struct device *dev, void *data)
관련함수: dev_get_drvdata
3.5 dev_get_drvdata #
device 구조체의 driver_data 필드의 참조 주소를 가져오는 wrapper 함수이다.
함수 원형은 다음과 같다:
void * dev_get_drvdata(struct device *dev)
관련함수: dev_set_drvdata
4 문자형 디바이스 드라이버 #
문자형 드라이버와 관련된 헤더 파일은 <linux/cdev.h>이다.
리눅스 커널에서 지원하는 대표적인 드라이버의 형태는 문자형, 블록형, 네트워크 드라이버 등이 있다. 쉽게 접근할 수 있고 가장 많이 사용되는 것이 문자형 디바이스 드라이버이다.
디바이스 파일 중 ls -l를 통해 접근권한 내용중 crw-rw---- 등과 같이 c로 시작하는 것이 문자형 디바이스 드라이버이다.
struct cdev {
struct kobject kobj;
struct module *owner;
struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
필드 | 설명 |
kobj | |
owner| |
ops | |
list | |
dev | |
count | |
4.2 cdev_init #
void cdev_init(struct cdev *cdev, struct file_operations *fops)
사용 예는 다음과 같다:
static struct file_operations xxx_fops = {
.read = xxx_read,
.write = xxx_write,
.open = xxx_open,
.release = xxx_release,
.owner = THIS_MODULE,
};
static struct cdev xxx_cdev = {
.kobj = {.name = "xxx", },
.owner = THIS_MODULE,
};
static int __init xxx_init(void)
{
dev_t dev = MKDEV(XXX_MAJOR, 0);
if (register_chrdev_region(dev, MAX_XXX_MINORS, "xxx"))
goto error;
cdev_init(&xxx_cdev, &xxx_fops);
...
4.3 cdev_add #
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
4.4 cdev_del #
void cdev_del(struct cdev *p)
4.5 register_chrdev_region #
본 함수는 <linux/fs.h>에 선언되어 있다.
int register_chrdev_region(dev_t from, unsigned count, const char *name)
관련함수: unregister_chrdev_region
4.6 unregister_chrdev_region #
본 함수는 <linux/fs.h>에 선언되어 있다.
void unregister_chrdev_region(dev_t from, unsigned count)
관련함수: register_chrdev_region
5 장치 번호 #
장치 번호는 dev_t 형으로 u32형이다. 상위 20비트가 주번호이며 하위 12비트가 부번호이다.
내부적으로 *chrdevs
?MAX_PROBE_HASH가 선언되어 있다. MAX_PROBE_HASH는 255이다. 즉, 주번호가 인덱스가 되어야 하는데, 20비트라면 255개만으로는 주번호를 모두를 포함할 수 없다. 그래서 커널에서는 주번호%MAX_PROBE_HASH 로 나눈 값을 인덱스로 사용하여 그 인덱스를 기준으로 linked list로 연결하게 된다.
unsigned int MAJOR(dev_t n)
unsigned int MINOR(dev_t n)
6 리눅스 디바이스 드라이버 개요 #
OS의 커널은 모놀리딕 Monolithic과 마이크로 micro 커널로 나눈다. 모놀리딕 커널은 커널이 하나의 커다란 덩어리로 구성된 것이고 마이크로 커널은 여러 개의 독립된 서버 형태로 구성된 것이다. 말하자면 전자는 처음부터 완성된 완구형 장난감이고, 후자는 레고와 같은 블록 조립식 장난감과 비슷한 개념이다.
서로간엔 장단점이 있기에 어느 것이 더 좋다고 할 수는 없다.
짐작하겠지만, 리눅스는 모놀리딕 커널이다. 그렇지만 재미있는 것이 일부 기능을 모듈이라는 동적 적재 가능한 형태로 확장이 가능하다는 것이다. 일반적으로 드라이버가 모듈형태로 사용되지만, 꼭 드라이버가 아니라 하더라고 모듈 형태로 일부 기능을 제공할 수 있다.
모듈 개념이 적용된 것은 커널 버전 1.2대에서 적용되었고 현재까지 그 개념은 이어지고 있다.
커널 2.5대에 접어 들면서 또 하나의 커다란 변화가 있었는데, 바로 kboject라는 드라이버 객체 개념이 추가되었다. 드라이버가 다양해 지고 복잡해 지면서 상호간의 연과성이 중요하게 되었다. 이에 따라 드라이버를 계층화 시켜 관리할 필요성이 점점 제기되었고, 이런 요구를 일관성 있고 체계 있게 구현/관리 하기 위하여 적용되었다고 할 수 있다.
이후 다룰 내용은 이들 객체에 대한 것이다. 장황한 설명보다는 직접 자료형과 API를 보면서 이해하는 것이 더 수월할 것이다. 그럼 리눅스 드라이버 모델에 빠져 봅시다~~~
7 디바이스 드라이버의 객체 개념 #
객체라고 하면 흔히 C++이나 JAVA와 같은 객체 지향 프로그래밍 언어를 생각한다. 사실 C에서 객체라는 말은 거의 사용하지 않는다. 그러나 C도 다른 객체 지향 프로그래밍 언어와 같이 객체 지향적으로 프로그래밍을 할 수 있다.
혹자는 C는 객체 지향 언어가 아니고 객체 지향이 아닌 순차적 프로그래밍 언어라고 할지도 모르겠다. 여하튼, 여기서 그런 것을 논하고 싶은 생각은 없다.
C에서 객체를 구현하는 것이 쉬운 것은 아니며, 이해하기도 쉬운 것은 아니다. 그러나 포인터와 struct를 이용하여 C에서도 객체를 구현할 수 있다. 또한 gcc라는 강력한 컴파일러 덕분에 표준이 아니긴 하지만, 확장된 C언어의 기능을 이용하여 다양한 것을 시도할 수 있다.
리눅스에서 다양한 디바이스를 지원하고 점점 더 복잡해지면서, 이전과 같은 열거식의 디바이스 관리는 점점 더 어려워졌다. 특히, USB와 같은 계층적 구조의 디바이스의 경우 단순 열거식 만으로는 몇가지 문제가 야기되었다.
예를 들어, USB 허브에 여러 디바이스가 물려 있다고 하자. 이런 경우 허브도 하나의 USB 디바이스고 그 허브에 물려 있는 것들도 디바이스에 해당한다. 이런 경우 그 중간에 물려 있는 장치인 허브를 호스트에서 제거하게 되면 어떻게 될까? 호스트 입장에서 보면 허브에 물려 있던 디바이스들도 제거된 것이다. 그런데 문제는 여기에 있다. 디바이스를 단순 열거하게 되면, 어떤 디바이스가 방금 제거된 허브에 물려 있던 디바이스인지 알 수가 없다는 것이다. 물론, 허브를 관리하는 디바이스 드라이버가 그 허브에 물린 디바이스에 대한 정보를 갖고 있다고 하면 한 해결 방법이 될 수도 있다. 그러나 이것도 한계가 있다. 허브에 또 다른 허브가 붙고 그 허브에 또 다른 허브가 붙는 식으로 계속 확장된다면 관리해야 하는 정보는 기하 급수적으로 는다. 이러한 문제를 해결하기 위하여 디바이스를 계층적으로 관리할 필요성이 제기 되었고 이를 위해 리눅스 커널 2.5 대에서 kobject라는 객체를 정의하고 이를 기반으로 계층적 디바이스 관리 체계를 정립하였다.
달리 말하면, 개발자에겐 kobject를 이해해야 한다는 과제가 주어진 것이다. 디바이스의 관리 편리성을 위해 제안된 것이 역설적으로 개발자에게 kobject를 이해해야 하는 어려움을 야기시켰다. 사실, 몰라도 개발하는데, 큰 불편함은 없다. 이러한 내용은 하부에 숨겨져 있어 실제 개발에서는 직접 다루는 일은 드물기 때문이다. 그렇지만, 분명 알아야 할 때가 올 것이며, 그 기본을 이해하지 못한다면 개발에 어려움이 있을 수도 있다.
계층적 구조를 갖기 위해서 포인터와 struct의 사용으로 그 구조를 따라가기가 만만치 않은 것이 사실이다. 그러나 핵심은 앞서 말한 것과 같이 디바이스 또는 그와 관련된 정보를 객체로 표현하고 이에 대한 최소 단위가 kobject이고 이를 위해 더 상위 개념들의 객체들이 파생되었다. 또한 kojbect는 refcount라는 참조 카운트를 갖는데, 이는 현재 kobject의 유효성을 알려준다. 예를 들어 refcount가 0이 아닌 값이면 kobject는 유효한 것이며, 그렇지 않고 0이면 더이상 kobject는 유효하지 않음을 의미한다.
덧붙여 hotplug인을 위한 event를 사용자 영역으로 넘겨줌으로써 디바이스의 상태 변화를 알려줘 적절한 조치를 취할 수 있도록 하고 있다. 이는 사용중 착탈식이 가능한 디바이스에 매우 유용한 기능 중 하나이다.
7.1 subsystem, kset, ktype, kobj의 관계 #
앞서 이야기 한 바와 같이 kobject라는 객체가 제안되었고 이에 따라 파생된 객체들이 kset, ktype, subsystem, class 등이다.
앞으로 나올 이야기지만, 간단히 kset, ktype, kobj의 관계에 대해 알아보자.
kset의 자료형은 struct kset;, ktype의 자료형은 struct kobj_type;, kobj의 자료형은 앞에서도 다룬 struct kobject;이다.
이들의 역학 관계를 간단히 살펴보면 다음과 같다:
kset --- kobj
|
+--->ktype+--->release()
| |
| +--->sysfs_ops
+--->kset_hotplug_ops
kset은 비슷한 유형(type)의 kobj를 갖는 상위 객체라고 할 수 있다. 유형이라고 하니 ktype과 혼동이 될 수 있는데, 이는 전혀 다른 의미이다. 그 의미는 ktype이 어떤 것을 정의하고 있는지 확인하면 알 수 있다. 바로 release와 sysfs에 대한 처리 함수들이 정의함을 할 수 있다. 즉 이것은 kobject 그 자체에 대한 유형을 처리하기 위한 정의이다. 너무 자세하게 설명하고 있는데, ktype에서 다시 보자.
kset의 또 하나의 중요한 역할 중 하나가 hotplug 기능에 대한 오퍼레이션을 정의하고 있다는 것이다. 이 역시 이후 자세히 보도록 하자.
여기서 설명하고자 하는 것은 kset, ktype, kobj가 어떤 관계인지를 파악하는데 도움을 주고자 한 것이다. 다소 모호하긴 하지만, 그래도 어느정도 이해가 되었으라 믿는다.
다음은 버스에 대한 계층적 구조를 도식화 한 것이다.
bus_subsys는 decl_subsys()를 통해 선언되어 있다. 이때 bus_subys 객체 내의 필드인 .kset.ktype은 bus_ktype를 포인트 하고 있다. 당연히 bus_subsys의 kobject의 이름은 "bus"로 선언된다.
xxx_bus->subsys내의 .kset.kobj.kset이 bus_subsys 내의 kset을 포인트하도록 한다.
8 kref #
kobject의 중요한 목적 중 하나가 refcount를 갖고 있다는 것이다. 즉, 유효성 여부를 판별하기 위한 것으로 계층적 구조 상에서 빼 놓을 수 없는 것이다.
kobject가 refcount를 직접 관리 하지 않고 kref라는 데이터 형을 정의하여 사용한다.
struct kref {
atomic_t refcount;
};
위 내용에서 atomic_t라는 자료형이 있다. 이는 원자적 처리를 보장해 주는 자료형이다. 원자라 함은 더이상 쪼개질 수 없는 것(양자물리학에서는 원자도 더 쪼개질 수 있다)으로 원자적이라 함은 다른 처리가 중간에 끼어들 수 없을 의미한다.즉 다른 어떤 방해도 없이 한번에 처리됨을 의미한다.
8.1 kref API #
함수 | 설명 |
kref_init | refcount 값을 1로 초기화 한다. |
kref_get | refcount 값을 1 증가 시킨다. |
kref_put | refcount 값을 1 감소 시킨다. 만약, 0이면 release 함수를 실행한다. |
8.1.1 struct kref #
struct kref {
atomic_t refcount;
};
refcont의 값을 1로 초기화 한다.
void kref_init(struct kref *kref);
atomic_inc를 이용하여 refcount 값을 1 증가 시킨다.
void kref_get(struct kref *kref);
atomic_dec_and_test()를 이용하여 refcount의 값을 1 감소 시킨다. 만약, 그 값이 0이면 release()함수를 실행하고 1을 반환한다. 그렇지 않다면 0을 반환한다.
int kref_put(struct kref *kref, void(*release)(struct kref *kref));
9 kobject #
kobject는 struct kobject;를 기본 자료 데이터 구조를 갖는다. 이는 에 정의되어 있다.
kobject가 LDM에서 가장 기본이 되는 자료형이다. 이는 단독으로 사용되지 않는다. 즉, 사용자가 이를 직접 사용해야 할 경우는 없다. 상위 자료형이나 API를 통해 kobject는 추상화된다. LDM이 내부적으로 어떻게 구성되는지를 이해하는데, kobject 자료형을 알면 이해하는데 도움이 된다.
struct kobject {
char *k_name;
char name[KOBJ_NAME_LEN];
struct kref kref;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct dentry *dentry;
};
Linux kernel v2.6.11
필드 | 설명 |
k_name | k_name은 포인터로 name을 가리킨다. |
name | 문자열의 이름 필드로 최대 20자를 가질 수 있다. |
kref | |
entry | |
parent | |
kset | |
ktype | |
dentry | |
k_name은 kobject의 name을 변경하게 될 때 이전 이름을 비교하기 위하여 사용된다. 만약, k_name이 가리키는 이름과 name의 이름이 같지 않다면 k_name이 가리키는 주소를 해제하고 k_name은 name을 지시하도록 한다.
9.1 kobject API #
함수 | 설명 |
kobject_set_name | |
kobject_name | |
kobject_init | |
kobject_cleanup | |
kobject_add | |
kobject_del | |
kobject_register | |
kobject_unregister | |
kobject_get | |
kobject_put | |
kobject_get_path | |
9.1.1 kobject_set_name #
kobj의 이름을 설정한다. 에 선언되어 있다.
int kobject_set_name(struct kobject *kobj, const char *fmt, ...)
fmt 이하의 인자는 printk와 같은 방식의 인자를 갖는다. 하나의 kobject가 만들어질 때 반드시 이름이 지정되어야 하며, 20자를 넘지 않도록 하는 것이 좋다. 만약, 이름이 20자 이상이 되는 경우엔 kobject의 name 필드를 동적으로 커널 메모리를 할당 받아 사용한다. 그러나 가급적이면, 20자를 넘지 않는 것이 좋을 것이다.
예시:
다음은 {drivers/base/bus.c}}}의 bus_register()의 일부 내용이다.
reval = kobject_set_name(&bus->subsys.kset.kobj, "%s", bus->name);
if (retval)
goto out;
9.1.2 kobject_name #
kobj의 k_name를 반환한다.
char * kobject_name(struct kobject *kobj);
9.1.3 kobject_init #
kobject 내의 refcont 값을 1로 설정하고 연결 리스트의 헤드인 entry를 초기화한다. kset_get을 통해 kobject가 속한 kset의 kobj의 refcount 값을 증가 시킨다. 만약, kobject_init에서 넘긴 kobj가 어떤 kset에도 속하지 않다면(kobj->kset이 NULL인 경우) kset에 속한 kobj의 refcount 값을 증가 시키는 것은 무시된다.
void kobject_init(struct kobject *kobj);
9.1.4 kobject_cleanup #
kobject 객체에 대한 자원 할당을 해제한다.
vodi kobject_cleanup(struct kobject *);
9.1.5 kobject_add #
자신이 속한 kset에 kobj을 추가한다. 에 선언되어 있다.
int kobject_add(struct kobject *kobj);
kobj의 parent가 지정되어 있지 않다면 kobj의 kset 내의 kobj를 parent를 지정한다.
kobject_add를 통해 생성된 kobject는 sysfs에서 하나의 디렉토리를 생성하게 된다. 자신의 parent를 기준으로 새로운 디렉토리를 생성한다.
이렇게 하여 계층적 구조를 갖게 된다.
9.1.6 kobject_del #
계층 상에서 객체를 제거한다.
void kobject_del(struct kobject *);
함수 내부적으로는 sysfs_remove_dir를 호출 후 unlink를 호출한다.
9.1.7 kobject_register #
객체를 초기화 하고 추가한다.
int kobject_register(struct kobject *);
kobject_register는 내부적으로 kobject_init과 kobject_add를 호출한다.이후 등록이 성공적이면 kobject_hotplug를 호출하여 새 객체가 ADD 되었음을 알리게 된다.
9.1.8 kobject_unregister #
hotplug를 사용한다면 디바이스가 제거(REMOVE) 됨을 알려준다. 이후 kobject_del를 통해 kobj의 sysfs의 정보를 삭제한다. 이후 kobject_put를 호출하여 kobject의 refcount 값을 하나 감소 시킨다. 이때 refcount가 0이면 ktype의 release를 통해 최종적으로 kobject 객체에 대한 자원을 완전히 해제하게 된다.
void kobject_unregister(struct kobject *);
9.1.9 kobject_get #
객체에 대한 refcount 값을 증가시킨다.
struct kobject *kobject_get(struct kobject *);
객체에 대한 refcount 값을 감소 시킨다.
void kobject_put(struct kobject *);
이 함수는 내부적으로 kref_put를 호출한다.
9.1.11 kobject_get_path #
객체와 관련된 경로명을 반환받는다.
char *kobject_get_path(struct kobject *kobj, int gfp_mask);
kset은 kobject를 포함하고 있는 상위 객체이다. 다시 말하면 kset은 kobjects를 담고 있는 하나의 그룻 역할을 한다. 새로운 kset 객체가 생성되면 그 kset 내의 kobject 객체가 만들어지고 이에 대응되는 디렉토리가 sysfs에 만들어진다. 반면에 새로운 kobject 객체가 생성되었다고 sysfs에 디렉토리나 파일이 만들어지는 것은 아니다. 하나의 kobject는 어느 kset에는 속해야만 한다. kset은 그 상위 객체인 subsys에 속해야만 한다.
kset의 자료형은 struct kset;이다. 이는 에 정의되어 있다.
struct kset {
struct subsystem *subsys;
struct kobj_type *ktype;
struct list_head list;
struct kobject kobj;
struct kset_hotplug_ops *hotplug_ops;
};
필드 | 설명 |
subsys | |
ktype | |
list | |
kobj | |
hotplug_ops | |
함수 | 설명 |
kset_init | |
kset_add | |
kset_register | |
kset_unregister | |
to_kset | |
kset_get | |
kset_put | |
kset의 자료형은 struct kset;이다. 이는 에 정의되어 있다.
struct kset {
struct subsystem *subsys;
struct kobj_type *ktype;
struct list_head list;
struct kobject kobj;
struct kset_hotplug_ops *hotplug_ops;
};
필드 | 설명 |
subsys | |
ktype | |
list | |
kobj | |
hotplug_ops | |
kset 객체를 초기화 한다. 초기화는 kset 내의 kobj 필드를 초기화(kobject_init)하고 연결 리스트의 헤드인 list 필드를 초기화한다.
void kset_init(struct kset *k);
새로운 kset 객체를 추가한다.
int kset_add(struct kset *k);
새로운 k 내의 필드 인 kobj에 대한 parent(kobj.parent가 NULL)와 kset(kobj.kset가 NULL)이 존재하지 않고 k가 어느 subsys(k->subsys가 NULL이 아니다)에 속해 있다면, k->kobj의 parent는 subsys 내의 kset.kobj를 가리킨다.
마지막으로 kobject_add를 통해 k->kobj를 해당하는 kset내에 추가한다.
커널 소스에서 kset_add를 직접 호출하기 보다는 새로운 kset이 추가되는 경우는 kset_register를 통하게 된다. 그 외 경우로 subsystem_register에서 kset_add를 호출하기도 한다.
10.1.4 kset_register #
int kset_register(struct kset *k);
10.1.5 kset_unregister #
void kset_unregister(struct kset *k);
struct kset *to_kset(struct kobject *kobj);
struct kset *kset_get(struct kset *k);
void kset_put(struct kset *k);
11 ktype #
struct kobj_type {
void (*release)(struct kobject *);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
sysfs는 가상 파일 시스템으로 루트 파일 시스템(/)에 sys라는 이름의 디렉토리에 마운트가 된다. 이는 사용자에게 앞서 말한 객체 또는 그에 대한 정보를 사용자가 이용할 수 있도록 구성된 파일 시스템이다.
일반 파일 시스템과 동일한 방식으로 동작한다. 차이점이라면 임의로 디렉토리 및 파일의 생성/제거 등은 할 수 없다. 그러나 파일의 읽기/쓰기/변경 등은 그 권한에 따라 사용자가 이용할 수 있다.
다음은 /sys 아래에 보여주는 디렉토리에 대한 한 예이다.
/sys
|-- block
|-- bus
|-- class
|-- devices
|-- firmware
|-- kernel
|-- module
`-- power
12.2 sysfs API #
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct atrribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
함수 | 설명 |
sysfs_create_dir | 디렉토리를 만든다. 직접 호출하여 사용하지 않는다. |
sysfs_remove_dir | 디렉토리를 삭제한다. 직접 호출하여 사용하지 않는다. |
sysfs_rename_dir | 디렉토리 명을 변경한다. 직접 호출하여 사용하지 않는다. |
sysfs_create_file | |
sysfs_update_file | |
sysfs_remove_file | |
sysfs_create_link | |
sysfs_remove_link | |
sysfs_create_bin_file | |
sysfs_remove_bin_file | |
sysfs_create_group | |
sysfs_remove_group | |
12.2.1 sysfs_create_dir #
객체를 위한 디렉토리를 생성한다. 함수는 에 선언되어 있다.
int sysfs_create_dir(struct kobject *kobj);
사용자가 직접 이를 호출하여 사용되지는 않는다. 이는 /lib/kobject.c 정의된 static int creat_dir() 내에서 호출되는 식으로 불려진다.
12.2.2 sysfs_remove_dir #
객체를 위한 디렉토리를 삭제한다.
void sysfs_remove_dir(struct kobject *kobj);
이 역시 사용자가 직접 호출하여 사용되지는 않는다. kobject_del을 호출하면 sysfs_remove_dir이 호출된다.
12.2.3 sysfs_rename_dir #
객체에 대한 디렉토리 이름을 변경한다.
int sysfs_rename_dir(struct kobject *kobj, const char *new_name);
그러나 이 함수를 직접 호출하여 사용하는 것은 바람직하지 않으며, kobject_rename를 사용토록 한다. kobject_rename 함수가 sysfs_rename을 호출한다.
12.2.4 sysfs_create_file #
sysfs에 파일을 생성한다.
int sysfs_create_file(struct kobject *, const struct attribute *);
예시:
다음은
drivers/base/driver.c의 driver_create_file()상에 사용된 예이다.
int driver_create_file(struct device_driver *drv, struct driver_attribute *attr)
{
int error;
if (get_driver(drv)) {
error = sysfs_create_file(&drv->kobj, &attr->attr);
put_driver(drv);
} else
error = -EINVAL;
return error;
}
12.2.5 sysfs_update_file #
객체의 attribute 파일의 timestamp를 갱신한다.
int sysfs_update_file(struct kobject *kobj, const struct attribute *attr);
파일 내용상의 어떤 변화를 주기 위한 것이 아니다. 사실, 이 함수는 hotplug를 사용하는 디바이스에서 유용할 수 있다. attribute에 어떤 정보를 변경했는데, 이에 해당하는 attribute의 파일 timestamp를 갱신함으로써 파일이 변경되었음을 확인 할 수 있도록 하는 것이다.
예를 들어 pci 디바이스에 어떤 새로운 정보가 갱신되었다면 해당 attribute 파일의 timestamp를 갱신할 수 있다. 이를 통해 사용자는 그 파일의 timestamp를 보고 어떤 변화가 있음을 감지할 수 있을 것이다.
12.2.6 sysfs_remove_file #
sysfs에 생성된 파일을 삭제한다.
void sysfs_remove_file(struct kobject *, const struct attribute *);
예시:
다음은
drivers/base/driver.c의 driver_remove_file()상에서 사용된 예이다.
void driver_remove_file(struct device_driver *drv, struct driver_attribute *attr)
{
if(get_driver(drv)) {
sysfs_remove_file(&drv->kobj, &attr->attr);
put_driver(drv);
}
}
12.2.7 sysfs_create_link #
심볼릭 링크를 생성한다.
int sysfs_create_link(struct kobject *kobj, struct kobject *target,
const char *name);
kobj는 name의 심볼릭 링크가 만들어질 객체이다. target는 심볼릭 링크가 가리킬 객체이다. 심볼릭 링크는 동일한 의미의 객체가 서로 계층 상에 존재할 때 이를 서로 다른 객체로 새로 생성하는 것보다는 심볼릭 링크를 통해 생성하는 것이 더 효율적일 것이다. 이를 위한 함수가 바로 sysfs_create_link이다.
예시:
다음은 리눅스 커널 2.6.11의
drivers/base/bus.c의 bus_add_device() 함수 내용 중 일부 이다.
int bus_add_device(struct device *dev)
{
struct bus_type *bus = get_bus(dev->bus);
int error = 0;
if (bus) {
...
sysfs_create_link(&bus->device.kobj, &dev->kobj, dev->bus_id);
}
return error;
}
위 sysfs_create_link에서 보면 /sys/bus/xxx/devices/ 아래 내용이 /sys/devices/ 아래 내용으로 bus_id라는 이름으로 심볼릭 링크됨을 확인 할 수 있다.
실제 /sys 상에서 나타는 결과를 보면 다음과 같다.
greendrm@devil:/sys/bus/pci/devices$ tree /sys/bus/pci/devices/
/sys/bus/pci/devices/
|-- 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0
|-- 0000:00:01.0 -> ../../../devices/pci0000:00/0000:00:01.0
|-- 0000:01:00.0 -> ../../../devices/pci0000:00/0000:00:01.0/0000:01:00.0
|-- 0000:03:03.0 -> ../../../devices/pci0000:00/0000:00:1e.0/0000:03:03.0
`-- 0000:03:06.0 -> ../../../devices/pci0000:00/0000:00:1e.0/0000:03:06.0
12.2.8 sysfs_remove_link #
객체 디렉토리 안에 있는 심볼릭 링크를 삭제한다.
void sysfs_remove_link(struct kobject *kobj, char *name);
12.2.9 sysfs_create_bin_file #
객체를 위한 바이너리 파일을 만든다.
int sysfs_create_bin_file(struct kobject *kobj, struct bin_attribute *attr);
sysfs_create_file과 비슷하지만, 생성되는 파일이 사람이 읽기 쉬운 text 기반의 파일이 아니라 바이너리 파일을 위한 것만 틀리다.
12.2.10 sysfs_remove_bin_file #
객체를 위한 바이너리 파일을 삭제한다.
int sysfs_remove_bin_file(struct kobject *kobj, struct bin_attribute *attr);
12.2.11 struct attribute_group #
여러개의 attribute 파일을 하나의 그룹으로 묶에 서브 디렉토리를 만들고 그 아래에 일괄적으로 만들 수 있다. 이를 위해 리눅스 커널에서는 attribute_group이라는 자료형을 정의하고 관련 API를 제공한다.
이는 에 정의되어 있다.
struct attribute_group {
const char *name;
struct attribute **attrs;
};
12.2.12 sysfs_create_group #
객체 아래에 서브 디렉토리를 만들고 attribute 파일들을 생성한다.
int sysfs_create_group(struct kobject *, const struct atrribute_group *);
예시:
다음은
drivers/base/power/sysfs.c 소스에 있는 내용 중 일부 이다.
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
...
}
static ssize_t state_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t n)
{
...
}
static DEVICE_ATTR(state, 0644, state_show, state_store);
static struct attribute *power_attrs[] = {
&dev_attr_state.attr,
NULL,
};
static struct attribute_group pm_attr_group = {
.name = "power",
.attrs = power_attrs,
};
int dpm_sysfs_add(struct device *dev)
{
return sysfs_create_group(&dev->kobj, &pm_attr_group);
}
void dpm_sysfs_remove(struct device *dev)
{
sysfs_remove_group(&dev->kboj, &pm_attr_group);
}
12.2.13 sysfs_remove_group #
그룹으로 생성된 attribute 관련 파일 및 디렉토리를 삭제한다.
void sysfs_remove_group(struct kobject *, const struct attribute_group *);
예시:
다음은
drivers/base/power/sysfs.c 소스에 있는 내용 중 일부 이다.
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
...
}
static ssize_t state_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t n)
{
...
}
static DEVICE_ATTR(state, 0644, state_show, state_store);
static struct attribute *power_attrs[] = {
&dev_attr_state.attr,
NULL,
};
static struct attribute_group pm_attr_group = {
.name = "power",
.attrs = power_attrs,
};
int dpm_sysfs_add(struct device *dev)
{
return sysfs_create_group(&dev->kobj, &pm_attr_group);
}
void dpm_sysfs_remove(struct device *dev)
{
sysfs_remove_group(&dev->kboj, &pm_attr_group);
}
위 사이트에서 libsysfs 라는 sysfs에 대한 common API를 제공하는 라이브러리를 구할 수 있다. 이를 통해 복잡한 sysfs에서 정보를 조금은 수월하게 취할 수 있다.
13.1 subsystem 개요 #
디바이스의 계층적 구조에서 가장 상위에 있는 객체라고 할 수 있다. 일반적으로 sysfs에서 가장 상위 디렉토리에 존재하지만, 항상 그런 것은 아니다. 예를 들어, block, bus, class, devices 등은 sysfs의 상위 디렉토리에 존재하지만, acpi와 같은 경우엔 bus/acpi 에 존재한다.
우선, subsystem의 기본 자료형은
struct subsystem;을 보자. 이것은
에 정의되어 있다.
struct subsystem {
struct kset kset;
struct rw_semaphore rwsem;
};
subsystem은 kset보다 더 포괄적인 개념이다. subsystem이 새롭게 어떤 기능을 제공하지는 않다. 단지, 내부적으로 kset 연결 리스트 탐색을 직렬화 serialization 한다. 이를 위해 rw_semaphore를 제공한다. 특별한 기능을 제공하진 않지만, kset은 반드시 하나의 subsystem에 속해야만 한다. 따라서 kset 객체 내의 subsys를 통해 같은 subsystem를 찾을 수 있다. 하지만 반대로 subsystem에서 자신에 속한 kset들을 직접 탐색할 수는 없다.
13.2 subsystem과 kset과의 관계 #
앞서 설명한 내용을 그림을 보면 다음과 같다.
subsystem
/ | \
/ | \
kset kset kset
kset들은 자신이 갖고 있는 subsys 필드를 통해 subsystem을 찾을 수 있지만, subsystem은 자신에 속한 kset을 직접 탐색할 수는 없다.
sturct subsystem {
struct kset kset;
...
};
struct kset {
struct subsystem *subsys;
...
struct list_head list;
...
};
13.3 subsystem on sysfs #
앞서 말했듯이 subsystem은 일반적으로 sysfs의 상위 디렉토리인 /sys/ 아래에 존재하게 된다. 그러나 일부는 sysfs의 상위 디렉토리에 존재하지 않는 경우도 있다.
/sys
|-- block
|-- bus
|-- class
|-- devices
|-- firmware
|-- kernel
|-- module
`-- power
이외에도 소스 상에서 찾을 수 있는 subsystem으로는 system_subsys, block_subsys, bus_subsys, class_subsys, devices_subsys, firmware_subsys, class_obj_subsys, acpi_subsys, edd_subsys, vars_subsys, efi_subsys, cdev_subsys, module_subsys, power_subsys, pci_hotplgu_slocks_subsys 등이 있다.
13.4 subsystem API #
함수 | 설명 |
decl_subsys | |
subsystem_init | |
subsystem_register | |
subsystem_unregister | |
subsystem_get | |
subsystem_put | |
새로운 subsystem을 정의한다. 에 선언되어 있다.
delc_subsys(name, type, hotplug_ops);
인자 | 설명 |
name | subsystem의 이름으로 name_subsys라는 subsystem 객체가 만들어진다. |
type | 필드내의 ktype을 설정한다. NULL로 생략가능하다. |
hotplug_ops | hotplug event 처리를 하고자 할 때 hotplus_ops를 등록한다. NULL로 생략가능하다 |
name 인자는 kobject의 name으로도 사용되므로 kobject 최대 이름 길이를 넘지 않도록 한다.
예시:
drivers/block/genhd.c에서 block subsystem을 선언하는 것을 간략히 살펴 보자.
...
1: static struct kobj_type ktype_block = {
2: .release = disk_release,
3: .sysfs_ops = &disk_sysfs_ops,
4: .default_attrs = default_attrs,
5: };
...
6: static struct kset_hotplug_ops block_hotplug_ops = {
7: .filter = block_hotplug_filter,
8: .hotplug = block_hotplug,
9: };
/* declare block subsys */
10: static decl_subsys(block, &ktype_block, &block_hotplug_ops);
10번째 줄에서 block_sussys라는 block subsystem 객체를 생성하였다. block의 kset에 대한 기본 ktype을 ktype_block으로 지정하고 hotplug event를 처리하기 위하여 block_hotplug_ops를 등록했다. 만약, ktype이나 hotplug 기능을 block이라는 전제 시스템에 기본적으로 제공하지 않고 특정 kset마다 별도로 설정하고자 한다면 이 두 값을 NULL로 선언하여 생략해도 무방하다.
13.4.2 subsystem_init #
subsys의 세마포어 rwsem과 kset을 초기화(kset_init()을 호출) 한다.
에 선언되어 있다.
void subsystem_init(struct subsystem *subsys);
직접 호출하여 사용되는 예는 없다. 일반적으로 susbsystem_register() 내에서 이를 호출하여 처리하기 때문이다. 그냥 이런 것이 있다는 정도만 알아두자.
13.4.3 subsystem_register #
새로운 subsystem을 등록한다. 에 선언되어 있다.
int subsystem_register(struct subsystem *s);
새로운 subsystem을 등록하면, kset_add를 호출하여 s 내의 kset을 추가한다. 정상적으로 rwsem을 사용하기 위하여 필드 내의 kset의 subsys가 자기 자신을 가리키도록 해야 한다.
13.4.4 subsystem_unregister #
subsystem 객체에 대한 자원 할당을 해제한다.
void subsystem_unregister(struct subsystem *subsys);
실질적으로 subsystem_unregister는 kset_unregister를 호출하고 kset_unregister는 kobject_register를 호출하게 된다. 실질적인 처리는 kobject_register에서 하게 되는 셈이다.
13.4.5 subsystem_get #
struct subsystem *subsys_get(struct subsystem *subsys);
13.4.6 subsystem_put #
void subsys_put(struct subsystem *subsys);
14 kobj_map #
디바이스 번호를 관리하기 위한 것이다. 이와 관련된 API는 헤더 파일에 정의되어 있다.
14.1 ?kobj_map API #
kobj_map API 에 대한 내용을 보고자 한다면
?kobj_map API 보기를 클릭하라.
15.1 bus on sysfs #
/sys/bus
|-- i2c
|-- ide
|-- ieee1394
|-- pci
|-- platform
|-- pnp
|-- scsi
|-- serio
`-- usb
bus API에 대한 내용을 보고자 한다면
?bus API 보기를 클릭하라.
16.1 device on sysfs #
/sys/devices
|-- pci0000:00
|-- platform
|-- pnp0
`-- system
device API에 대한 내용을 보고자 한다면
?device API 보기를 클릭하라.
driver API에 대한 내용을 보고자 한다면
?driver API 보기를 클릭하라.
18 class #
앞서 말한 kobject의 최상위 객체에 해당하는 것이 subsystem이라고 했다. 새로운 타입의 디바이스가 추가되어야 하는데, 새로운 subsystem을 생성하는 것은 권장하지 않는다. 대신에 class라는 객체를 정의해서 새로운 디바이스 타입에 대한 속성을 정의하도록 하고 있다.
기존의 subsystem, kset 등은 디바이스에 종속적으로 결정되는 경향이 있다. 같은 디바이스에서 파생된 속성이라면 그 최상위 객체는 동일할 수 있다. 그러나 class라는 개념은 디바이스와는 상관없이 그 속성에 따라 결정이 된다.
예를 들어 input이라는 입력 시스템에 대해서는 어느 디바이스에 속하든, 같은 class를 갖을 수 있다는 의미이다.
18.1 class on sysfs #
/sys/class
|-- graphics
|-- i2c-adapter
|-- ieee1394
|-- ieee1394_host
|-- ieee1394_node
|-- ieee1394_protocol
|-- input
|-- mem
|-- misc
|-- net
|-- pci_bus
|-- printer
|-- scsi_device
|-- scsi_generic
|-- scsi_host
|-- sound
|-- tty
|-- usb
|-- usb_host
`-- vc
class API 에 대한 내용을 보고자 한다면
?class API 보기를 클릭하라.
18.3 class의 사용 예 #
다음은 커널 버전 2.6.13의 drivers/mtd/mtdchar.c에 포함된 내용 중 class와 관련된 부분을 중심으로 살펴본다.
static int __init init_mtdchar(void)
{
if (register_chrdev(...)) {
...
}
mtd_class = class_create(THIS_MODULE, "mtd");
if (IS_ERR(mtd_class)) {
...
}
register_mtd_user(¬ifier);
return 0;
}
class_create 함수에 의하여 sysfs 상에 /sys/class/mtd 가 만들어진다.
18.3.2 클래스 디바이스 생성/제거 #
클래스 디바이스를 생성하기 위하여 mtd에서는 mtd_notifier 구조체를 정의하고 그 안에 add와 remove라는 함수 포인터를 정의하였다. 이 함수 안에서 class_device_create/remove를 호출하도록 하고 있다.
static void mtd_notify_add(struct mtd_info *mtd)
{
if (!mtd)
return;
class_device_create(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2),
NULL, "mtd%d", mtd->index);
class_device_create(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),
NULL, "mtd%dro", mtd->index);
}
static void mtd_notify_remove(struct mtd_info *mtd)
{
if (!mtd)
return;
class_device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2));
class_device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1));
}
static struct mtd_notifier notifier = {
.add = mtd_notify_add,
.remove = mtd_notify_remove,
};
이 두 add와 remove 함수는 register_mtd_user와 unregister_mtd_user 함수를 통해 호출이 된다.
static void __exit cleanup_mtdchar(void)
{
unregister_mtd_user(¬ifier);
class_destroy(mtd_class);
unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
}
unregister_mtd_user를 통해 notifier의 remove 함수를 호출하여 /sys/class/mtd/ 아래에 만들어진 mtd관련 디바이스 디렉토리를 삭제하게 된다.
이후 class_destroy를 통해 /sys/class 아래의 mtd 디렉토리까지 삭제하게 된다.
리눅스의 디바이스를 접근하기 위하여 파일로써 특별한 노드를 생성하였다. 아직까지 이 방법을 사용하고 있으며, 커널 2.3 이후 버전에서는 devfs라는 커널 레벨에서 노드를 생성 시켜주는 디바이스 파일 시스템이라는 번잡한 방법을 제공하기 시작했다.
그러나 이 방법은 기존의 문제를 그대로 안고 있으며, 커널과 사용자 간의 디바이스 관리를 혼란스럽게 만들고 있다. 이런 이유로 이 devfs라는 더 이상 사용하지 않을 것으로 보이고 있다.
이후 대안으로 제시된 것이 커널 2.5 버전 이후에 적용되고 있는 udev 라는 디바이스 시스템이다. 이는 장기적인 관점에서 접근하고 있으며, 아직까지는 초보적인 수준의 기능을 제공하고 있다.
그러나 궁극적으로는 udev를 추구하고 있다.
udev의 궁극적인 목표는 다음과 같다:
- /dev를 대체할 동적 방법론
- 디바이스 이름 생성 방법론
- 시스템 디바이스(sysfs)에서 정보를 취합할 API를 제공이다.
현재 이러한 목표를 이루기 위하여 크게 세 부분으로 나눠 작업이 이뤄지고 있다.
- udev - /dev를 대체할 동적 방법론
- namedev - 디바이스 이름 생성 방법론
- libsysfs - sysfs에 대한 common API
udev는 앞서 설명한 것처럼 sysfs와 밀접한 관계가 있으며, 각 디바이스에 탐지/인식/제거/변경 등의 event를 커널 레벨에서 받아 처리하기 위하여 hotplug 기능이 필수적이다.
이러한 모든 기초가 되는 것이 현재 2.6에서 차근 차근 준비가 되어 가고 있으며, 일부 기능은 현재도 사용 가능하다.
21.1 firmware API #
함수 | 설명 |
request_firmware | |
request_firmware_nowait | |
release_firmware | |
21.1.1 request_firmware #
int request_firmware(const struct firmware **fw, char *name, struct device *device);
21.1.2 request_firmware_nowait #
int request_firmware_nowait(struct module *module, char *name, struct device *device,
void *context, void (*cont)(const struct firmware *fw,
void *context));
21.1.3 release_firmware #
void release_firmware(struct firmware *fw);