文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Linux怎么实现设备阻塞/非阻塞读写

2023-06-16 14:10

关注

本篇内容介绍了“Linux怎么实现设备阻塞/非阻塞读写”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

设备阻塞IO的实现

当我们读写设备文件的IO时,最终会回调驱动中相应的接口,而这些接口也会出现在读写设备进程的进程(内核)空间中,如果条件不满足,接口函数使进程进入睡眠状态,即使读写设备的用户进程进入了睡眠,也就是我们常说的发生了阻塞。In a word,读写设备文件阻塞的本质是驱动在驱动中实现对设备文件的阻塞,其读写的流程可概括如下:

1. 定义-初始化等待队列头

//定义等待队列头  wait_queue_head_t waitq_h;//初始化,等待队列头  init_waitqueue_head(wait_queue_head_t *q); //或//定义并初始化等待队列头  DECLARE_WAIT_QUEUE_HEAD(waitq_name);

上面的几条选择中,***一种会直接定义并初始化一个等待头,但是如果在模块内使用全局变量传参,用着并不方便,具体用哪种看需求。

我们可以追一下源码,看一下上面这几行都干了什么:

//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;

wait_queue_head_t

--36-->这个队列用的自旋锁

--27-->将整个队列"串"在一起的纽带

然后我们看一下初始化的宏:

#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)

DECLARE_WAIT_QUEUE_HEAD()

--60-->根据传入的字符串name,创建一个名为name的等待队列头

--57-->初始化上述task_list域,竟然没有用内核标准的初始化宏,无语。。。

2. 将本进程添加到等待队列

为等待队列添加事件,即进程进入睡眠状态直到condition为真才返回。**_interruptible的版本版本表示睡眠可中断,_timeout**版本表示超时版本,超时就会返回,这种命名规范在内核API中随处可见。

void wait_event(wait_queue_head_t *waitq_h,int condition); void wait_event_interruptible(wait_queue_head_t *waitq_h,int condition); void wait_event_timeout(wait_queue_head_t *waitq_h,int condition); void wait_event_interruptible_timeout(wait_queue_head_t *waitq_h,int condition);

这可是等待队列的核心,我们来看一下

wait_event

└── wait_event

└── _wait_event

├── abort_exclusive_wait

├── finish_wait

├── prepare_to_wait_event

└── ___wait_is_interruptible

 #define wait_event(wq, condition)                                 do {                                                                      if (condition)                                                           break;                                                    __wait_event(wq, condition);                               } while (0)

wait_event

--246-->如果condition为真,立即返回

--248-->否则调用__wait_event

#define ___wait_event(wq,condition,state, exclusive, ret, cmd) \   ({                                                            \     for (;;) {                                                 \        long __int = prepare_to_wait_event(&wq, &__wait, state);\                                                                \         if (condition)                                          \                   break;                                              \        if (___wait_is_interruptible(state) && __int) {         \            __ret = __int;                                      \        if (exclusive) {                                        \            abort_exclusive_wait(&wq, &__wait,                  \            state, NULL);                                       \            goto __out;                                         \        }                                                       \        break;                                                  \     }                                                          \     cmd;                                                       \   }                                                            \   finish_wait(&wq, &__wait);                                   \   __out:  __ret;                                               \  })

--206-->死循环的轮询

--209-->如果条件为真,跳出循环,执行finish_wait();进程被唤醒

--212-->如果进程睡眠的方式是interruptible的,那么当中断来的时候也会abort_exclusive_wait被唤醒

--222-->如果上面两条都不满足,就会回调传入的schedule(),即继续睡眠

模板

struct wait_queue_head_t xj_waitq_h; static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset) {    if(!condition)    //条件可以在中断处理函数中置位         wait_event_interruptible(&xj_waitq_h,condition); } static file_operations fops = {     .read = demo_read, }; static __init demo_init(void){     init_waitqueue_head(&xj_waitq_h); }

IO多路复用的实现

对于普通的非阻塞IO,我们只需要在驱动中注册的read/write接口时不使用阻塞机制即可,这里我要讨论的是IO多路复用,即当驱动中的read/write并没有实现阻塞机制的时候,我们如何利用内核机制来在驱动中实现对IO多路复用的支持。下面这个就是我们要用的API

int poll(struct file *filep, poll_table *wait);void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

当应用层调用select/poll/epoll机制的时候,内核其实会遍历回调相关文件的驱动中的poll接口,通过每一个驱动的poll接口的返回值,来判断该文件IO是否有相应的事件发生,我们知道,这三种IO多路复用的机制的核心区别在于内核中管理监视文件的方式,分别是位,数组,链表,但对于每一个驱动,回调的接口都是poll。

模板

struct wait_queue_head_t waitq_h;static unsigned int demo_poll(struct file *filp, struct poll_table_struct *pts){    unsigned int mask = 0;     poll_wait(filp, &wwaitq_h, pts);    if(counter){         mask = (POLLIN | POLLRDNORM);     }    return mask; }static struct file_operations fops = {     .owner  = THIS_MODULE,     .poll   = demo_poll, };static __init demo_init(void){     init_waitqueue_head(&xj_waitq_h); }

其他API

刚才我们讨论了如何使用等待队列实现阻塞IO,非阻塞IO,其实关于等待队列,内核还提供了很多其他API用以完成相关的操作,这里我们来认识一下 

//在等待队列上睡眠sleep_on(wait_queue_head_t *wqueue_h); sleep_on_interruptible(wait_queue_head_t *wqueue_h);//唤醒等待的进程  void wake_up(wait_queue_t *wqueue); void wake_up_interruptible(wait_queue_t *wqueue);

“Linux怎么实现设备阻塞/非阻塞读写”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-人工智能
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯