Linux阻塞与非阻塞IO--阻塞IO

本文探讨了Linux中阻塞式IO和非阻塞IO的工作原理,通过实例讲解了如何在驱动中使用等待队列实现阻塞,并对比了两者在应用中的行为。还介绍了如何在设备驱动中创建和管理非阻塞访问的流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux阻塞与非阻塞IO

当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程
序对应的线程挂起,直到设备资源可以获取为止。对于非阻塞 IO,应用程序对应的线程不会挂
起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。

阻塞IO
在这里插入图片描述
上图中应用程序调用 read 函数从设备中读取数据,当设备不可用或数据未准备好的
时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给
应用程序。

1 int fd;
2 int data = 0;
3 
4 fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
5 ret = read(fd, &data, sizeof(data)); /* 读取数据 */

阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将
CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完
成唤醒工作。 Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作,如果我们要
在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体
wait_queue_head_t 表示。

等待队列的使用:
0001.定义等待队列头部 ;wait_queue_head_t r_wait;
0002.初始化等待队列头部 ; init_waitqueue_head(&newchrkey->r_wait);
0003.等待事件;wait_event(dev->r_wait, atomic_read(&dev->release_key));
0004.唤醒事件 ;wake_up_interruptible(&dev->r_wait);

代码中有具体说明

阻塞式IO例程

/*驱动*/

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define KEYVALUE 0X01   //按键值
#define INVAKEY  0XFF   //无效按键值
#define KEY_NUM 1

/*

001-004为阻塞方式的应用:等待队列实现
使用方法一:
	0001.定义等待队列头部
	0002.初始化等待队列头部
	0003.等待事件
	0004.唤醒事件

使用方法二:
	0001.定义等待队列头部
	0002.初始化等待队列头部
	0003.定义等待队列元素
	0004.添加、移除等待队列
	0005.等待事件
	0006.唤醒队列

*/

struct irq_keydesc {
	int gpio;
	int irqnum;//中断号
	unsigned char key_value;//按键对应的键值
	char name[10];//名字
	irqreturn_t (*handler)(int,void *);//中断服务函数
};

struct newchrkey_dev{

	struct device_node *dev_nod;
	int key_gpio;
	int major;
	int minor;
	dev_t devid;//设备号
	struct cdev cdev;
	struct device *device;//设备
	struct class *cls;
	struct irq_keydesc irq_key[KEY_NUM];//案件描述数组
	struct timer_list timer;//定义一个定时器
	unsigned char curkeynum;//当前按键号

	atomic_t my_atomic;//有效的按键键值
	atomic_t release_key;// 标记是否完成一次完成的按键,包括按下和释放
	
	//001.定义等待队列头
	wait_queue_head_t r_wait;
	
};

struct newchrkey_dev *newchrkey;

/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key0_handler(int irq,void *dev_id){
	struct newchrkey_dev *dev = (struct newchrkey_dev *)dev_id;

	dev->curkeynum = 0;
	dev->timer.data = (volatile long)dev_id;

	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));//10ms定时
	
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description	: 定时器服务函数,用于按键消抖,定时器到了以后
 *				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg	: 设备结构变量
 * @return 		: 无
 */
void timer_function(unsigned long arg){
	unsigned char value;
	unsigned char num;
	struct irq_keydesc *irq_key;
	struct newchrkey_dev *dev = (struct newchrkey_dev *)arg;

	num = dev->curkeynum;
	irq_key = &dev->irq_key[num];

	value = gpio_get_value(irq_key->gpio);//读取IO值
	if(value == 0){//按下按键
		atomic_set(&dev->my_atomic,irq_key->key_value);
	}else{//按键松开
		atomic_set(&dev->my_atomic, 0x80|irq_key->key_value);
		atomic_set(&dev->release_key, 1);//标记松开按钮
	}

	//004.唤醒进程
	if(atomic_read(&dev->release_key)){
		//wake_up(&dev->r_wait);
		wake_up_interruptible(&dev->r_wait);
	}
}
static int keyio_init(void){
	unsigned char i=0;
	char name[10];
	int ret = 0;

	//01.从设备树获取key的节点信息
	newchrkey->dev_nod = of_find_node_by_path("/gpio_key");
	if(newchrkey->dev_nod == NULL){
		printk("can not find gpio_key int dts!!\n");
		return -EINVAL;
	}else{
		printk("gpio_key have been found!!\n");
	}

	//02.提取GPIO
	for(i=0;i<KEY_NUM;i++){
		newchrkey->irq_key[i].gpio = of_get_named_gpio(newchrkey->dev_nod, "key-gpio", i);
		if(newchrkey->irq_key[i].gpio < 0){
			printk("can not get key%d \n");
		}
	}

	//02.获取gpio属性信息
	//newchrkey->key_gpio = of_get_named_gpio(newchrkey->dev_nod, "key-gpio", 0);
	//if(newchrkey->key_gpio < 0){
	//	printk("can not get key-gpio!!\n");
	//	return EINVAL;
	//}
	for(i=0;i<KEY_NUM;i++){
		printk("newchrkey->irq_key[%d].gpio = %d\n",i,newchrkey->irq_key[i].gpio);
	}

	//初始化key用到的所有io,设置为中断模式
	for(i=0;i<KEY_NUM;i++){
		memset(newchrkey->irq_key[i].name,0,sizeof(name));//缓冲区清零
		sprintf(newchrkey->irq_key[i].name,"key%d",i);//组合名字
		gpio_request(newchrkey->irq_key[i].gpio, name);
		gpio_direction_input(newchrkey->irq_key[i].gpio);
		newchrkey->irq_key[i].irqnum = irq_of_parse_and_map(newchrkey->dev_nod, i);

#if 0
		newchrkey->irq_key[i].irqnum = gpio_to_irq(newchrkey->irq_key[i].gpio);
#endif
		printk("key%d:gpio=%d,irq_num=%d \n",i,newchrkey->irq_key[i].gpio,newchrkey->irq_key[i].irqnum);
	}

	/* 初始化key所使用的IO */
	//注意!!!!
	//gpio_request()使用过后,不用gpio_free()释放,当第二次insmod时会出现段错误。。
	//本人就犯了这个错误
	//gpio_request(newchrkey->key_gpio, "--key");	/* 请求IO */

	//03.申请中断
	newchrkey->irq_key[0].handler = key0_handler;
	newchrkey->irq_key[0].key_value = KEYVALUE;

	for(i=0;i<KEY_NUM;i++){
		ret = request_irq(newchrkey->irq_key[i].irqnum, newchrkey->irq_key[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, newchrkey->irq_key[i].name, newchrkey);
		if(ret<0){
			printk("irq %d request failed!!\n",newchrkey->irq_key[i].irqnum);
			return -EFAULT;
		}
	}

	//03.设置gpio为输入
	//ret = gpio_direction_input(newchrkey->key_gpio);
	///if(ret<0){
	///	printk("can not set gpio !!\n");
	//}
	//printk("gpio number have gotten!!\n");

	//创建定时器
	init_timer(&newchrkey->timer);
	newchrkey->timer.function = timer_function;
	return 0;
}

static int key_drv_open (struct inode *inod, struct file *filp){
	printk("----------------%s---------------\n",__FUNCTION__);
	
	filp->private_data = newchrkey;

	return 0;
}
static ssize_t key_drv_read (struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
	//printk("----------------%s---------------\n",__FUNCTION__);

	int ret;
	unsigned char keyvalue = 0;
	unsigned char releasekey = 0;
	struct newchrkey_dev *dev = (struct newchrkey_dev *)filp->private_data;

#if 0
	//003.等待事件.使用该函数时,应用程序进程无法被kill,因此驱动程序会一直使用,无法卸载,使用wait_event_interruptible()可解决
	//wait_event(dev->r_wait, atomic_read(&dev->release_key));
	ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->release_key));
	if(ret){
		goto wait_error;
	}
#endif
	//定义一个等待队列项(元素)
	DECLARE_WAITQUEUE(wait, current);
	if(atomic_read(&dev->release_key)==0){//按键没按下
		add_wait_queue(&dev->r_wait, &wait);//将对列项添加到等待队列头
		__set_current_state(TASK_INTERRUPTIBLE);//当前进程设置为可被打断状态
		schedule();//切换,调度其它进程执行

		if(signal_pending(current))	{			/* 判断是否为信号引起的唤醒 */
			ret = -ERESTARTSYS;
			goto wait_error;
		}
	}
	remove_wait_queue(&dev->r_wait, &wait);		/* 唤醒以后将等待队列移除 */

	keyvalue = atomic_read(&dev->my_atomic);
	releasekey = atomic_read(&dev->release_key);

	if (releasekey) { /* 有按键按下 */	
		if (keyvalue & 0x80) {
			keyvalue &= ~0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		} else {
			goto data_error;
		}
		atomic_set(&dev->release_key, 0);/* 按下标志清零 */
	} else {
		goto data_error;
	}
	return 0;
	
	/*
	if(gpio_get_value(dev->key_gpio) == 0){//按键按下
		while(!gpio_get_value(dev->key_gpio));
		atomic_set(&dev->my_atomic,KEYVALUE);
	}else{
		atomic_set(&dev->my_atomic,INVAKEY);
	}

	val = atomic_read(&dev->my_atomic);
	ret = copy_to_user(buf,&val,sizeof(val));
	return ret;
	*/
wait_error:
	__set_current_state(TASK_RUNNING);		/* 设置任务为运行态 */
	remove_wait_queue(&dev->r_wait, &wait);	/* 将等待队列移除 */
	return ret;
	
data_error:
	return -EINVAL;
}
static ssize_t key_drv_write (struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){
	printk("----------------%s---------------\n",__FUNCTION__);


	return 0;
}
static int key_drv_release (struct inode *inod, struct file *filp){
	printk("----------------%s---------------\n",__FUNCTION__);
	atomic_inc(&newchrkey->my_atomic);


	return 0;
}	

static struct file_operations newchrkey_fops = {
	.owner = THIS_MODULE,
	.open = key_drv_open,
	.read = key_drv_read,
	.write = key_drv_write,
	.release = key_drv_release,
};

static int __init key_drv_init(void){
	printk("----------------%s---------------\n",__FUNCTION__);

	newchrkey = (struct newchrkey_dev *)kzalloc(sizeof(struct newchrkey_dev), GFP_KERNEL);
	if(newchrkey == NULL){
		printk("newchrkey kzalloc failed!!!\n");
		return -1;
	}

	int ret;
	
	//注册字符设备驱动
	//1.向系统申请设备号
	if(newchrkey->major){
		newchrkey->devid = MKDEV(newchrkey->major, 0);
		register_chrdev_region(newchrkey->devid, 1, "Pf_key");
	}else{
		printk("register chrdev!!\n");
		/*犯过错误:一下函数的第一个参数没有取地址,在insmod时出现段错误*/
		alloc_chrdev_region(&newchrkey->devid, 0, 1, "Pf_key");
		
		newchrkey->major = MAJOR(newchrkey->devid);
		newchrkey->minor = MINOR(newchrkey->devid);
		printk(">>>>>>>>>>>register chrdev!!\n");
		
	}
	printk("major: %d ,minor: %d \n",newchrkey->major,newchrkey->minor);

	//2.初始化cdev
	newchrkey->cdev.owner = THIS_MODULE;
	cdev_init(&newchrkey->cdev, &newchrkey_fops);

	//3.添加一个cdev到系统
	cdev_add(&newchrkey->cdev, newchrkey->devid, 1);

	//4.创建一个设备
	newchrkey->cls = class_create(THIS_MODULE, "Pf_key");
	if(IS_ERR(newchrkey->cls)){
		return PTR_ERR(newchrkey->cls); 
	}
	newchrkey->device = device_create(newchrkey->cls, NULL, newchrkey->devid, NULL, "Pf_key");
	if(IS_ERR(newchrkey->device)){
		return PTR_ERR(newchrkey->device); 
	}

	//5.初始按键
	atomic_set(&newchrkey->my_atomic,INVAKEY);
	atomic_set(&newchrkey->release_key,0);

	//002.初始化等待队列头
	init_waitqueue_head(&newchrkey->r_wait);
	
	keyio_init();
	return 0;
}


static void __exit key_drv_exit(void){
	printk("----------------%s---------------\n",__FUNCTION__);

	//注意!!!!
	//gpio_request()使用过后,不用gpio_free()释放,当第二次insmod时会出现段错误
	//gpio_free(newchrkey->key_gpio);

	unsigned int i=0;
	//删除定时器
	del_timer(&newchrkey->timer);

	//释放中断
	for(i=0;i<KEY_NUM;i++){
		free_irq(newchrkey->irq_key[i].irqnum, newchrkey);
	}

	cdev_del(&newchrkey->cdev);
	unregister_chrdev_region(newchrkey->devid, 1);

	device_destroy(newchrkey->cls, newchrkey->devid);
	class_destroy(newchrkey->cls);

	kfree(newchrkey);

}

module_init(key_drv_init);
module_exit(key_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("PEIFENG");

/*APP*/
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

/* 定义按键值 */
#define KEYVALUE	0XF0
#define INVAKEY		0X00

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, ret;
	char *filename;
	int keyvalue;
	
	if(argc != 2){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开key驱动 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	/* 循环读取按键值数据! */
	while(1) {
		ret = read(fd, &keyvalue, sizeof(keyvalue));
		if (ret < 0) {	
			
		}else{
			if(keyvalue) 
				printf("key value = %#X\r\n",keyvalue);
		}
	}

	ret= close(fd); /* 关闭文件 */
	if(ret < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

///
非阻塞IO
在这里插入图片描述
上图中,应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或
数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新
读取数据,这样一直往复循环,直到数据读取成功。

1 int fd;
2 int data = 0;
3 
4 fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
5 ret = read(fd, &data, sizeof(data)); /* 读取数据 */

非阻塞式IO见下篇《Linux阻塞与非阻塞IO–非阻塞式IO》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值