Linux Kernel List

1 链表的数据结构

链表是通过指针将一系列数据节点连接成一条数据链的数据结构。相对于数组,链表具有更好的动态性。且对增加删除操作效率高于数组。

1.1 单链表

单链表是最简单的一类链表,特点是仅有一个指针域指向后续节点。数据结构如图所示:

这里写图片描述

1.2 双链表

双链表设计了两个指针域,前驱和后继分别指向上一个节点和下一个节点。双链表可以从两个方向遍历,其数据结构图如下:
这里写图片描述

1.3 循环链表

循环链表的特点是尾节点的后继指向首节点,

2 Linux的内核链表实现(3.4)

Linux kernel中的内核链表实现位于include/linux/list.h中,其实现为循环双向链表,且带有表头指针

2.1数据结构定义

头结点的定义位于include/linux/type.h

struct list_head {
    struct list_head *next, *prev;
};

这里定义的链表节点没有数据域:由于Linux内核中,不是在链表节点包含数据域,而是在数据域中包含链表节点。即链表节点包含一个struct list_head的节点。

2.2链表的申明和初始化

Linux只是定义了链表节点,而未定义专门的链表头。链表头式通过如下宏定义初始化的。

#define LIST_HEAD_INIT(name) { &(name), &(name) } //头节点和尾节点都指向自己。

#define LIST_HEAD(name) \   
    struct list_head name = LIST_HEAD_INIT(name)

内核中还提供了一个方法来初始化链表

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

2.3 链表判空

内核中提供了两个方法判空的,list_empty简单的判断当前节点的next节点是不是指向自己本身;list_empty_careful在上面的基础上,还判断了当前节点的prev和当前节点的next指向同一个节点。

static inline int list_empty(const struct list_head *head)
{
    return head->next == head;
}

static inline int list_empty_careful(const struct list_head *head)
{
    struct list_head *next = head->next;
    return (next == head) && (next == head->prev);
}

2.4 链表插入

插入操作有两种:在表头插入和在表尾插入。

static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}

static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}

static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    next->prev = new;
    new->next = next;
    new->prev = prev;
    prev->next = new;
}

2.5 删除

list删除节点后,当前节点的prev、next指针分别被设为LIST_POSITION2LIST_POSITION1两个特殊值,这样设置是为了保证不在链表中的节点项不可访问链表中的节点–对LIST_POSITION1LIST_POSITION2的访问都将引起页故障;

list_del_init 将节点从链表中删除之后,调用INIT_LIST_HEAD()将节点置为空链状态。

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
    next->prev = prev;
    prev->next = next;
}

static inline void __list_del_entry(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
}

static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->next = LIST_POISON1;
    entry->prev = LIST_POISON2;
}

static inline void list_del_init(struct list_head *entry)
{
    __list_del_entry(entry);
    INIT_LIST_HEAD(entry);
}

2.6 移动

内核链表提供了将原来属于一个链表的节点移动到另一个链表的操作。

list_move 会把节点从第一个链表删除,插入到另一个链表头部。

list_move_tail 会把节点从第一个链表删除,插入到另一个链表尾部。

static inline void list_move(struct list_head *list, struct list_head *head)
{
    __list_del_entry(list);
    list_add(list, head);
}

static inline void list_move_tail(struct list_head *list,
                  struct list_head *head)
{
    __list_del_entry(list);
    list_add_tail(list, head);
}

2.7 链表合并

链表合并,也就是整个链表的插入功能,list_splice:在list1非空时,list1链表的内容将被挂接在list2链表上,位于list2和list2.next(原list2表的第一个节点)之间,也就是list2头部,原表头指针的list1的next、prev仍然指向原来的节点。如下(虚箭头为next指针):

这里写图片描述

list_splice_initlist_splice的基础上,调用INIT_LIST_HEAD(list1)将list1设置为空链。

同时提供了list_splice_tail,用于在list1非空时,把list1插入到list2尾部。也提供了list_splice_tail_init,在插入基础上,调用INIT_LIST_HEAD(list1)将list1设置为空链。

static inline void __list_splice(const struct list_head *list,
                 struct list_head *prev,
                 struct list_head *next)
{
    struct list_head *first = list->next;
    struct list_head *last = list->prev;

    first->prev = prev;
    prev->next = first;

    last->next = next;
    next->prev = last;
}

static inline void list_splice(const struct list_head *list,
                struct list_head *head)
{
    if (!list_empty(list))
        __list_splice(list, head, head->next);
}

static inline void list_splice_tail(struct list_head *list,
                struct list_head *head)
{
    if (!list_empty(list))
        __list_splice(list, head->prev, head);
}

static inline void list_splice_init(struct list_head *list,
                    struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head, head->next);
        INIT_LIST_HEAD(list);
    }
}

static inline void list_splice_tail_init(struct list_head *list,
                     struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head->prev, head);
        INIT_LIST_HEAD(list);
    }
}

2.8 遍历

Linux链表将遍历操作抽象成几个宏。

1、Linux提供了由链表节点到数据项的宏list_entry:

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)

container_of 的宏定义位于include/linux/kernel.h

#define container_of(ptr, type, member) ({               \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);  \
    (type *)( (char *)__mptr - offsetof(type,member) );})

offsetof宏定义在include/linux/stddef.h

#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif

这里使用的是一个利用编译器技术的小技巧,即先求得结构成员在与结构中的偏移量,然后根据成员变量的地址反过来得出属主结构变量的地址。

container_of()offsetof()并不仅用于链表操作,这里最有趣的地方是((type *)0)->member,它将0地址强制”转换”为type结构的指针,再访问到type结构中的member成员。在container_of宏中,它用来给typeof()提供参数(typeof()是gcc的扩展,和sizeof()类似,typeof返回的是传入对象的数据类型),以获得member成员的数据类型;在offsetof()中,这个member成员的地址实际上就是type数据结构中member成员相对于结构变量的偏移量。

对于给定一个结构,offsetof(type,member)是一个常量,list_entry()正是利用这个不变的偏移量来求得链表数据项的变量地址。如下图所示:

这里写图片描述

2.遍历的宏定义list_for_each()list_for_each_entry,前者的pos指向节点,用于遍历节点;后者的pos指向节点对应的数据,用于遍历节点,并获取节点的数据成员。

#define list_for_each(pos, head)                                \
    for (pos = (head)->next; pos != (head); pos = pos->next)

#define list_for_each_entry(pos, head, member)                  \
    for (pos = list_entry((head)->next, typeof(*pos), member);   \
         &pos->member != (head);                                   \
         pos = list_entry(pos->member.next, typeof(*pos), member))

list_for_each_entry_continue 用于遍历不是从链表头开始,而是从已知的某个节点pos开始的情况。

list_for_each_entry_reverse 反向遍历。

2.9 其他

list_replace(struct list_head *old, struct list_head *new) 将old替换为new

list_is_last(const struct list_head *list,
const struct list_head *head)
测试list是否是head链表的最后一项。

3 HASH链表hlist

hlist与list的区别是hlist是单指针表头链表,如下图:

这里写图片描述

3.1 hlist的初始化

struct hlist_head {
    struct hlist_node *first;
};

struct hlist_node {
    struct hlist_node *next, **pprev;
};

#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
    h->next = NULL;
    h->pprev = NULL;
}

hlist的其他操作与list类似,只是因为表头和节点的数据结构不同,插入操作如果发生在表头和首节点之间,以往的方法就行不通了:表头的first指针必须修改指向新插入的节点,却不能使用类似list_add()这样统一的描述。为此,hlist节点的prev不再是指向前一个节点的指针,而是指向前一个节点(可能是表头)中的next(对于表头则是first)指针(struct list_head **pprev),从而在表头插入的操作可以通过一致的*(node->pprev)访问和修改前驱节点的next(或first)指针。

4 read-copy update(RCU)

RCU通过延迟写操作来提高同步性能。list中xxx_rcu的方法就是使用了此特性,具体可以查看RCU相关文档。

<think>嗯,用户让我介绍一下Linux内核,我需要先理清楚要讲哪些方面。首先得明确Linux内核是什么,是操作系统的核心部分对吧。然后可能需要讲讲它的历史和起源,比如Linus Torvalds在1991年开发的那个项目。接下来应该解释内核的主要功能,比如进程管理、内存管理、设备驱动等等。 然后,可能用户想知道架构设计,比如它属于类Unix系统,但和Unix的具体区别在哪里。还有内核的模块化设计,动态加载模块这个特点很重要。另外,内核的不同版本类型,比如稳定版、长期支持版这些,用户可能不太清楚,需要说明一下。 开源特性也是重点,GPL协议的意义,社区协作的开发模式。应用场景方面,服务器、嵌入式系统、Android这些例子比较典型。可能还需要提到安全性和性能优化,但不用太深入,保持概述级别。 要注意避免使用太多技术术语,用通俗易懂的中文解释。还要检查结构是否清晰,分点分段是否合适。是否需要举例子帮助理解?比如进程调度如何工作,或者内存管理的作用。可能用户是初学者,所以需要保持简洁明了,同时覆盖关键点。 另外,用户可能对内核和操作系统的区别有疑问,要明确内核是操作系统的一部分,负责核心功能,而其他部分如用户界面、应用程序属于用户空间。最后是否需要推荐学习资源或进一步的方向?可能对用户有帮助,但根据问题本身,可能不需要,保持回答紧扣主题即可。 确保使用正确的LaTeX格式,行内公式用$...$,独立公式单独成段。不过这个问题可能不太需要数学公式,但如果有涉及性能指标或算法,可能需要简单提及。不过根据问题,可能不需要,所以暂时不用考虑公式部分。 最后检查语言是否流畅,是否符合中文表达习惯,有没有遗漏重要信息。比如是否提到实时性补丁(如PREEMPT_RT),或者容器技术如Docker对内核的影响?可能属于进阶内容,概述中点到为止即可。总之,要全面而简洁地介绍Linux内核,覆盖定义、历史、功能、架构、版本、开源、应用等方面。</think>Linux 内核是操作系统的核心组件,负责管理硬件资源、提供基础服务并协调用户程序与硬件之间的交互。以下是其关键特性的分步解析: 1. **基础定义** - 位于操作系统底层,遵循**单体式内核架构**(Monolithic Kernel) - 采用**模块化设计**(Loadable Kernel Modules,LKM),支持动态加载/卸载功能 - 通过系统调用(如 `syscall`)提供用户空间接口 2. **历史演进** - 1991年由 Linus Torvalds 基于 MINIX 系统开发 - 采用 GPLv2 许可证($GPL \geq 2$) - 版本号遵循 `主版本.次版本.修订号` 体系,如 `6.9.0` 3. **核心子系统** | 子系统 | 功能描述 | |----------------|-------------------------------------------------------------------------| | 进程调度 | 使用 CFS(完全公平调度器),时间复杂度 $O(1)$ 的调度算法 | | 内存管理 | 实现虚拟内存(VM)、NUMA 优化,页表管理采用多级结构(x86_64使用4级页表) | | 文件系统 | 支持 ext4/Btrfs/XFS 等,通过 VFS(虚拟文件系统)抽象层统一接口 | | 设备驱动 | 包含 60% 以上的内核代码,遵循统一设备模型(UDM) | | 网络协议栈 | 实现 TCP/IP 协议栈,支持从 L2(数据链路层)到 L7(应用层)的处理 | 4. **关键技术特性** ```c // 示例:内核链表实现(include/linux/list.h) struct list_head { struct list_head *next, *prev; }; ``` - 实时性扩展(PREEMPT_RT 补丁) - 容器支持(cgroups/namespace) - 安全机制(SELinux/AppArmor) 5. **性能指标** - 上下文切换延迟:通常 < 10μs(x86_64) - 中断响应时间:实时内核可达到 μs 级 - 最大进程数:由 `pid_max` 参数控制(默认 32768) 6. **应用场景** - 服务器领域(90% 的公有云使用 Linux 内核) - 嵌入式系统(Android 内核基于 Linux 4.19 LTS) - 超级计算机(TOP500 全部采用 Linux) 建议学习路径:从内核源码目录结构(`arch/`, `drivers/`, `fs/` 等)入手,配合 `procfs`(`/proc`) 和 `sysfs`(`/sys`) 进行实时状态观测。推荐使用 `ftrace` 或 `perf` 工具进行性能分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值