Linux设备模型 (2)

简介:

上一篇文章《Linux设备模型 (1)》主要介绍了Linux设备模型在用户空间的接口sysfs,用户通过这个接口可以一览内核设备的全貌。本文将从Linux内核的角度来看一看这个设备模型是如何构建的。

在Linux内核里,kobject是组成Linux设备模型的基础,一个kobject对应sysfs里的一个目录。从面向对象的角度来说,kobject可以看作是所有设备对象的基类,因为C语言并没有面向对象的语法,所以一般是把kobject内嵌到其他结构体里来实现类似的作用,这里的其他结构体可以看作是kobject的派生类。Kobject为Linux设备模型提供了很多有用的功能,比如引用计数,接口抽象,父子关系等等。引用计数本质上就是利用kref实现的,至于kref的细节可以参考我之前的文章《Linux内核里的“智能指针”》。

另外,Linux设备模型还有一个重要的数据结构kset。Kset本身也是一个kobject,所以它在sysfs里同样表现为一个目录,但它和kobject的不同之处在于kset可以看作是一个容器,如果你把它类比为C++里的容器类如list也无不可。Kset之所以能作为容器来使用,其内部正是内嵌了一个双向链表结构struct list_head。对于list_head的细节可以参考《玩转C链表》一文。

下面这幅图可以用来表示kobject和kset在内核里关系。

在接下来的篇幅里我们会逐步看到这个关系图在内核里是如何建立的。本文的示例代码可以从这里下载,下文中的两个实作都在这个示例代码里。

 

Kobject

Kobject在Linux内核里的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct  kobject {
     const  char       *name;
     struct  list_head    entry;
     struct  kobject      *parent;
     struct  kset     *kset;
     struct  kobj_type    *ktype;
     struct  sysfs_dirent *sd;
     struct  kref     kref;
     unsigned int  state_initialized:1;
     unsigned int  state_in_sysfs:1;
     unsigned int  state_add_uevent_sent:1;
     unsigned int  state_remove_uevent_sent:1;
     unsigned int  uevent_suppress:1;
};

在《Linux设备模型 (1)》里面我们介绍到内核里的设备之间是以树状形式组织的,在这种组织架构里比较靠上层的节点可以看作是下层节点的父节点,反映到sysfs里就是上级目录和下级目录之间的关系,在内核里,正是kobject帮助我们实现这种父子关系。在kobject的定义里,name表示的是kobject在sysfs中的名字;指针parent用来指向kobject的父对象;Kref大家应该比较熟悉了,kobject通过它来实现引用计数;Kset指针用来指向这个kobject所属的kset,下文会再详细描述kset的用法;对于ktype,如果只是望文生义的话,应该是用来描述kobject的类型信息。Ktype的定义如下:

1
2
3
4
5
struct  kobj_type {
     void  (*release)( struct  kobject *kobj);
     const  struct  sysfs_ops *sysfs_ops;
     struct  attribute **default_attrs;
};

函数指针release是给kref使用的,当引用计数为0这个指针指向的函数会被调用来释放内存。sysfs_ops和attribute是做什么用的呢?前文里提到,一个kobject对应sysfs里的一个目录,而目录下的文件就是由sysfs_ops和attribute来实现的,其中,attribute定义了kobject的属性,在sysfs里对应一个文件,sysfs_ops用来定义读写这个文件的方法。Ktype里的attribute是默认的属性,另外也可以使用更加灵活的手段,本文的重点还是放在default attribute。

 

下面看一个实作。在这个实作里,我们定义一个内嵌kobject的结构。

1
2
3
4
struct  my_kobj {
     int  val;
     struct  kobject kobj;
};

最终我们的目的是在内核里构建这样的架构。

对应sysfs里的目录关系是:

mykobj1/
|-- mykobj2
| |-- name
| `-- val
|-- name
`-- val

这是module_init代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static  int  __init mykobj_init( void )
{
     printk(KERN_INFO "mykobj_init\n" );
 
     obj1 = kzalloc( sizeof ( struct  my_kobj), GFP_KERNEL);
     if  (!obj1) {
         return  -ENOMEM;
     }
     obj1->val = 1;
 
     obj2 = kzalloc( sizeof ( struct  my_kobj), GFP_KERNEL);
     if  (!obj2) {
         kfree(obj1);
         return  -ENOMEM;
     }
     obj2->val = 2;
 
     my_type.release = obj_release;
     my_type.default_attrs = my_attrs;
     my_type.sysfs_ops = &my_sysfsops;
 
     kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1" );
     kobject_init_and_add(&obj2->kobj, &my_type, &obj1->kobj, "mykobj2" );
 
     return  0;
}

这段代码可以分作三个部分。第一部分是分配obj1和obj2并赋值;第二部分是初始化kobj_type变量my_type;第三部分是调用kobject_init_and_add函数来初始化kobject并把它加入到设备模型的体系架构(也就是上文中提到的内核中的那棵树)中。kobject_init_and_add是简化的写法,这个函数也可以分两步完成:kobject_init和kobject_add。

int  kobject_init_and_add( struct  kobject *kobj, struct  kobj_type *ktype,
              struct  kobject *parent, const  char  *fmt, ...);
 
void  kobject_init( struct  kobject *kobj, struct  kobj_type *ktype);
int  kobject_add( struct  kobject *kobj, struct  kobject *parent,
         const  char  *fmt, ...);

kobject_init用来初始化kobject结构,kobject_add用来把kobj加入到设备模型之中。在实作中,我们先对obj1进行初始化和添加的动作,调用参数里,parent被赋为NULL,表示obj1没有父对象,反映到sysfs里,my_kobj1的目录会出现在/sys下,obj2的父对象设定为obj1,那么my_kobj2的目录会出现在/sys/my_kobj1下面。

前面提到,kobject也提供了引用计数的功能,虽然本质上是利用kref,但也提供了另外的接口供用户使用。

1
2
struct  kobject *kobject_get( struct  kobject *kobj);
void  kobject_put( struct  kobject *kobj);

kobject_init_and_add和kobject_init这两个函数被调用后,kobj的引用计数会初始化为1,所以在module_exit时要记得用kobject_put来释放引用计数。

我们再回到实作中,看看如何使用ktype。代码里,my_attrs是这样定义的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct  attribute name_attr = {
     .name = "name" ,
     .mode = 0444,
};
 
struct  attribute val_attr = {
     .name = "val" ,
     .mode = 0666,
};
 
struct  attribute *my_attrs[] = {
     &name_attr,
     &val_attr,
     NULL,
};

结构体struct attribute里的name变量用来指定文件名,mode变量用来指定文件的访问权限。这里需要着重指出的是,数组my_attrs的最后一项一定要赋为NULL,否则会造成内核oops。

sysfs_ops的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ssize_t my_show( struct  kobject *kobj, struct  attribute *attr, char  *buffer)
{
     struct  my_kobj *obj = container_of(kobj, struct  my_kobj, kobj);
     ssize_t count = 0;
 
     if  ( strcmp (attr->name, "name" ) == 0) {
         count = sprintf (buffer, "%s\n" , kobject_name(kobj));
     } else  if  ( strcmp (attr->name, "val" ) == 0) {
         count = sprintf (buffer, "%d\n" , obj->val);
     }
 
     return  count;
}
 
ssize_t my_store( struct  kobject *kobj, struct  attribute *attr, const  char  *buffer, size_t  size)
{
     struct  my_kobj *obj = container_of(kobj, struct  my_kobj, kobj);
 
     if  ( strcmp (attr->name, "val" ) == 0) {
         sscanf (buffer, "%d" , &obj->val);
     }
 
     return  size;
}
 
struct  sysfs_ops my_sysfsops = {
     .show = my_show,
     .store = my_store,
};

读文件会调用my_show,写文件会调用my_store。

最后是module_exit:

1
2
3
4
5
6
7
8
9
10
11
12
static  void  __exit mykobj_exit( void )
{
     printk(KERN_INFO "mykobj_exit\n" );
 
     kobject_del(&obj2->kobj);
     kobject_put(&obj2->kobj);
     
     kobject_del(&obj1->kobj);
     kobject_put(&obj1->kobj);
 
     return ;
}

kobject_del的作用是把kobject从设备模型的那棵树里摘掉,同时sysfs里相应的目录也会删除。这里需要指出的是,释放的顺序应该是先子对象,后父对象。因为kobject_init_and_add和kobject_add这两个函数会调用kobject_get来增加父对象的引用计数,所以kobject_del需要调用kobject_put来减少父对象的引用计数。在本例中,如果先通过kobject_put来释放obj1,那kobject_del(&obj2->kobj)就会出现内存错误。

在这个实作中,我们建立了两个对象obj1和obj2,obj1是obj2的父对象,如果推广开来,obj1可以有更多的子对象。在Linux内核中,这种架构方式其实并无太大的实际价值,有限的用处之一是在sysfs里创建子目录(Linux内核里有这种用法,这种情况下,直接调用内核提供的kobject_create来实现,不需要自定义数据结构并内嵌kobject),而且,创建子目录也是有其他的办法的。我们知道,Linux设备模型最初的目的是为了方便电源管理,这就需要从上到下的遍历,在这种架构里,通过obj1并无法访问其所有的子对象。这个实作最大的意义在于可以让我们比较清晰的理解kobject如何使用。通常情况下,kobject只需要在叶节点里使用,上层的节点要使用kset。

 

Kset

Kset的定义如下:

1
2
3
4
5
6
struct  kset {
     struct  list_head list;
     spinlock_t list_lock;
     struct  kobject kobj;
     const  struct  kset_uevent_ops *uevent_ops;
};

Kset结构里的kobj表明它也是一个kobject,list变量用来组织它所有的子对象。

 

我们直接看一个实作。在这个实作里,我们将构建如下的架构。

对应sysfs里的目录关系是:

my_kset/
|-- mykobj1
|   |-- name
|   `-- val
`-- mykobj2
    |-- name
    `-- val

这个实作和前一个差别很小,下面只简略的引用一些代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static  int  __init mykset_init( void )
{
     printk(KERN_INFO "mykset_init\n" );
 
     my_kset = kset_create_and_add( "my_kset" , NULL, NULL);
     if  (!my_kset) {
         return  -ENOMEM;
     }
 
     // Allocate obj1 and obj2
     // ...
 
     obj1->kobj.kset = my_kset;
     obj2->kobj.kset = my_kset;
 
     // Init my_type
     // ...
 
     kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1" );
     kobject_init_and_add(&obj2->kobj, &my_type, NULL, "mykobj2" );
 
     return  0;
}
 
static  void  __exit mykset_exit( void )
{
     printk(KERN_INFO "mykset_exit\n" );
 
     // Release obj1 and obj2
     // ...
 
     kset_unregister(my_kset);
 
     return ;
}

在module_init里,我们首先调用kset_create_and_add创建my_kset,接下来把my_kset赋给obj1和obj2,最后调用kobject_init_and_add来添加obj1和obj2。这里需要注意的是,kobject_init_and_add参数里的parent都是NULL,在这种情况下,obj1和obj2的父对象由kobject结构里的kset指针决定,在这个实作里就是my_kset。在module_exit里,我们还需要额外调用kset_unregister来释放之前创建的my_kset。

 

注:看过LDD3的读者应该对Linux Device Model一章中的subsystem还有印象,我在这里注明一下,从2.6.23开始Linux内核就抛弃了subsystem,subsystem其实只是kset的一个马甲,所以抛弃它对Linux设备模型并没什么影响。

目录
相关文章
|
1月前
|
安全 Linux 网络虚拟化
Linux网络名称空间和Veth虚拟设备的关系
在讨论Linux网络名称空间和veth(虚拟以太网对)之间的关系时,我们必须从Linux网络虚拟化的核心概念开始。Linux网络名称空间和veth是Linux网络虚拟化和容器化技术的重要组成部分,它们之间的关系密不可分,对于构建隔离、高效的网络环境至关重要。😊
|
1月前
|
Cloud Native Linux 网络虚拟化
深入理解Linux veth虚拟网络设备:原理、应用与在容器化架构中的重要性
在Linux网络虚拟化领域,虚拟以太网设备(veth)扮演着至关重要的角色🌐。veth是一种特殊类型的网络设备,它在Linux内核中以成对的形式存在,允许两个网络命名空间之间的通信🔗。这篇文章将从多个维度深入分析veth的概念、作用、重要性,以及在容器和云原生环境中的应用📚。
深入理解Linux veth虚拟网络设备:原理、应用与在容器化架构中的重要性
|
11天前
|
传感器 物联网 Linux
物联网设备的操作系统之争:Linux vs RTOS
【6月更文挑战第4天】在遥远的数码星球,物联网城中的Linux先生与RTOS小姐展开激烈角逐,分别在操作系统领域各显神通。Linux先生以其开源、兼容性强、功能丰富占据服务器、桌面及嵌入式设备市场,适合处理复杂任务和需要强大计算能力的设备。而RTOS小姐以实时性、高效响应和低资源占用见长,适用于资源有限、强调实时性的物联网设备。设备制造商在两者间抉择,引发物联网设备操作系统的选择大战。通过Python与FreeRTOS示例,展现了两者在智能家居和生产线控制等场景的应用。在物联网世界,Linux与RTOS共同推动设备智能化,为生活带来更多便捷。
62 3
|
23天前
|
设计模式 安全 Java
【Linux 系统】多线程(生产者消费者模型、线程池、STL+智能指针与线程安全、读者写者问题)-- 详解
【Linux 系统】多线程(生产者消费者模型、线程池、STL+智能指针与线程安全、读者写者问题)-- 详解
|
23天前
|
存储 网络协议 Linux
【Linux 网络】网络基础(一)(局域网、广域网、网络协议、TCP/IP结构模型、网络传输、封装和分用)-- 详解(下)
【Linux 网络】网络基础(一)(局域网、广域网、网络协议、TCP/IP结构模型、网络传输、封装和分用)-- 详解(下)
|
23天前
|
存储 网络协议 安全
【Linux 网络】网络基础(一)(局域网、广域网、网络协议、TCP/IP结构模型、网络传输、封装和分用)-- 详解(上)
【Linux 网络】网络基础(一)(局域网、广域网、网络协议、TCP/IP结构模型、网络传输、封装和分用)-- 详解(上)
|
1月前
|
网络协议 Shell Linux
LabVIEW 在NI Linux实时设备上访问Shell
LabVIEW 在NI Linux实时设备上访问Shell
19 0
|
1月前
|
存储 安全 算法
【Linux | C++ 】基于环形队列的多生产者多消费者模型(Linux系统下C++ 代码模拟实现)
【Linux | C++ 】基于环形队列的多生产者多消费者模型(Linux系统下C++ 代码模拟实现)
37 0
|
1月前
|
算法 Linux 数据安全/隐私保护
【Linux | C++ 】生产者消费者模型(Linux系统下C++ 代码模拟实现)
【Linux | C++ 】生产者消费者模型(Linux系统下C++ 代码模拟实现)
17 0
|
1月前
|
网络协议 Linux Shell
【linux网络(一)】初识网络, 理解四层网络模型
【linux网络(一)】初识网络, 理解四层网络模型