终于有时间继续更新blog了。让我们来看看非常牛b的第六章到底讲了些什么吧
一 阻塞型IO的实现
休眠的两条原则
a 永远不要在原子上下文中休眠。即我们的程序不能在拥有自旋锁 seqlock,等休眠。而用于信号量时休眠是可以的。
b 对唤醒后的状态不要作任何假设,必须验证我们等待的条件。
静态定义和初始化一个等待队列头
DECLARE_WAIT_QUEUE_HEAD(name);
动态
wait_queue_head_t my_queue;
init_waitqueue_head(&my_queue);
简单休眠
wait_event_interruptible(queue,condition)
wake_up_interruptible(queue)
成对使用
高级休眠
初始化等待队列入口
DEFINE_WAIT(my_wait)
或者
wait_queue_t my_wait;
init_wait (&my_wait)
将等待队列入口添加到队列中
prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state)
进入schedule
清理工作
finish_wait(wait_queue_head_t * queue, wait_queue_t * wait)
书中的例子使用两个等待队列,读的等待队列使用简单休眠的方式,而写的使用了高级休眠的方式,代码如下
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; } 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; }
/* 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; }
|
非常的详细的代码
编译测试,开窗口a
cat /dev/cddtp
程序阻塞,直到开窗口b
ls -l > /dev/cddtp
窗口a才输出
drwxrwxrwx 3 com.acce trusted 1024 May 17 15:01 RecordedSound
drwxrwxrwx 2 root root 1024 May 17 14:59 drm
drwxrwxrwx 9 root root 1024 May 17 15:01 public
代码里面也实现了非阻塞的IO
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
当没有数据可以读的时候立即返回 EAGIN.
二 poll and select
在驱动的file_operations增加一个poll函数
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; }
|
void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);
poll_wait函数并不阻塞,程序中poll_wait(filp, &dev->inq
, wait)这句话的意思并不是说一直等待outq信号量可获得,真正的阻塞动作是上层的select/poll函数中完成的。select/poll会在一个循环中对每个需要监听的设备调用它们自己的poll支持函数以使得当前进程被加入各个设备的等待列表。若当前没有任何被监听的设备就绪,则内核进行调度(调用schedule)让出cpu进入阻塞状态,schedule返回时将再次循环检测是否有操作可以进行,如此反复;否则,若有任意一个设备就绪, select/poll都立即返回。
hrpLinux联盟hrpLinux联盟用户端程序需要调用select()来进行select.
hrpLinux联盟
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); |
hrpLinux联盟 其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合,numfds的值是需要检查的号码最高的文件描述符加1。timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若没有文件描述符准备好则返回。struct timeval数据结构为:
hrpLinux联盟hrpLinux联盟
struct timeval hrpLinux联盟 { hrpLinux联盟 int tv_sec; /* seconds */ hrpLinux联盟 int tv_usec; /* microseconds */ hrpLinux联盟 }; |
hrpLinux联盟 除此之外,我们还将使用下列API:
hrpLinux联盟hrpLinux联盟 FD_ZERO(fd_set *set)――清除一个文件描述符集;
hrpLinux联盟 FD_SET(int fd,fd_set *set)――将一个文件描述符加入文件描述符集中;
hrpLinux联盟 FD_CLR(int fd,fd_set *set)――将一个文件描述符从文件描述符集中清除;
hrpLinux联盟 FD_ISSET(int fd,fd_set *set)――判断文件描述符是否被置位。
三 异步通知
用户程序必须执行 2 个步骤来使能来自输入文件的异步通知. 首先, 它们指定一个进程作为文件的拥有者. 当一个进程使用 fcntl 系统调用发出 F_SETOWN 命令, 这个拥有者进程的 ID 被保存在 filp->f_owner 给以后使用. 这一步对内核知道通知谁是必要的. 为了真正使能异步通知, 用户程序必须设置 FASYNC 标志在设备中, 通过 F_SETFL fcntl 命令.
在这 2 个调用已被执行后, 输入文件可请求递交一个 SIGIO 信号, 无论何时新数据到达. 信号被发送给存储于 filp->f_owner 中的进程(或者进程组, 如果值为负值).
例如, 下面的用户程序中的代码行使能了异步的通知到当前进程, 给 stdin 输入文件:
从内核的观点:
-
1. 当发出 F_SETOWN, 什么都没发生, 除了一个值被赋值给 filp->f_owner.
-
2. 当 F_SETFL 被执行来打开 FASYNC, 驱动的 fasync 方法被调用. 这个方法被调用无论何时 FASYNC 的值在 filp->f_flags 中被改变来通知驱动这个变化, 因此它可正确地响应. 这个标志在文件被打开时缺省地被清除. 我们将看这个驱动方法的标准实现, 在本节.
-
3. 当数据到达, 所有的注册异步通知的进程必须被发出一个 SIGIO 信号.
驱动调用的 2 个函数对应下面的原型:
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
void kill_fasync(struct fasync_struct **fa, int sig, int band);
在file* 结构中含有f_owner,来标识异步通知的进程
/* remove this filp from the asynchronously notified filp's */
scull_p_fasync(-1, filp, 0);