3.6.1.非阻塞IO (文件锁的概念)
3.6.1.1、阻塞与非阻塞
阻塞:顾名思义,就是指在执行设备操作时若不能获得资源则
挂起
操作,直到满足可操作的条件后再进行操作,被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件满足。
函数不能立即返回
。
非阻塞:就是反过来,进程在不能进行设备操作时并不挂起,它或者放弃,或者不停的查询,直到可以进行位置。(函数立即返回,缺点是你不知道它是否执行成功了。)
3.6.1.2、为什么有阻塞式
(1)常见的阻塞:wait、pause、sleep等函数;read或write某些文件时 (这两个本身是非阻塞的,看你需要操作的文件是否是阻塞和非阻塞。)
(2)
非阻塞式
的好处 :利于发挥CPU的性能。当前进程等待,交出CPU的使用权,给其它进程使用。
3.6.1.3、非阻塞
(1)为什么要实现非阻塞: 有时候需要做些别的事情,没必要一直在那等待。
(2)如何实现非阻塞IO访问:O_NONBLOCK和fcntl
3.6.2.阻塞式IO的困境
3.6.2.1、程序中读取键盘、 ,键盘就是标准输入,stdin,对应文件描述符0
3.6.2.2、程序中读取鼠标、。鼠标需要open打开/dev/input/mouse1(我的鼠标设备文件是mouse0,具体哪一个文件具体分析)
3.6.2.3、程序中同时读取键盘和鼠标()
3.6.2.4、
问题分析
:先阻塞在了鼠标那里,等鼠标结束,再执行键盘,顺序反了没用。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char** argv)
{
/*
// 读取键盘 ,键盘就是标准输入,stdin,对应文件描述符0
char buf[100];
memset(buf, 0, sizeof(buf));
printf("before read.\n"); //命令行的输出是行缓冲的,敲回车才能显示,没敲回车,是阻塞式的。
read(0, buf, 5);
printf("读出的内容是:[%s].\n", buf);
*/
/*
// 读取鼠标
int fd = -1;
char buf[200];
fd = open("/dev/input/mouse0", O_RDONLY); //我这个鼠标输出是mouse0
if (fd < 0)
{
perror("open:");
return -1;
}
memset(buf, 0, sizeof(buf));
printf("before read.\n");
read(fd, buf, 50);
printf("读出的内容是:[%s].\n", buf);
*/
//同时读取鼠标和键盘。 面临阻塞式问题,必须先用鼠标,再用键盘,否则面临阻塞。
//先读鼠标,主要也是这些文件是属于阻塞式的。
int fd = -1;
char buf[200];
fd = open("/dev/input/mouse1", O_RDONLY); //我这个鼠标输出是mouse0、具体环境具体分析。尝试。
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);
printf("读出的内容是:[%s].\n", buf);
exit(0);
}
3.6.3.并发式IO(重要!)的解决方案
(1)也可以使用fork创建子进程,父进程执行一个,子进程执行一个。
3.6.3.1、非阻塞式IO(这个demo使用的是非阻塞式的。使用while(1)循环,使程序一直再读内容,使用步骤是先打开文件,使用
O_NONBLOCK设定文件描述符为非阻塞方式,鼠标是在open文件时,设置权限,键盘是使用fcntl函数。
)
为什么用并发式IO:是为了解决阻塞式IO的困境,阻塞式IO的困境在于必须按照程序的顺序执行,否则将会阻塞在那里。用户体验极差。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
/*
*由阻塞变为非阻塞文件的操作。
*/
int main(int argc, char** argv)
{
/*
//先打开鼠标文件
int fd = -1;
char buf[100] = {0};
int flag = -1;
fd = open("/dev/input/mouse0", O_RDONLY | O_NONBLOCK);
if(fd < 0)
{
perror("open");
exit(-1);
}
//将0号文件,就是通用输入文件描述符变为非阻塞式的。
flag = fcntl(0, F_GETFL); //先获取原来的文件描述符状态。
flag |= O_NONBLOCK; //使用位或,添加非阻塞属性。
fcntl(0, F_SETFL, flag); //更新状态,第三个参数表明需要设置的值。
//这三部将0号文件变为非阻塞文件
while(1) //在while中读取这些文件。
{
int ret = -1;
memset(buf, 0, sizeof(buf));
ret = read(fd, buf, 10);
if(ret > 0)
{
printf("鼠标输出的内容是:[%s]/\n", buf);
}
memset(buf, 0, sizeof(buf));
ret = read(0, buf, 10);
if(ret > 0)
{
printf("键盘输入的内容为[%s].\n", buf);
}
}
*/
/*
//读取鼠标
int fd = -1;
char buf[200];
//打开文件 //O_NONBLOCK变为非阻塞的,
fd = open("/dev/input/mouse0", O_RDONLY | O_NONBLOCK);
if(fd < 0)
{
perror("open");
exit(0);
}
//读文件
memset(buf, 0, sizeof(buf));
printf("打印之前before read\n");
read(fd, buf, 10);
printf("读出的内容是:[%s].\n",buf);
*/
/*
//读取键盘, 键盘就是标准的输入,stdin,0..因为这个文件已经打开了,所以不需要open
int flag = -1;
char buf[100] = {0};
//把0号文件描述符变成非阻塞式的
flag = fcntl(0, F_GETFL); //先获取原来的文件标志
flag |= O_NONBLOCK; //添加非阻塞属性
fcntl(0, F_SETFL, flag); //更新flag
//经过这三步之后,文件就变成了非阻塞式的了
memset(buf, 0, sizeof(buf));
printf("before read.\n");
read(0, buf, 10);
printf("读出的内容为:【%s】\n", buf);//直接读下来,即使没有内容也没有阻塞。
*/
//在父子进程同时执行这个阻塞式IO
pid_t pid = -1;
pid = fork();
if(pid >0)
{
//父进程
int fd = -1;
char buf[200];
//打开文件 //O_NONBLOCK变为非阻塞的,
fd = open("/dev/input/mouse1", O_RDONLY );
if(fd < 0)
{
perror("open");
exit(0);
}
//读文件
memset(buf, 0, sizeof(buf));
printf("打印之前before read\n");
read(fd, buf, 10);
printf("读出的内容是:[%s].\n",buf);
}
else if(pid == 0)
{
//子进程
//读取键盘, 键盘就是标准的输入,stdin,0..因为这个文件已经打开了,所以不需要open
int flag = -1;
char buf[100] = {0};
//把0号文件描述符变成非阻塞式的
//flag = fcntl(0, F_GETFL); //先获取原来的文件标志
//flag |= O_NONBLOCK; //添加非阻塞属性
//fcntl(0, F_SETFL, flag); //更新flag
//经过这三步之后,文件就变成了非阻塞式的了
memset(buf, 0, sizeof(buf));
printf("before read.\n");
read(0, buf, 10);
printf("读出的内容为:【%s】\n", buf);//直接读下来,即使没有内容也没有阻塞。
}
else
{
//错误
printf("fork出现错误\n");
}
exit(0);
}
3.6.3.2、多路复用IO
3.6.3.3、异步通知(异步IO)
3.6.4.IO多路复用原理
3.6.4.1、何为IO多路复用:就像一个多路开关,一开始先阻塞在那里,然后哪一路需要使用,就用开关导通那一路。
(1)IO multiplexing
(2)用在什么地方?多路非阻塞式IO。
(3)select(类似监视函数,)和poll(这两个函数本身是阻塞式的,内部实现机理:其内部也是以自动轮询的方式来实现的,非阻塞的形式。)
(4)外部阻塞式,内部非阻塞式自动轮询多路阻塞式IO。
while(1) 是以浪费CPU的使用权为代价的,sleep()是以在睡眠期间交出CPU的使用权,二者使用情景不同。
3.6.4.2、select(
线程不安全的
)函数介绍:int FD_ISSET(int fd, fd_set *set); 将fd放到第二个参数里面去测试,如果返回值是正的,就表示这路IO发生了。void FD_SET(int fd, fd_set *set); 将文件描述符添加到集合中。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>
/*
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
*/
int main(int argc, char** argv)
{
//打开鼠标的文件
int fd = -1, ret = -1;
char buf[200] = {0};
fd_set myset; //是一long类型的数组
struct timeval timeout;
fd = open("/dev/input/mouse0", O_RDONLY);
if(fd < 0)
{
perror("open");
exit(-1);
}
//当前有两个fd,一个0一个fd
FD_ZERO(&myset); //清空集合
FD_SET(0, &myset);
FD_SET(fd, &myset);
timeout.tv_sec = 10;
timeout.tv_usec = 0;
ret = select(fd+1, &myset, NULL, NULL, &timeout); //第一个参数是文件描述符的作用。因为文件描述符是从0开始的,所以最大文件描述符+1
if(ret < 0)
{
perror("select");
exit(-1);
}
else if(ret == 0)
{
printf("超时了\n");
}
else
{
//等到了一路IO,然后去检测是哪一个IO,去处理
if(FD_ISSET(0, &myset))
{
//这里处理键盘
memset(buf, 0 , sizeof(buf));
ret = read(0, buf, 10);
if(ret < 0)
{
perror("read");
exit(-1);
}
printf("键盘读出的内容是:[%s]\n", buf);
}
if(FD_ISSET(fd, &myset))
{
//在这里处理鼠标
memset(buf, 0 , sizeof(buf));
ret = read(fd, buf, 10);
if(ret < 0)
{
perror("read");
exit(-1);
}
printf("键盘读出的内容是:[%s]\n", buf);
}
}
close(fd);
close(0);
exit(0);
}
3.6.4.3、poll(
常用,同样线程不安全
)函数介绍:
要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞(可以同时读写)。 第二个参数 nfds 制定数组中元素个数(文件描述符都是从0开始的)。第三个参数指定 poll 函数返回前等待多长时间。第一个参数是一个结构体数组,需要设置fd,和events事件
结构体数组: 结构体数组是指能够存放多个结构体类型的一种数组形式。
# include < sys/ poll. h>
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
pselect,ppoll允许应用安全的等待文件描述符准备好或捕捉到一个信号
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
/*
struct pollfd
{
int fd;
short events;
short revents;
};
*/
int main(int argc, char** argv)
{
/*
struct pollfd myfd[2] = {0}; //结构体数组的使用,每一个数组里面都有一个结构体类型。
myfd[0].fd = 2;
myfd[0].events = 3;
myfd[0].revents = 4;
myfd[1].fd = 2;
myfd[1].events = 3;
myfd[1].revents = 4;
int i = 0;
for(i = 0; i<2; i++)
{
printf("%d\n",myfd[i].fd);
printf("%d\n",myfd[i].events);
printf("%d\n",myfd[i].revents);
}
*/
//读取鼠标
int fd = -1, ret = -1;
char buf[200] = {0};
struct pollfd myfds[2] = {0}; //结构体数组
fd = open("/dev/input/mouse0", O_RDONLY);//鼠标、、鼠标打开文件调整一下。
if(fd < 0)
{
perror("open");
exit(-1);
}
//初始化我们的pollfd
myfds[0].fd = 0; //键盘
myfds[0].events = POLLIN; //等待读操作。
myfds[1].fd = fd; //鼠标
myfds[1].events = POLLIN; //等待读操作。
ret = poll(myfds, fd+1, 10000); //因为文件描述符是从0开始的。
if(ret < 0)
{
perror("poll");
exit(-1);
}
else if(ret == 0)
{
printf("超时了\n");
}
else
{
int i;
for(i = 0; i<2; i++)
{
//等到了一路IO,然后去判断到底是哪个IO到了,去处理
if(myfds[i].events == myfds[i].revents)
{
//鼠标到了处理鼠标,键盘到了处理键盘。
memset(buf, 0, sizeof(buf));
ret = read(i? fd : 0, buf, 10);
if(ret <0 )
{
perror("read");
exit(-1);
}
printf("鼠标读到的内容是[%s]\n", i? buf:"无数据");
printf("键盘读到的内容是[%s]\n", i? "无数据":buf);
}
}
}
exit(0);
}
3.6.5.IO多路复用实践(网络通信,常用。)
3.6.5.1、用select函数实现同时读取键盘鼠标
3.6.5.2、用poll函数实现同时读取键盘鼠标
3.6.6.异步IO
3.6.6.1、何为异步IO
(1)几乎可以认为:异步IO就是操作系统用软件实现的一套中断响应系统。
(2)异步IO的工作方法是:我们当前进程注册一个异步IO事件(使用signal注册一个信号SIGIO的处理函数),然后当前进程可以正常处理自己的事情,当异步事件发生后当前进程会收到一个SIGIO信号从而执行绑定的处理函数去处理这个异步事件。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <string.h>
int fdmouse = -1;
//绑定到SIGIO信号,在函数内处理异步通知事件。异步IO
void func(int sig)
{
char buf[200] = {0};
if(sig != SIGIO)
exit(-1);
read(fdmouse, buf, 10);
printf("鼠标读到的内容是:[%s]\n",buf);
}
int main(int argc, char** argv)
{
//读取鼠标
char buf[200] ={0};
int flag = -1;
fdmouse = open("/dev/input/mouse1", O_RDONLY);
if(fdmouse < 0)
{
perror("open");
exit(-1);
}
//把鼠标的文件描述符设置为可以接受异步的IO
flag = fcntl(fdmouse, F_GETFL); //获得当前文件描述符的状态
flag |= O_ASYNC;
fcntl(fdmouse, F_SETFL, flag); //重新设置文件描述符,添加一个接受O_ASYNC
//把异步IO事件的接受进程,设置为当前进程。
fcntl(fdmouse, F_SETOWN, getpid()); //F_SETOWN:设置将要在文件描述词fd上接收SIGIO 或 SIGURG事件信号的进程或进程组标识 。
//注册当前进程的SIGIO信号的捕获函数
signal(SIGIO, func); //自动将信号的number(number是8)传给func
//printf("鼠标读到的内容是:[%s]\n",buf);
//正常情况下执行键盘的读取,将读取鼠标注册为异步IO后,发生读取鼠标事件,则去读取鼠标。类似中断。
#if 1
while(1)
{
memset(buf, 0, sizeof(buf));
//printf("before 键盘 read.\n");
read(0, buf, 5);
printf("键盘读出的内容是:[%s].\n", buf);
}
#endif
exit(0);
}
3.6.6.2、涉及的函数:*(
必须有其他进程正在执行,程序没停止,才可以进行这种中断响应。
)
(1)fcntl(F_GETFL、F_SETFL、O_ASYNC、F_SETOWN)
F_SETOWN:设置将要在文件描述词fd上接收SIGIO 或 SIGURG事件信号的进程或进程组标识 。
(2)signal或者sigaction(SIGIO)
3.6.3.代码实践
3.6.7.存储映射IO
3.6.7.1、mmap(内存映射,是共享,而不是复制。一块内存是另一块的影子,指向同一块物理内存。)函数:man手册,文件和内存对应起来。
3.6.7.2、LCD显示和IPC(进程间通信)之共享内存
3.6.7.3、存储映射IO的特点
(1)共享而不是复制,减少内存操作
(2)处理大文件时效率高(譬如图像,图像是连续的),小文件不划算