linux 内核 wait queue源码分析


参考:https://www.cnblogs.com/apprentice89/archive/2013/05/09/3068274.html
内核版本:4.4
源码地址:https://gitee.com/llcccc/qemu_arm/tree/master/SourceCode/Driver/2020-12/01_wait_queue_test

1 简单的例子

1.1 测试源码

先使用一个简单的例子来测试一下wait_queue的基本使用, 然后再进行结构体讲解和源码分析
代码基本的思路如下:
第1, 创建一个wait queue头
第2, 代码的一处地方调用wait_event进行等待
第3, 代码的另一处地方调用wake_up进行唤醒
代码实际创建了2个线程, 一个负责调用wait_event, 一个负责调用wake_up

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <linux/sched.h> //wake_up_process()
#include <linux/kthread.h> //kthread_create()、kthread_run()
#include <linux/delay.h>
#include <linux/time.h>
#include "my_error.h"

/* 代码思路

   1 定义等待队列头
   2 创建线程1 用于执行wait_event
   3 创建线程2 用于wake up

   执行流程
   1 线程 add_wait_func 执行wait_event, 等待唤醒
   2 线程 add_wait_func 执行wake_up 唤醒,让执行wait_event 继续执行
*/

#define WQ_TEST_NAME "wait queue test"

// 定义等待队列头
static DECLARE_WAIT_QUEUE_HEAD(wq_head);

static struct task_struct *add_wait1 = NULL;
static struct task_struct *up_wait = NULL;
static int count = 0; // 当为0时,满足wait_event唤醒条件
static int break_flag = 0; // 线程退出标志位

static int add_wait_func(void *data)
{
    while(1)
    {
        if (break_flag)
        {
            break;
        }
        // 代码卡在这里, 直到有人调用wake_up唤醒
        wait_event(wq_head, count == 0);
        printk("wait event done \n");
        count = 1;
    }
    return 0;
}

static int up_wait_func(void *data)
{
    while(1)
    {
        if (break_flag)
        {
            break;
        }
        msleep(1000);
        printk("wait up done\n");

        count = 0; // 让wait_event满足唤醒条件
        // 唤醒wait event
        wake_up(&wq_head);
    }

    return 0;
}

static int wq_test_init(void)
{
    int ret = 0;

    add_wait1 = kthread_create(add_wait_func, NULL, "add_wait1");
    if(IS_ERR(add_wait1))
    {
        PRINT_ERR("crate thread fail \n");
        ret = PTR_ERR(add_wait1);
        add_wait1 = NULL;
        return ret;
    }

    up_wait = kthread_create(up_wait_func, NULL, "up_wait");
    if(IS_ERR(up_wait))
    {
        PRINT_ERR("crate thread fail \n");
        ret = PTR_ERR(up_wait);
        up_wait = NULL;
        return ret;
    }
    count = 1;
    break_flag = 0;
    wake_up_process(add_wait1);
    wake_up_process(up_wait);
    
    PRINT_INFO("%s init \n", WQ_TEST_NAME);
    return 0;
}

static void wq_test_exit(void)
{
    break_flag = 1;

    // 唤醒 add_wait_func线程, 避免卡住
    count = 0;
    wake_up(&wq_head);

    kthread_stop(add_wait1);
    kthread_stop(up_wait);

    PRINT_INFO("%s exit \n", WQ_TEST_NAME);
}

module_init(wq_test_init);
module_exit(wq_test_exit);

MODULE_LICENSE("GPL");

1.2 测试log

通过log可以看到,只有当调用完wait up后, wait event才会被唤醒
在这里插入图片描述

2 源码分析

2.1 主要结构体介绍

涉及的结构体非常简单, 主要就2个, 一个是等待队列头, 一个是等待队列
等待队列头就如同链表头一样,后续会把等待队列挂载到等待队列头上面
等待队列头

// 等待队列头的结构体
struct __wait_queue_head {
	spinlock_t		lock; // 锁,操作链表肯定需要锁来进行保护
	struct list_head	task_list; //链表
};
typedef struct __wait_queue_head wait_queue_head_t;

在这里插入图片描述
等待队列

typedef struct __wait_queue wait_queue_t;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);

struct __wait_queue {
	unsigned int		flags; //标志位
	void			*private; // 私有变量, 一般指向current
	wait_queue_func_t	func; // 回调函数
	struct list_head	task_list; // 链表
};

在这里插入图片描述

2.2 wait_event 主要流程

wait_event实际是一个宏,有2个参数, 第1个参数是等待队列头,所以就需要先定义一个等待队列头。一般使用宏 DECLARE_WAIT_QUEUE_HEAD 来实现, 就是简单的给里面的变量赋初始值

#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {				\
	.lock		= __SPIN_LOCK_UNLOCKED(name.lock),		\
	.task_list	= { &(name).task_list, &(name).task_list } }

#define DECLARE_WAIT_QUEUE_HEAD(name) \
	wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

wait_event第2个参数是用来当做判断条件的,如果为真则跳出wait_event, 为假的话才会往后执行

#define wait_event(wq, condition)					\
do {									\
	// might_sleep 可以忽略, 主要是起到调试作用
	might_sleep();							\
	// 条件为真的话就直接返回了
	if (condition)							\
		break;							\
	__wait_event(wq, condition);					\
} while (0)

__wait_event 只是一层封装,继续调用 ___wait_event

#define __wait_event(wq, condition)					\
	// 这里是3条下划线
	(void)___wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0,	\
			    schedule())

___wait_event 有2个重要的函数, 一个是prepare_to_wait_event, 会把等待队列头和等待队列关联起来了
另一个是schedule(),由cmd = schedule()传入进来, 进行调度让出CPU

// 本来是一个宏,因为每行都有续行符导致看起来比较碍眼, 所以全部删除了,就当做函数来看吧 
#define ___wait_event(wq, condition, state, exclusive, ret, cmd)	
({									
	__label__ __out;						
	// 定义了一个等待队列
	wait_queue_t __wait;						
	long __ret = ret;	/* explicit shadow */			
									
	// 初始化链表
	INIT_LIST_HEAD(&__wait.task_list);				
	// exclusive = 0,
	if (exclusive)							
		__wait.flags = WQ_FLAG_EXCLUSIVE;			
	else					
		// yes			
		__wait.flags = 0;					
									
	for (;;) {							
		// state = TASK_UNINTERRUPTIBLE
		// 调用了函数 prepare_to_wait_event,后面分析
		long __int = prepare_to_wait_event(&wq, &__wait, state);
				
		// 再次判断是否满足退出条件					
		if (condition)						
			break;						
									
		// __int 一般返回0, 所以这里条件不会成立
		if (___wait_is_interruptible(state) && __int) {		
			__ret = __int;		
			// exclusive = 0, 不满足			
			if (exclusive) {				
				abort_exclusive_wait(&wq, &__wait,	
						     state, NULL);	
				goto __out;				
			}						
			break;						
		}							
									
		// cmd = schedule()
		// cmd 是schedule, 到这里当前进程就让出了CPU, 休眠在了这里, 以此这里不会消耗CPU了
		cmd;							
	}			
	// 后面分析					
	finish_wait(&wq, &__wait);					
__out:	__ret;								
})

再来分析 ___wait_event 里面的prepare_to_wait_event

long prepare_to_wait_event(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
	unsigned long flags;

	if (signal_pending_state(state, current))
		return -ERESTARTSYS;

	// 设置私有变量
	wait->private = current;
	// 设置了回调函数, 这里划重点
	wait->func = autoremove_wake_function;

	spin_lock_irqsave(&q->lock, flags);
	// 如果等待队列的链表是空的,则连接到等待队列头上
	if (list_empty(&wait->task_list)) {
		if (wait->flags & WQ_FLAG_EXCLUSIVE)
			__add_wait_queue_tail(q, wait);
		else
			// wait->flags = 0, 添加到链表头
			__add_wait_queue(q, wait);
	}
	// state = TASK_UNINTERRUPTIBLE
	// 当前进程设置为不可打断状态
	set_current_state(state);
	spin_unlock_irqrestore(&q->lock, flags);

	return 0;
}

到这里,等待队列头和等待队列才关联起来了
在这里插入图片描述
finish_wait 函数很简单,主要就是删除链表


void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
	unsigned long flags;

	// 当前进程设置为运行状态
	__set_current_state(TASK_RUNNING);
	
	// 等待队列从链表中删除
	// 这里判断的时候比较小心, 因为当调用wake_up时也会有同样的操作
	if (!list_empty_careful(&wait->task_list)) {
		spin_lock_irqsave(&q->lock, flags);
		list_del_init(&wait->task_list);
		spin_unlock_irqrestore(&q->lock, flags);
	}
}

在这里插入图片描述
大体的流程如下图
在这里插入图片描述

2.3 wake_up 主要流程

讲完了wait_event, 接下来就是讲如何唤醒,唤醒就调用wake_up

wake_up 只用传一个参数,那就是等待队列头, 不过wake_up是一个宏,里面还有其他一些默认的参数

#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)

__wake_up 主要就是调用 __wake_up_common

void __wake_up(wait_queue_head_t *q, unsigned int mode,
			int nr_exclusive, void *key)
{
	unsigned long flags;
	// 在看代码主要代码流程的时候,类似上锁 解锁的操作可以选择性忽略
	spin_lock_irqsave(&q->lock, flags);
	// mode = TASK_NORMAL, nr_exclusive = 1, key = NULL
	// 调用 __wake_up_common
	__wake_up_common(q, mode, nr_exclusive, 0, key);
	spin_unlock_irqrestore(&q->lock, flags);
}

__wake_up_common 主要就是遍历等待队列头的链表, 将节点依次取出来, 然后调用回调函数

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
			int nr_exclusive, int wake_flags, void *key)
{
	wait_queue_t *curr, *next;

    // mode = TASK_NORMAL, nr_exclusive = 1, wake_flags = 0, key = NULL
	list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
		// 之前传入的curr->flags = 0
		unsigned flags = curr->flags;
        // 回调 autoremove_wake_function
		if (curr->func(curr, mode, wake_flags, key) &&
				(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
			break;
	}
}

autoremove_wake_function 就做2件事, 1 调用 default_wake_function, 2 删除节点, 因为这里也有删除节点的操作,因此在finish_wait才会调用list_empty_careful 小心翼翼的删除节点

int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	// 执行成功返回1
	int ret = default_wake_function(wait, mode, sync, key);

	if (ret)
		list_del_init(&wait->task_list);
	return ret;
}

default_wake_function 会调用try_to_wake_up, 这个函数比较复杂, 但最后会把当前进程设置为TASK_RUNNING,加入到CPU队列中去运行从而达到唤醒功能, 执行成功的话会返回1

int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,
			  void *key)
{
	return try_to_wake_up(curr->private, mode, wake_flags);
}

wake_up大致流程如下
在这里插入图片描述

3 总结

主要流程如下
1 定义等待队列头

2 调用wait_event进行等待
2.1) wait_event 首先把自己链接到等待队列头的链表中
2.2) 然后调用schedule()让出CPU

====>3 调用 wake_up 进行唤醒
====>3.1)最后调用try_to_wake_up,把进程添加到CPU队列中, 相当于唤醒当前进程
====>3.2)删除节点

2.3) 当被唤醒后, 调用finish_wait把自己从链表中删除

其实wait_event 被唤醒时还要判断第2个参数是否为真, 为真则终止等待, 如果只把第2个参数设置为真是否可以结束等待?
答案是不行, 因此进程已经调用了schedule(), 需要重新添加到CPU队列中才可以
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux waitqueue 是一种 Linux 内核中的同步机制,它用于控制进程的执行顺序,使进程之间能够协调进行。 它通过让一个进程在等待另一个进程完成某个操作时进入睡眠状态,并在另一个进程完成操作后唤醒等待的进程。这样,它可以避免进程在不同步的情况下同时进行某些操作,从而减少系统资源的浪费。 因此,waitqueue 机制是 Linux 内核中常用的一种机制,它可以保证系统的正确性和高效性。 ### 回答2: 在Linux操作系统中,waitqueue是一种用于进程或线程等待的机制。 当一个进程或线程需要等待某个条件满足时,它可以使用waitqueue机制来挂起自己的执行。等待队列waitqueue)是一个数据结构,用于维护等待某个事件发生的进程或线程的列表。 当一个条件被满足时,比如某个共享资源变为可用,就会唤醒等待该条件的进程或线程。唤醒的过程是通过使用wake_up函数来实现的。 当一个进程或线程需要等待条件满足时,它会调用wait_event函数,将自己加入到等待队列中,并将自己标记为等待状态。之后,该进程或线程就会进入睡眠状态,并且由调度器决定运行其他进程或线程。 当条件满足时,比如共享资源变为可用,唤醒该条件的进程或线程的时候,会调用wake_up函数来唤醒等待的进程或线程。被唤醒的进程或线程会从wait_event的调用处继续执行,并继续执行后续逻辑。 需要注意的是,使用waitqueue机制需要配合锁机制使用,以避免竞态条件的产生。在加入等待队列和唤醒过程中,需要对共享资源进行加锁保护,以防止并发访问导致的数据不一致性。 总之,waitqueueLinux中一种用于进程或线程等待的机制,它通过等待队列来管理等待某个条件满足的进程或线程,并通过唤醒函数来唤醒等待的进程或线程。它是实现同步和互斥的重要工具之一,能够实现进程或线程之间的协作与同步。 ### 回答3: Linux中的waitqueue(等待队列)是一种用于进程调度的机制。它允许一个或多个进程阻塞并等待某个特定条件的满足。 waitqueue是一个数据结构,类似于一个队列,用于存储等待某个条件满足的进程。当一个进程等待某个条件时,它会将自己添加到waitqueue中,并进入睡眠状态。 在Linux内核中,waitqueue通常与锁(如spinlock或mutex)结合使用。当一个进程需要等待某个条件时,它需要先获取锁,在锁的保护下将自己添加到waitqueue中,然后释放锁并进入睡眠状态。当条件满足时,另一个进程会获取相同的锁,唤醒等待在waitqueue中的进程。 waitqueue的实现依赖于内核调度器。当一个进程被唤醒时,它会从睡眠状态返回到可运行状态,并进入内核调度器的调度队列,等待分配CPU执行。 waitqueue提供了一种线程同步的机制,使得进程可以等待某个条件满足而不需要忙等待。它在很多Linux内核中的子系统中广泛使用,如设备驱动、文件系统等。 总结来说,waitqueueLinux内核中用于进程调度的一种机制,它允许一个或多个进程等待某个条件的满足。它借助锁和睡眠状态实现进程的阻塞和唤醒,依赖于内核调度器进行进程的调度。waitqueue在提供进程同步、避免忙等待等方面发挥了重要作用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值