本章讲的是driver的高级操作,比如:
1, 实现了ioctl,device driver可以满足user mode一些特定的操作。
2, 和user mode做好sync的几种方式。
3, 如何让process进入sleep,以及如何wake up。
4, 非阻塞IO操作,以及读写完成以后如何通知user mode。
ioctl
ioctl可以说device driver肯定会用到的,因为一个device往往提供了不止读写的功能,还可以通过user mode来控制。这部分用来控制的操作,就得用ioctl来实现。ioctl分两部分看,user mode的调用部分和kenel mode的实现部分。
user mode的调用就是一个函数:
int ioctl(int fd, unsigned long cmd, ...);
用户态通过之前open device拿到的fd,加上cmd,以及必要的参数,就可以通过ioctl和kernel driver通信。在kernel mode,ioctl就是一个callback:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
device driver需要实现这个callback,这样当kernel收到user mode送下来的ioctl,如果是driver自定义的cmd,就会调用driver的ioctl。其中的参数,inode就是open的时候在kernel对应的device file,filp则是open device拿到的file descriptor,cmd 和arg就是送下来的cmd和必要的参数。需要注意的是,如果user mode在调用时没有传递这个arg,那么driver在自己的ioctl callback里收到的arg就是随机值,不要使用。因为ioctl是可变参数的函数,编译器也不会检查到这种错误。
下面介绍ioctl cmd,这个其实就是个ioctl code,用来标记需要的操作,比如你是要query device的信息,或者需要给device传递数据等,都需要对应不同的cmd,这样每一个cmd值都对应了某种操作,方便driver里面来区分。这些cmd需要user mode和kernel mode保持一致,因此这些cmd的定义要share给user mode。
Choosing the ioctl Commands
kernel里有现成的utility让driver developer生成合适的cmd,一个cmd主是由四个域组成的bitmask:type,number,direction,size。
type就是driver自己选的magic number,说白了就是个和kernel不冲突的字符就行,这样可以保证cmd的唯一性,kernel中已经使用了一些magic number并且列在ioctl-number.txt里面,driver需要避免使用这些magic number,magic number占8bit(_IOC_TYPEBITS),driver中应该只使用这一个magic number;number可以理解为cmd的index,因为一个device driver一般支持很多的ioctl,那么可以用index作为cmd的一部分,占8bit(_IOC_NRBITS);direction表示这个ioctl产生的数据流方向,比如读(_IOC_READ),或者是写(_IOC_WRITE),或者既有读又有写(_IOC_READ|_IOC_WRITE),或者不读不写(_IOC_NONE),如果是读,就需要kenrel往user mode写memory,如果是写,就是user mode向kernel写memory;size表示传递的数据大小,一般就是sizeof(arg),占用13或14bit(_IOC_SIZEBITS),架构相关,所以可能不同。
kernel提供的utility function:
_IO(type,nr) //没有参数的ioctl
_IOR(type,nr,datatype) //需要从driver read data的ioctl
_IOW(type,nr,datatype) //需要向driver write data的ioctl
_IOWR(type,nr,datatype) //需要同时读写driver
type和nr都是driver传进来的参数,datatype一般就是sizeof(arg)就可以了。kernel同时提供了对应的函数让driver方便的从cmd中获取type/nr/size等:
_IOC_DIR(nr) //??
_IOC_TYPE(nr) //获取cmd中的magic number
_IOC_NR(nr) //获取cmd中的index
_IOC_SIZE(nr) //获取cmd中的arg size
这里有一个例子:
/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC 'k'
/* Please use a different 8-bit number in your code */
#define SCULL_IOCRESET
_IO(SCULL_IOC_MAGIC, 0)
/*
* S means "Set" through a ptr,
* T means "Tell" directly with the argument value
* G means "Get": reply by setting through a pointer
* Q means "Query": response is on the return value
* X means "eXchange": switch G and S atomically
* H means "sHift": switch T and Q atomically
*/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET
_IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC,3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC,4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC,7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC,8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)
#define SCULL_IOC_MAXNR 14
ioctl的参数,可以是int值,也可以是pointer。
The Return Value
如果user mode传下来不支持的ioctl cmd,driver肯定要返回fail,但是应该返回什么值呢?返回-ENOTTY或者-EINVAL都可以。
The Predefined Commands
因为kernel自身支持一些ioctl,并且这些ioctl是在driver的callback被调用之前就会被parse,如果driver定义和kernel一样的cmd,有可能会导致kernel hanle了这个ioctl,driver不会再收到这个ioctl,从而导致user mode拿到了非期望的数据。
这些predefined的cmd的type主要分成三组:
1, 任何file通用的cmd(普通文件,device,FIFO,socket)
2, 普通文件适用的cmd
3, 文件系统适用的cmd
device driver通常可能需要关心第一种,第一种主要有:FIOCLEX, FIONCLEX, FIOASYNC, FIOQSIZE, FIONBIO。
Using the ioctl Argument
关于ioctl的参数,有两种,int或者pointer,如果是int可以直接使用,如果是pointer,需要注意。
因为这个指针是user mode指针,因此使用之前一定要检查安全性。也可以直接使用copy_from_user/copy_to_user来防止使用无效的user mode指针,使用之前通过access_ok提前检查一下user mode指针的有效性:
#include <asm/uaccess.h>
int access_ok(int type, const void *addr, unsigned long size);
type就是检查user mode地址addr的读写权限,如果需要读,就设置VERIFY_READ,写或者既读又写就用VERIFY_WRITE,addr就是user mode地址,size就是要检查的memory大小(byte)。如果access_ok返回true,就可以使用,如果是false,返回错误-EFAULT。实际上,access_ok大多数情况下只是检查addr是否是user mode地址,仅此而已。而且,大多数driver其实并不需要调用access_ok来检查指针的有效性。
此处有一个例子:
int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok( )
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err) return -EFAULT;
除了使用copy_from_user/copy_to_user以外,kernel还提供了一些函数用来做小数据的copy,比如1/2/4/8个bytes这种。
put_user(datum, ptr)
__put_user(datum, ptr)
如果数据小,使用put_user,而不是copy_to_user,但是使用前需要access_ok检查user mode ptr是否有效。copy的数据大小取决于ptr指针的类型,如果是char指针,就copy一个byte;如果是unsigned int指针,就copy四个byte,最多可以支持8个byte。put_user会检查memory的访问结果,如果失败会返回fail。__put_user没有检查,因此需要driver自己调用access_ok来检查。
get_user(local, ptr)
__get_user(local, ptr)
和__put_user类似,使用__get_user前检查access_ok,确保ptr是有效的user mode ptr。使用get_user不用检查,因为返回值说明了一切。
如果传输的data size超过1/2/4/8bytes,就会报错,比如“conversion to non-scalar type requested.”,那么此时必须使用copy_to_user/copy_from_user。
Capabilities and Restricted Operations
对device的访问,需要做权限的检查,一般这个检查在device file打开的时候就检查过了,但是有些特殊的操作,可能需要driver来检查,比如磁带驱动,可以允许读,但是不允许格式化等。
因此,kernel提供了一种capabilities. 它不像root用户,要么拥有很大的权限,可以做一切事情,要么就是普通用户,很多事情都受限。capabilities提供了一种按照分组管理权限的方式,只授予用户或者程序必要的权限,无关的操作不给于权限即可。有两个系统调用可以做这个事情:capget和capset。这些capabilities是kernel固定死的,除非修改kernel的code,否则不能修改。这些capabilities有:CAP_DAC_OVERRIDE,CAP_NET_ADMIN,CAP_SYS_MODULE,CAP_SYS_RAWIO,CAP_SYS_ADMIN,CAP_SYS_TTY_CONFIG。
driver在执行用户态程序的请求之前,需要先检查capability,使用下面的接口:
#include <linux/sched.h>
int capable(int capability);
//sample code
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
The Implementation of the ioctl Commands
这里就是一个sample:
switch(cmd) {
case SCULL_IOCRESET:
scull_quantum = SCULL_QUANTUM;
scull_qset = SCULL_QSET;
break;
case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_quantum = arg;
break;
case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
return scull_quantum;
case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
retval = __get_user(scull_quantum, (int __user *)arg);
if (retval = = 0)
retval = __put_user(tmp, (int __user *)arg);
break;
case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
scull_quantum = arg;
return tmp;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;
//user mode使用
int quantum;
ioctl(fd,SCULL_IOCSQUANTUM, &quantum);/* Set by pointer */
ioctl(fd,SCULL_IOCTQUANTUM, quantum); /* Set by value */
ioctl(fd,SCULL_IOCGQUANTUM, &quantum);/* Get by pointer */
quantum = ioctl(fd,SCULL_IOCQQUANTUM); /* Get by return value */
ioctl(fd,SCULL_IOCXQUANTUM, &quantum); /* Exchange by pointer */
quantum = ioctl(fd,SCULL_IOCHQUANTUM, quantum); /* Exchange by value */
Blocking I/O
很多时候,当user mode调用了ioctl,读写设备数据时,因为读写的buffer不能立即使用,导致user mode请求不能马上满足,这个时候往往需要把进程block住。
Introduction to Sleeping
在讲block I/O之前,说一下什么是sleep。sleep是进程的一种状态,在sleep状态下,进程被从当前可运行的进程列表中移除,并设置为不可调度的状态,因此进程不会被调度,直到等待的资源可用或者被中断唤醒,否则一直不会被执行。
device driver中经常碰到需要sleep的情况,在实现进程sleep时,有几个原则需要注意:
1, 不要在atomic的context里sleep。什么是atomic context?就是一段code,这段code不允许在执行的过程中被打断,从而存在并发运行的可能性。比如driver拿到了spinlock,seqlock,或者RCU lock,那就不能sleep。
2, 如果你关闭了中断,也不能sleep。因为唤醒进程就是靠中断来的,如果中断被关闭,进程就不会被唤醒。
3, 虽然semaphore之类的锁允许持有的时候sleep,但是这个sleep的时间越少也好,因为如果你拿了锁sleep,等待这个锁的线程也会sleep。
4, 如果拿了semaphore,进入sleep,唤醒你的线程也需要拿这个锁,就存在潜在的风险:唤醒线程拿不到锁,就无法把你唤醒。
5, 被唤醒时,要再次确认是被你期望的事件唤醒的,因为在sleep的过程中,这个世界已经变了,也许进程等待的数据已经被别的进程拿走,也有可能进程是被别的信号唤醒的,所以当进程醒来时,要确认自己是被期望的事件唤醒的。
6, 在sleep之前,要确认有人能把你唤醒。否则一旦睡眠,就再也没机会被唤醒了。
如果需要实现sleep和唤醒,可以使用wait_queue,wait_queue中就是一些等待事件发生的process的list,当期望的事件发生时,这些process都会被唤醒。wait_queue的初始化:
DECLARE_WAIT_QUEUE_HEAD(name); //静态初始化
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue); //动态初始化
Simple Sleeping
wait的时候需要wait在某个特定的queue上,当queue对应的event发生时被唤醒,函数:
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
上面的几个wait里面,queue就是wait_queue_head_t,注意是按值传递参数。condition是一个表达式,返回true表示被唤醒并继续执行,因为condition中间可能会被调用多次,因此要保证在被调用多次的情况下,condition没有副作用。
上面的函数中,wait_event是不可中断的等待,除非等待的事件发生,否则无法被唤醒,用的较少。wait_event_interruptible是可以被中断唤醒的wait,当返回值是非零值时,说明是被signal唤醒的,此时应该返回-ERESTARTSYS。wait_event_timeout和wait_event_interruptible_timeout都有一个timeout值,如果等待超时也会被唤醒,因此这两个也要check返回值,确保是被期望的事件唤醒。
和sleep相对应的是wake_up:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
wait_up可以唤醒这个queue上所有等待的进程,wake_up_interruptible只能唤醒可以被中断唤醒的进程。如果你是wait interruptible,那么这两种唤醒方式对你没有区别,通常来说,如果你用wait_event,唤醒就用wake_up,如果你用wait_event_interruptible,唤醒就用wake_up_interruptbile。
此处有一个例子:
static DECLARE_WAIT_QUEUE_HEAD(wq);
static int flag = 0;
ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
printk(KERN_DEBUG "process %i (%s) going to sleep\n", current->pid, current->comm);
wait_event_interruptible(wq, flag != 0);
flag = 0;
printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
return 0; /* EOF */
}
ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
printk(KERN_DEBUG "process %i (%s) awakening the readers...\n",
current->pid, current->comm); flag = 1;
wake_up_interruptible(&wq);
return count; /* succeed, to avoid retrial */
}
这里有一个有意思的地方,sample code的wait contiditon是一个flag值,flag不为零就会被唤醒。如果同时有两个进程等在这个queue上,那么谁会被唤醒呢?如果是在单核CPU上,毫无疑问只有一个进程会被唤醒;如果是多核的CPU,那么这两个进程都会被唤醒,因为这两个进程有可能同时看到非零的flag,后面的flag = 0,有可能谁都机会看到,这种行为有时候是会出现问题的。后面会有解决办法。
Blocking and Nonblocking Operations
取决于open file的时候是否设置了O_NONBLOCK,默认情况下没有设,也就说默认情况下open/read/write都是block的,没有可用资源就会等待。如果设置了这个flag,表示user mode不想等待,所以没有可用资源就立即返回-EAGAIN。
device driver一般都实现了input和output buffer,这样当user mode读写数据时,可以通过buffer作为缓冲,即便device本身比较慢,和user mode的交互也不会因此频繁被block而进入sleep,这样的话就减少了context switch,提高了传输的效率。
这里也有实现block read/write的一个例子:
struct scull_pipe {
wait_queue_head_t inq, outq; /* read and write queues */
char *buffer, *end; /* begin of buf, end of buf */
int buffersize; /* used in pointer arithmetic */
char *rp, *wp; /* where to read, where to write */
int nreaders, nwriters; /* number of openings for r/w */
struct fasync_struct *async_queue; /* asynchronous readers */
struct semaphore sem; /* mutual exclusion semaphore */
struct cdev cdev; /* Char device structure */
};
static ssize_t scull_p_read (struct file *filp, char _ _user *buf, size_t count, loff_t *f_pos)
{
struct scull_pipe *dev = filp->private_data;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
while (dev->rp == dev->wp) { /* nothing to read */
up(&dev->sem); /* release the lock */
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG("\"%s\" reading: going to sleep\n", current->comm);
if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
/* otherwise loop, but first reacquire the lock */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
/* ok, data is there, return something */
if (dev->wp > dev->rp)
count = min(count, (size_t)(dev->wp - dev->rp));
else /* the write pointer has wrapped, return data up to dev->end */
count = min(count, (size_t)(dev->end - dev->rp));
if (copy_to_user(buf, dev->rp, count)) {
up (&dev->sem);
return -EFAULT;
}
dev->rp += count;
if (dev->rp == dev->end)
dev->rp = dev->buffer; /* wrapped */
up (&dev->sem);
/* finally, awake any writers and return */
wake_up_interruptible(&dev->outq);
PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);
return count;
}
Advanced Sleeping
这里描述了process sleep的实现细节。
How a process sleeps
struct wait_queue_entry {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head entry;
};
struct wait_queue_head {
spinlock_t lock;
struct list_head head;
};
typedef struct wait_queue_head wait_queue_head_t;
如何让一个进程wait在queue里面呢?
第一步先分配并初始化一个wait_queue_t结构体, 再把它加到wait queue里面。这样唤醒的人直到去哪里唤醒等待者。
下一步修改process的运行状态,设置为sleep。进程有很多状态,比如TASK_RUNNING,TASK_INTERRUPTIBLE, TASKU_UNINTERRUPTIBLE,后面两个说明进程正在sleep,分别对应wait_interruptible和wait_uninterruptible。当然,device driver中一般不需要自己来管理进程状态,如果真的需要,可以使用kernel提供的接口:
void set_current_state(int new_state);
再下一步是check一下等待的条件是否已经被满足,因为你在wait之前,有可能已经有人调用了wake up,如果此时不去check,有可能以后再也check不到了。例如这样check的code:
if (!condition)
schedule( );
只有此时等待的事件仍然没有发生,才通过调用schedule放弃CPU,让CPU选择其他能运行的进程运行。在schedule函数返回时,说明进程已经被某种事件唤醒,并开始执行了,但是如果发现condition已经满足,那么此时要自己再把之前设置的进程状态恢复。
Manual sleeps
上面用的sleep都是kernel实现好的接口,直接调用就可以了。当然,在某些情况下,driver可能需要手动做sleep。
首先,也是要初始化wait queue:
//静态初始化
DEFINE_WAIT(my_wait);
//动态初始化
wait_queue_t my_wait;
init_wait(&my_wait);
下一步就是把wait queue entry添加到wait queue里面去,并且设置进程状态。这两步都是通过下面的接口来做:
void prepare_to_wait(wait_queue_head_t *queue,
wait_queue_t *wait,
int state);
上面的接口中,queue就是wait queue head,wait是process wait entry,state是进程新的状态,一般是TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE。
再调用了prepare_to_wait以后,需要对wait的condition做检查,如果还需要wait,就调用schedule函数。在schedule函数返回时,process已经被唤醒,此时需要调用kernel的另外一个interface:
void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
然后再检查wait的condition是否满足,还是process是被signal唤醒的,以及是否还需要再wait。这里有一个例子介绍手动sleep的方法:
/* Wait for space for writing; caller must hold device semaphore. On
* error the semaphore will be released before returning. */
static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{
while (spacefree(dev) = = 0) { /* full */
DEFINE_WAIT(wait);
up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG("\"%s\" writing: going to sleep\n",current->comm);
prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);
if (spacefree(dev) = = 0)
schedule( );
finish_wait(&dev->outq, &wait);
if (signal_pending(current))
return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
return 0;
}
/* How much space is free? */
static int spacefree(struct scull_pipe *dev)
{
if (dev->rp = = dev->wp)
return dev->buffersize - 1;
return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1;
}
static ssize_t scull_p_write(struct file *filp, const char _ _user *buf, size_t count,
loff_t *f_pos)
{
struct scull_pipe *dev = filp->private_data;
int result;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
/* Make sure there's space to write */
result = scull_getwritespace(dev, filp);
if (result)
return result; /* scull_getwritespace called up(&dev->sem) */
/* ok, space is there, accept something */
count = min(count, (size_t)spacefree(dev));
if (dev->wp >= dev->rp)
count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */
else /* the write pointer has wrapped, fill up to rp-1 */
count = min(count, (size_t)(dev->rp - dev->wp - 1));
PDEBUG("Going to accept %li bytes to %p from %p\n", (long)count, dev->wp, buf);
if (copy_from_user(dev->wp, buf, count)) {
up (&dev->sem);
return -EFAULT;
}
dev->wp += count;
if (dev->wp = = dev->end)
dev->wp = dev->buffer; /* wrapped */
up(&dev->sem);
/* finally, awake any reader */
wake_up_interruptible(&dev->inq); /* blocked in read( ) and select( ) */
/* and signal asynchronous readers, explained late in chapter 5 */
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
PDEBUG("\"%s\" did write %li bytes\n",current->comm, (long)count);
return count;
}
Exclusive waits
在wait queue里面,可能有很多的进程在wait,但是很多情况下只有一个进程能获取到资源,其他的进程可能醒来之后做了check,然后又sleep了。如果这样的进程特别多,每次wake up都有很多的进程做无用功,这对CPU来说是一种浪费,所以有exclusive wait这种方式,保证只有一个进程会被唤醒,别的进程仍然sleep。这样的进程需要设置在自己的wait queue entry里设置WQ_FLAG_EXCLUSIVE这个标记,kernel会把带有这个标记的wait queue entry都放在wait queue的最后面,不带这个标记的wait queue entry放到wait queue的前面。这样,当wake up被调用的时候,kernel会把wait queue的wait entry依次唤醒,直到碰到第一个带有WQ_FLAG_EXCLUSIVE标记的,把它唤醒以后,后面的wait queue entry就不会被唤醒了。
什么时候需要使用WQ_FLAG_EXCLUSIVE呢?考虑下下面两个条件:有可能多个process在wait同一个资源,而当资源产生时,只要一个process醒来就一定可以处理完,没必要唤醒多个process。这种情况下,就需要使用exclusive wait。接口:
void prepare_to_wait_exclusive(wait_queue_head_t *queue,
wait_queue_t *wait,
int state);
在prepare wait时使用prepare_to_wait_exclusive,而不是prepare_wait就可以了。注意,wait_event那里是无法修改wait类型的,只能在prepare wait的时候来修改。
The details of waking up
当process被wakeup当时候,最开始执行的是wait queue entry里的function。这个function是kernel的default_wake_up,这个函数会设置process的状态,然后开始执行,必要的时候会做context switch。device driver除非必要,否则不要override kernel的wake up函数。下面driver调用的wake up函数的所有版本:
wake_up(wait_queue_head_t *queue);
wake_up_interruptible(wait_queue_head_t *queue);
wake_up函数会把wait queue里的所有不带exclusive wait的进程全部唤醒,如果存在exclusive wait的进程,唤醒第一个并结束唤醒过程;wait_up_interruptible和wake_up函数类似,区别在于不会唤醒uninterruptible wait的进程。这两个函数在返回之前,有可能会唤醒多个进程并开始调度。
wake_up_nr(wait_queue_head_t *queue, int nr);
wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);
这两个函数和上面的两个功能类似,区别在于,这两个会唤醒nr个exclusive wait的进程,而不只是一个。如果nr是0,代表唤醒所有的exclusive进程。
wake_up_all(wait_queue_head_t *queue);
wake_up_interruptible_all(wait_queue_head_t *queue);
唤醒所有的进程,不考虑是否有exclusive wait。(interruptible版本只唤醒interruptible的wait)
wake_up_interruptible_sync(wait_queue_head_t *queue);
除非调用wake up的人是atomic context,比如拿了spinlock,或者是中断处理程序,否则调用者往往被待唤醒的进程抢占了CPU。有时候不希望这种事发生,调用者就使用这个函数,表示调用者可以被schedule,但是要等我把剩下的一点点事情做完。
poll and select
使用non-block I/O的程序一般都会是用poll select机制。这种机制包含三个系统调用:poll,epoll,select。
poll,select和epoll都是类似的函数,功能也类似,主要用于用户态application通过nonblock的方式访问fd,也可以是block的方式。poll和select是一样的功能,只是当时有两个不同的组分别做了实现,epoll是poll的扩展版本,可以支持多达上千个fd。在kernel的device driver中,需要实现的都是同一个函数:
unsigned int (*poll) (struct file *filp, poll_table *wait);
device driver的poll有两部分组成:
1, 调用poll_wait,等待一个或者多个wait queue,检测他们状态的变化,如果都不能做I/O,那就block wait。
2, 如果有可用的,返回bitmask,表明哪些操作是可用的,并且用non-block的方式实现。
这个接口里,第一个参数是filp就不说了,第二个参数是poll_table结构体,是kernel用来实现poll/epoll/select的。driver并不需要关心里面是什么内容,只需要调用poll_wait,并把这个poll_table传递进去就可以了。kernel之所以把这个结构体传递给driver,是因为需要driver把自己的wait queue添加到poll_table里,当wait queue需要被唤醒时,kernel可以知道。poll_wait接口原型:
void poll_wait (struct file *, wait_queue_head_t *, poll_table *);
在driver调用了poll_wait之后,还有一个任务是返回bit mask,告诉user mode哪些操作可以不用block就能完成。比如说,device有data到了,那么对于user mode来说read操作可以马上完成不用被block,driver的poll就应该让user mode知道这个状态。
这些bitmask有:
POLLIN
如果non-block的read可用,返回这个flag。
POLLRDNORM
如果device有normal data可用,返回这个,一般readable的device返回POLLIN | POLLRDNORM。
POLLRDBANDP
out of band的data到达,通知user mode。device driver一般没有这个。
POLLPRI
高优先级的data(out of band)到达,可以non-block的读取。
POLLHUP
如果process看到了file end,driver需要返回这个。
POLLERR
错误条件发生,不懂啥意思。。
POLLOUT
如果设备可以non-block的方式写,设上这个flag。
POLLWRNORM
略
POLLRDBAND和POLLWRBAND,这两个只能适用于scoket的filp,其他device driver不适用。
这里是poll的一个sample code:
static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = 0;
/*
* The buffer is circular; it is considered full
* if "wp" is right behind "rp" and empty if the
* two are equal.
*/
down(&dev->sem);
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if (dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM; /* readable */
if (spacefree(dev))
mask |= POLLOUT | POLLWRNORM; /* writable */
up(&dev->sem);
return mask;
}
上面的例子中,只是把inq和outq两个wait queue添加到了wait里面,并且根据具体的状态设置mask。
Interaction with read and write
poll select的目的是让user application提前知道哪些操作可以non-block的方式完成,更重要的是,可以同时wait在多个data stream上。关于driver中poll的实现,有几个原则需要遵循:
Reading data from the device
1, 只要input buffer有东西,也就是user mode可以读了,那么read调用应当在copy了数据之后立即返回,不等待。尽管有可能copy出去的数据没有read函数期望的那么多。这种情况下,poll返回POLLIN | POLLRDNORM。
2, 如果input buffer里没有东西,理论上read调用应该被block;如果设置了O_NONBLOCK,那么就返回-EAGAIN。这种情况下,poll返回0,即没有数据可用。
3. 如果到达了end of file,read立即返回0,无论有没有设置O_NONBLOCK。poll应该返回POLLHUB。
Writing to the device
1, 只要output buffer里有空间,write调用应该在write了数据之后立即返回,不等待。尽管有可能写进去的数据没有write函数期望的那么多。这种情况下,poll返回POLLOUT | POLLWRNORM。
2, 如果output bufer里没有空间,理论上write调用应该被block,直到空间可用;如果设置了O_NONBLOCK,那么就返回-EAGAIN。这种情况下,poll返回device不可以写。如果device已经满了,不能接受数据,那么write调用可以返回-ENOSPC,无论是否设置O_NONBLOCK。
3, write里的实现,不可以等待data真正传输完成。这里的意思,应该是write调用完后,不要求driver真的把数据写到里device,而应该在fsync这样的callback里实现。device driver一般不实现fsync,所以这里不再讨论。
后面继续讨论了poll背后的实现,涉及到kernel是如何让user mode的poll/select/epoll调用等待在对应的wait queue上。其实也简单,user mode进到kernel以后,kernel根据要等待的fd个数,创建poll_table_entry,有多少个fd就有多少个entry,这些entry最后都被封装在poll_table_struct里,被传递给每一个device driver的poll。而每一个entry里,其实就是一个wait queue,当这些wait queue都返回时,poll/select/epoll才会返回。