阻塞和非阻塞 IO
是
Linux
驱动开发里面很常见的两种设备访问模式,在编写驱动的时候一定要考虑到阻塞和非阻塞。
一.阻塞和非阻塞 IO
(1)阻塞访问
阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时,并不挂起,它要么放弃,要么不停地查询,直至可以进行操作为止。
在阻塞访问时,不能获取资源的进程将进入休眠,它将 CPU
资源“礼让”给其他进程。因为阻塞的进程会进入休眠状态,所以必须确保有一个地方能够唤醒休眠的进程,否则,进程就真的“寿终正寝”了。唤醒进程的地方最大可能发生在中断里面,因为在硬件资源获得的同时往往伴随着一个中断。而非阻塞的进程则不断尝试,直到可以进行 I/O
。
1.图解表示
2.代码表示
int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */
(2)非阻塞
若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的 xxx_read() 、 xxx_write () 等操作应立即返回,read() 、write() 等系统调用也随即被返回,应用程序收到-EAGAIN 返回值。
1.图解表示
2.代码表示
int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */
二.等待队列五大组合
(1)等待队列头
阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将
CPU
资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完
成唤醒工作。
Linux
内核提供了等待队列
(wait queue)
来实现阻塞进程的唤醒工作,如果我们要
在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体
wait_queue_head_t
表示,
wait_queue_head_t
结构体定义在文件
include/linux/wait.h
中。
(2)等待队列项
等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可
用的时候就要将这些进程对应的等待队列项添加到等待队列里面。
DECLARE_WAITQUEUE(name, tsk)
name
就是等待队列项的名字,tsk 表示这个等待队列项属于哪个任务
(
进程
)
,一般设置为
current
,在Linux 内核中
current
相 当 于 一 个 全 局 变 量 ,表 示 当 前 进 程 。因 此
DECLARE_WAITQUEUE 就是给当前正在运行的进程创建并初始化了一个等待队列项。
(3)将队列项添加/移除等待队列头
当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,
只有添加到等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待
队列项从等待队列头中移除即可。
(4)等待唤醒
当设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下两个函数:
void wake_up(wait_queue_head_t *q) //功能:唤醒所有休眠进程
void wake_up_interruptible(wait_queue_head_t *q)//功能:唤醒可中断的休眠进程
(5)等待事件
除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒
等待队列中的进程,和等待事件有关。
三.代码案例
1.驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/io.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
int gpio_nu;
//用来模拟管脚的状态
int value = 0;
/**
* @description: 中断处理函数 test_key
* @param {int} irq :要申请的中断号
* @param {void} *args :
* @return {*}IRQ_HANDLED
*/
irqreturn_t test_key(int irq, void *args)
{
value = !value;
return IRQ_RETVAL(IRQ_HANDLED);
}
int misc_open(struct inode *node, struct file *file)
{
printk("hello misc_open \n");
return 0;
}
int misc_release(struct inode *node, struct file *file)
{
printk("hello misc_release bye bye\n");
return 0;
}
ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
if (copy_to_user(ubuf, &value, sizeof(value)) != 0)
{
printk("copy_to_user error\n");
return -1;
}
return 0;
}
//文件操作集
struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read};
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "test_wq",
.fops = &misc_fops,
};
/**
* @brief beep_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,
* @param inode : 文件索引
* @param file : 文件
* @return 成功返回 0
*/
int beep_probe(struct platform_device *pdev)
{
int ret = 0;
printk("beep_probe\n");
//of_find_node_by_path 函数通过路径查找节点,/test_key 是设备树下的节点路径
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error\n");
return -1;
}
//of_get_named_gpio 函数获取 GPIO 编号
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if (gpio_nu < 0)
{
printk("of_get_named_gpio is error\n");
return -1;
}
//设置 GPIO 为输入模式
gpio_direction_input(gpio_nu);
//获取 GPIO 对应的中断号
irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d \n", irq);
/*申请中断,irq:中断号名字
test_key:中断处理函数
IRQF_TRIGGER_RISING:中断标志,意为上升沿触发
"test_key":中断的名字
*/
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if (ret < 0)
{
printk("request_irq \n");
return -1;
}
//注册杂项设备
ret = misc_register(&misc_dev);
if (ret < 0)
{
printk("misc_register is error\n");
return -1;
}
printk("misc_register is successd \n");
}
int beep_remove(struct platform_device *pdev)
{
printk("beep_remove \n");
return 0;
}
const struct platform_device_id beep_idtable = {
.name = "beep_test",
};
const struct of_device_id of_match_table_test[] = {
{.compatible = "keys"},
{},
};
struct platform_driver beep_driver = {
//3. 在 beep_driver 结构体中完成了 beep_probe 和 beep_remove
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test",
.of_match_table = of_match_table_test
},
//4 .id_table 的优先级要比 driver.name 的优先级要高,优先与.id_table 进行匹配
.id_table = &beep_idtable
};
static int beep_driver_init(void)
{
//1.我们看驱动文件要从 init 函数开始看
int ret = 0;
//2. 在 init 函数里面注册了 platform_driver
ret = platform_driver_register(&beep_driver);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register ok \n");
return 0;
}
static void beep_driver_exit(void)
{
printk("gooodbye! \n");
free_irq(irq, NULL);
misc_deregister(&misc_dev);
platform_driver_unregister(&beep_driver);
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
2.应用代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd;
int value;
//打开设备节点
fd = open("/dev/test_wq",O_RDWR);
if(fd < 0)
{
//打开设备节点失败
perror("open error \n");
return fd;
}
while(1)
{
read(fd,&value,sizeof(value));
printf("value is %d \n",value);
}
close(fd);
return 0;
}