Linux中的BIO和NIO
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
还没有评论,来说两句吧...