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中返回

linux 5种IO模型总结

 

 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);
    }
}

执行结果如下:

linux 5种IO模型总结

 

 可见即使没有数据也不会阻塞住,而是返回一个错误然后继续运行,这里加了个sleep的操作,不然的话,CPU会很快被while循环占满。

 3、异步IO

异步IO没有阻塞非阻塞的概念,纯纯是非阻塞的,可以按照中断来理解。

glibc提供了libaio, 是使用线程加阻塞调用模拟的,据说不咋滴。我们看下内核提供的异步IO接口。注意包含头文件<linux/aio_abi.h>

也就是说这里提供的接口时linux特有的系统调用,不是被标准C所接收,应当注意这一点。

 

 

 

 

 

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;
}

 执行结果如下:

linux 5种IO模型总结

 

 

5、IO多路复用

这个我就不想花太多时间了,就是epoll,select这些。有空会为epoll开一个专题讲。

 

上一篇:c++小练习——函数递归调用小实践


下一篇:5.Java数据类型