Linux中的BIO和NIO
1. 概念
-
BIO(阻塞IO)
执行某个操作时,若不能获得资源,则挂起进程知道满足条件获取资源后再执行。挂起进程的唤醒一般发生在中断里,因为硬件资源的获取一般伴随着一个中断
//以阻塞的方式从串口读取一个字符 char buf[20]; fd = open("/dev/ttyS1", O_RDWR); //阻塞读取字符 res = read(fd, &buf, 1); printf("%c\n", buf);
-
NIO(非阻塞IO)
执行某个操作时,若不能获得资源,不会挂起进行,要么放弃,要么不停的查询,直到可以进行操作为止
//以非阻塞的方式从串口读取一个字符 char buf[20]; fd = open("/dev/ttyS1", O_RDWR | O_NONBLOCK); //读取字符,读取操作立即返回,所以需要循环读取 while(read(fd, &buf, 1) == -1) continue; printf("%c\n", buf);
-
改变文件的读写方式
除了可以在文件打开时定义读写方式外还可以通过ioctl()和fcntl()改变读写方式。
void ioctl(...); void fcntl(...);
2. 等待队列
等待队列是BIO的底层实现方式
- 等待队列与Linux内核的进程调度紧密结合
- 信号量是依赖于等待队列实现的
//定义等待队列
wait_queue_head_t queue;
//初始化头
void init_waitqueue_head(&queue);
//定义+初始化头
DECLARE_WAIT_QUEUE_HEAD(name);
//定义元素
DECLARE_WAITQUEUE(name, tsk);
//添加到队列
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
//从队列移除
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
//等待事件
void wait_event(queue, condition);
void wait_event_timeout(queue, condition);
void wait_event_interruptible(queue, condition);
void wait_event_interruptible_timeout(queue, condition);
//唤醒队列
void wake_up(queue);
void wake_up_interruptible(queue);
在等待队列中休眠(与wake_up成对使用)
void sleep_on(queue);
void interruptible_sleep_on(queue);
3. 轮询操作
-
select()、poll()系统调用都是BIO中查询文件的方式,一次可以查询多个文件描述符,其中任一个变得可写或可读时返回。是一种多路复用的思想
-
select()、poll()在文件量增大时,性能降低
//任一文件变得可读、可写、异常时返回 int select( //监听的文件的最高fd+1 int numfds, //监听的读文件 fd_set *readfds, //监听的写文件 fd_set *writefds, //监听的异常文件 fd_set *exceptfds, //监听超时 struct timeval *timeout ); //下面的宏用于操作fd_set文件描述符集合 //清空 FD_ZERO(fd_set *set); //添加 FD_SET(int fd, fd_set *set); //删除 FD_CLR(int fd, fd_set *set); //判断 FD_ISSET(int fd, fd_set *set); //poll操作与select操作类似 int poll(struct pollfd *fds, nfds_t nfds, int timeout);
-
epoll()是poll()的扩展,是一种事件驱动的操作
-
epoll不会由于fd的数量变大而降低性能
//创建epoll,设置监听的fd数量,epoll本身占用一个fd int epoll_create(int size); //关闭epoll fd void epoll_close(); //等待事件产生,events是输出(从内核得到的事件集合),maxevents是本次最多接收的事件数 //timeout是超时(毫秒),-1表示永久 //返回本次接收到的事件数 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); //设置epoll监听的fd //参数op: EPOLL_CTL_ADD 注册新的fd到epfd中 //参数op: EPOLL_CTL_MOD 修改已注册的fd的监听事件 //参数op: EPOLL_CTL_ADD 从epfd中删除一个fd int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //struct epoll_event是监听的事件类型 struct epoll_event { //EPOLLIN 可读 //EPOLLOUT 可写 //EPOLLPRI 有紧急数据可读(socket紧急数据) //EPOLLERR fd发生错误 //EPOLLHUP fd被挂断 //EPOLLONESHOT 一次性监听,一次监听事件完成后,需要再次把fd加入epfd中 //EPOLLET epoll设为边缘触发模式(Edge Triggered) //默认是水平触发模式(Level Triggered) //ET是高速模式,当fd从非就绪变为就绪时,不会发送就绪通知 __uint32_t events; epoll_data_t data; };
-
少量fd的查询用select、poll,大量fd的查询用epoll