IO模型就是对文件的不同读写方式,在驱动中对硬件数据的读写通过读写设备文件来实现,而读取设备文件根据不同需求也有不同的方式,所以研究了不同的IO模型分为以下五种:非阻塞IO、阻塞IO、IO多路复用、信号驱动IO、异步IO模型
当进程通过read/write读取硬件数据时,不管硬件数据是否准备好,read/write函数都立即返回,
非阻塞IO函数可能读到的数据并不是本次准备好的数据,可以在打开文件时添加O_NONBLOCK来实现非阻塞打开
***********应用程序************
int fd=open("/dev/mycdev",O_RDWR|O_NONBLOCK);//以非阻塞的模式打开文件
read(fd,buf,sizeof(buf));
*********驱动程序**************
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{
int ret;
if(file->f_flags&O_NONBLOCK)
{
1.读取硬件的数据
2.copy_to_user将硬件数据传递到用户空间
}
return 0;
}
当进程在读取硬件数据时,如果此时硬件数据准备就绪就读取,没有准备就绪进程就阻塞在read函数位置一直等到数据就绪,当硬件数据准备就绪后,硬件会发起硬件中断将休眠的进程唤醒,休眠进程会停止阻塞,将准备好的数据读取走,可分为可中断休眠和不可中断休眠状态
函数实现
***********应用程序************
int fd=open("/dev/mycdev",O_RDWR);//以阻塞的模式打开文件
read(fd,buf,sizeof(buf));
*********驱动程序**************
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{
int ret;
if(file->f_flags&O_NONBLOCK)
{
1.读取硬件的数据
2.copy_to_user将硬件数据传递到用户空间
}
else//阻塞IO
{
1.判断硬件数据是否准备好
2.如果数据没有准备好,将进程切换为休眠状态
3.读取硬件数据
4.copy_to_user
}
return 0;
}
当应用程序中同时实现对多个硬件数据读取时就需要用到IO多路复用,IO多路复用有select/poll/epoll三种实现方式,如果进程同时监听的多个硬件数据都没有准备好,进程切换进入休眠状态,当一个或多个硬件数据准备就绪后,休眠的进程会被唤醒,读取准备好的硬件数据
***************应用程序**************************
int fd1,fd2;
//打开设备文件
fd1=open("/dev/input/mouse0",O_RDWR);
if(fd1<0)
{
printf("打开鼠标设备文件失败\n");
exit(-1);
}
fd2=open("/dev/myled0",O_RDWR);
if(fd2<0)
{
printf("打开设备文件失败\n");
exit(-1);
}
//定义事件集合
fd_set readfds;
//清空事件集合
FD_ZERO(&readfds);
//将要监听的文件描述符添加到可读集合中
FD_SET(fd1,&readfds);
FD_SET(fd2,&readfds);
//等待事件就绪
int ret=select(fd2+1,&readfds,NULL,NULL,NULL);
if(ret<0)
{
printf("select err\n");
exit(-1);
}
//判断事件是否发生
//没发生的事件会被清除出可读集合,所以只需要判断文件描述符是否还在集合中就可以知道事件是否发生
if(FD_ISSET(fd1,&readfds))
{
read(fd1,buf,sizeof(buf));
printf("鼠标事件发生%s\n",buf);
}
if(FD_ISSET(fd2,&readfds))
{
read(fd2,buf,sizeof(buf));
printf("自定义设备事件发生%s\n",buf);
}
close(fd1);
close(fd2);
***********************VFS(虚拟文件系统层)*********
sys_select()
{
1.在内核申请一片内存用于保存从用户空间的文件描述符集合中拷贝的文件描述符,拷贝完毕后用户的事件集合被清空
2.根据文件描述符集合中的每一个文件描述符按照fd->fd_array[fd]->struct file对象->操作方法对象->poll方法 ,按照这个路线回调每个fd对应的驱动中的poll方法
3.判断每个文件描述符的poll方法的返回值,如果所有的poll方法的返回值都为0,表示没有任何硬件数据准备就绪,此时将进程切换为休眠态(可中断休眠态)
4.当休眠的进程收到一个或者多个事件就绪的唤醒提示后,在这里根据事件集合中的每一个文件描述符再次回调poll方法,找出发生事件的文件描述符
5.将发生事件的文件描述符重新拷贝回用户空间的事件集合
}
*************************驱动程序****************
//所有的io复用方式在驱动中对应的操作方法都是poll方法
__poll_t (*poll) (struct file *file, struct poll_table_struct *wait)
{
//向上提交等待队列头
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
/*功能:将等待队列头向上层提交
参数:
filp:文件指针,将poll方法第一个参数填进去
wait_address:要向上提交的等待队列头地址
p:设备驱动和上层关联的通道,将poll方法的第二个参数填进去
返回值:无*/
//判断condition的值,根据事件是否发生给一个合适的返回值
if(condition)
{
return POLLIN;//POLLIN表示读 POLLLOUT表示写
}
else
{
return 0;
}
}
当进程发起一个IO操作,会向内核注册一个信号处理函数,如何进程返回不阻塞,当内核数据就绪时,会发送一个信号给进程,以便进程在信号处理函数时调用IO读取数据
当进程发起一个IO操作,进程返回(不阻塞),但也不能返回结果。内核把整个IO处理完后,会通知进程结果,如果IO操作成功则进程直接获取到数据。