linux中阻塞IO的个人理解总结

本文探讨了在Linux中阻塞IO的概念及其必要性,通过举例解释了阻塞IO如何工作,包括等待事件和设置任务为可中断睡眠两种方式,并分析了在多进程环境下阻塞IO的行为。同时,文章提到了阻塞IO对CPU使用率的影响。
摘要由CSDN通过智能技术生成

阻塞IO

在文章正题之前,先上一段代码(也是为什么会又阻塞或者非阻塞乃至异步):

//app.c
//省略无关代码
while(1)
    {
   
        printf("read file \r\n");
        ret = read(fd, &str[0], sizeof(char));
        if(str[0] == '0')
        {
   

        }
        else if(str[0] == '1')
        {
   
            printf("value is %x\r\n",str[0]);
        }     
    }

一般如果我们在驱动层不对调用该驱动的进程进行处理,就像以上代码在while循环里面一直死循环,将会导致如下现象.
在这里插入图片描述
在运行该APP,并且驱动层并没有对其进行阻塞,非阻塞以及异步的处理,就会导致这一个APP占用CPU很高,如果应用程序多了,是不是就不太好了呢?那么您一定会有疑问了,那该怎么办?我们就带着疑问来言归正传,在这篇文章最后我们再看看使用了阻塞这个使用率会变成多少?

  • 需要阻塞IO原因
    当应用程序需要读取设备数据,但是设备并没有准备好数据提供给应用程序;或者应用程序需要往设备中写入数据,但是设备现在繁忙,数据缓冲区是满的不能接受应用层的数据。等等这些情况下,有必要让此应用程序(一个进程)进入阻塞状态,直到能操作唤醒当前进程.
    举个栗子:比如我们下班回家累了一天,打算洗澡放松,但是现在没有热水,所以不能直接用冷水洗,那么就需要热水,但是我们不洗澡啥也不愿干,所以就一直盯着热水器,啥也不干,哪也不去,直到热水器上的温度从5°变为40°左右感觉水温不凉的时候就洗澡.
    注意 :这里面黄色标注的就是阻塞的特点.需要和异步进行区别,关于异步的会在另一篇单独进行阐述.
  • 预备知识
  • 睡眠状态:linux会把运行程序形成一个队列,通过调度器进行切换调度。所谓睡眠就是将一个程序(进程)从运行队列中移除,直到等待的时间将其唤醒再次加入到运行队列中,再有调度器进行调度.
  • 让程序(进程)进入睡眠几点需要注意:
    • 驱动除了信号量以外,持有自旋锁,原子,互斥锁等这些并发竞争操作时候,或者中断关闭情况下,是不能进入睡眠的.可以想象,如果应用在操作一个设备时候,当这个设备驱动正在持有一个自旋锁,那么调用这个驱动的应用如果进入睡眠,另一边如果等待这个自旋锁的进程是否就不会得到这个锁呢,别的同样道理.
    • 如果驱动持有的是信号量,那么另一边等待这个信号量的线程也会进入睡眠状态,所以任何一个可能持有这个信号量的睡眠都不应该太久,应该短暂,并且不能阻塞当前运行线程也就是调用的驱动文件.
      Tips:以上几点需要仔细考虑
      本文涉及到的内核头文件主要有:
      ----->linux/wait.h
      eg1.—第一种睡眠方式:等待事件
DECLEARE_WAIT_QUEUE_HEAD(name)//定义等待队列头
/*
wait_queue_head_t    name;//对上面的宏进行展开是一下样子
init_waitqueue_head(&name);
*/
int flag = 0;//提供唤醒睡眠的条件
/*以下只是一个使用例子,所以不代表某个函数*/
void read_date(void)
{
   
	...
	wait_event_interruptible(name,flag != 0);
	flag = 0;
	...
}
void write_date(void)
{
   
	...
	flag = 1;
	wake_up_interruptible(&name);//这里并没有写错,再read函数里面调用的是一个宏,宏内部处理了,所以不用取地址.
	...
}

以上是一个很简单的一个模板,当应用读数据调用驱动层的read后,会进入睡眠状态(当然前提是在调用read前任何操作flag的值仍然是0).
当应用层写数据,调用驱动层的write后,因为write将flag变成1,并且唤醒队列。导致调用此进程退出睡眠。

首先最简单的一种阻塞睡眠方式:
方式1:
定义等待队列,并且在read函数内部设置等待条件,这里等待原子变量为1就退出睡眠.如图:
在这里插入图片描述

下面需要一个动作让该原子变量变为1,我们就用另一个app写一个数据调用write,在驱动的write函数内部将原子变量变为1.唤醒前文中的进程,如图:(这里需要注意,必须调用wakeup函数,才会让等待队列去查看条件是否可以让其醒来.)
在这里插入图片描述

那么如果我们app1去读该驱动设备,第一次会让该进程app1进入睡眠(原子变量初始时候设置为0,等待队列是判断该原子变量是否为1才不会进入睡眠状态).
直到让原子变量为1才会向下执行.请看下面现象:
在这里插入图片描述
综上所述,确实如此,会让进程一直阻塞在该语句,并不会向下执行。在这里插入图片描述
那么问题来了。。。。
当同时两个进程都读这个驱动操作,那么这两个进程都会进入睡眠状态,当一个进程写这个驱动后.这个原子变量就会变为1,并且会唤醒一个进程.具体唤醒哪一个,另一个进程会怎么样呢?
下面来看看现象:

//编者只是在驱动层read函数中加上打印进程号语句,其他都没有变动.
static ssize_t _key_read(struct file * pfile, char __user * _date, size_t size, loff_t * pvoid)
{
   
    char date[1] = {
   0x88};
    printk("sleep task %d\r\n",current->pid);
    wait_event_interruptible(key_wait_head,(atomic_read(&akey_dev_s.key_flag) == 1));
    printk("wake up task %d\r\n",current->pid);
    atomic_set(&akey_dev_s.key_flag,0);
    if(copy_to_user(_date,date,1))
    {
   
        return -1;   
    }
    return 0
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值