7.1 阻塞与非阻塞 IO

  阻塞操作是指在执行设备操作的时候,若不能获取资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。

  非阻塞操作的进程在不能进行设备操作时,并不挂起,要么放弃,要么不停的查询,直到可以进行操作为止。

  驱动程序应提供这样的能力:当应用程序进行 read()、write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应在设备驱动的 xxx_write()、xxx_read()等操作中将进程阻塞直到资源可取,此后,应用程序的 read()、write() 等调用才返回,整个过程仍然进行了正确的设备访问,但用户无感知;若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的 xxx_write()、xxx_read() 等操作应立即返回, read()、write() 等调用也随即返回,应用程序收到 -EAGAIN 的返回值。

7.1.1 等待队列

  在 Linux 驱动中,可使用等待队列(wait queue)来实现阻塞进程的唤醒。等待队列以队列为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问。

 /**
* 等待队列头数据结构
* 定义等待队列头:
* wait_queue_head_t my_queue;
*/
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
 /**
* 初始化等待队列头部
*/
extern void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *); #define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), #q, &__key); \
} while ()
 /** 定义并初始化等待队列头部 */
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
 /** 定义等待队列元素,用于定义并初始化一个名为 name 的等待队列元素 */
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
 /** 添加等待队列:将等待队列元素 wait 添加到等待队列头部 q 指向的双向链表中 */
extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
 /** 删除等待队列:将等待队列元素 wait 从由等待队列头部 q 指向的链表中删除 */
extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
 /**
* 等待事件
* wq:作为等待队列头部的队列被唤醒
* condition:此参数必须满足,否则继续阻塞
* wait_event 和 wait_event_interruptible 区别是后者可以被信号打断,而前者不能
* 加上 _timeout 后表示阻塞等待的超时时间,以 jiffy 为但闻,在第三个参数的 timeout 到达时,不论 condition 是否满足,均返回
*/
wait_event(wq, condition);
wait_event_interruptible(wq, condition);
wait_event_timeout(wq, condition, timeout);
wait_event_interruptible_timeout(wq, condition, timeout);
 /**
* 唤醒队列
* 唤醒以 q 作为等待队列头部的队列中的所有进程
* wake_up 与 wait_event 或 wait_event_timeout 成对使用
* wake_up_interruptible 与 wait_event_interruptible 或 wait_event_interruptible_timeout 成对使用
* wake_up 可唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 的进程
* wake_up_interruptible 只能唤醒处于 TASK_INTERRUPTIBLE 的进程
*/
void wake_up(wait_queue_head_t *q);
void wake_up_interruptible(wait_queue_head_t *q);

7.1.2 globalmem 增加队列操作

  增加约束:把 globalmem 中的全局内存变为一个 FIFO,只有当 FIFO 有数据的时候(即有进程把数据写到这个 FIFO 而且没有没有被读进程读空),读进程才能把数据读出,而且读取后的数据会从 globalmem 的全局内存中被拿掉;只有当 FIFO 不是满的时候(即还有一些空间未被写,或写满后被读进程从这个 FIFO 中读出了数据),写进程才能往这个 FIFO 中写数据。

 #include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/sched/signal.h> ///< 内核>5.0 使用
//#include <linux/sched.h> #define GLOBALFIFO_SIZE 0x1000
//#define MEM_CLEAR 0X1
#define GLOBALFIFO_MAGIC 'g'
#define MEM_CLEAR _IO(GLOBALFIFO_MAGIC, 0)
#define GLOBALFIFO_MAJOR 230
#define DEVICE_NUMBER 10 static int globalfifo_major = GLOBALFIFO_MAJOR;
module_param(globalfifo_major, int, S_IRUGO); struct globalfifo_dev {
struct cdev cdev;
/**
* 目前 FIFO 中有效数据长度
* current_len = 0, 表示 FIFO 为空
* current_len = GLOBALFIFO_SIZE, 表示 FIFO 满
*/
unsigned int current_len;
unsigned char mem[GLOBALFIFO_SIZE];
struct mutex mutex;
wait_queue_head_t r_wait; ///< 读等待队列头
wait_queue_head_t w_wait; ///< 写等待队列头
}; struct globalfifo_dev *globalfifo_devp; /**
* 这里涉及到私有数据的定义,大多数遵循将文件私有数据 pirvate_data 指向设备结构体,
* 再用 read write llseek ioctl 等函数通过 private_data 访问设备结构体。
* 对于此驱动而言,私有数据的设置是在 open 函数中完成的
*/
static int globalfifo_open(struct inode *inode, struct file *filp)
{
/**
* NOTA:
* container_of 的作用是通过结构体成员的指针找到对应结构体的指针。
* 第一个参数是结构体成员的指针
* 第二个参数是整个结构体的类型
* 第三个参数为传入的第一个参数(即结构体成员)的类型
* container_of 返回值为整个结构体指针
*/
struct globalfifo_dev *dev = container_of(inode->i_cdev, struct globalfifo_dev, cdev);
filp->private_data = dev;
return ;
} static int globalfifo_release(struct inode *inode, struct file *filp)
{
return ;
} static long globalfifo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct globalfifo_dev *dev = filp->private_data; switch(cmd){
case MEM_CLEAR:
mutex_lock(&dev->mutex);
memset(dev->mem, , GLOBALFIFO_SIZE);
printk(KERN_INFO "globalfifo is set to zero\n");
mutex_unlock(&dev->mutex);
break;
default:
return -EINVAL;
} return ;
} static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = ;
switch(orig) {
case : /** 从文件开头位置 seek */
if(offset < ){
ret = -EINVAL;
break;
}
if((unsigned int)offset > GLOBALFIFO_SIZE){
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case : /** 从文件当前位置开始 seek */
if((filp->f_pos + offset) > GLOBALFIFO_SIZE){
ret = -EINVAL;
break;
}
if((filp->f_pos + offset) < ){
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
break;
} return ret;
} static ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
unsigned int count = size;
int ret = ;
struct globalfifo_dev *dev = filp->private_data; DECLARE_WAITQUEUE(wait, current); ///< 将当前进程加入到 wait 等待队列
mutex_lock(&dev->mutex);
add_wait_queue(&dev->w_wait, &wait); ///< 添加等待队列元到读队列头中 /** 判断设备是否可写 */
while(dev->current_len == GLOBALFIFO_SIZE){
/** 若是非阻塞访问, 设备忙时, 直接返回 -EAGAIN */
if(filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
} __set_current_state(TASK_INTERRUPTIBLE); ///<改变进程状态为睡眠
schedule();
if(signal_pending(current)){ ///< 因为信号而唤醒
ret = -ERESTARTSYS;
goto out2;
}
} if(count > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len; if(copy_from_user(dev->mem + dev->current_len, buf, count)){
ret = -EFAULT;
goto out;
} else {
dev->current_len += count;
printk(KERN_INFO "written %u bytes(s), current len:%d\n", count, dev->current_len); wake_up_interruptible(&dev->r_wait); ///< 唤醒读等待队列
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait, &wait); ///< 移除等待队列
set_current_state(TASK_RUNNING);
return ret;
} /**
* *ppos 是要读的位置相对于文件开头的偏移,如果该偏移大于或等于 GLOBALFIFO_SIZE,意味着已经独到文件末尾
*/
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
unsigned int count = size;
int ret = ;
struct globalfifo_dev *dev = filp->private_data; DECLARE_WAITQUEUE(wait, current); ///< 将当前进程加入到 wait 等待队列
mutex_lock(&dev->mutex);
add_wait_queue(&dev->r_wait, &wait); ///< 添加等待队列元到读队列头中 /** 等待 FIFO 非空,即判断设备是否可读 */
while(dev->current_len == ) {
/** 若是非阻塞访问, 设备忙时, 直接返回 -EAGAIN */
/** filp->f_flags 是用户空间 */
if(filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
} /**
* 阻塞访问,调度其他进程执行
* FIFO 为空的情况下,读进程阻塞,必须依赖写进程往 FIFO 里面写东西唤醒它;
* 但写的进程为了 FIFO,它必须拿到这个互斥体来访问 FIFO 这个临界资源;
* 如果读进程把自己调度出去之前不释放这个互斥体,那么读写进程之间就死锁了
*/
__set_current_state(TASK_INTERRUPTIBLE); ///<改变进程状态为睡眠
mutex_unlock(&dev->mutex);
schedule();
if(signal_pending(current)){ ///< 因为信号而唤醒
ret = -ERESTARTSYS;
goto out2;
} mutex_lock(&dev->mutex);
} /** 要读取的字节数大于设备文件中的有效数据长度 */
if(count > dev->current_len)
count = dev->current_len; /** 从用户空间拷贝数据 */
if(copy_to_user(buf, dev->mem, count)) {
ret = -EFAULT;
goto out;
} else {
/** FIFO 中数据前移 */
memcpy(dev->mem, dev->mem + count, dev->current_len - count);
dev->current_len -= count; ///< 有效数据长度减少
printk(KERN_INFO "read %u bytes(s), current_len: %d\n", count, dev->current_len); wake_up_interruptible(&dev->w_wait); ///< 唤醒写等待队列 ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->r_wait, &wait); ///< 移除等待队列
set_current_state(TASK_RUNNING);
return ret;
} static const struct file_operations globalfifo_fops = {
.owner = THIS_MODULE,
.llseek = globalfifo_llseek,
.read = globalfifo_read,
.write = globalfifo_write,
.unlocked_ioctl = globalfifo_ioctl,
.open = globalfifo_open,
.release = globalfifo_release,
}; /**
* @brief globalfifo_setup_cdev
*
* @param dev
* @param index 次设备号
*/
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
{
int err;
int devno = MKDEV(globalfifo_major, index); /** 使用 cdev_init 即是静态初始化了 cdev */
cdev_init(&dev->cdev, &globalfifo_fops);
dev->cdev.owner = THIS_MODULE; /** 设备编号范围设置为1,表示我们只申请了一个设备 */
err = cdev_add(&dev->cdev, devno, );
if(err)
printk(KERN_NOTICE "Error %d adding globalfifo%d\n", err, index);
} static int __init globalfifo_init(void)
{
int ret;
int i;
dev_t devno = MKDEV(globalfifo_major, ); if(globalfifo_major)
ret = register_chrdev_region(devno, DEVICE_NUMBER, "globalfifo");
else {
ret = alloc_chrdev_region(&devno, , DEVICE_NUMBER, "globalfifo");
globalfifo_major = MAJOR(devno);
} if(ret < )
return ret; globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
if(!globalfifo_devp){
ret = -ENOMEM;
goto fail_malloc;
} for(i = ; i < DEVICE_NUMBER; i++){
globalfifo_setup_cdev(globalfifo_devp + i, i);
} mutex_init(&globalfifo_devp->mutex); /** 初始化读写等待队列 */
init_waitqueue_head(&globalfifo_devp->r_wait);
init_waitqueue_head(&globalfifo_devp->w_wait); fail_malloc:
unregister_chrdev_region(devno, );
return ret;
} static void __exit globalfifo_exit(void)
{
int i;
for(i = ; i < DEVICE_NUMBER; i++) {
cdev_del(&(globalfifo_devp + i)->cdev);
}
kfree(globalfifo_devp);
unregister_chrdev_region(MKDEV(globalfifo_major, ), );
} module_init(globalfifo_init);
module_exit(globalfifo_exit);

  编译验证:

  插入模块:insmod globalfifo.ko

  创建设备节点:mknod /dev/globalfifo c 230 0

  启动两个进程:

    • 读进程,在后台运行:cat /dev/globalfifo &
    • 写进程,在前台运行:echo 'hello world' > /dev/globalfifo

七、设备驱动中的阻塞与非阻塞 IO(一)的更多相关文章

  1. 七、设备驱动中的阻塞与非阻塞 IO(二)

    7.2 轮询 7.2.1 介绍 在用户程序中的 select() 和 poll() 函数最终会使设备驱动中的 poll() 函数被执行. 设备驱动程序中的轮询函数原型: /** 用于询问设备是否可以非 ...

  2. Linux设备驱动中的阻塞和非阻塞I/O

    [基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作.被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到 ...

  3. 蜕变成蝶~Linux设备驱动中的阻塞和非阻塞I/O

    今天意外收到一个消息,真是惊呆我了,博客轩给我发了信息,说是俺的博客文章有特色可以出本书,,这简直让我受宠若惊,俺只是个大三的技术宅,写的博客也是自己所学的一些见解和在网上看到我一些博文以及帖子里综合 ...

  4. Linux设备驱动中的IO模型---阻塞和非阻塞IO【转】

    在前面学习网络编程时,曾经学过I/O模型 Linux 系统应用编程——网络编程(I/O模型),下面学习一下I/O模型在设备驱动中的应用. 回顾一下在Unix/Linux下共有五种I/O模型,分别是: ...

  5. Linux设备驱动中的阻塞和非阻塞I/O <转载>

    Green 博客园 首页 新随笔 联系 订阅 管理 Linux设备驱动中的阻塞和非阻塞I/O   [基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件 ...

  6. 《linux设备驱动开发详解》笔记——8阻塞与非阻塞IO

    8.1 阻塞与非阻塞IO 8.1.0 概述 阻塞:访问设备时,若不能获取资源,则进程挂起,进入睡眠状态:也就是进入等待队列 非阻塞:不能获取资源时,不睡眠,要么退出.要么一直查询:直接退出且无资源时, ...

  7. Linux设备驱动中的异步通知与异步I/O

    异步通知概念: 异步通知的意识是,一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上的“中断”概念,比较准确的称谓是“信号驱动的异步IO”,信号是在软件层次 ...

  8. linux驱动编写之阻塞与非阻塞

    一.概念 应用程序使用API接口,如open.read等来最终操作驱动,有两种结果--成功和失败.成功,很好处理,直接返回想要的结果:但是,失败,是继续等待,还是返回失败类型呢?  如果继续等待,将进 ...

  9. IO模型浅析-阻塞、非阻塞、IO复用、信号驱动、异步IO、同步IO

    最近看到OVS用户态的代码,在接收内核态信息的时候,使用了Epoll多路复用机制,对其十分不解,于是从网上找了一些资料,学习了一下<UNIX网络变成卷1:套接字联网API>这本书对应的章节 ...

随机推荐

  1. 《Java编程思想》读书笔记<一>

    第二章 一切皆对象 java是面向对象的语言. 1.我们怎么操作对象? 每种语言都有自己的操纵内存中元素的方式,java使用引用操作内存中元素(对象).引用可以独立存在,例如:String s:表示创 ...

  2. retrofit2+rxjava+okhttp网络请求实现

    第一步:添加依赖: compile 'io.reactivex:rxandroid:1.2.0' compile 'com.squareup.retrofit2:adapter-rxjava:2.1. ...

  3. Django配置Mysql数据库 (Pycharm)

    Django配置MySQL数据库方法 一.settings.py文件中修改数据库配置为下面的内容: # Database # https://docs.djangoproject.com/en/2.0 ...

  4. zabbix日志报错解决

    [root@bogon ldap]# cat /tmp/zabbix_server.log 9135:20181204:085433.351 using configuration file: /us ...

  5. CentOS定时备份MySQL数据库

    1.编写备份脚本 vi /usr/sbin/mysql_dy_backup.sh #!/bin/bash #备份路径 BACKUP=/data/backup/sql/dy #当前时间 DATETIME ...

  6. 修改Windows 2008以后系统的NTP服务设置

    @echo off echo autor OAK @echo off echo -------------------------------- @echo off echo setup time r ...

  7. python 并发编程 同步调用和异步调用 回调函数

    提交任务的两张方式: 1.同步调用 2.异步调用 同步调用:提交完任务后,就在原地等待任务执行完后,拿到结果,再执行下一行代码 同步调用,导致程序串行执行 from concurrent.future ...

  8. C# StreamReader与StreamWriter

    原文:https://www.cnblogs.com/kissdodog/archive/2013/01/27/2878667.html StreamReader实现了抽象基类TextReader类, ...

  9. Axios 的基本使用

    Axios 是一个基于 promise 的HTTP 库, 可以用在浏览器和 node.js 中. 1. 从浏览器创建 XMLHttpRequests 2. 从node.js 创建 http 请求 3. ...

  10. algorithm下的常用函数

    algorithm下的常用函数 max(),min(),abs() max(x,y)返回x和y中最小的数字 min(x,y)返回x和y中最大的数字 abs(x)返回x的绝对值,注意x应当是整数,如果是 ...