上一节我们分析了linux驱动中阻塞的实现,利用等待队列的休眠和唤醒机制实现,这一节我们探讨一下非阻塞的实现!
其实在非阻塞中,我们可以直接打开一个设备,进行读取和写入操作,但是这样做很不好,因为使用的是非阻塞,所以无论能不能写入或者读取都会返回,就像小明去买衣服,去到商店,商店开门了,买了衣服回来,这固然是好,可是万一商店老板来晚了一分钟,小明看到一关门就回去了,要是他等待多一分钟,就可以买到了,所以我们都希望无论是读还是写,都不要无功而返,可是非阻塞又不会等待或者休眠,那怎么办,这时就要在应用程序是调用select或者poll函数了,这两个函数会先进行判断,看文件能否进行非阻塞的读写(即能否获取资源),若能则返回,否则一直在等待,但是不休眠,直到条件满足或者等待时间到达了再返回。
实际上,这两个函数无论调用哪个,其结果都是调用底层驱动的poll()函数,所以我们要在驱动中实现poll()函数,现在我们先分析一下应用程序中的select()函数。
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
参数分析:fd_set可以看做是一个集合,里面存放了很多的文件描述符,所以readfds是存放读取的文件描述符,我们通过select监视这些文件描述符的读取变化,如果里面有文件可以读取了,select就会返回一个大于0的数,不然会一直等待,但是不会休眠,知道条件满足或者定义的时间timeout到达,就会返回。
writefds是写文件描述符的集合,原理同读的一样,只要有文件可以读取了,就会发生变化,返回一个大于0的数,不然也是一直等待或者时间到达才会返回,并且不会休眠。errorfds这个是异常文件描述符的集合,有文件出现异常时,会返回,与那里和其他的一样。
maxfdp是一个整数,是集合中所有文件描述符的范围,即集合中最大文件描述符的值加1.
struct timeval,是一个常用的结构体,其原型如下所示:
struct timeval{
long tv_sec; //秒
long tv_usec; //微秒
}
当定义了timeout后,时间到达,该函数也会返回。
返回值:负值,select发送错误;正值:有文件可读可写或者出现异常;0值:等待超时,没有可读可写或者异常的文件。
当select返回后,我们可以根据返回值进一步判断能够进行写操作,或者是读操作,其中select的使用,涉及到以下几个宏:
FD_ZERO(fd_set
*fdset):清空fdset与所有文件句柄的联系。
FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。
FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否 可读写,返回值大于0表示可读写。
一个通过调用select进行非阻塞读写的例子:
fd = open("/dev/globalfifo",O_RDONLY | O_NONBLOCK);
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(fd,&rfds);
FD_SET(fd,&wfds);
.........
select(fd+1,&rfds,&wfds,NULL,NULL); //函数会在这里一直轮询,知道有文件可读写才会返回,接着进行非阻塞访问
if(FD_ISSET(fd,&rfds))// 进一步判断,该文件是否可以进行读操作,若是,则进行非阻塞的读操作
read(...);
if(FD_ISSET(fd,&wfds))// 进一步判断,该文件是否可以进行写操作,若是,则进行非阻塞的写操作
write(...);
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以上是应用程序select的分析,现在我们来探讨下驱动中的poll函数如何实现,因为顶层的poll、select或者epoll,其底层驱动都是调用poll函数实现的。设备驱动中poll()函数的原型是:
unsigned int (*poll) (struct file *filp,poll_table *wait);
函数返回可以立即执行操作的位掩码,有这几种:POLLIN(设备可读),POLLRDNORM(数据可读),POLLOUT(设备可写),POLLWRNORM(数据可写),一般设备可读返回:POLLIN | POLLRDNORM,可写则返回:POLLOUT | POLLWRNORM。
poll函数主要完成两个步骤:
步骤一:对可能引起设备文件状态变化的等待队列调用poll_wait函数,将对应的等待队列头部添加到poll_table中。
void poll_wait(struct file *filp, wait_queue_heat_t *queue,poll_table *wait); //使用poll_wait将等待队列添加到poll_table中
步骤二:返回一个用来描述操作是否可以立即执行,无阻塞读、写访问的掩码。
poll函数的驱动模板:
static unsigned int xxx_poll(struct file *filp, poll_table *wait){
....
unsigned int mask = 0;
down((&sem);
poll_wait(filp,&read,wait);
poll_wait(filp,&write,wait);
if(read_buffer_not_empty)
mask |= POLLIN | POLLRDNORM;
if(write_buffer_not_full)
mask |= POLLOUT | POLLWRNORM;
up(&sem);
return mask;
}