Linux阻塞IO(高级字符设备二)

  阻塞IO属于同步 IO,阻塞IO在Linux内核中是非常常用的 IO 模型,所依赖的机制是等待队列。

一、等待队列介绍

  在 Linux 驱动程序中,阻塞进程可以使用等待队列来实现。等待队列是内核实现阻塞和唤醒的内核机制,以双循环链表为基础结构,由链表头和链表项两部分组成,分别表示等待队列头和等待队列元素
在这里插入图片描述
  等待队列头使用结构体 wait_queue_head_t 来表示,等待队列头是一个等待队列的头部,这个结构体定义在文件 include/linux/wait.h 里面,结构体内容如下所示:

struct _wait_queue_head
{spinlock_t lock; //自旋锁struct list_head task_list //链表头
};
typefef struct _wait_queue_head wait_queue_head_t;

  等待队列项使用结构体 wait_queue_t 来表示,等待队列项是等待队列元素,该结构体同样定义在文件 include/linux/wait.h 里面,结构体内容如下所示:

struct _wait_queue
{
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct _wait_queue wait_queue_t;

二、等待队列 API

2.1、定义并初始化等待队列头

  等待队列要想被使用,第一步就是对等待队列头进行初始化,有俩种办法如下所示:

2.1.1、方法一

  使用 DECLARE_WAIT_QUEUE_HEAD 宏静态创建等待队列头,宏定义如下:

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

  参数 name 表示要定义的队列头名字。通常以全局变量的方式定义,如下所示:

DECLARE_WAIT_QUEUE_HEAD(head);
2.1.1、方法二

  使用 init_waitqueue_head 宏动态初始化等待队列头,宏定义如下:

#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \__init_waitqueue_head((q), #q, &__key); \} while (0)

  参数 q 表示需要初始化的队列头指针。使用宏定义如下所示:

wait_queue_head_t head; //等待队列头
init_waitqueue_head(&head); //初始化等待队列头指针

2.2、创建等待队列项

  一般使用宏 DECLARE_WAITQUEUE(name,tsk)给当前正在运行的进程创建并初始化一个等待队列项,宏定义如下:

#define DECLARE_WAITQUEUE(name, tsk) \
struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)

  第一个参数 name 是等待队列项的名字,第二个参数 tsk 表示此等待队列项属于哪个任务(进程),一般设置为 current。在 Linux 内核中 current 相当于一个全局变量,表示当前进程。
  创建等待队列项如下所示:

DECLARE_WAITQUEUE(wait,current); //给当前正在运行的进程创建一个名为wait的等待队列项。
add_wait_queue(wq,&wait); //将 wait 这个等待队列项加到wq 这个等待队列当中

2.3、添加/删除队列

  当设备没有准备就绪(如没有可读数据)而需要进程阻塞的时候,就需要将进程对应的等待队列项添加到前面创建的等待队列中,只有添加到等待队列中以后进程才能进入休眠态。当设备可以访问时(如有可读数据),再将进程对应的等待队列项从等待队列中移除即可。
  等待队列项添加队列函数如下所示
  函数原型:
    void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
  函数功能:
    (通过等待队列头)向等待队列中添加队列项
  参数含义:
    wq_head 表示等待队列项要加入等待队列的等待队列头
    wq_entry 表示要加入的等待队列项函数
  返回值
    无
  等待队列项移除队列函数如下所示
  函数原型:
    void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
  函数功能:
    要删除的等待队列项所处的等待队列头
  参数含义:
    第一个参数 q 表示等待队列项要加入等待队列的等待队列头
    第二个参数 wait 表示要加入的等待队列项函数
  返回值:
    无

2.4、等待事件

  除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程,使用如下所示的宏,是不可中断的阻塞等待。

#define __wait_event(wq_head, condition) (void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0, schedule())

  宏定义功能:
    不可中断的阻塞等待,让调用进程进入不可中断的睡眠状态,在等待队列里面睡眠直到condition 变成真,被内核唤醒。
  参数含义:
    第一个参数 wq: wait_queue_head_t 类型变量
    第二个参数 condition : 等待条件,为假时才可以进入休眠。如果condition 为真,则不会休眠
  除此之外,wait_event_interruptible 的宏是可中断的阻塞等待。

#define __wait_event_interruptible(wq_head, condition) ___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0, schedule())

  宏含义功能:
    可中断的阻塞等待,让调用进程进入可中断的睡眠状态,直到condition 变成真被内核唤醒或信号打断唤醒。
  参数含义:
    第一个参数 wq :wait_queue_head_t 类型变量
    第二个参数 condition :等待条件。为假时才可以进入休眠。如果condition 为真,则不会休眠。

  wait_event_timeout() 宏也与 wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且 condition 为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回 0。
  wait_event_interruptible_timeout() 宏与 wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回 ERESTARTSYS 错误码。
  wait_event_interruptible_exclusive() 宏同样和   wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程
  注意:调用的时要确认 condition 值是真还是假,如果调用 condition 为真,则不会休眠。

2.5、等待队列唤醒

  当设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下俩个函数

函数原型:wake_up(wait_queue_head_t *q)
函数功能:唤醒所有休眠进程
参数含义:q 表示要唤醒的等待队列的等待队列头
函数原型:wake_up_interruptible(wait_queue_head_t *q)
函数功能:唤醒可中断的休眠进程
参数含义:q 表示要唤醒的等待队列的等待队列头

三、等待队列使用方法

  步骤一:初始化等待队列头,并将条件置成假(condition=0)。
  步骤二:在需要阻塞的地方调用 wait_event(),使进程进入休眠状态。
  步骤三:当条件满足时,需要解除休眠,先将条件(condition=1),然后调用wake_up函数唤醒等待队列中的休眠进程。

四、阻塞IO驱动程序示例

4.1、阻塞IO驱动程序

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include  <linux/wait.h>struct device_test{dev_t dev_num;  //设备号int major ;  //主设备号int minor ;  //次设备号struct cdev cdev_test; // cdevstruct class *class;   //类struct device *device; //设备char kbuf[32];int  flag;  //标志位
};struct  device_test dev1;  DECLARE_WAIT_QUEUE_HEAD(read_wq); //定义并初始化等待队列头/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{file->private_data=&dev1;//设置私有数据printk("This is cdev_test_open\r\n");return 0;
}/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{struct device_test *test_dev=(struct device_test *)file->private_data;if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据{printk("copy_from_user error\r\n");return -1;}test_dev->flag=1;//将条件置1wake_up_interruptible(&read_wq); //并使用wake_up_interruptible唤醒等待队列中的休眠进程return 0;
}/**从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{struct device_test *test_dev=(struct device_test *)file->private_data;wait_event_interruptible(read_wq,test_dev->flag); //可中断的阻塞等待,使进程进入休眠态if (copy_to_user(buf, test_dev->kbuf, strlen( test_dev->kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据{printk("copy_to_user error\r\n");return -1;}return 0;
}static int cdev_test_release(struct inode *inode, struct file *file)
{return 0;
}/*设备操作函数*/
struct file_operations cdev_test_fops = {.owner = THIS_MODULE, //将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块.open = cdev_test_open, //将open字段指向chrdev_open(...)函数.read = cdev_test_read, //将open字段指向chrdev_read(...)函数.write = cdev_test_write, //将open字段指向chrdev_write(...)函数.release = cdev_test_release, //将open字段指向chrdev_release(...)函数
};static int __init chr_fops_init(void) //驱动入口函数
{/*注册字符设备驱动*/int ret;/*1 创建设备号*/ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); //动态分配设备号if (ret < 0){goto err_chrdev;}printk("alloc_chrdev_region is ok\n");dev1.major = MAJOR(dev1.dev_num); //获取主设备号dev1.minor = MINOR(dev1.dev_num); //获取次设备号printk("major is %d \r\n", dev1.major); //打印主设备号printk("minor is %d \r\n", dev1.minor); //打印次设备号/*2 初始化cdev*/dev1.cdev_test.owner = THIS_MODULE;cdev_init(&dev1.cdev_test, &cdev_test_fops);/*3 添加一个cdev,完成字符设备注册到内核*/ret =  cdev_add(&dev1.cdev_test, dev1.dev_num, 1);if(ret<0){goto  err_chr_add;}/*4 创建类*/dev1. class = class_create(THIS_MODULE, "test");if(IS_ERR(dev1.class)){ret=PTR_ERR(dev1.class);goto err_class_create;}/*5  创建设备*/dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");if(IS_ERR(dev1.device)){ret=PTR_ERR(dev1.device);goto err_device_create;}return 0;err_device_create:class_destroy(dev1.class);                 //删除类err_class_create:cdev_del(&dev1.cdev_test);                 //删除cdeverr_chr_add:unregister_chrdev_region(dev1.dev_num, 1); //注销设备号err_chrdev:return ret;
}static void __exit chr_fops_exit(void) //驱动出口函数
{/*注销字符设备*/unregister_chrdev_region(dev1.dev_num, 1); //注销设备号cdev_del(&dev1.cdev_test);                 //删除cdevdevice_destroy(dev1.class, dev1.dev_num);       //删除设备class_destroy(dev1.class);                 //删除类
}
module_init(chr_fops_init);
module_exit(chr_fops_exit);

4.2、阻塞IO使用API要点

DECLARE_WAIT_QUEUE_HEAD(read_wq); //定义并初始化等待队列头
wake_up_interruptible(&read_wq); //使用wake_up_interruptible唤醒等待队列中的休眠进程
wait_event_interruptible(read_wq,test_dev->flag); //可中断的阻塞等待,使进程进入休眠态

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/118753.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C笔记:引用调用,通过指针传递

代码 #include<stdio.h> int max1(int num1,int num2) {if(num1 < num2){num1 num2;}else{num2 num1;} } int max2(int *num1,int *num2) {if(num1 < num2){*num1 *num2; // 把 num2 赋值给 num1 }else{*num2 *num1;} } int main() {int num1 0,num2 -2;int…

SLAM从入门到精通(三边测量法详解)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 三边测量&#xff0c;或者说叫三角定位&#xff0c;是定位系统中很常见的一种测量方法。它最主要的原理就是依靠已有的三个特征坐标和半径&#xf…

Windows10系统安装telnet命令

简介 telnet命令可以测试目标服务器端口是否开通&#xff0c;使用命令 telnet ip地址 端口&#xff0c;输入命令后回车&#xff0c;如果进入输入状态&#xff0c;则表示目标服务器端口已开通&#xff0c;可以通过外网访问 Windows10系统安装步骤 1.打开控制面板 2.选择程序…

Hadoop3教程(三十一):(生产调优篇)异构存储

文章目录 &#xff08;157&#xff09;异构存储概述概述异构存储的shell操作 &#xff08;158&#xff09;异构存储案例实操参考文献 &#xff08;157&#xff09;异构存储概述 概述 异构存储&#xff0c;也叫做冷热数据分离。其中&#xff0c;经常使用的数据被叫做是热数据&…

Python Opencv实践 - 车辆统计(2)检测线绘制,车辆数量计数和显示

针对我所使用的视频&#xff0c;对上一节的代码进行了修改&#xff0c;增加了更多参数。 Python Opencv实践 - 车辆统计&#xff08;1&#xff09;读取视频&#xff0c;移除背景&#xff0c;做预处理_亦枫Leonlew的博客-CSDN博客示例中的图像的腐蚀、膨胀和闭运算等需要根据具…

操作系统-浅谈CPU与内存

目录 计算机的基本组成CPU内存虚拟内存内存分段内存分页 CPU与内存的交互过程高速缓存cache 所有图片均来自&#xff1a;小林coding 计算机的基本组成 计算机由软件和硬件组成 硬件由CPU(中央处理器&#xff09;存储器(内存外存&#xff09;外部设备组成。 软件由应用软件和系…

【算法】模拟退火算法(SAA,Simulated Annealing Algorithm)

模拟退火算法&#xff08;SAA&#xff09;简介 模拟退火算法&#xff08;SAA&#xff0c;Simulated Annealing Algorithm&#xff09;的灵感来源于工艺铸造流程中的退火处理&#xff0c;随着铸造温度升高&#xff0c;分子运动趋于无序&#xff0c;徐徐冷却后&#xff0c;分子运…

ES6初步了解Symbol的用法

ES6中为我们新增了一个原始数据类型Symbol&#xff0c;让我为大家介绍一下吧&#xff01; Symbol它表示是独一无二的值 Symbol要如何创建 第一种创建方式&#xff1a; let sy Symbol()第二种创建方式&#xff1a; let sy Symbol.for()具体独一无二在哪呢&#xff1f;它们的地…

开源思维导图白板工具

https://okso.app https://drawio.com https://tldraw.com https://excalidraw.com

18 Transformer 的动态流程

博客配套视频链接: https://space.bilibili.com/383551518?spm_id_from333.1007.0.0 b 站直接看 配套 github 链接&#xff1a;https://github.com/nickchen121/Pre-training-language-model 配套博客链接&#xff1a;https://www.cnblogs.com/nickchen121/p/15105048.html 机…

npm install 报node-sass command failed

一、前言 最近在前端项目Vue项目install时会出现node-sass command failed的错误&#xff0c;原因是NodeJS和node-sass的版本不对应导致的&#xff0c;本文将给出解决方案。 二、解决方案 以下是NodeJS和node-sass版本的对照关系&#xff1a;

C++前缀和算法的应用:摘水果 原理源码测试用例

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 题目 在一个无限的 x 坐标轴上&#xff0c;有许多水果分布在其中某些位置。给你一个二维整数数组 fruits &#xff0c;其中 fruits[i] [positioni, amounti] 表示共…

1221. 四平方和--(暴力,二分)

题目&#xff1a; 1221. 四平方和 - AcWing题库 思路1&#xff1a;暴力 暴力枚举 1.枚举顺序为从a到c&#xff0c;依次增大。 2.tn-a*a-b*b-c*c&#xff0c;求得dsqrt(t) 3.判断求出的d是否成立。d要求&#xff1a;d*dt&&d>c #include<iostream> #include&…

pytorch实战---IMDB情感分析

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

原始航片匀色调色方法

使用PhotoRC 2.0软件&#xff0c;对原始航片进行批量匀色&#xff0c;可以自动处理和人机交互&#xff0c;保留exif信息。 软件下载链接&#xff1a; https://pan.baidu.com/s/1Jj4cMpq8xzYvSa1hhozH-g?pwdndfm 提取码&#xff1a;ndfm

知识点滴 - Email地址不区分大小写

电子邮件地址本身对字符大小写不敏感。这意味着实际的电子邮件地址&#xff0c;如 "exampleemail.com"&#xff0c;并不区分字母的大小写。无论你输入的是大写字母还是小写字母&#xff0c;它仍然会到达同一个电子邮件账户。例如&#xff0c;如果您的电子邮件地址是 …

Linux 用户必备的 Git 图形化工具

Git 是一个免费的开源分布式版本控制系统&#xff0c;用于软件开发和其他几个版本控制任务。它旨在根据速度、效率和数据完整性来处理从小到大的项目。 Linux 用户主要可以通过命令行管理 Git&#xff0c;但是&#xff0c;有几个图形用户界面 (GUI) Git 客户端可以促进在 Linux…

DevOps持续集成-Jenkins(3)

文章目录 DevOpsDevOps概述Jenkins实战3&#xff1a;实战1和实战2的加强版&#xff08;新增SonarQube和Harbor&#xff09;⭐环境准备⭐项目架构图对比Jenkins实战1和实战2&#xff0c;新增内容有哪些&#xff1f;SonarQube教程采用Docker安装SonarQube &#xff08;在Jenkins所…

linux-防火墙

目录 一、防火墙概念 1.软件防火墙 2.iptables默认规则 3.iptables的五链 4.iptables动作 5.四表五链 6.iptables实例 一、防火墙概念 linux下防火墙一般分为软件防火墙、硬件防火墙 硬件防火墙&#xff1a;在硬件的级别实现防火墙过滤功能&#xff0c;性能高&#xf…

centos服务器搭建安装Gitlab教程使用教程

1、更新服务器&#xff1a; sudo yum update -y && sudo yum upgrade -y 2、下载Gitlab的RPM包 https://packages.gitlab.com/gitlab/gitlab-cece表示开源el表示centos 选64位el8对应CentOS8 本教程以centos8为例&#xff0c;在服务器中&#xff0c;下载centos8的…