linux中的等待队列-51

Posted 杨斌并

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux中的等待队列-51相关的知识,希望对你有一定的参考价值。

linux中的等待队列


等待队列的原理和运行过程

休眠和唤醒的基本原理是当驱动程序等待某种事件发生时会进入休眠状态,当该事件发生后,就会唤醒休眠状态的驱动代码。这就相当于A和B两台电子设备在做两种不同的工作,但必须B做完后A才能做。最好的方法是A先关机或进入低功耗状态,等B做完后再打开或唤醒A。这样A不仅避免消耗不必要的电能,而且又保证了工作的时序性,否则A就得在B工作时一直运转,而且帮不了B任何忙,只能在那消耗不必要的能量。

等待队列工作的过程就是休眠和唤醒的过程。当系统检测到某件事还没做完,就会调用set_current_state 函数将当前线程设为休眠状态(将当前线程加入等待队列),直到工作完成后,会调用wake_up_interruptible 函数来唤醒等待队列中的所用等待线程,然后会根据调度算法决定哪一个线程继续执行,其他未执行的线程继续等待。要完成等待队列的休眠和唤醒工作。


等待队列的api

  1. 定义等待队列头
static wait_queue_head_t my_queue;
  • 结构体(#include <linux/wait.h>)
struct __wait_queue_head {
	spinlock_t		lock;
	struct list_head	task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
  1. 初始化等待队列头
init_waitqueue_head(&my_queue);
//或者
DECLARE_WAIT_QUEUE_HEAD(name)
  1. 定义等待队列
DECLARE_WAITQUEUE(name, tsk)
  • 定义并初始化-一个wait_queue_t类型的变量
  • name表示wait_queue_t类型的变量名。
  • tsk 是进程描述符指针,也就是task_struct 结构体指针。一般该参数会使用current宏获取当前进程的task_struct 结构体指针。
  1. 添加,移除等待队列
extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
  • add_wait_queue 函数用于将等待队列wait 添加到等待队列头q指向的等待队列链表中。
  • remove_wait_queue函数用于将等待队列wait从等待队列头q指向的等待队列链表中移除。
  1. 等待事件
wait_event (wait_queue_head, condition)
wait_event_interruptible (wait_queue_head, condition)
wait_event_timeout (wait_queue_head, condition, timeout)
wait_event_interruptible_timeout (wait_ queue_head, condition, timeout)

上面4个宏都用于等待满足某一个条件(condition)。 如果条件还没有满足,这4个宏都会被阻塞,直到条件满足才返回。其中wait_queue_head 是一个等待队列头变量(注意,不是等待队列头指针变量),condition 就是要满足的条件。timeout 是超时时间,以jiffy为单位。如果超时时间到了,不管是否满足condition条件,wait event timeout 和wait event_ interruptible_ timeout 宏都会立即返回。这4个宏中带interruptible的两个宏与不带interruptible 的两个宏的区别是带interruptible的两个宏可以被中断信号打断,而另两个宏不可以被中断信号打断。

  1. 唤醒等待队列
#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_interruptible(x)	__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

wake_up和wake_up_interruptible 宏会将wait_queue_head指向的等待队列链表中所用的等待队列都唤醒。大家注意,这里说的是唤醒wait_queue_head 链表中所有的等待队列,如果链表中有10个等待队列(10 个休眠进程),那么这10 个等待队列都会被唤醒,当然,唤醒之后仍然只能有1个进程运行,其他9个仍然在唤醒之后继续进入休眠状态。至于这10个进程谁会执行,就要看调用schedule函数后系统调用的调度算法了.

实例

  • wait_queue.c
//
//  wait_queue.c
//  seqlock_demo
//
//  Created by lianfei on 2021/7/14.
//

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>


#define DEVICE_RCU_NAME "wq"
#define MAX_BUFFER_SIZE 20
//储存写入字符串的数组
static char buffer[MAX_BUFFER_SIZE];
//单前已写入的字符数
static int buffer_char_count = 0;
static wait_queue_head_t my_queue;

static ssize_t demo_read(struct file *file, char __user * buf, size_t count, loff_t *ppos){
    
    ssize_t result = 0;
    
    if (buffer_char_count > 0) {
        if (copy_to_user(buf, buffer, buffer_char_count)) {
            return  -EINVAL;
        }
        
        wake_up_interruptible(&my_queue);
        
        result = buffer_char_count;
        buffer_char_count = 0;
        return  result;
        
    }else{
        return  result;
    }
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos){
    ssize_t result = 0;
    //休眠单前的进程,直到buffer_char_count == 0
    wait_event_interruptible(my_queue, buffer_char_count == 0);
    if (copy_from_user(buffer, buf, count)) {
        return -EINVAL;
    }
    
    result = count;
    buffer_char_count = result;
    
    return result;
}

static int demo_release(struct inode *node, struct file *file){
    return 0;
}

static int demo_open(struct inode *node, struct file *file){
    return 0;
}

static struct file_operations dev_fops={
    .owner = THIS_MODULE,
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write
};

static struct miscdevice misc={
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_RCU_NAME,
    .fops = &dev_fops
};



static int demo_init(void){

    int ret=misc_register(&misc);
    if(ret < 0 ){
        printk("atomic_init is error\\n");
        return -1;
    }
    printk("demo_init_success\\n");
    init_waitqueue_head(&my_queue);
    return ret;
}

static void demo_exit(void){
    printk("ademo_exit_success\\n");
    misc_deregister(&misc);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("binbing.Yang");
  • 测试脚本
  • wait_queue.sh
#!/system/bin/sh
echo adcd > /dev/wq
echo string > /dev/wq
cat /dev/wq

本节实现的Linux驱动会创建一个/dev/wq设备文件,并允许向设备文件写入字符串以及从设备文件读取字符串。向设备文件写入字符串后,必须先读取才能继续写字符串。如果连续向设备文件写入字符串,除了第1次以外,其他写入字符串的内核进程都会休眠,直到读取了设备文件中的字符串才会唤醒这些被休眠的进程,并且只会有1个进程执行,其他的进程继续休眠。

以上是关于linux中的等待队列-51的主要内容,如果未能解决你的问题,请参考以下文章

linux中的等待队列-51

linux中的等待队列-51

在Linux驱动程序中,使用等待队列的作用?

Linux——Linux驱动之使用等待队列降低CPU的占用率应用实战(阻塞与非阻塞等待队列的基本概念相关函数代码实战)

Linux——Linux驱动之使用等待队列降低CPU的占用率应用实战(阻塞与非阻塞等待队列的基本概念相关函数代码实战)

linux系统中实现阻塞都有哪些方法 等待队列 自旋锁 中断