【1】IO多路复用
IO多路复用:在同一app应用程序中想同时监听多个文件描述符,
使用select、poll、epoll在监听,如果数据都没有准备好,select
poll、epoll就会阻塞。当硬件的数据准备好的时候就会产生中断,
在驱动的中断处理函数中唤醒select、poll、epoll即可。当他们
被唤醒之后,然后判断文件描述符的集合中哪个文件描述的数据准备
好了,如果数据准备好了,就从硬件将将数据读取到用户空间即可。
fd1 = open("mma",权限);
fd2 = open("mpu",权限);
将fd1和fd2放入到文件描述符的集合中(读表)
select(maxfd+1,&读表,NULL,NULL,NULL);
if(FD_ISSET(fd1,读表)){
read(fd1,buf,sizeof(buf));
}
if(FD_ISSET(fd2,读表)){
read(fd2,buf,sizeof(buf));
}
------------------------------------------------
vfs:
sys_select:
1.在内核空间分配表的内存,然后调用copy_from_user
将用户空间的文件描述符表拷贝到内核空间。
2.从文件描述符表中取出文件描述符
fd1 fd2 fd3
fd1-->fd_array[fd1]--->file--->poll(指针)-->driver1_poll()
ret1 = driver1_poll();
ret1=0 ret2=0 ret3=0 进程休眠
在虚拟文件系统层,实现了poll_table结构体
调用driver1和driver2中的poll,如果得到的结果都是
0,表示两个驱动的数据都没有准备好,如果数据都没
准备好就将进程休眠。
3.休眠的进程被驱动的wake_up 唤醒
当某一个时间点有一个或者多个进程同时调用wake_up
唤醒了这个休眠的进程。然后它会重新全部调用一下
驱动中的poll函数,获取到返回值,
fd1 fd2 fd3
fd1-->fd_array[fd1]--->file--->poll(指针)-->driver1_poll()
ret1 = driver1_poll();
ret1=POLLIN ret2=0 ret3=POLLIN
将ret1 ret3对应的文件描述符找到fd1 fd3,
并将fd1 fd3放到文件描述符表中
4.将文件描述符表返回到用户空间copy_to_user。
-------------------------------------------------
驱动:
driver1 driver2
driver1_open driver2_open
dirver1_poll dirver2_poll
driver1_read driver2_read
wake_up wake_up
unsigned int (*poll) (struct file *, struct poll_table_struct *);
grep ".poll = " * -nR
//搜索内核中已经实现的poll函数,通过内核实现的poll函数
//来完成自己的poll函数
通过上述代码参考知道,在poll函数中实现的步骤如下
1.定义mask=0
2.调用poll_wait,不会阻塞,它只是完成等待队列头的提交
3.当条件为真设置mask
if(condition == 1){
mask = POLLIN/POLLOUT;
}
4.返回mask
【2】epoll的使用:
#include <sys/epoll.h>
int epoll_create(int size);
功能:创建一个epoll的实例
参数:
@size:已经不再使用,可以填写任意值
返回值:成功返回epoll的文件描述符,失败返回-1;
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:控制箱epfd中添加、修改、删除本次要监听的事件的类型
参数:
@epfd:epoll的文件描述符
@op :EPOLL_CTL_ADD 添加事件
EPOLL_CTL_MOD 修改事件
EPOLL_CTL_DEL 删除事件
@fd :文件描述符
@event:事件的结构体对象
struct epoll_event {
uint32_t events; //EPOLLIN EPOLLOUT
epoll_data_t data; /* User data variable */
};
返回值:成功返回0,失败返回-1
int epoll_wait(int epfd, struct epoll_event *revents,
int maxevents, int timeout);
功能:完成阻塞,如果有数据准备好,就返回
参数:
@epfd :epoll的文件描述符
@revents :返回的事件结构体
@maxevents:最大的结构体个数
@timeout :超时时间
返回值:1.成功返回准备好的文件描述符的个数
2.超时返回0
3.失败返回-1;
【3】面试题:select/poll/epoll的区别?
select:
1.select监听的最大的文件描述符是1024个
2.select每次会清空文件描述符表,每次都需要拷贝用户空间的表到内核空间,效率低
3.select被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低
poll:
1.poll监听的最大的文件描述符没有个数限制
2.poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可
3.poll被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低
epoll:(本世纪最好用的io多路复用机制)
1.epoll监听的最大的文件描述符没有个数限制
2.epoll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可
3.epoll被唤醒之后epoll直接能拿到唤醒的文件描述符,将文件描述符拷贝到用户空间即可
不需要轮询,效率高。
【4】异步通知
当硬件的数据没有准备好的时候,应用程序自由执行。
当硬件的数据准备好的时候,硬件会给驱动发送一个中断,
在中断的处理函数中给应用程序发送一个信号,当应用程序
收到这个信号的时候,执行信号处理函数,在信号处理函数
中将数据读取到即可。
user:
1.注册信号处理函数
signal(SIGIO,信号处理函数);
2.调用驱动的fasync函数
unsigned int flags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|FASYNC);
3.指定接收信号的进程
fcntl(fd,F_SETOWN,getpid());
------------------------------------------------------
VFS:
sys_fcntl:
err = do_fcntl(fd, cmd, arg, filp);
switch(cmd)
case F_SETFL:
err = setfl(fd, filp, arg);
if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op &&
filp->f_op->fasync) {
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
------------------------------------------------------
kernel:fops: .fasync = driver_fasync;
driver_fasync();
1.实现驱动的fasync函数
grep ".fasync =" * -nR
struct fasync_struct *fapp;
int fasync_helper(int fd, struct file * filp,
int on, struct fasync_struct **fapp)
功能:初始异步通知结构体,并把异步通知结构体放入队列。
2. 实现发送信号的过程
kill_fasync(&fapp, SIGIO, POLL_IN);