IO模型
1. 非阻塞
1.1 概念
当一个应用层程序,读取硬件数据,不管数据有没有准备好,read函数都不会阻塞,而是立即返回数据。
1.2 模型
应用层:
fd = open("/dev/myled",O_RDWR | O_NONBLOCK); //以非阻塞方式打开
read(fd,buf,sizeof(buf)); //读取硬件中的数据
内核层:
if(file->f_flags & O_NONBLOCK) //判断是否以非阻塞方式打开
{
用户以非阻塞方式打开
读取硬件数据
不管硬件中的数据是否准备好,都会立即返回硬件中的数据
}
硬件层:
温湿度传感器值
2. 阻塞
2.1 工作原理
当一个应用层程序,去读取硬件的数据,如果硬件中的数据没有准备好,此时进程进入休眠状态。
当硬件中的数据准备好,硬件会产生中断。在中断处理函数中,唤醒休眠的进程。
当进程被唤醒之后,从硬件中读取数据,将数据读取到内核空间。
再次调用copy_to_user,将内核空间中的数据,拷贝到用户空间
2.2 模型
定义等待队列头
wait_queue_head_t wq;
初始化等待队列头
init_waitqueue_head(&wq);
让进程进入可中断的等待态
wait_event_interruptible(wq, condition);
condition = 0为假,表示数据没有准备好,数据不可读,进程进入休眠
condition = 1为真,表示数据准备好,数据可读
唤醒可中断的等待态
condition = 1;为真
wake_up_interruptible(&wq);
3. IO多路复用
3.1 工作原理
在同一个应用层程序,如果想同时读取多个文件描述数据,此时就需要使用IO多路复用机制
使用slect/poll/epoll可以监听多个文件描述符
如果所有的文件描述符数据没有准备好,进程进入休眠状态
如果有一个或者多个文件描述符数据准备好,唤醒休眠的进程
当进程唤醒之后,从内核中读取到准备好的文件描述符集合
从准备好的文件描述符集合中,找到准备好的文件描述符
从文件描述符中读取数据
3.2 模型
应用层:
fd1 = open("/dev/myled0",O_RDWR); //打开led灯设备文件
fd2 = open("/dev/input/mouse0",O_RDWR); //打开鼠标文件
fd_set readfds; //定义读表集合
FD_ZERO(&readfds); //清空读表集合
FD_SET(fd1, &readfds); //将fd1加入读表集合中
FD_SET(fd2, &readfds); //将fd2加入读表集合中
select(fd2+1,&readfds,NULL,NULL,NULL);//监听
if(FD_ISSET(fd1,&readfds))//判断读表集合中fd1数据是否准备好
{
//读取fd1文件描述符中的数据
}
if(FD_ISSET(fd2,&readfds))//判断读表集合中fd2数据是否准备好
{
//读取fd2文件描述符中的数据
}
close(fd1);
close(fd2);内核层:
应用层select/poll/epoll对应底层poll函数
__poll_t myled_poll (struct file *file, struct poll_table_struct *wait)
{
//定义mask变量返回值 __poll_t mask = 0;
//调用poll_wait函数 poll_wait(file,&wq,wait);
//判断数据是否可读
//返回mask变量
}
4. epoll的使用
4.1 select/poll/epoll区别
select
select监听最大文件描述符为1024个
select每次都有清空表的过程,需要反复构造表,反复将用户空间表拷贝到内核空间
当数据准备好之后,select需要在再次遍历表,找到准备好的文件描述符,效率低
poll
poll监听最大文件描述符没有个数限制
poll没有清空表的过程,不需要返回构造表
当数据准备好之后,poll需要在再次遍历表,找到准备好的文件描述符,效率低
epoll
epoll监听最大文件描述符没有个数限制
epoll没有清空表的过程,不需要返回构造表
当数据准备好之后,无需遍历,epoll可以直接找到准备好的对应的文件描述符,直接从准备好的文件描述符中读取数据,效率高
4.2 epoll模型
应用层
int epoll_create(int size);
函数功能:创建epoll实例
参数:
size:没有实际含义,只需要填写大于0的值
返回值:
成功返回创建epoll实例对应的文件描述符
失败返回-1int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数功能:对epoll实例控制函数
参数:
epfd:创建epoll的实例
op:操作的方式
EPOLL_CTL_ADD:向epoll实例中添加要监听的文件描述符
EPOLL_CTL_MOD:修改epoll实例中监听的文件描述符
EPOLL_CTL_DEL:删除epoll实例中监听的文件描述符
fd:被操作的文件描述符
event:事件结构体
typedef union epoll_data {
void *ptr;
int fd; ==========> 被操作的文件描述
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; //监听事件类型 EPOLLINepoll_data_t data; //用户数据
};
返回值:
成功返回0,失败返回-1int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout)
函数功能:阻塞等待准备好的文件描述符
参数:
epfd:创建epoll的实例
events:返回准备好的事件结构体指针
maxevents:监听最大文件描述符个数
timeout:超时事件 填写-1,表示不关系超时事件
返回值:
成功返回准备好的文件描述符个数
失败返回-1
5. 异步通知
5.1 引入
使用阻塞、非阻塞、IO多路复用,都是用户主要调用接口,等待数据返回
使用阻塞、非阻塞、IO多路复用,如果数据没有准备好,需要阻塞等待,并且进程进入休眠状态
在休眠的这段时间,任何其他的事情都不可以去做,所以内核中引入异步通知
能够在硬件的数据准备好的时候,底层给应用层发送一个通知
应用层接收到这个通知之后,调用read函数去读取数据
在没有接收到这个通知,应用层可以去做其他的事情,不需要一直阻塞等待
5.2 工作原理
用户需要在应用层空间需要注册信号函数
如果硬件的数据没有准备好,这个进程去做其他的事情就可以
如果硬件的数据准备好,硬件会产生一个中断
在中断处理函数中,给进程发送一个通知
进程接收到这个通知之后,执行信号处理函数
在信号处理函数中读取数据(信号的发送和应用层程序执行属于异步)
5.3 模型
应用层:
fd = open("/dev/myled",O_RDWR);
signal(SIGIO,信号处理函数);
//应用层的fcntl函数,对应底层fasync函数
unsigned int flag = fcntl(fd,F_GETFL); //获取文件的属性
fcntl(fd,F_SETFL, flag | FASYNC);
//告诉驱动接收信号的进程
fcntl(fd,F_SETOWN,getpid());
内核层:linux@ubuntu:~/linux-5.10.61$ grep ".fasync = " * -nR
drivers/char/random.c:1994: .fasync = random_fasync,
drivers/char/random.c:2003: .fasync = random_fasync,
struct fasync_struct *fasync;
int myled_fasync(int fd, struct file *filp, int on)
{
return fasync_helper(fd, filp, on, &fasync);
}
kill_fasync(&fasync, SIGIO, POLL_IN) //发信号