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开一个专题讲。