Linux应用编程之高级IO


前言

本文是看了朱有鹏老师的Linux应用编程之高级IO的一些学习理解


一、阻塞与非阻塞

1.阻塞

当前进程的执行条件不满足,内核将进程挂起,直到进程的执行条件满足。(linux在设计时,默认阻塞式)

(1)常见的阻塞
wait、pause、sleep等函数本身就是阻塞式的
read或write某些文件时(read和write本身没有此属性,看读取对象是阻塞还是非阻塞的,比如键盘和鼠标就是阻塞式的)。

(2)阻塞式的好处
有利于操作系统的性能发挥

2.非阻塞

进程的执行条件不满足,也不会阻塞当前线程

(1)为什么要实现非阻塞
提高效率,解决阻塞式IO的困境

(2)如何实现非阻塞IO访问:O_NONBLOCK和fcntl
打开文件时+O_NONBLOCK
fcntl给文件描述符加上非阻塞

3.阻塞式IO的困境

阻塞式IO的困境主要体现在多路IO的时候会出现问题,就是当前这个进程需要多个分别进行的IO,比如进程要同时进行读鼠标和读键盘的操作,读鼠标和读键盘都是阻塞式的,如果我在读鼠标的同时又想读键盘,但是此时我已经被阻塞在了鼠标,如果没有鼠标操作,就会一直被阻塞,中途我想进行读键盘的操作也不行,必须等鼠标阻塞结束之后才能进行键盘操作,相当于强制性的必须依照顺序来运行,而这往往不是我们想要看到的

4.实例证明阻塞式IO的困境

(1)程序中读取键盘
(2)程序中读取鼠标
(3)程序中同时读取键盘和鼠标

(1)程序中读取键盘

int main(void)
{
	// 读取键盘
	// 键盘就是标准输入,stdin
	
	char buf[100];
	
	memset(buf, 0, sizeof(buf));
	printf("before read.\n");
	read(0, buf, 5);
	printf("读出的内容是:[%s].\n", buf);
	
	return 0;
}

在这里插入图片描述

注意是行缓冲的,在输入回车时,才结束输入。

(2)程序中读取鼠标

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);
	return 0;
}

在这里插入图片描述

(3)程序中同时读取键盘和鼠标
希望实现动键盘就显示键盘的内容,动鼠标就显示鼠标的内容;
但是下面的程序实际现象是:顺着程序先鼠标再键盘则顺利显示;先键盘的话,会被鼠标阻塞住。

#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));
	printf("before 键盘 read.\n");
	read(0, buf, 5);//键盘就是标准输入,stdin,用0表示,它是默认打开的。
	printf("键盘读出的内容是:[%s].\n", buf);
	
	return 0;
}

在这里插入图片描述

5.总结

阻塞式IO的困境就在于其实现不了并发式IO的操作,因为其阻塞的缘故,只能体现为一个执行完了,然后再执行下一个,然而有时候我们想同时执行,二者互不干涉,也就是所谓的并发式IO,接下来我们就来看一看并发式IO的解决方案

二、并发式IO的解决方案

并发式IO:所谓并发式IO,即上节中提及的鼠标和键盘都启动,各干各的,互不干扰
并发式IO的解决方案
非阻塞式IO、多路复用IO、异步通知(异步IO)

1、非阻塞式IO

(1)使用fcntl函数, 将上节中阻塞式的鼠标和键盘读取改为非阻塞式的。
(2)性能不是很好,有点像轮询方式,CPU不停的查看IO,处理IO
(3)所以有了多路复用IO、异步通知(异步IO)

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
#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/mouse1", 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));
		ret = read(fd, buf, 50);
		if (ret > 0)
		{
			printf("鼠标读出的内容是:[%s].\n", buf);
		}
		
		// 读键盘
		memset(buf, 0, sizeof(buf));
		ret = read(0, buf, 5);
		if (ret > 0)
		{
			printf("键盘读出的内容是:[%s].\n", buf);
		}
	}
	
	return 0;
}
 
 
/*
int main(void)
{
	// 读取鼠标
	int fd = -1;
	char buf[200];
	
	fd = open("/dev/input/mouse1", O_RDONLY | O_NONBLOCK);
	if (fd < 0)
	{
		perror("open:");
		return -1;
	}
	
	memset(buf, 0, sizeof(buf));
	printf("before read.\n");
	read(fd, buf, 50);
	printf("读出的内容是:[%s].\n", buf);
	
	
	return 0;
}
*/
 
/*
int main(void)
{
	// 读取键盘
	// 键盘就是标准输入,stdin
	
	char buf[100];
	int flag = -1;
	
	// 把0号文件描述符(stdin)变成非阻塞式的
	flag = fcntl(0, F_GETFL);		// 先获取原来的flag
	flag |= O_NONBLOCK;				// 添加非阻塞属性
	fcntl(0, F_SETFL, flag);		// 更新flag
	// 这3步之后,0就变成了非阻塞式的了
	
	memset(buf, 0, sizeof(buf));
	printf("before read.\n");
	read(0, buf, 5);
	printf("读出的内容是:[%s].\n", buf);
	
	return 0;
}
*/

2、多路复用IO

(1)英文为:IO multiplexing
(2)用于解决并发式IO,解决了非阻塞式IO的缺陷
(3)涉及select函数、poll函数。两个函数设计思想一样,外部特征不一样。
(4)实现原理外部阻塞式,内部非阻塞式自动轮询 多路阻塞式IO
外部阻塞式(select/poll函数本身是阻塞式的),内部非阻塞式自动轮询(select/poll自动轮询时A,B)多路阻塞式IO(键盘A,鼠标B,这两者本身是阻塞式的)。
只要AB至少有一个输入,则select由阻塞返回:
(5)多路复用实践
用poll函数实现同时读取键盘鼠标

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };
#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;
}

用select函数实现同时读取键盘鼠标

设置超时时间,即阻塞的时间不能太长,如果很久都没有IO来激活select,则表明超时了。

     /* According to POSIX.1-2001, POSIX.1-2008 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>
			//nfds:最大的文件描述符+1
       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

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

#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;//设置超时时间,即阻塞的时间不能太长,如果很久都没有IO来激活select,则表明超时了。
	
	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);//+1,是因为0~fd,则共有fd+1个文件描述符
	if (ret < 0)//错误
	{
		perror("select: ");
		return -1;
	}
	else if (ret == 0)//表明超时
	{
		printf("超时了\n");
	}
	else//>0表示有一路IO激活了
	{
		// 等到了一路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;
}

3、异步IO

(1)何为异步IO?
几乎可以认为,异步IO就是操作系统用软件实现的一套中断响应系统。类比硬件中断。
(2)异步IO的工作方法:
当前进程注册一个异步IO事件(使用signal注册一个信号SIGIO的处理函数),当异步事件没有发生的时候当前进程可以正常处理自己的事情;
当异步事件发生后,当前进程会收到一个SIGIO信号,从而执行绑定的处理函数,来处理这个异步事件。
(3)涉及的函数
fcntl函数,signal或者sigaction(SIGIO)。
fcntl函数,主要设置异步通知:
fcntl(F_GETFL(获取flag)、F_SETFL(修改属性信息)、O_ASYNC(表明可以接收异步通知)、F_SETOWN(设置通知谁(一般都是通知当前进程)))
signal或者sigaction(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/mouse1", 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));
		read(0, buf, 5);
		printf("键盘读出的内容是:[%s].\n", buf);
	}
		
	return 0;
}

总结:阻塞式IO困境主要在于阻塞式IO不能实现并发操作,我们上面阐述了有三种并发式IO的解决方案:非阻塞式IO,多路复用IO,异步IO,其中非阻塞式IO效率最差,相当于让cpu不断的读取IO,占用了很大一部分cpu。多路复用IO主要实现原理是外部阻塞式,内部非阻塞式轮询 多路阻塞式IO。异步IO就是类似于裸机开发中的中断,当进程没有收到异步事件时正常执行,收到异步事件后暂停去处理异步事件。

三、存储映射IO

1、反映在mmap函数

把一个文件和一段内存映射起来。比如LCD设备文件和显存的对应。

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);

2、例子

LCD显示,IPC之共享内存
IPC之共享内存
当进程A与进程B要以共享内存的方式进行信息交互的时候
进程A会开辟一块内存空间,进程B也会开辟一块内存空间,进程A与进程B的内存空间实际上都是内核中同一块物理内存空间的映射
在这里插入图片描述

3、存储映射IO的特点

共享而不是复制,减少内存操作。
处理大文件时效率高(一般用于视频处理),小文件不划算。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值