当前位置:   article > 正文

Linux-应用编程-学习笔记(18):对于阻塞式IO困境的解决

阻塞式io

Linux-应用编程-学习笔记(18):对于阻塞式IO困境的解决

前言:内核默认的IO状态基本都为阻塞式,这是因为通过阻塞式的方式能够发挥操作系统的性能,让CPU时刻工作在被需要的情况下。但是只是单纯的阻塞式设计可能会带来一些危害,所以如何设计一种IO多路复用的状态是非常重要的。

一、阻塞式IO

1. 非阻塞式IO和阻塞式IO的区别

为了学习非阻塞IO用法,首先要弄清楚非阻塞和阻塞的区别。非阻塞式IO是用户发出IO请求后不进行等待,直接获得一个结果,通常使用时用O_NONBLOCK配合fcntl来完成。阻塞式IO是当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU,常见的阻塞有wait、pause、sleep等函数,read或write某些文件时。

2. 阻塞式IO的好处

对于内核来说,内部大部分默认的IO方式都设置为了阻塞式,这样的好处是为了充分发挥操作系统的性能,让CPU时刻工作在被需要的情况。比如对于A进程来说,它需要满足一定的条件才能继续往后进行,但是可能在短时间内该条件不能够满足,那么该进程会阻塞住,并交出CPU供其他进程使用。等到条件满足时,阻塞的地方解除阻塞,CPU回到该进程继续执行。这样极大程度地提高了CPU的利用率,减少原地踏步的时间,提高了整体系统的效率。

3. 阻塞式IO的困境

但是对于一个进程来说,里面可能有2个阻塞式IO的地方,这就面临着一个问题:先阻塞的地方需要满足条件后才能去执行后阻塞的地方,也就是如果后阻塞的地方虽然达到了条件,但是先阻塞的地方卡住了,后面的结果还是没法得到。
举个例子:设置read函数来读取鼠标和键盘输入的内容,先对鼠标进行阻塞式访问,再对键盘进行阻塞式访问,此时先晃动鼠标得到鼠标的内容,再键盘输入得到键盘的内容。但是如果先键盘输入,那么进程会一直阻塞在鼠标输入那里,直到晃动鼠标才能够通过,这就带来了一个输入必须有先后顺序的困扰。

二、解决阻塞式IO的困境

1. 非阻塞式IO方式

最简单的解决方法就是将2个IO位置改变为非阻塞的方式,类似于一种轮询的方式,通过循环读取鼠标和键盘来执行对应的IO操作。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
	// 读取鼠标
	int fd = -1;
	int flag = -1;
	char buf[200];
	int ret = -1;
	
	fd = open("/dev/input/mouse0", O_RDONLY | O_NONBLOCK);
	if (fd < 0)
	{
		perror("open:");
		return -1;
	}
	
	// 把0号文件描述符(stdin)变成非阻塞式的
	flag = fcntl(0, F_GETFL);		// 先获取原来的flag
	flag |= O_NONBLOCK;				// 添加非阻塞属性
	fcntl(0, F_SETFL, flag);		// 更新flag
	// 这3步之后,0就变成了非阻塞式的了
	
	while (1)
	{
		// 读鼠标
		memset(buf, 0, sizeof(buf));
		//printf("before 鼠标 read.\n");
		ret = read(fd, buf, 50);
		if (ret > 0)
		{
			printf("鼠标读出的内容是:[%s].\n", buf);
		}
		
		// 读键盘
		memset(buf, 0, sizeof(buf));
		//printf("before 键盘 read.\n");
		ret = read(0, buf, 5);
		if (ret > 0)
		{
			printf("键盘读出的内容是:[%s].\n", buf);
		}
	}
	
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

在这里插入图片描述

2. IO多路复用的方式

IO多路复用的方式通常需要借助select或poll函数,表现形式为外部阻塞式,内部非阻塞式自动轮询多路阻塞式IO
外部阻塞式的意思是select/poll函数对外表现为阻塞式,也就是最普通的阻塞式方式,两个IO都被封装在了select/poll中。内部非阻塞式自动轮询的意思是,在封装的内部,对于鼠标和键盘这两个输入一直处于自动轮询的方式,谁满足条件谁输出。多路阻塞式IO的意思是鼠标和键盘的封装内部仍然为阻塞式IO。

那么内部仍然是阻塞式IO的话跟之前不久一样了吗,还是会卡住?答案当然不是了,对于是否满足输出条件已经在最外层的select/poll中进行判断了,所以内部IO虽然还是阻塞式的,但是如果判断进来以后说明条件已经满足,即虽然是阻塞式,但是一定会执行。在select内部封装的两个IO相当于并行的,不存在先后顺序,只要满足条件就会到对应的分支去执行对应的操作

(1)select函数

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>


int main(void)
{
	// 读取鼠标
	int fd = -1, ret = -1;
	char buf[200];
	//定义一个文件描述符集
	fd_set myset;
	//定义溢出时间的结构体
	struct timeval tm;
	
	fd = open("/dev/input/mouse0", O_RDONLY);
	if (fd < 0)
	{
		perror("open:");
		return -1;
	}
	
	// 当前有2个fd,一共是fd一个是0
	// 处理myset
	FD_ZERO(&myset);	//先清零
	FD_SET(fd, &myset); //绑定鼠标
	FD_SET(0, &myset);  //绑定键盘
	
	tm.tv_sec = 10;		//设置最大等待时间为10s
	tm.tv_usec = 0;
	//       int select(int nfds, fd_set *readfds, fd_set *writefds,
    //             fd_set *exceptfds, struct timeval *timeout);
	ret = select(fd+1, &myset, NULL, NULL, &tm);
	if (ret < 0)
	{
		perror("select: ");
		return -1;
	}
	else if (ret == 0)
	{
		printf("超时了\n");
	}
	else
	{
		// 等到了一路IO,然后去监测到底是哪个IO到了,处理之
		if (FD_ISSET(0, &myset))
		{
			// 这里处理键盘
			memset(buf, 0, sizeof(buf));
			read(0, buf, 5);
			printf("键盘读出的内容是:[%s].\n", buf);
		}
		
		if (FD_ISSET(fd, &myset))
		{
			// 这里处理鼠标
			memset(buf, 0, sizeof(buf));
			read(fd, buf, 50);
			printf("鼠标读出的内容是:[%s].\n", buf);
		}
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

在这里插入图片描述
(2)poll函数

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>

int main(void)
{
	// 读取鼠标
	int fd = -1, ret = -1;
	char buf[200];
	struct pollfd myfds[2] = {0};
	
	fd = open("/dev/input/mouse0", O_RDONLY);
	if (fd < 0)
	{
		perror("open:");
		return -1;
	}
	
	// 初始化我们的pollfd
	myfds[0].fd = 0;			// 键盘
	myfds[0].events = POLLIN;	// 等待读操作
	
	myfds[1].fd = fd;			// 鼠标
	myfds[1].events = POLLIN;	// 等待读操作

	ret = poll(myfds, fd+1, 10000);
	if (ret < 0)
	{
		perror("poll: ");
		return -1;
	}
	else if (ret == 0)
	{
		printf("超时了\n");
	}
	else
	{
		// 等到了一路IO,然后去监测到底是哪个IO到了,处理之
		if (myfds[0].events == myfds[0].revents)
		{
			// 这里处理键盘
			memset(buf, 0, sizeof(buf));
			read(0, buf, 5);
			printf("键盘读出的内容是:[%s].\n", buf);
		}
		
		if (myfds[1].events == myfds[1].revents)
		{
			// 这里处理鼠标
			memset(buf, 0, sizeof(buf));
			read(fd, buf, 50);
			printf("鼠标读出的内容是:[%s].\n", buf);
		}
	}

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

在这里插入图片描述

3. 异步IO的方式

异步IO可以理解为操作系统用软件实现的一套中断响应系统。
它的工作方式为:我们当前进程注册一个异步IO事件(使用signal注册一个信号SIGIO的处理函数),然后当前进程可以正常处理自己的事情,当异步事件发生后当前进程会收到一个SIGIO信号从而执行绑定的处理函数去处理这个异步事件

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

int mousefd = -1;

// 绑定到SIGIO信号,在函数内处理异步通知事件
void func(int sig)
{
	char buf[200] = {0};
	
	if (sig != SIGIO)
		return;

	read(mousefd, buf, 50);
	printf("鼠标读出的内容是:[%s].\n", buf);
}

int main(void)
{
	// 读取鼠标
	char buf[200];
	int flag = -1;
	
	mousefd = open("/dev/input/mouse0", O_RDONLY);
	if (mousefd < 0)
	{
		perror("open:");
		return -1;
	}	
	// 把鼠标的文件描述符设置为可以接受异步IO
	flag = fcntl(mousefd, F_GETFL);
	flag |= O_ASYNC;
	fcntl(mousefd, F_SETFL, flag);
	// 把异步IO事件的接收进程设置为当前进程
	fcntl(mousefd, F_SETOWN, getpid());
	
	// 注册当前进程的SIGIO信号捕获函数
	signal(SIGIO, func);
	
	// 读键盘
	while (1)
	{
		memset(buf, 0, sizeof(buf));
		//printf("before 键盘 read.\n");
		read(0, buf, 5);
		printf("键盘读出的内容是:[%s].\n", buf);
	}
		
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/343373
推荐阅读
相关标签
  

闽ICP备14008679号