Linux内核(linux-5.2.9)--内核对象(类型无关的双循环链表)
Posted 尚书左仆射
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核(linux-5.2.9)--内核对象(类型无关的双循环链表)相关的知识,希望对你有一定的参考价值。
前一段时间学习了Linux源码,过程比较艰辛。虽说工作中也不会去开发操作系统,但“他山之石可以攻玉”,其中的设计思想和解决方案还是可以参考的嘛。抽空陆续把所学的模块整理一下,方便后面及时参阅,温故知新。
内核对象是一种统称,驱动、设备、总线类型等都可以看作是内核对象。表示内核对象的结构是kobject,相当于Linux驱动模型的“基类”。kobject的定义如下(include/linux/kobject.h):
struct kobject
const char *name; //kobject的名字
struct list_head entry; //将kobject连接到kset的连接件
struct kobject *parent; //指向kobject的父对象的指针
struct kset *kset; //如果kobject已经链接到kset,则指向该kset
struct kobj_type *ktype; //kobject的类型
struct kernfs_node *sd; /* sysfs directory entry 指向kobject在sysfs内部树中的节点*/
struct kref kref; //kobject的引用计数
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release; //任务和cpu描述信息
#endif
unsigned int state_initialized:1; //如果为1,表示kobject已经被初始化
unsigned int state_in_sysfs:1; //如果为1,表示kobject已经被添加到内核关系树中
unsigned int state_add_uevent_sent:1; //如果为1,表示kobject已经发送过添加事件到用户空间
unsigned int state_remove_uevent_sent:1; //如果为1,表示kobject已经发送过删除事件到用户空间
unsigned int uevent_suppress:1; //如果为1,表示“抑制”发送事件到用户空间
;
类似这种 [ unsigned int state_initialized:1; ] 的位段操作,具体使用方式可以参考以下链接 http://c.biancheng.net/view/2037.html
在Linux内核中,有大量的数据结构需要用到双循环列表,例如进程、文件、模块、页面等。若采用双循环列表的传统实现方式,需要为这些数据结构维护各自的链表,并且为每个链表都要设计插入、删除等操作函数。
在传统的双循环列表实现中,如果创建某种数据结构的双循环链表,通常采用的方法是在这个数据结构的类型定义中加入两个(指向该数据结构的对象的)指针next和prev。例如:
typedef struct foo
...
struct foo *prev;
struct foo *next;
...
foo_t;
使用这种方式构建循环列表,一种数据结构的链表操作函数不能用于操作其他数据结构的链表。示意图如下:
而Linux内核采用了一种类型无关的双循环链表实现方式。其思想是将指针prev和next从具体的数据结构中提取出来构成一种通用的“双链表”数据结构list_head。list_head被作为一个成员嵌入到要拉链的数据结构(宿主数据结构)中。这样,只需要一套通用的链表操作函数就可以将list_head成员作为“连接件”,把宿主数据结构链接起来。将连接件转换为宿主结构,使用的是contain_of宏。示意图如下:
其中contain_of宏定义如下(include/linux/kernel.h):
#define container_of(ptr, type, member) ( \\
void *__mptr = (void *)(ptr); \\
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \\
!__same_type(*(ptr), void), \\
"pointer type mismatch in container_of()"); \\
((type *)(__mptr - offsetof(type, member))); )
contain_of 宏的实现原理:
考虑成员member相对于类型为type的宿主结构的起始地址的偏移量。对于所有该类型的宿主对象,这个偏移量是固定的。并且可以在假设宿主对象地址为0,通过返回member域的地址获得,即等于 (unsigned long)(&((type *)0)->member) 。这样,将宿主对象的member域的地址 (pointer)减去这个偏移量,就可以得到宿主对象的地址,再将它转换为type类型的指针。
list_head结构定义(include/linux/types.h):
struct list_head
struct list_head *next, *prev;
;
对应的list列表操作函数定义于 include/linux/list.h 文件中。
在Linux内核中的双循环链表实现方式:
- 链表结构作为一个成员嵌入到宿主数据结构内;
- 可以将链表结构放在宿主结构内的任何地方;
- 可以为链表结构取任何名字;
- 宿主结构可以有多个链表结构。
再回过头来看kobject的 struct list_head entry 成员就知道是怎么回事了。
其中还有一个与组织链表相关的就是 kset 成了,其定义如下(include/linux/kobject.h):
/**
* struct kset - a set of kobjects of a specific type, belonging to a specific subsystem.
*
* A kset defines a group of kobjects. They can be individually
* different "types" but overall these kobjects all want to be grouped
* together and operated on in the same manner. ksets are used to
* define the attribute callbacks and other common events that happen to
* a kobject.
*
* @list: the list of all kobjects for this kset
* @list_lock: a lock for iterating over the kobjects
* @kobj: the embedded kobject for this kset (recursion, isn't it fun...)
* @uevent_ops: the set of uevent operations for this kset. These are
* called whenever a kobject has something happen to it so that the kset
* can add new environment variables, or filter out the uevents if so
* desired.
*/
struct kset
struct list_head list; //这个kset的所有kobject的链表
spinlock_t list_lock; //用于遍历这个kset的所有kobject的自旋锁
struct kobject kobj; //这个kset的内嵌kobject(递归)
const struct kset_uevent_ops *uevent_ops; //这个kset的uevent操作集。相应函数在kobject发生了某种事件时被调用,kset可以添加新的环境变量,或者过滤掉一些uevent。
__randomize_layout;
从面向对象的角度来看,kset就是一个顶层包含类;kset集成了它自己的kobject,本身就可以作为kobject对待。kset包含一系列的kobject,将它们组织成一个链表,kset的list域为表头,被包含的kobject通过entry域链入此链表,kobject还通过kset域指回到包含它的kset。
每个kobject都有一个parent域。大多数情况下,被包含的kobject通过它指向包含它的kset,更精确地说,是kset内嵌的kobject。实际上,被包含的kobject也有可能将parent指向另外的kobject,或者设置为NULL。Kset和kobject的关系图如下:
需要注意的:
- 图中所有被包含的kobject实际上是嵌入在某个其他类型之内,甚至可能是其他kset之内的;
- 也存在不包含于任何kset的kobject,即,它们的kset域为NULL。
通过上面的方式,kobject被组织成层次结构。而kset的存在是为了对层次在它之下的kobject施行相同模式的操作。kset定义了一个uevent操作表,对于一个层次在它之下的kobject,并且层次路径上没有其他的kset,如果这个kobject上发生了某种事件,就会调用操作表中的相应函数,以便通知用户空间。
kset功能总结:
þ 作为包含一组对象的容器,kset可以被内核用来跟踪“所有块设置”或者“所有PCI设备驱动”;
þ 作为一个目录级的“粘合剂”,将设备模型中的内核对象粘在一起。每个kset都内嵌一个kobject,可以作为其他kobject的父对象,通过这种方式构造设备模型层次。
þ kset可以支持kobject的“热插拔”,影响热插拔事件被报告给用户空间的方式。
以上是关于Linux内核(linux-5.2.9)--内核对象(类型无关的双循环链表)的主要内容,如果未能解决你的问题,请参考以下文章