《深入理解Linux内核(第三版)》笔记(二),第三章进程(1)

本文详细介绍了Linux内核中进程的组织结构,包括进程描述符`task_struct`、线程信息`thread_info`以及它们在内存中的布局。通过双向链表和`list_head`实现进程的关联,并分析了进程如何通过`pidhash`散列表进行高效查找。此外,还探讨了进程的等待队列和状态转换,如`wait_queue_head_t`的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇匹配《第三版》的第三章,进程,上半部分,主要是进程相关的一些关键数据结构等

相关源文件

kernel/sched.c 和 include/linux/sched.h
这两个文件是主干文件,其中会依赖
include/asm-i386/thread_info.h
arch/i386/kernel/init_task.c
include/linux/list.h
kernel/pid.c
include/linux/pid.h
include/linux/wait.h
等文件

进程结构

毫无疑问,在进程调度中,进程结构体是最关键的数据结构。

// include/linux/sched.h/line:528
struct task_struct {
	...
}

// // include/linux/sched.h/line:167
typedef struct task_struct task_t;

另外一个比较重要的结构体是 thread_info

// include/asm-i386/thread_info.h/line:28
struct thread_info {
	...
}
// 从文件路径可以看出,这个结构体是和硬件相关联的,需要具备硬件抽象的功能
// 为啥这个也重要呢,因为它常驻内核,是 task 在内核中的代表
// include/asm-i386/thread_info.h/line:110
#define alloc_thread_info(tsk) kmalloc(THREAD_SIZE, GFP_KERNEL)

// include/asm-i386/thread_info.h/line:113
#define free_thread_info(info)	kfree(info)

// include/asm-i386/thread_info.h/line:88,屏蔽低13位
static inline struct thread_info *current_thread_info(void)

Linux 中的进程管理,是通过双向链表实现的,核心结构体:

// include/linux/list.h/line: 28
struct list_head {
	struct list_head *next, *prev;
};
// 链表相关的其他操作也在这个文件里
// 这个 list_head 的设计还是比较精妙的
// struct task_struct 中的 tasks 元素,把前后的进程描述符串联起来。

// 下面这个宏定义,可以通过 list 找到包含它的结构体;这个比较厉害
/**
 * list_entry - get the struct for this entry
 * @ptr:	the &struct list_head pointer.
 * @type:	the type of the struct this is embedded in.
 * @member:	the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

/**
 * container_of - cast a member of a structure out to the containing structure
 *
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({			\
        const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
        (type *)( (char *)__mptr - offsetof(type,member) );})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

// 把上面的宏展开下:
/*
 * 假设:
 * ptr: cur_ptr
 * type: struct task_struct
 * member: tasks
*/
{
	const typeof(((struct task_struct *)0)->tasks) *__mptr = (cur_ptr);
	(struct task_struct *)((char *)__mptr - ((size_t)&((struct task_struct *)0)->tasks));
}

// 上面用到了 typeof,这是 GNU C 的一个扩展,没有被包括在 32 个 C 语言关键字里面
// 和宏搭配使用能取得一个比较好的结果
// 编译器可以根据一个已知变量,推测出要定义的变量的类型
// 对上面展开的解释:
// 定义了一个 struct list_head 指针,并将当前的 list_head 指针对其进行赋值
// 得到当前的指针了,通过 char * 类型做差,便得到了这个指针元素所在的结构体,最后进行强制转换一下
// 当然,最后要说的一件事情,是这个是一个 {} 包起来的一个代码块,调用者将这个代码块直接赋给某个变量
// 比如:
struct task_struct* task_curr = list_entry(ptr, type, member)
// 也许会有疑问,为什么要做这么复杂的操作?能想到的两个优势:
// 1,C 语言级别的模板编程
// 2,可以解除复杂的依赖关系

这时候,引入了一个很关键的变量:init_task

// arch/i386/kernel/init_task.c/line: 32
/*
 * Initial task structure.
 *
 * All other task structs will be allocated on slabs in fork.c
 */
struct task_struct init_task = INIT_TASK(init_task);
// 这就是传说中的 0 号进程的进程描述符
// 显然,这是个静态分配的进程描述符。所有的故事,都是从这里开始的

进程相关操作

解析下几个比较重要的宏。

// include/linux/sched.h/line: 998
#define SET_LINKS(p) do {					\
	if (thread_group_leader(p))				\
		list_add_tail(&(p)->tasks,&init_task.tasks);	\
	add_parent(p, (p)->parent);				\
	} while (0)

// 相关调用
// include/linux/sched.h/line: 1022
#define thread_group_leader(p)	(p->pid == p->tgid)
// include/linux/list.h/line: 78
static inline void list_add_tail(struct list_head *new, struct list_head *head)
// include/linux/sched.h/line: 990
#define add_parent(p, parent)	list_add_tail(&(p)->sibling,&(parent)->children)


// 假设
struct task_struct *curr;
p = curr;

/* 对上面这个宏进行展开 */
// 也就是说,只有线程组的 leader 才是真正意义的进程,才会被插到 init_task 引领的进程链表中
// 这就有一个疑问,需要后面确认,get_pid 得到的是线程 pid,还是它的 tgid?
if (curr->pid == curr->tgid) { 	// thread_group_leader 的两者相等
	// 双向链表,init_task 的前一个就是整个链表的尾部
	// 注意 -> 的优先级比 & 高;tasks 元素是结构体,有内存分配,不是指针
	list_add_tail(&(curr)->tasks, &init_task.tasks);
}
// 这行代码,把 curr 的 sibling 实体,插到了 parent 的 children 中
list_add_tail(&(curr)->sibling, &((curr)->parent)->children)
// 所以说,每个线程都被当做一个独立的进程来看待,都有它自己的父母兄弟
// sibing 元素是分配了内存的结构体实体
// parent 是指针,它指向的实体的内存,是在他的父 task 的进程描述符里分配的
// children 是实体,但是这里是 curr 的 parent 的实体,不是 curr 本身的
// parent 的 children 将是这个双向链表的头,表头后面跟的是子 task 的 sibling
// 同时也可以通过 curr 的 sibling,对这个双向链表进行访问

// 想法:
// linux 的 task 使用这种双向链表的方式进行组织,就好像把一个 task 实体分饰了多个角色
// 每个 list_head 的实体都在一定的维度上代表了这个 task
// 这里有一个疑惑,为什么要保持父子进程的这种关系?
// 暂时先不研究了,猜测和多用户有关
// include/linux/sched.h/line: 992
#define REMOVE_LINKS(p) do {					\
	if (thread_group_leader(p))				\
		list_del_init(&(p)->tasks);			\
	remove_parent(p);					\
	} while (0)

// 相关调用
// include/linux/list.h/line: 217
static inline void list_del_init(struct list_head *entry)
// include/linux/sched.h/line: 989
#define remove_parent(p)	list_del_init(&(p)->sibling)

// 展开。假设:
struct task_struct *curr;
p = curr;
// 则
if (curr->pid == curr->tgid) {
	list_del_init(&(curr)->tasks);	// 此时,进程描述符还在,但是 head_list 已经不在 init_task 链表里了
}
list_del_init(&(curr)->sibling);	// 同样,进程描述符还在,但是它的 parent 的 children 链表里就没有这个元素了
// 下面这两个宏,是获得当前进程描述符的前面或者后面的进程描述符
// 是的,通过 head_list 链表元素,反向找到包含该元素的进程描述符;list_entry 上面已经分析过了
#define next_task(p)	list_entry((p)->tasks.next, struct task_struct, tasks)
#define prev_task(p)	list_entry((p)->tasks.prev, struct task_struct, tasks)
#define for_each_process(p) \
	for (p = &init_task ; (p = next_task(p)) != &init_task ; )

// 具体使用时后面要加语句块
for (p = &init_task; (p = next_task(p)) != &init_task; ) {
	;	// do something
}
// 所有的判断运算符,都是从左到右的顺序;所以先运算 != 左边的括号里的内容
// 先取下个指针,再判断是否相等

关于 TASK_RUNNING 状态的链表,Linux 还是做了优先级的处理的,关联平时我们说的 nice 值。
关键结构体 struct prio_array:

// kernel/sched.c/line: 185
struct prio_array {
	unsigned int nr_active;
	unsigned long bitmap[BITMAP_SIZE];
	struct list_head queue[MAX_PRIO];
};
// 展开
// 具体运算式的意义暂时不细究了
#define BITMAP_SIZE ((((MAX_PRIO+1+7)/8)+sizeof(long)-1)/sizeof(long))
/*
 * Priority of a process goes from 0..MAX_PRIO-1,			// 0..139
 * valid RT * priority is 0..MAX_RT_PRIO-1,					// 0..99
 * and SCHED_NORMAL tasks are in the range MAX_RT_PRIO..MAX_PRIO-1.	// 100..139
 * Priority values are inverted: lower p->prio value means higher priority.
 *
 * The MAX_USER_RT_PRIO value allows the actual maximum
 * RT priority to be separate from the value exported to user-space.
 * This allows kernel threads to set their priority to a value higher than any user task.
 * Note: MAX_RT_PRIO must not be smaller than MAX_USER_RT_PRIO.
 */
#define MAX_USER_RT_PRIO	100
#define MAX_RT_PRIO			MAX_USER_RT_PRIO
#define MAX_PRIO			(MAX_RT_PRIO + 40)		// 140

// 从其实现的功能来看,这个结构体应该以单例的形式来实现
// 找了一下,没有直接找到这个结构体的实例化
// 猜测是在 struct runqueue 中被实例化的

// kernel/sched.c/line: 198
struct runqueue {
	...
	prio_array_t *active, *expired, arrays[2];
	...
};

// struct runqueue 的实例化:
// kernel/sched.c/line: 281
static DEFINE_PER_CPU(struct runqueue, runqueues);

// include/asm-generic/percpu.h/line: 29
#define DEFINE_PER_CPU(type, name) \
    __typeof__(type) per_cpu__##name
// "##"是片段连接符

// 展开
static __typeof__(struct runqueue) per_cpu__runqueues;
// 为每个 cpu 都分配一个数组吗?不应该啊,进程队列应该是整个软件内核的资源
// 当然了,估计还要往后看,也许可运行 task 队列就是针对每个 cpu 讲的呢;每个 cpu 维护自己的可运行 task
// kernel/sched.c/line: 566
static void dequeue_task(struct task_struct *p, prio_array_t *array)

// kernel/sched.c/line: 574
static void enqueue_task(struct task_struct *p, prio_array_t *array)

pidhash 散列表

pidhash.1 —— pidhash的组织形式和生成

要解决的问题:给定一个 PID 数值后,找到其对应的进程描述符指针。
这是一个比较不太容易实现的事情;kernel 通过散列表来对其进行实现。

// 这是一个非常关键且核心的数据
// kernel/pid.c/line: 31
static struct hlist_head *pid_hash[PIDTYPE_MAX];
// 四个 PID 哈希表的起始地址

// 这定义了一个结构体指针的数组;数组的大小是个很棒的设计
enum pid_type
{
	PIDTYPE_PID,	// 轻量进程
	PIDTYPE_TGID,	// 轻量进程组
	PIDTYPE_PGID,	// 真正的进程
	PIDTYPE_SID,	// 会话组
	PIDTYPE_MAX
};

// 分析一下:
// line31,是一个结构体指针的数组,数组里面的元素是一个指针
// 可以假设,指针指向的实体是一个结构体(这个结构体里只有一个元素,是一个指针)
// 但是,实际上&更复杂的是,这个数组里的元素(一个指针),是另一个数组的起始地址!!!
// 实际上,有四个数组,这四个数组就是传说中的四个 PID 的 hash 表;
// 四个 PID 哈希表的初始化:

// kernel/pid.c/line: 32
static int pidhash_shift;	// 注意这是一个静态变量啊

// kernel/pid.c/line: 255
void __init pidhash_init(void)
{
	int i, j, pidhash_size;
	unsigned long megabytes = nr_kernel_pages >> (20 - PAGE_SHIFT);	// 猜测 nr_kernel_pages 是和硬件相关的
	pidhash_shift = max(4, fls(megabytes * 4));
	pidhash_shift = min(12, pidhash_shift);
	pidhash_size = 1 << pidhash_shift;		// 所以,每个 hash 表的大小总是 2^x
	...
	for (i = 0; i < PIDTYPE_MAX; i++) {
		// 申请的大小 = 数组数 * (struct hlist_head)的大小
		pid_hash[i] = alloc_bootmem(pidhash_size * sizeof(*(pid_hash[i])));
		if (!pid_hash[i])
			panic("Could not alloc pidhash!\n");
		// 申请得到了一个预期大小的数组空间,空间的基地址存放在了 pid_hash 中
		for (j = 0; j < pidhash_size; j++) {
			// 对数组中的每一个元素都进行初始化(赋值 0)
			// 这个宏的参数是 struct hlist_head 类型的指针
			// 括号运算符的优先级是高于 & 的;所以,是对一个二维数组的元素取地址
			INIT_HLIST_HEAD(&pid_hash[i][j]);
		}
	}
}
// kernel/pid.c/line: 30
#define pid_hashfn(nr) hash_long((unsigned long)nr, pidhash_shift)
// fn 应该是 function 的意思
// 给定一个数,返回在哈希表中的位置
// 不管是向哈希表新插入元素,还是访问哈希表中的数据,都使用同样的操作,所以同样的输入会得到同样的输出
// 注意,这是 h 的 list_node;下面对这两个双向链表做一下对比
// list_head vs hlist_head:
struct list_head {	// Simple doubly linked list implementation.
	struct list_head *next, *prev;
};
/*
 * Double linked lists with a single pointer list head.
 * Mostly useful for hash tables where the two pointer list head is too wasteful.
 * You lose the ability to access the tail in O(1).
 * lose: 因为,头节点指向的只有一个指针,指向一个 node;尾指针的next 指向空
 */
struct hlist_head {
	struct hlist_node *first;
};
struct hlist_node {
	struct hlist_node *next, **pprev;
};
// 注意,hlist_node 的第二个元素是 **pprev,是个双重指针
// 为什么呢?因为链表的第一个 hlist_node 也要指向它的前一个节点
// 但是它的前一个节点不是 hlist_node 而是 hlist_head,所以不能直接使用节点指针
// 所以直接指向 hlist_head 里的元素 first,而 first 是一个指针,所以 pprev 就是一个双重指针了

// 为了理解 hlist_node,下面分析几个相关的函数
// include/linux/list.h/line:563
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)	// 这是个头插操作啊
{
	struct hlist_node *first = h->first;	// 存放的是一个地址,hlist_node 的地址,对于一个空头,first 应该是空
	n->next = first;	// 因为是头插,所以把链表的第一个节点放到新节点的 next 上
	if (first)			// 现在 first 指向的是链表的旧的第一个节点,如果不是空
		first->pprev = &n->next;	// 用新插入节点的next的地址(指针的地址,双重指针)赋给链表的旧的第一个节点的 pprev
	h->first = n;	// 把新节点放到链表头上
	// 这时候,h->first 已经指向新节点了
	// 但是,现在关心的不是 first 指向了哪里而是first这个指针变量的实体的地址
	n->pprev = &h->first;
}

pidhash.2 —— pidhash 和进程描述符的关联

// 进程描述符中的另一个关键元素:
// include/linux/sched.h/line:528
struct task_struct {
	...
	/* PID/PID hash table linkage. */
	struct pid pids[PIDTYPE_MAX];	// line: 589
	...
}

// include/linux/pid.h/line: 13
struct pid
{
	/* Try to keep pid_chain in the same cacheline as nr for find_pid */
	int nr;
	struct hlist_node pid_chain;
	/* list of pids with the same nr, only one of them is in the hash */
	struct list_head pid_list;
};

以举例的方式来说明两者的关联:
假设一个进程的 PID 号(或者 TGID 。。。)为 nr,对 nr 进行 pid_hashfn 得到它在 pid_hash[x] 指向的数组的偏移。
基于 PID 的类型,对进程描述符的 pids 这个数组中的第 x 个元素:

  • 将 pid.nr 赋值;猜测,这个值就等于它本身的 pid 或者 tgid 或者。。寻找的时候可以用
  • 将 pid.pid_chain 插入到 pid_hash[x][n].first 中;这样就可以通过 pid_hash[x] 找到这个 pid 结构体了
  • pid.pid_list 是用来维持 tgid,pgrp,session 等关系的;要知道,线程组、进程组什么的,和父子进程、兄弟进程是不同的概念
// 解析下,知道一个 PID 的数值以后,怎么找到它对应进程描述符的 pid 元素
struct pid * fastcall find_pid(enum pid_type type, int nr)
{
	struct hlist_node *elem;
	struct pid *pid;
	hlist_for_each_entry(pid, elem,
			&pid_hash[type][pid_hashfn(nr)], pid_chain) {
		if (pid->nr == nr)
			return pid;
	}
	return NULL;
}

#define hlist_for_each_entry(tpos, pos, head, member)			 \
	for (pos = (head)->first;					 \
	     pos && ({ prefetch(pos->next); 1;}) &&			 \
		({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
	     pos = pos->next)

{
	for (elem = head->first; pos; pos = pos->next) {
		// 下面这两行,是在宏定义里带来的,所以每次都会被运行,但是不需要用户手动去写
		prefetch(pos->next);	// 这个先不看
		// 通过 pid 的元素 pid_chain(也就是pos),获得 pid 结构体的地址
		pid = hlist_entry(pos, typeof(*pid), pid_chain);
		if (pid->nr == nr)	// 如果遍历到的这个结构体的 nr 和目标 nr 相等,那么就对了
			return pid;
	}
}

所以,当知道一个 PID 以后:
 - 可以定位到 hash 表中的位置
 - 这个位置里放的是一个链表头,因为也许会有不同的 PID 被映射到 hash 表的同一个位置;这就需要用链表来组织
 - 找到这个链表头以后,需要遍历这个链表,以确认和这个 PID 真正相等的

这里会有两层链表:
1)hash 表的头指针指向的是一个链表:被映射到同一个位置,但是 PID 不同
2)基于 nr 找到想要的 pid_chain 了,而 pid 的 pid_list 元素代表的是一个双向链表,代表线程组、会话组什么的

pidhash.3 —— 几个常用的宏

// 这两个宏要搭配用的
#define do_each_task_pid(who, type, task)				\
	if ((task = find_task_by_pid_type(type, who))) {		\
		prefetch((task)->pids[type].pid_list.next);		\
		do {

#define while_each_task_pid(who, type, task)				\
		} while (task = pid_task((task)->pids[type].pid_list.next,\
						type),			\
			prefetch((task)->pids[type].pid_list.next),	\
			hlist_unhashed(&(task)->pids[type].pid_chain));	\
	}

/* 展开 */
if ((task = find_task_by_pid_type(type, who))) {	// 如果至少存在一个的话
	prefetch((task)->pids[type].pid_list.next);
	do {
		;
	} while (task = pid_task((task)->pids[type].pid_list.next,type),
		prefetch((task)->pids[type].pid_list.next),
		hlist_unhashed(&(task)->pids[type].pid_chain));
}

// 相关引用
// kernel/pid.c/line:207
// 给定 PID 的数组和类型,返回指向的 task;但是,符合条件的 task 有可能是一个链表,但这个返回的是链表的头
task_t *find_task_by_pid_type(int type, int nr)
{
	struct pid *pid;
	pid = find_pid(type, nr);	// 上面分析过了,这里返回的是 pid 的结构体
	if (!pid)
		return NULL;
	return pid_task(&pid->pid_list, type);	// 通过 pids[type].pid_list 找到它所在的 task_t 结构体
}

// include/linux/pid.h/line:22
#define pid_task(elem, type) list_entry(elem, struct task_struct, pids[type].pid_list)

// 再重点看下这个语句
while (task = pid_task((task)->pids[type].pid_list.next,type),
		prefetch((task)->pids[type].pid_list.next),
		hlist_unhashed(&(task)->pids[type].pid_chain))
// 这几行代码还是很厉害的
// 这个 while 语句用的是逗号运算符,最终的值,是最后那个语句

// 被链接到 hash 表里的节点,pprev 都不是零,所以函数会返回 0
// 未被链接到 hash 表里的节点,pprev 都是是零,所以函数会返回 1
// include/linux/list.h/line:504
static inline int hlist_unhashed(const struct hlist_node *h)
{
	return !h->pprev;
}

// 还是那个点,被映射到 hash 表同一个位置上,有可能会有很多个元素,这些元素被链接成一个链表
// 而这个链表还带着一个结构体,这个结构体的里的另一个同伴,pid_list 是一个正规意义的双向链表,循环的
// 这个双向链表中,直接挂到 hash 位置链表上的那个节点,是头
// while 里的第一个语句会不停的取下一个 pid_list
// 当取到某个 pid_list 所对应的 pid_chain 的 pprev 不为空时,便是遍历了这个 pid_list

下面这几个函数和上面的操作类似,就不拆解了,直接读代码就好。

// kernel/pid.c/line:147
int fastcall attach_pid(task_t *task, enum pid_type type, int nr)

// kernel/pid.c/line:192
void fastcall detach_pid(task_t *task, enum pid_type type)

组织进程

// include/linux/wait.h/line:51
struct __wait_queue_head {
	spinlock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
// include/linux/wait.h/line:33
struct __wait_queue {
	unsigned int flags;
#define WQ_FLAG_EXCLUSIVE	0x01
	struct task_struct * task;
	wait_queue_func_t func;
	struct list_head task_list;
};
// include/linux/wait.h/line:30
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int sync, void *key);
// 以下两个函数分别静态、动态的初始化一个  wait_queue_head_t
// include/linux/wait.h/line:74
#define DECLARE_WAIT_QUEUE_HEAD(name) \
	wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

// include/linux/wait.h/line:80
static inline void init_waitqueue_head(wait_queue_head_t *q)
// include/linux/wait.h/line:325
#define DEFINE_WAIT(name)						\
	wait_queue_t name = {						\
		.task		= current,				\
		.func		= autoremove_wake_function,		\
		.task_list	= {	.next = &(name).task_list,	\
					.prev = &(name).task_list,	\
				},					\
	}

// kernel/wait.c/line:123
int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)

// include/linux/wait.h/line:93
static inline void init_waitqueue_func_entry(wait_queue_t *q,
					wait_queue_func_t func)
// 可以自定义一个唤醒函数
// kernel/wait.c/line:14
void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
	unsigned long flags;
	wait->flags &= ~WQ_FLAG_EXCLUSIVE;
	spin_lock_irqsave(&q->lock, flags);
	__add_wait_queue(q, wait);
	spin_unlock_irqrestore(&q->lock, flags);
}

// include/linux/wait.h/line:119
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
{
	list_add(&new->task_list, &head->task_list);
}
// 上面这个函数可以理解,就是把 queue 插到 queue_head 中了
// 但是现在的疑问是,queue_head 应该有很多个,这若干个 queue_head 是怎么串联起来的呢
// kernel/wait.c/line:25
void fastcall add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait)
// kernel/wait.c/line:36
void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

// include/linux/wait.h/line:101
static inline int waitqueue_active(wait_queue_head_t *q)

进入等待

进程可以在若干种情况下,进入等待状态。

// kernel/sched.c/line:3199
void fastcall __sched sleep_on(wait_queue_head_t *q)
{
	SLEEP_ON_VAR
	current->state = TASK_UNINTERRUPTIBLE;
	SLEEP_ON_HEAD
	schedule();
	SLEEP_ON_TAIL
}
// 函数里的几个宏,展开以后还是比较好理解的
// 复杂的地方在 schedule(),这个无疑是进程中最复杂的函数了;暂时先不看了,后面章节会再讲

// 随便找个 sleep_on 的调用场景:
// drivers/scsi/atari_scsi.c/line:498
static DECLARE_WAIT_QUEUE_HEAD(falcon_fairness_wait);
// drivers/scsi/atari_scsi.c/line:554
static void falcon_get_lock( void )
{
	...
	while( !in_interrupt() && falcon_got_lock && stdma_others_waiting() )
		sleep_on( &falcon_fairness_wait );
	...
}
// 这说明,wait_queue_head_t 是一个比较上层的数据结构了,可以在应用层直接实例化变量
// 回到上面 __add_wait_queue() 这个函数下面的疑问,应用模块是知道自己的等待队列的头在哪里的
// 下面这几个函数或者宏略微有些超纲,简单看看先,暂时不深入分析了
// kernel/sched.c/line:3171
void fastcall __sched interruptible_sleep_on(wait_queue_head_t *q)
// kernel/sched.c/line:3212
long fastcall __sched sleep_on_timeout(wait_queue_head_t *q, long timeout)
// kernel/sched.c/line:3184
long fastcall __sched interruptible_sleep_on_timeout(wait_queue_head_t *q, long timeout)
// kernel/wait.c/line:59
void fastcall prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
// kernel/wait.c/line:78
void fastcall prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
// kernel/wait.c/line:97
void fastcall finish_wait(wait_queue_head_t *q, wait_queue_t *wait)

// include/linux/wait.h/line:172
#define wait_event(wq, condition) 					\
do {									\
	if (condition)	 						\
		break;							\
	__wait_event(wq, condition);					\
} while (0)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值