【Linux驱动】Linux阻塞IO —— 阻塞读取按键状态(等待队列实现)

这篇具有很好参考价值的文章主要介绍了【Linux驱动】Linux阻塞IO —— 阻塞读取按键状态(等待队列实现)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

上一节获取按键状态时,是在应用层以循环的方式不断读取按键状态,但是我们实际关注的只是当按键被按下时发生的情况,所以大多数时间拿到的状态都是我们不需要的结果。

【Linux驱动】Linux阻塞IO —— 阻塞读取按键状态(等待队列实现),Linux驱动(I.MX6ULL),linux,运维,服务器

对此,当按键被释放时,让 read 接口处于阻塞状态,等按键被按下再解除阻塞。

一、等待队列API

要使用等待队列涉及到两个概念:等待队列头、等待项

等待队列通常使用链表实现,等待队列头便是链表的头节点,在Linux内核中使用 wait_queue_head_t 类型来表示等待队列头;等待项是等待队列中的一个子节点,通常是以线程为单位,将等待项加入到等待队列,相当于让线程处于休眠状态,在Linux内核中使用 wait_queue_t 类型来表示。相关API声明在 <linux/wait.h> 文件中。

1、初始化等待队列

在使用等待队列之前,一般需要“声明 + 初始化”,接口原型如下(本质是宏)

/**
 * @param q 等待队列
 */
void init_waitqueue_head(wait_queue_head_t *q);

2、向等待队列添加等待项

向等待队列添加等待项之前需要先初始化等待项,等待项的初始化比较特殊,无需事先声明变量,使用的接口原型如下

/**
 * @param name 等待项的名字
 * @param tsk  当前所属任务(进程),一般填current,current 是一个全局变量,表示当前进程
 */
DECLARE_WAITQUEUE(name, tsk);

// 示例
// wait     自己拟定的变量名(无需事先声明),可以认为此处便是在“声明+定义”一个等待项
// current  Linux内核的全局变量,表示当前进程
DECLARE_WAITQUEUE(wait, current);

向等待队列添加等待项,add_wait_queue 接口函数不会主动陷入阻塞,需要我们手动设置进程状态;当等待项被唤醒时,会自动从等待队列移除。接口原型如下:

/**
 * @param q     等待队列头
 * @param wait  等待项,需要和前面 DECLARE_WAITQUEUE 的第一个参数保持一致
 */
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

拓展:wait_event_interruptible 在符合等待条件的时候,会自动进入到等待队列挂起,被唤醒时从挂起的位置开始继续运行。但由于无法确定等待条件何时触发,也就不知道在哪个位置挂起,这里推荐使用 add_wait_queue。

3、唤醒等待队列中的等待项

所谓唤醒,其实是将线程/进程由休眠态转变为就绪态,换句话说是将等待项(进程)从等待队列移到运行队列

  • 唤醒:先移出等待队列,再移入运行队列
  • 移除:移出等待队列

唤醒等待项的接口原型如下:

/**
 * @param q     等待队列头
 */
void wake_up(wait_queue_head_t *q);
void wake_up_interruptible(wait_queue_head_t *q);

注意:wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进 程,而 wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。所以在将等待项加入到等待队列以后务必要设置进程的状态

4、从等待队列移除等待项

当遇到一些异常情况导致进程终止时,我们需要主动将等待项从等待队列移除。接口原型如下:

/**
 * @param q     等待队列头
 * @param wait  等待项
 */
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

二、内核API

1、设置进程状态

如果使用 wake_up_interruptible 来唤醒等待项(进程),需要在将等待项(进程)加入到等待队列后,设置进程的状态为 TASK_INTERRUPTIBLE。在内核中使用 __set_current_state 来设置进程状态。比如要设为 TASK_INTERRUPTIBLE

__set_current_state(TASK_INTERRUPTIBLE);

当进程进入到执行队列,此时要将进程状态设为运行状态。

__set_current_state(TASK_RUNNING);

2、重新调度进程

调用 schedule 函数会主动触发调度器,让内核选择下一个要运行的进程。在调用 schedule 函数之前,会先调用 __set_current_state 将进程置于睡眠态或等待条件的状态。这样的话进程会在当前位置阻塞,下一次进程被唤醒时,会从阻塞的地方继续向下执行。

schedule();

三、驱动实现

1、定义等待队列

在字符设备的结构体中声明一个等待队列的头节点,附加一个设备状态

typedef enum {
	Pressed = 0U,
	Released = 1U
}key_status;

struct chrdev_t 
{
    // ... ...
    key_status			status;				/* 设备状态 */
	wait_queue_head_t 	wait_head;			/* 等待队列的头节点 */
};
static struct chrdev_t chrdev;

2、初始化等待队列

在驱动入口函数中初始化该等待队列,初始化设备状态变量,因为我们计划在按键处于释放状态时,新建一个等待项并添加到等待队列,所以需要一个变量来保存按键状态。

/* 驱动入口函数 */
static int __init xxx_init(void)
{
	/* 初始化等待队列头 */
	init_waitqueue_head(&chrdev.wait_head);
    chrdev.status = Released;
   
    // ... 
}

3、向等待队列添加等待项

每当应用层调用 read 接口,在 read 操作函数中,判断按键状态,如果为释放状态则添加到等待队列,同时将当前进程设为休眠态;等待队列中的等待项被唤醒时,再重新设为运行态。

static ssize_t chrdev_read(struct file *pfile, char __user * pbuf, size_t size, loff_t * poff)
{
    // open 操作函数中 pfile->private_data = &chrdev;
	struct chrdev_t* pdev = pfile->private_data;

	if (pdev->status == Released)
	{
		/* 初始化等待项 */
		DECLARE_WAITQUEUE(wait_item, current);

		printk("加入等待队列\n");
		add_wait_queue(&pdev->wait_head, &wait_item);   // 添加到等待队列
		__set_current_state(TASK_INTERRUPTIBLE);        // 当前进程设为休眠态
		schedule();                                     // 重新调度进程(此时会阻塞,等待被唤醒)
		__set_current_state(TASK_RUNNING);              // 被唤醒后,设为运行态  
	}
	printk("等待项被唤醒\n");

    // ...
}

4、唤醒等待队列

当按键任务处理完毕,此时需要唤醒等待队列中的等待项。这里是搭配定时器实现了按键消抖,真正的处理逻辑在定时器的回调函数中。

static irqreturn_t key0_handler(int irq, void * dev)
{
	mod_timer(&timer, timer_delay(50));

	return IRQ_RETVAL(IRQ_HANDLED);
}

/* 定时器回调函数 */
void timer_callback(unsigned long arg)
{
	struct chrdev_t* pdev = (struct chrdev_t*)arg;
	pdev->status = Pressed;

	// 按键处理逻辑

	pdev->status = Released;
	// 唤醒等待项
	wake_up_interruptible(&pdev->wait_head);
}

四、应用测试

在应用程序中,调用一次 read 函数来检测是否加入了等待队列,以及按键按下是否被唤醒。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void printHelp()
{
    printf("usage: ./xxxApp <driver_path>\n");
}

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        printHelp();
        return -1;
    }
    
    char* driver_path = argv[1];      
    int state = 0;
    int ret = 0;
    int fd = 0;

    fd = open(driver_path, O_RDONLY);
    if (fd < 0)
    {
        perror("open file failed");
        return -2;
    }
    ret = read(fd, &state, sizeof(state));
    if (ret < 0)
    {
        printf("read data error\n");
    }

    close(fd);
    return 0;
}

程序开始运行时,因为加入到等待队列,一开始会阻塞

【Linux驱动】Linux阻塞IO —— 阻塞读取按键状态(等待队列实现),Linux驱动(I.MX6ULL),linux,运维,服务器

当按键按下,等待项被唤醒,进程会从挂起的地方,也就是 schedule() 的下一行开始运行

【Linux驱动】Linux阻塞IO —— 阻塞读取按键状态(等待队列实现),Linux驱动(I.MX6ULL),linux,运维,服务器文章来源地址https://www.toymoban.com/news/detail-784836.html

到了这里,关于【Linux驱动】Linux阻塞IO —— 阻塞读取按键状态(等待队列实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32MP157驱动开发——按键驱动(工作队列)

    定时器、下半部 tasklet,它们都是在中断上下文中执行,它们无法休眠。当要处理更复杂的事情时,往往更耗时。这些更耗时的工作放在定时器或是下半部中,会使得系统很卡;并且循环等待某件事情完成也太浪费CPU 资源了。如果使用线程来处理这些耗时的工作,那就可以解

    2024年02月15日
    浏览(36)
  • Linux搭建Web服务器(一)——阻塞与非阻塞、同步与异步、Linux五种IO模型

    目录 0x01 阻塞与非阻塞、同步与异步 阻塞与非阻塞 同步与异步 总结 0x02 Unix、Linux上的五种IO模型 阻塞(blocking) 非阻塞(non-blocking——NIO) IO复用(IO multiplexing) 信号驱动(signal-driven) 异步(asynchronous) 为了理清楚这几个概念,我们可以从 数据就绪 以及 数据读写 层面

    2023年04月10日
    浏览(62)
  • linux 信号原理 信号处理设置signal, 信号发送kill,信号等待sigsuspend,信号阻塞sigprocmask,一网打尽信号使用

    ​ 专栏内容 : postgresql内核源码分析 手写数据库toadb 并发编程 个人主页 :我的主页 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. ================================ 信号是一种软中断的方式,让进程陷入中断处理调用中; linux 下信号也是一种进程间通信的手段;进

    2024年02月13日
    浏览(39)
  • 5.1阻塞和非阻塞、同步和异步 5.2Unix、Linux上的五种IO模型

    典型的一次IO的两个阶段是什么?数据就绪和数据读写 数据就绪:根据IO操作的就绪状态 阻塞 非阻塞 数据读写:根据应用程序和内核的交互方式 同步 异步 陈硕:在处理IO的时候,阻塞和非阻塞都是同步IO,只有使用了特殊的API才是异步IO。 一个典型的网络接口调用,分为两

    2024年02月12日
    浏览(41)
  • Tensorflow入门(2)——深度学习框架Tesnsflow & 线程+队列+IO操作 & 文件读取案例

    Tensorflow入门(1)——深度学习框架Tesnsflow入门 环境配置 认识Tensorflow 在训练样本的时候,希望读入的训练样本时有序的 • tf.FIFOQueue 先进先出队列,按顺序出队列 • tf.RandomShuffleQueue 随机出队列 FIFOQueue(capacity, dtypes, name=‘fifo_queue’) 创建一个以先进先出的顺序对元素进行排

    2024年02月17日
    浏览(46)
  • Linux——信号处理函数与阻塞状态的进程

    这篇博客记录一下我在编写一个简单的多进程回声服务器的时候出现的问题。 这个问题就在于忽略了几个有关于信号处理函数的基本常识: 用通俗的话讲信号注册函数(signal、sigaction)的功能:进程告诉操作系统,当以后收到向信号注册函数传入的信号时,你帮我调用一下信号

    2024年02月13日
    浏览(39)
  • 【Linux】生产者消费者模型(阻塞队列与环形队列)和POSIX信号量

    我们这里举一个例子,来解释生产者消费者模型,我们学生–消费者,供应商–生产者,超市–交易场所,我们买东西只需要关系售货架子上是否有商品即可,没有了商品,超市从供应商进行供货。供应商和供应商不能同时向一个货架进行供货,所以生产者之间是互斥的关系

    2024年02月03日
    浏览(37)
  • 【Linux】生产者消费者模型:基于阻塞队列和环形队列 | 单例模式线程池

    死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。 当多线程并发执行并都需要访问临界资源时,因为每个线程都是不同的执行流,这就有可能 导致数据不一致问题 ,为了避免此问题的发生

    2024年01月24日
    浏览(47)
  • 【Linux】进程状态&&僵尸进程和孤儿进程&&阻塞、挂起和运行

    个人主页 : zxctscl 如有转载请先通知 上一篇博客中提到 【Linux】进程初步理解,这次继续来分享与进程有关的知识。 Linux的进程状态就是struct task_struct内部的一个属性。 为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在

    2024年04月14日
    浏览(37)
  • Linux之【多线程】生产者与消费者模型&BlockQueue(阻塞队列)

    举个例子:学生要买东西,一般情况下都会直接联系厂商,因为买的商品不多,对于供货商来说交易成本太高,所以有了交易场所超市这个媒介的存在。目的就是为了集中需求,分发产品。 消费者与生产者之间通过了超市进行交易。当生产者不需要的时候,厂商可以继续生产

    2024年02月02日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包