驱动会存在一个等待队列,专门为监听某一事件存在,该等待队列存放需要被唤醒的进程;
poll函数会注册一个wait_queue_t 结构到对应的队列上,当条件满足时,调用wake_up,阻塞的队列就会被唤醒进行调度;
wait queue是Linux内核里的基本功能,以队列为基础数据结构,与进程的调度有密切关系;能够实现核心的异步通知机制; 等待对队列也可以用来同步对资源的访问,Linux信号量机制也是由等待队列来实现的。
wait queue是Linux内核里的基本功能,以队列为基础数据结构,与进程的调度有密切关系;能够实现核心的异步通知机制; 等待对队列也可以用来同步对资源的访问,Linux信号量机制也是由等待队列来实现的。
poll(system V)select(BSD)用来查询设备的状态,以便用户程序获知是否对设备进行非阻塞访问;都需要驱动程序的poll函数支持。
重要的API,void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
最终调用 __pollwait
|
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p) { struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt); //poll_wqueues 管理结构 struct poll_table_entry *entry = poll_get_entry(pwq); //在管理结构上申请一个表项 if (!entry) return; get_file(filp); entry->filp = filp; entry->wait_address = wait_address; //等待队列 entry->key = p->_key; init_waitqueue_func_entry(&entry->wait, pollwake); //唤醒后执行函数pollwake entry->wait.private = pwq; //私有数据指向管理结构 add_wait_queue(wait_address, &entry->wait); //添加entry->wait到wait_address等待队列 } |
加入到等待队列;
poll_wait函数并不阻塞,真正阻塞是在上层的select/poll函数中完成。select/poll会在一个循环中对每一个需要监听的设备调用它们自己的poll支持函数以使得当前进程被加入到各个设备的等待列表;若当前没有任何被监听的设备就绪,则内核进行调度,让出CPU进入阻塞状态,schedule返回时将再次循环检查是否可以进行操作,如此反复,若有任意一个就绪,select/poll返回;
阻塞是指在执行操作时,若获取不到资源,则进程挂起直到满足条件再进行操作;
非阻塞进程是在不能进行设备操作时,并不挂起;
*********************************************************************
fasync异步通知
异步通知类似于中断机制,设备驱动发送一个信号给内核,告知内核有数据可读,在条件不满足之前不会造成阻塞; poll和阻塞性IO在条件不满足时会造成阻塞;
用户态:
signal(SIGIO, &input_handler); /* dummy sample; sigaction() is better */ fcntl(STDIN_FILENO, F_SETOWN, getpid()); oflags = fcntl(STDIN_FILENO, F_GETFL); fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); |
做三件事情:
1、指定SIGIO的处理函数;
2、指定一个进程作为文件的属主(filp->owner),告知内核信号应该发给哪个进程;
3、设备文件中添加FASYNC标记,驱动中就会调用对应的fasync函数
|
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ...... int (*fasync) (int, struct file *, int); ...... } |
内核态:
信号处理流程,及设置文件属主的操作内核处理是通用的;
与poll类似,存在一个管理结构 struct fasync_struct,同样以队列形式来管理;当条件满足时,内核会调用kill_fasync( )函数,依次向异步队列结构对象发送SIGIO信号;
|
struct fasync_struct { spinlock_t fa_lock; int magic; int fa_fd; struct fasync_struct *fa_next; /* singly linked list */ struct file *fa_file; struct rcu_head fa_rcu; }; |
fasync_helper():
添加异步对象; fapp由驱动设备管理,每个驱动设备对应一个fapp,该设备所有异步对象挂在其链上;
|
/* * fasync_helper() is used by almost all character device drivers * to set up the fasync queue, and for regular files by the file * lease code. It returns negative on error, 0 if it did no changes * and positive if it added/deleted the entry. */ int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) { if (!on) //on为0时,表示删除异步对象 return fasync_remove_entry(filp, fapp); return fasync_add_entry(fd, filp, fapp); //on为1时,表示添加异步对象 } |
kill_fasync():
发送信号到内核,唤醒上层进程;依次向异步队列结构对象发送SIGIO信号;
kill_fasync(&devp->hd_async_queue, SIGIO, POLL_IN);
POLL_IN 表示设备可读;POLL_OUT:表示设备可写
异步通知相关的数据结构和struct wait_queue是一致的,这两种情况都涉及等待同一个事件,区别是这个struct file被用来替代struct task_struct。