linux 5种IO模型总结
linux中一切皆文件,文件皆可读写,读写即IO. 因此IO在linux中绝对是一个重要的角色。
针对IO是否是同步/异步,阻塞/非阻塞,以及IO复用等,以下是5种IO模型的总结
1、同步阻塞式IO
这个最好理解:
我们举个读取鼠标事件的例子,如下:
首先先找到这个输入设备,以及它的设备名 ls /dev/input/ 以及 cat /proc/bus/input/devices
int main() { //the open flag could only be O_RDONLY, O_WRONLY, O_RDWR int fd = open("/dev/input/mouse0", O_RDONLY); char buf[512]; while(1) { printf(" --- begin read ---\n"); ssize_t bytes = read(fd, buf, sizeof(buf)); printf(" === end of read ===\n"); if(bytes < 0) { perror("read /dev/input/mouse0 failed\n"); } else { printf("read %d bytes.\n", bytes); for(int i = 0; i < bytes; i++) { printf("%x ", buf[i]); } printf("\n\n"); } } return 0; }
可以看到,默认的read方式是阻塞的,缓冲区中没有数据就会阻塞在read中,好在阻塞的时候会释放CPU。当有数据的时候,内核会将数据从内核空间拷贝到用户空间,并从read中返回

2、同步非阻塞式IO
这里需要借助fcntl把上述已经打开的fd设置成非阻塞的
static void sync_nonblock_io_mode(void) { //the open flag could only be O_RDONLY, O_WRONLY, O_RDWR int fd = open("/dev/input/mouse0", O_RDONLY); // set file status to non block int flags = fcntl(fd, F_GETFL); flags |= O_NONBLOCK; int ret = fcntl(fd, F_SETFL, flags); if(ret == -1) { perror("fail to set fd to non block mode\n"); } char buf[512]; while(1) { printf(" --- begin read ---\n"); ssize_t bytes = read(fd, buf, sizeof(buf)); printf(" === end of read ===\n"); if(bytes < 0) { perror("read /dev/input/mouse0 failed\n"); } else { printf("read %d bytes.\n", bytes); for(int i = 0; i < bytes; i++) { printf("%x ", buf[i]); } printf("\n\n"); } sleep(1); } }
执行结果如下:

可见即使没有数据也不会阻塞住,而是返回一个错误然后继续运行,这里加了个sleep的操作,不然的话,CPU会很快被while循环占满。
3、异步IO
异步IO没有阻塞非阻塞的概念,纯纯是非阻塞的,可以按照中断来理解。
glibc提供了libaio, 是使用线程加阻塞调用模拟的,据说不咋滴。我们看下内核提供的异步IO接口。注意包含头文件<linux/aio_abi.h>
也就是说这里提供的接口时linux特有的系统调用,不是被标准C所接收,应当注意这一点。
大概就是aio_read的时候,如果有数据就能直接读出来,如果没有,那么因为这是一个异步读请求,aio_read会直接返回,
当内核有数据ready的时候,会copy到用户空间,然后通知到用户注册的signal handler.
4、信号驱动的IO
在我看来这不就是异步IO的一种吗。预先注册好回调函数,内核会通过SIGIO通知,并调用进程的回调函数
注意,这种方式不适合信号产生频繁,数据量很大的方式,因为这样内核要不断把数据复制到用户态,性能会变得较差
int g_fd = 0;
void signal_handler(int sig)
{
char buf[512];
printf(" --- begin read ---\n");
ssize_t bytes = read(g_fd, buf, sizeof(buf));
printf(" === end of read ===\n");
if(bytes < 0) {
perror("read /dev/input/mouse0 failed\n");
} else {
printf("read %d bytes.\n", bytes);
for(int i = 0; i < bytes; i++) {
printf("%x ", buf[i]);
}
printf("\n\n");
}
}
static void signal_io_mode(void)
{
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = signal_handler;
sigaction(SIGIO, &act, NULL); // 监听SIGIO事件
g_fd = open("/dev/input/mouse0", O_RDONLY);
//这几步设置需要注意
fcntl(g_fd, F_SETOWN, getpid());
int flags = fcntl(g_fd, F_GETFL, 0);
flags |= O_NONBLOCK;
flags |= O_ASYNC;
fcntl(g_fd, F_SETFL, flags);
while(1) {
printf("do some other things...\n");
sleep(1);
}
}
int main()
{
//sync_nonblock_io_mode();
signal_io_mode();
return 0;
}
执行结果如下:

5、IO多路复用
这个我就不想花太多时间了,就是epoll,select这些。有空会为epoll开一个专题讲。
浙公网安备 33010602011771号