前面已经详细分析过了阻塞访问方式,下面就来继续分析一下非阻塞的访问方式。
什么是非阻塞的访问方式呢?非阻塞操作的进程在不能进行设备操作时,并不挂起,他或者是放弃当前的进程执行,或者是不停地进行查询,知道进程可以进行操作为止。实际上就是常说的轮询的方式进行设备的访问。
select()和poll()调用的本质是一样的,在性能上也不存在明显的差异,只是select监视的文件描述符数量有限,下面分别看下这两个系统调用。
首先看下select()系统调用,其函数原型为:
- int select(int nfds, fd_set *readfds, fd_set *writefds,
- fd_set *exceptfds, struct timeval *timeout);
- struct timeval {
- __kernel_time_t tv_sec; /* seconds */
- __kernel_suseconds_t tv_usec; /* microseconds */
- };
- void FD_CLR(int fd, fd_set *set); //将一个文件描述符从文件描述符集中清除
- int FD_ISSET(int fd, fd_set *set); //判断文件描述符是否被置位
- void FD_SET(int fd, fd_set *set); //将一个文件描述符加到文件描述符集中
- void FD_ZERO(fd_set *set); //清除一个文件描述符集
select()的接口主要是建立在叫fd_set类型的基础上,它是一组文件描述符的集合,来看下面的定义:
- typedef struct {
- unsigned long fds_bits [__FDSET_LONGS];
- } __kernel_fd_set;
- #define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)
- #define __FD_SETSIZE 1024
- #define __NFDBITS (8 * sizeof(unsigned long))
上面大概介绍了一下selece(),下面来看poll()。
poll()系统调用的原型是:
- int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- struct pollfd {
- int fd; //文件描述符
- short events; //等待的事件
- short revents; //实际发生的事件
- };
起执行过程如下:
首先将用户传入的pollfd数组拷贝到内核空间,然后查询每个文件描述符对应的设备的状态,最后将获得的数据传送到用户空间并执行释放内存和剥离等待队列等工作。
设计上无论是select()还是poll()系统调用,其最终都会引发设备驱动中的poll()被执行,那么下面就来看下设备驱动中的poll()函数:
首先来看下在设备驱动中的poll()函数的原型:
- unsigned int (*poll) (struct file *, struct poll_table_struct *);
对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头添加到poll_table。最后返回表示是否能对设备进行无阻塞读、写访问的掩码。
- typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
- typedef struct poll_table_struct {
- poll_queue_proc qproc;
- unsigned long key;
- } poll_table;
- static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
- {
- if (p && wait_address)
- p->qproc(filp, wait_address, p);
- }
下面把poll()系统调用涉及的主要代码在这里贴出来,大家可以深入解析一下:
- SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
- long, timeout_msecs)
- {
- struct timespec end_time, *to = NULL;
- int ret;
- if (timeout_msecs >= 0) {
- to = &end_time;
- poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
- NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
- }
- ret = do_sys_poll(ufds, nfds, to);
- if (ret == -EINTR) {
- struct restart_block *restart_block;
- restart_block = ¤t_thread_info()->restart_block;
- restart_block->fn = do_restart_poll;
- restart_block->poll.ufds = ufds;
- restart_block->poll.nfds = nfds;
- if (timeout_msecs >= 0) {
- restart_block->poll.tv_sec = end_time.tv_sec;
- restart_block->poll.tv_nsec = end_time.tv_nsec;
- restart_block->poll.has_timeout = 1;
- } else
- restart_block->poll.has_timeout = 0;
- ret = -ERESTART_RESTARTBLOCK;
- }
- return ret;
- }
- int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
- struct timespec *end_time)
- {
- struct poll_wqueues table;
- int err = -EFAULT, fdcount, len, size;
- /* Allocate small arguments on the stack to save memory and be
- faster - use long to make sure the buffer is aligned properly
- on 64 bit archs to avoid unaligned access */
- long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
- struct poll_list *const head = (struct poll_list *)stack_pps;
- struct poll_list *walk = head;
- unsigned long todo = nfds;
- if (nfds > current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
- return -EINVAL;
- len = min_t(unsigned int, nfds, N_STACK_PPS);
- for (;;) {
- walk->next = NULL;
- walk->len = len;
- if (!len)
- break;
- if (copy_from_user(walk->entries, ufds + nfds-todo,
- sizeof(struct pollfd) * walk->len))
- goto out_fds;
- todo -= walk->len;
- if (!todo)
- break;
- len = min(todo, POLLFD_PER_PAGE);
- size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
- walk = walk->next = kmalloc(size, GFP_KERNEL);
- if (!walk) {
- err = -ENOMEM;
- goto out_fds;
- }
- }
- poll_initwait(&table);
- fdcount = do_poll(nfds, head, &table, end_time);
- poll_freewait(&table);
- for (walk = head; walk; walk = walk->next) {
- struct pollfd *fds = walk->entries;
- int j;
- for (j = 0; j < walk->len; j++, ufds++)
- if (__put_user(fds[j].revents, &ufds->revents))
- goto out_fds;
- }
- err = fdcount;
- out_fds:
- walk = head->next;
- while (walk) {
- struct poll_list *pos = walk;
- walk = walk->next;
- kfree(pos);
- }
- return err;
- }
- static int do_poll(unsigned int nfds, struct poll_list *list,
- struct poll_wqueues *wait, struct timespec *end_time)
- {
- poll_table* pt = &wait->pt;
- ktime_t expire, *to = NULL;
- int timed_out = 0, count = 0;
- unsigned long slack = 0;
- /* Optimise the no-wait case */
- if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
- pt = NULL;
- timed_out = 1;
- }
- if (end_time && !timed_out)
- slack = estimate_accuracy(end_time);
- for (;;) {
- struct poll_list *walk;
- for (walk = list; walk != NULL; walk = walk->next) {
- struct pollfd * pfd, * pfd_end;
- pfd = walk->entries;
- pfd_end = pfd + walk->len;
- for (; pfd != pfd_end; pfd++) {
- /*
- * Fish for events. If we found one, record it
- * and kill the poll_table, so we don't
- * needlessly register any other waiters after
- * this. They'll get immediately deregistered
- * when we break out and return.
- */
- if (do_pollfd(pfd, pt)) {
- count++;
- pt = NULL;
- }
- }
- }
- /*
- * All waiters have already been registered, so don't provide
- * a poll_table to them on the next loop iteration.
- */
- pt = NULL;
- if (!count) {
- count = wait->error;
- if (signal_pending(current))
- count = -EINTR;
- }
- if (count || timed_out)
- break;
- /*
- * If this is the first loop and we have a timeout
- * given, then we convert to ktime_t and set the to
- * pointer to the expiry value.
- */
- if (end_time && !to) {
- expire = timespec_to_ktime(*end_time);
- to = &expire;
- }
- if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
- timed_out = 1;
- }
- return count;
- }
- static unsigned int globalmem_poll(struct file *filp,poll_table *wait)
- {
- unsigned int mask = 0;
- struct globalmem_dev *dev = filp->private_data;
- down(&dev->sem);
- poll_wait(filp,&dev->r_wait,wait); //加读等待队列头
- poll_wait(filp,&dev->w_wait,wait); //加写等待队列头
- if(dev->current_len != 0)
- mask |= POLLIN | POLLRDNORM; //表示数据可获得
- if(dev->current_len != GLOBALFIFO_SIZE)
- mask |= POLLOUT | POLLWRNORM; //表示数据可写入
- up(&dev->sem);
- return mask;
- }
- static const struct file_operations globalmem_fops = {
- .poll = globalmem_poll,
- }
下面再编写一个应用程序用于监控globalmem的可读可写状态,代码如下(未加头文件):
- #define FIFO_CLEAR 0x1;
- #define BUFFER_LEN 20;
- mian()
- {
- int fd,num;
- char rd_ch[BUFFER_LEN];
- fd_set rfds,wfds;
- fd = open("/dev/globalmem",O_RDONLY | O_NONBLOCK);
- if(fd != -1){
- if(ioctl(fd,FIFO_CLEAR,0)<0)
- printf("ioctl command failed\n");
- while(1){
- 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))
- printf("poll monitor:can be read\n");
- if(FD_ISSET(fd,&wfds))
- printf("poll monitor:can be written\n");
- }
- }else{
- printf("Device open failure\n");
- }
- }