为什么将select和poll机制放在一起是因为两个基本是类似的,在内核中两个机制也是在同一个文件中实现的。相比较之前的阻塞数据访问在驱动使用等待队列,select和poll只是系统提供了一种I/O读写的多路复用机制(底层也是等待队列),通过这一套机制在同一个线程中可以实现多个设备文件fd的数据读写操作。如果没有系统的这套操作流程,无论是使用阻塞还是非阻塞的读写方式都是没有办法使用一个线程完成多个设备I/O读写,最简单粗暴的方式是每一个设备fd分配一个独立线程完成数据读写工作。在读select、poll源码前,需要先了解的知识点:
- 等待队列
- 文件系统(主要是进程的打开文件描述符表以及struct file)
- poll机制
1. 概述
今天我们来分析下IO多路复用机制,在Linux中是通过select/poll/epoll
机制来实现的。先看一下阻塞IO模型与非阻塞IO模型的特点:
-
阻塞IO模型:在IO访问的时候,如果条件没有满足,会将当前任务切换出去,等到条件满足时再切换回来。
-
缺点:阻塞IO操作,会让处于同一个线程的执行逻辑都在阻塞期间无法执行,这往往意味着需要创建单独的线程来交互。
-
-
非阻塞IO模型:在IO访问的时候,如果条件没有满足,直接返回,不会block该任务的后续操作。
-
缺点:非阻塞IO需要用户一直轮询操作,轮询可能会来带CPU的占用问题。
-
对单个设备IO操作时,问题并不严重,如果有多个设备呢?比如,在服务器中,监听多个Client的收发处理,这时候IO多路复用就显得尤为重要了,来张图:
如果这个图,让你有点迷惑,那就像个男人一样,man
一下select/poll
函数吧:
1.1 select
// sizeof(fd_set) = 128bytes = 1024bit
// 也就是说最多可以检测1024个文件描述符
// fd_set是传入传出参数(指针)
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
- nfds : 委托内核检测的最大文件描述符+1
- readfds : 要检测的读文件描述符集合(就是委托内核检测读缓冲区中是否有数据进来)
- writefds : 要检测的写文件描述符集合(委托内核检测写缓冲区是否能写,没满就可以写)
- exceptfds : 没啥用
- timeout : 这个数据类型我们之前已经接触过了(一个结构体,包含s和ms两部分),用于设置超时时间
- 返回值 : -1表示失败, >0(n), 检测的集合中有n个描述符发生了变化
// 下面这些函数用于对fd_set进行各种操作
// 将参数fd指定的文件描述符置0
void FD_CLR(int fd, fd_set* set);
// 判断fd对应的文件描述符标志位是0还是1,返回对应的0和1
void FD_ISSET(int fd, fd_set* set);
// 将参数fd指定的文件描述符置1
void FD_SET(int fd, fd_set* set);
// 全部初始化为0
void FD_ZERO(fd_set* set);
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
/*select*/
#include <sys/select.h>
/*exit*/
#include <stdlib.h>
int main(){
//1.创建文件mkfifo fifo1 fifo2 fifo3
//2.打开文件
int fd1 = open("fifo1", O_RDONLY);
int fd2 = open("fifo2", O_RDONLY);
int fd3 = open("fifo3", O_RDONLY);
char buff1[32] = {0};
char buff2[32] = {0};
char buff3[32] = {0};
//1.创建文件描述符集合
fd_set readfds;
FD_ZERO(&readfds);//清空集合
fd_set readfds_temp;用来备份原集合的
FD_ZERO(&readfds_temp);//清空集合
//2.记录表中最大的文件描述符
int max_fd = 0;
//3.将fd添加到集合中
FD_SET(fd1,&readfds);
max_fd=(max_fd>fd1?max_fd:fd1);//找最大的文件描述符
FD_SET(fd2,&readfds);
max_fd=(max_fd>fd2?max_fd:fd2);//
FD_SET(fd3,&readfds);
max_fd=(max_fd>fd3?max_fd:fd3);//
while(1){
//select会将没有就绪的文件描述符擦除
readfds_temp=readfds;
//4.select找就绪的文件描述符
//成功 返回就绪的文件描述符的个数
if (-1==(select(max_fd+1,&readfds_temp,NULL,NULL,NULL)))
{
perror("select error");
exit(-1);
}
if(FD_ISSET(fd1, &readfds_temp))
{
memset(buff1, 0, 32);
read(fd1, buff1, 32);
printf("buff1 = %s\n", buff1);
}
if(FD_ISSET(fd2, &readfds_temp))
{
memset(buff2, 0, 32);
read(fd2, buff2, 32);
printf("buff2 = %s\n", buff2);
}
if(FD_ISSET(fd3, &readfds_temp))
{
memset(buff3, 0, 32);
read(fd3, buff3, 32);
printf("buff3 = %s\n", buff3);
}
}
close(fd1);
close(fd2);
close(fd3);
return 0;
}