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内核中的双循环链表实现方式:

  1. 链表结构作为一个成员嵌入到宿主数据结构内;
  2. 可以将链表结构放在宿主结构内的任何地方;
  3. 可以为链表结构取任何名字;
  4. 宿主结构可以有多个链表结构。

再回过头来看kobjectstruct 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的关系图如下:

需要注意的:

  1. 图中所有被包含的kobject实际上是嵌入在某个其他类型之内,甚至可能是其他kset之内的;
  2. 也存在不包含于任何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)--内核对象(类型无关的双循环链表)的主要内容,如果未能解决你的问题,请参考以下文章

内核在Linux内核中添加对jffs2的支持

Linux内核简介

Linux内核安全模块学习-内核密钥管理子系统

linux进程为啥有用户栈和内核栈,

RK3399平台开发系列讲解(内核设备树篇)3.5Linux内核对DTB文件的解析

Linux内核分析——第一章 Linux内核简介