Linux 内核中的双向链表及其在进程管理中的应用
在 Linux 内核中,双向链表是一种非常重要的数据结构,广泛用于各种场景,尤其是在进程管理和调度中。本文将详细介绍 list_head
数据结构的实现及其在进程链表和运行队列中的应用。
1. 双向链表的实现
list_head
数据结构
Linux 内核定义了一个简单的 list_head
结构来实现双向链表。list_head
包含两个指针:next
和 prev
,分别指向链表中的下一个和上一个元素。
struct list_head {
struct list_head *next, *prev;
};
初始化链表
使用 LIST_HEAD
宏可以创建并初始化一个新的链表头:
#define LIST_HEAD(name) \
struct list_head name = { &(name), &(name) }
这个宏声明了一个类型为 list_head
的新变量,并将其 next
和 prev
指针初始化为指向自身,形成一个空的循环链表。
常用操作函数和宏
内核提供了多个函数和宏来操作双向链表:
list_add(n, p)
:将n
指向的元素插入到p
所指向的元素之后。list_add_tail(n, p)
:将n
指向的元素插入到p
所指向的元素之前。list_del(p)
:删除p
所指向的元素。list_empty(p)
:检查由p
指定的链表是否为空。list_entry(p, t, m)
:返回类型为t
的数据结构的地址,其中t
中含有list_head
字段,而list_head
字段的名字为m
,地址为p
。list_for_each(p, h)
:对表头地址h
指定的链表进行扫描,在每次循环时,通过p
返回指向链表元素的list_head
结构的指针。list_for_each_entry(p, h, m)
:与list_for_each
类似,但返回包含list_head
结构的数据结构的地址,而不是list_head
结构本身的地址。
2. 进程链表
进程链表的结构
进程链表将所有进程的描述符链接起来。每个 task_struct
结构都包含一个 list_head
类型的 tasks
字段,用于链接前后进程。
struct task_struct {
...
struct list_head tasks;
...
};
进程链表的头
进程链表的头是 init_task
描述符,它是所谓的 0 进程(process 0)或 swapper 进程的进程描述符。init_task
的 tasks.prev
字段指向链表中最后插入的进程描述符的 tasks
字段。
插入和删除进程
SET_LINKS
和 REMOVE_LINKS
宏分别用于从进程链表中插入和删除一个进程描述符。这些宏考虑了进程间的父子关系。
扫描进程链表
for_each_process
宏用于扫描整个进程链表:
#define for_each_process(p) \
for (p = &init_task; (p = list_entry((p)->tasks.next, \
struct task_struct, tasks)) != &init_task; )
这个宏从指向 init_task
的指针开始,依次移动到下一个任务,直到再次回到 init_task
为止。
3. TASK_RUNNING 状态的进程链表
运行队列
当内核需要选择一个新进程在 CPU 上运行时,必须只考虑处于 TASK_RUNNING
状态的进程。早期的 Linux 版本将所有可运行进程放在一个链表中,但这会导致较高的开销。Linux 2.6 引入了新的运行队列机制,以提高调度程序的效率。
多个可运行进程链表
Linux 2.6 实现了多个可运行进程链表,每个优先级对应一个不同的链表。每个 task_struct
描述符包含一个 list_head
类型的 run_list
字段,用于链入对应优先级的可运行进程链表。
struct task_struct {
...
struct list_head run_list;
...
};
运行队列的数据结构
运行队列的主要数据结构是 prio_array_t
,包含以下字段:
nr_active
:链表中进程描述符的数量。bitmap
:优先权位图,当且仅当某个优先权的进程链表不为空时设置相应的位标志。queue
:140 个优先权队列的头结点。
struct prio_array_t {
int nr_active;
unsigned long bitmap[5];
struct list_head queue[140];
};
插入和删除进程
enqueue_task(p, array)
:将进程描述符插入某个运行队列的链表。
void enqueue_task(struct task_struct *p, struct prio_array_t *array) {
list_add_tail(&p->run_list, &array->queue[p->prio]);
__set_bit(p->prio, array->bitmap);
p->array = array;
}
dequeue_task(p, array)
:从运行队列的链表中删除一个进程的描述符。
4. 总结
双向链表在 Linux 内核中扮演着重要的角色,特别是在进程管理和调度中。通过 list_head
数据结构和相关的操作函数及宏,内核能够高效地管理进程链表和运行队列。这种设计不仅简化了代码,提高了性能,还为内核开发者提供了强大的工具来处理复杂的系统管理任务。
希望本文能帮助读者更好地理解 Linux 内核中的双向链表及其在进程管理中的应用。如果你有任何问题或建议,欢迎留言讨论。