基础IO模型
场景假设
假设妈妈有一个孩子,孩子在房间里睡觉,妈妈需要及时获知孩子是否醒了,如何做?
- 时不时进房间看一下:简单,空闲时间还能干点别的,但是很累
- 进到房间陪着孩子一起睡觉,孩子醒了会吵醒妈妈:不累,但是不能干别的了
- 妈妈在客厅干活,小孩醒了他会自己走出房门告诉妈妈:互不耽误
阻塞型模型
- 当程序调用某些接口时,如果期望的动作无法触发,那么进程会进入阻塞态(等待状态,让出CPU的调度),当期望动作可以被触发了,那么会被唤醒,然后处理事务。
- 阻塞I/O模式是最普遍使用的I/O模式,大部分程序使用的都是阻塞模式的I/O 。
- 前面学习的很多读写函数在调用过程中会发生阻塞。
读操作中的:read、recv、recvfrom、gets、scanf、fgets、getchar
写操作中的:write、send
其他操作:accept、connect
实例:
#include <stdio.h>
int main(int argc, char const *argv[])
{
char buf[64] = {
0};
while (1)
{
gets(buf); //进程进入等待状态,发生CPU调度
printf("stdin -- %s\n", buf);
}
return 0;
}
程序运行后使用top命令查看当前进程的资源占用(CPU)非常小,进入了阻塞状态。所以大多数的系统接口都优先设计成阻塞型,以便能随时释放CPU资源。
非阻塞型模型
- 当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做轮询)。
- 应用程序不停的轮询内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ...);
功能:
获取/改变文件属性(linux中一切皆文件)
参数:
fd:文件描述符
cmd:设置的命令
F_GETFL //获取文件的属性
F_SETFL //设置文件的属性
第三个参数:由第二个参数决定,set时候需要设置的值,get时候填0
返回值:
文件状态标志(文件的属性)
-1 :失败
典型代码
int flag;//文件状态的标志
flag = fcntl(fd, F_GETFL); //读
flag |= O_NONBLOCK;//改 O_NONBLOCK = 0x00004000
fcntl(fd, F_SETFL, flag);//写
结论:非阻塞非常占用CPU,不到万不得已,不要用这种属性。
异步IO模型(非重点)
- 通过信号方式,当内核检测到设备数据后,会主动给应用发送信号SIGIO。
- 应用程序收到信号后做异步处理即可。
- 应用程序需要把自己的进程号告诉内核,并打开异步通知机制。
标准模板
//将APP进程号告诉内核
fcntl(fd, F_SETOWN, getpid());
//使能异步通知
int flag;
flag = fcntl(fd, F_GETFL);
flag |= O_ASYNC; //也可以用FASYNC标志
fcntl(fd, F_SETFL, flag);
//异步通知
signal(SIGIO, handler);
示例:用异步IO的方式监听鼠标的数据(普通用户读取鼠标需要增加root权限)
用这个命令可以查看鼠标的原始数据
sudo hexdump /dev/input/mouse0
用阻塞模读取鼠标数据
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
char buf[64] = {
0};
int len;
//打开鼠标设备
int fd = open("/dev/input/mouse0", O_RDONLY);
if(fd < 0)
{
perror("open err");
return -1;
}
while (1)
{
//阻塞型
len = read(fd, buf, 64);
if(len < 0)
{
perror("read err");
return -1;
}
else
{
printf("len = %d\n", len);
}
}
return 0;
}
用异步IO模型来读取鼠标数据
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
static int fd;
void handler(int signum)
{
char buf[64] = {
0};
int len;
//如果一旦这个方法被调用,也就意味着SIGIO信号被发出了
//代表当前设备有数据
len = read(fd, buf, 64);
if(len < 0)
{
perror("read err");
}
else
{
printf("len = %d\n", len);
}
}
int main(int argc, char const *argv[])
{
//打开鼠标设备
fd = open("/dev/input/mouse0", O_RDONLY);
if(fd < 0)
{
perror("open err");
return -1;
}
//以下代码是改成异步IO模式
#if 1
//将APP进程号告诉内核
fcntl(fd, F_SETOWN, getpid());
//使能异步通知
int flag;
flag = fcntl(fd, F_GETFL);
flag |= O_ASYNC; //也可以用FASYNC标志
fcntl(fd, F_SETFL, flag);
//异步通知
signal(SIGIO, handler);
#endif
while (1)
{
//可以做一些别的事情
printf("喝茶\n");
sleep(3);
}
return 0;
}
ps:当进程调用sleep函数进入休眠状态时,如果收到SIGIO信号,sleep函数的调用会被中断,进程会转而执行信号处理函数。处理完信号后,进程通常不会继续休眠,而是继续执行后续的代码。这种机制允许进程在休眠期间响应某些异步事件,如输入/输出操作完成等。