1.为什么有阻塞IO
(1)常见的阻塞:wait、pause、sleep等函数,read和write某些特殊文件时
(2)阻塞式的好处,在阻塞等待的进程不消耗CPU运行,提高性能。
2.如何实现非阻塞IO访问
(1)O_NONBLOCK和fcntl
3.阻塞式IO的困境
(1)程序中读取键盘
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
char buf[100];
memset(buf,0,sizeof(buf));
printf("before read\n");
//读取键盘键盘就是标准输入,stdin
read(0,buf,2);//read默认是阻塞的
printf("读出的内容是:[%s]\n",buf);
return 0;
}
(2)程序中读取鼠标
这里的鼠标输入在/dev/input/mouse1有的是在mouse0下面通过cat /dev/input/mouse0或者mouse1然后移动鼠标出现乱码则就是鼠标输入设备。
#include <string.h>
#include <unistd.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/mouse0",O_RDONLY);
if(fd < 0)
{
perror("open");
return -1;
}
memset(buf,0,sizeof(buf));
printf("before read\n");
//读取鼠标
read(fd,buf,5);//read默认是阻塞的
printf("读出的内容是:[%s]\n",buf);
return 0;
}
(3)程序中同时读取键盘鼠标
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd = -1;
char buf[200];
fd = open("/dev/input/mouse0",O_RDONLY);//打开鼠标
if(fd < 0)
{
perror("open");
return -1;
}
memset(buf,0,sizeof(buf));
printf("before 鼠标 read\n");
//读取鼠标
read(fd,buf,5);//read默认是阻塞的
printf("鼠标读出的内容是:[%s]\n",buf);
memset(buf,0,sizeof(buf));
printf("before 键盘 read\n");
//读取键盘键盘就是标准输入,stdin
read(0,buf,2);//read默认是阻塞的
printf("键盘读出的内容是:[%s]\n",buf);
return 0;
}
(4)问题分析
像这种情况就只能先读鼠标再读键盘这就是阻塞式的缺陷
4.并发式IO
(1)非阻塞式IO
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd = -1;
int ret = -1;
int flag = -1;
char buf[200];
fd = open("/dev/input/mouse0",O_RDONLY|O_NONBLOCK);//打开鼠标
if(fd < 0)
{
perror("open");
return -1;
}
//把0号文件描述符(stdin)变成非阻塞式
//int fcntl(int fd, int cmd, ... * arg * );
flag = fcntl(0,F_GETFL); //先获取原来的flag
flag |= O_NONBLOCK; //添加非阻塞属性
fcntl(0,F_SETFL,flag); //更新flag
//上面三步将0这个文件描述符变为非阻塞式的
while(1)
{
memset(buf,0,sizeof(buf));
//printf("before 鼠标 read\n");
//读取鼠标
ret = read(fd,buf,5);//read默认是阻塞的
if(ret > 0)
{
printf("鼠标读出的内容是:[%s]\n",buf);
}
memset(buf,0,sizeof(buf));
//printf("before 键盘 read\n");
//读取键盘键盘就是标准输入,stdin
ret = read(0,buf,5);//read默认是阻塞的
if(ret > 0)
{
printf("键盘读出的内容是:[%s]\n",buf);
}
}
return 0;
}
5.多路复用IO
(1)何为IO多路复用(IO multiplexing)
(2)用在什么地方:多路非阻塞式IO
(3)select和poll(本身是阻塞式函数)
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数1:文件描述符的个数(文件IO中最大文件描述符+1)
参数2:要读的文件描述符的集合(相当于结构体,放多个需要读的文件描述符)
参数3:要写的多个文件描述符
参数4:看文件描述符是否出错
参数5:设置超时时间(如果这段事间一直没有IO事件发生就返回0)
返回值:返回发生了几次IO事件
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数1:多个文件描述符的集合
参数2:放入这个集合的文件描述符个数+1
参数3:超时时间
返回值:返回发生了几次IO事件
select函数实现
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
int main(void)
{
int fd = -1;
int ret = -1;
fd_set myset;
char buf[200];
struct timeval tm;
fd = open("/dev/input/mouse0",O_RDONLY);//打开鼠标
if(fd < 0)
{
perror("open");
return -1;
}
//当前有两个fd,1个是fd,1个是0
//处理myset
FD_ZERO(&myset);//先清零
FD_SET(fd,&myset);//设置fd
FD_SET(0,&myset);//设置0这个fd
tm.tv_sec = 10;//设置秒
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));
//读取键盘键盘就是标准输入,stdin
read(0,buf,5);//read默认是阻塞的
printf("键盘读出的内容是:[%s]\n",buf);
}
else if(FD_ISSET(fd,&myset))
{
//这里处理鼠标
memset(buf,0,sizeof(buf));
read(fd,buf,5);//read默认是阻塞的
printf("鼠标读出的内容是:[%s]\n",buf);
}
}
return 0;
}
poll函数实现:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <poll.h>
int main(void)
{
int fd = -1;
int ret = -1;
struct pollfd myfds[2] = {0};
char buf[200];
struct timespec tm;
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;//等待读操作
tm.tv_sec = 10000;
//int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//ret = poll(myfds,2,tm.tv_sec);//2个文件描述符,10000毫秒
ret = poll(myfds,fd,10000);//2个文件描述符,10000毫秒,这里写fd文件运行>直接退出
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));
//读取键盘键盘就是标准输入,stdin
read(0,buf,5);//read默认是阻塞的
printf("键盘读出的内容是:[%s]\n\r",buf);
}
if(myfds[1].events == myfds[1].revents)
{
//这里处理鼠标
memset(buf,0,sizeof(buf));
read(fd,buf,50);//read默认是阻塞的
printf("鼠标读出的内容是:[%s]\n",buf);
}
}
return 0;
}
(4)外部阻塞式,内部非阻塞式自动轮询多路阻塞式IO
6.异步通知(异步IO)
(1)异步IO就是操作系统用软件实现的一套中断系统。
(2)异步IO的工作方法:我们当前进程注册一个异步IO事件(使用signal注册一个信号SIGIO的处理函数),然后当前进程可以正常处理自己的事情,当异步事件发生后当前进程会收到一个SIGIO信号从而执行绑定的处理函数去处理这个异步事件。
(3)涉及函数(fcntl(F_GETFL、F_SETFL、O_ASYNC、F_SETOWN))、signal或sigaction(SIGIO)
(4)代码实现
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int mousefd = -1;
//异步通知函数,绑定SIGIO信号,在函数内处理异步通知事件
void func(int sig)
{
char buf[200] = {0};
if(sig != SIGIO)
return;
//读取鼠标
read(mousefd,buf,5);//read默认是阻塞的
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));
//读取键盘键盘就是标准输入,stdin
read(0,buf,5);//read默认是阻塞的
printf("键盘读出的内容是:[%s]\n",buf);
}
return 0;
}
7.存储映射IO
(1)mmap函数
(2)LCD显示和IPC之共享内存
(3)存储映射的特点:共享而不是复制,减少内存操作。处理大文件时效率高,小文件不划算
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);