Linux中IO多路复用

本篇博客将说明linux中的阻塞IO、非阻塞IO以及这两种进行IO操作的困境,最后说明解决这种困境的方法,也就是IO多路复用

1、阻塞式IO

阻塞式IO是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后在进行操作。被挂起的进程进入睡眠状态,被从调度运行器的运行队列中移走,直到等待的条件满足。内核中默认的都是阻塞式的实现方式,因为这种实现方式效率比较高,被阻塞时,该进程并不占用CPU,而是将CPU交出,执行其他的进程。

2、非阻塞式IO

非阻塞式Io是指当前进程不能够进行设备操作的时候,并不会挂起,它要么放弃,要么不停的查询,直到可以进行操作为止。这种方式看似效率很高,但是当长时间得不到资源的时候那么,该进程将会长时间占用CPU,从而拉低整个系统的效率。非阻塞式访问有两种方法,一种时当文件open的时候使用O_NONBLOCK,以另一种是对已经打开的问文件进行属性修改,使用fctl()。如下:


//打开是设置
fd = open("/dev/input/mouse1", O_RDONLY | O_NONBLOCK);	//非阻塞方式访问 鼠标


// 把0号文件描述符(stdin)变成非阻塞式的
	flag = fcntl(0, F_GETFL);		// 先获取原来的flag
	flag |= O_NONBLOCK;				// 添加非阻塞属性
	fcntl(0, F_SETFL, flag);		// 更新flag
	// 这3步之后,0就变成了非阻塞式的了

3、阻塞与非阻塞IO的困境

就现在来看,将系统中的所有IO操作都使用阻塞式的访问不就行了吗?但是,事实并不是想象中的那么简单,在Linux内核中存在着各种我们想不到的情况,都需要兼顾得到。下面举一个例子说明阻塞与非阻塞的困境。

在一个进程中要进行3个IO操作,分别是A、B、C。他们都是以阻塞的方式打开进行访问的,并且执行顺序是A、B、C。现在有个情况是A因为条件不满足而被阻塞,所以就执行不了B、C,但是B或者C的执行条件都是满足的,这样就导致了B、C不能执行的现象,显然这不是我们想看的的结果。

阻塞式IO在自己进行单个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;
	char buf[200];
	
	fd = open("/dev/input/mouse1", O_RDONLY);
	if (fd < 0)
	{
		perror("open:");
		return -1;
	}
	
	memset(buf, 0, sizeof(buf));
	printf("before 鼠标 read.\n");
	read(fd, buf, 50);
	printf("鼠标读出的内容是:[%s].\n", buf);
		
	// 读键盘
	memset(buf, 0, sizeof(buf));		//键盘就是标准输入  对应的文件描述符是0
	printf("before 键盘 read.\n");
	read(0, buf, 5);
	printf("键盘读出的内容是:[%s].\n", buf);
	
	return 0;
}

程序最终的现象是,程序开始阻塞在读鼠标内容的位置,移动鼠标或者单击鼠标,控制台有数据的打印,然后阻塞在读键盘的位置,键盘输入内容,然后控制台有数据打印,程序运行结束。这样看好像没有什么问题,但是这是我们知道程序的原理的前提下进行的操作。当我们先敲击键再单击机鼠标的时候,是没有任何现象的。这样显然是不合理的,因为,这样对于用户来说这根本就是致命的bug的存在。

4、多路IO复用

对于像这种多路IO同时进行操作的问题,我们有很多种方法解决。例如将鼠标与键盘都设置成非阻塞的方式,但是要将键盘与鼠标的读放在一个while循环中。或者使用异步IO的方式,将鼠标或者键盘其中的一个做为主函数,另一个作为类似于中断的函数,当有数据可读时就挂起主程序去执行“中断”函数。等等吧。这里我要详细介绍的就是使用IO多路复用的方式实现多路IO同时进行操作的时候问题。

复用的意思时不用每个进程/线程来操控单独的一个IO,只需一个进程/线程来操控多个IO。使用非阻塞IO的用用程序通常会使用select()和poll()系统调用查询是否可以对设备进行无阻塞的访问(select和poll系统调用最终都会使得设备驱动中的pull函数被执行,这个关于驱动的知识放到以后在讲述),其实select与pull系统调用本质是一样的。在应用程序中最广泛的是select()系统调用,其函数原型如下:

       int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds的值是需要检测的号码最高的fd加1
  • readfds、writefds、exceptfds分别是被select()监听的读、写和一场处理的文件描述符集合,readfds中文件集中的任何一个文件变得可读,select()函数就会返回;同理,writefds文件集中的任何一个文件变得可读,select也返回。
  • timeout参数是一个执行struct timevel类型的指针,这个参数设置对监听的IO所等待的最长时间,其数据结构定义:
           struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

下面的一些操作是用来设置、清除、判断文件描述符集合:

       void FD_CLR(int fd, fd_set *set);    //将一个文件描述符从文件描述符集合中清除
       int  FD_ISSET(int fd, fd_set *set);  //判断文件描述符是否被置位
       void FD_SET(int fd, fd_set *set);    //将一个文件描述符加入文件描述符集合中
       void FD_ZERO(fd_set *set);           //清除一个文件描述符集合

用一个实例进行说明select()函数的的使用

/*
使用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/mouse1", 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;		//等待时间
	tm.tv_usec = 0;

	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;
}

使用poll()函数实现IO多路复用

/*
使用select函数进行对鼠标与键盘的监听
*/#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/mouse1", 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;
}

注明:本博客是根据朱有鹏老师的l《linux嵌入式核心课程》视频所作的总结

猜你喜欢

转载自blog.csdn.net/David_361/article/details/86106563