熟悉类unix系统的朋友应该都知道,unix的哲学思想有三个:
1 一切皆文件
2 用简单的命令组合实现复杂的事情
3 不记得了
今天要分析的高级io,自然离不开文件,驱动程序就更离不开文件了,熟悉unix编程的人都应该清楚,对于各种数据的读写,包括网络,文件,管道等都是通过fd去读写的,而这个fd正是通过打开文件获取的,对于驱动程序(/dev/目录)的访问和一些系统信息(/proc/目录),都是以读写文件的方式访问。
言归正传,我们今天的主题是从驱动程序角度分析高级io,自然要从驱动程序说起,上述内容,都是要读者认识到io从读写文件发起。
unix系统为我们提供了两个最常用的操作文件的系统调用 read,write,
对于read write 有分为阻塞io,非阻塞io,非阻塞io有分为基于信号量的io,io复用(select,poll,epoll).
下面我们就从驱动程序角度去分析这些io
实现unix驱动程序,要给文件系统提供接口,将驱动程序的放到文件系统中,这样使用驱动的程序才能访问它,当然有的不会再/dev文件夹下放置访问点,则可以通过类似mknod去创建访问该设备的文件。
linux为驱动开发者提供了一系列接口要求驱动程序开发者去实现。这其中就包含文件的读写接口,和高级io的一些接口。
下面列举下本文关心的接口,类似于系统提供了如下回调函数给开发者(在操作文件的时候):
int (*open)(struct inode *inode, struct file *filp); 打开文件时调用
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); // 读取文件时调用
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//写文件时候调用
unsigned int (*poll) (struct file *, struct poll_table_struct *);// 调用 select poll epill时候调用
int (*release) (struct inode *, struct file *); //关闭文件时候调用
int (*fasync) (int, struct file *, int); //基于信号的io使用
当用户打开一个驱动设备的时候,就会调用open(可以阻塞的包括read write read方法)
1 经典的阻塞io,当调用read write open的时候,如果没有数据(可写入空间),或者设备没有准备好的时候,会将当前进程放入阻塞队列(包括独占阻塞和非独占,这里就详细说明),所以就会阻塞,当数据准备完成之后,就会调用notify,阻塞的进程将会被唤醒。
2 如果读写进程将访问该文件的模式设置成非组赛的,则当数据没有准备好的时候,驱动程序会马上返回-AGAIN ,则系统调用马上返回,读写进程可能会进行重试,也可能是一次探测。
3 多路复用,包括poll select epoll,这种io会观察很多文件描述符是否可读写,如果不可读写则会阻塞,知道某个设备可读写或者超时,这个功能则是设备驱动的poll函数实现,内核会调用进程关心的描述符对应的设备的poll方法,驱动程序告诉内核自己是否有数据(可写入空间),可读或者可写,或者可读写,如果所有设备都没有数据(可写入空间),则内核会将调用进程阻塞,知道某个设备有数据(可写入空间)后notify,唤醒进程。进程被唤醒后,就可以根据poll系函数返回的结果对该设备进行读写(当然有可能读写的时候还会阻塞,为什么自己去猜)
4 基于信号量
基于信号量的io,就是调用驱动程序的fasync函数,当用户通过fcntl函数设置文件描述符的所属进程,和告诉内核当数据可读或者可写之后通知进程。就会调用驱动程序的fasync函数,驱动程序会将改进程放入一个队列。 之后当有数据 (或者空间)的时候,调用kill_fasync,给改进程发送一个信号,进程就可以读写数据了。
以上就是各种高级io的实现原理