ldd3笔记-第6章-高级字符程序操作part2

来源:互联网 发布:linux安装jdk教程 编辑:程序博客网 时间:2024/05/16 11:25


6.2 阻塞IO
    当无法立即响应某个请求时,应该阻塞进程,将其休眠直到响应。
    6.2.1 休眠简介
        当一个进程被置为睡眠,它被标识并且从调度器的运行队列中去除.直到发生某些事情改变了那个状态。
        为了能在linux上安全地休眠,有以下规则:
        1 不要再原子上下文中休眠
            根据第五章知道,原子上下文中可能已经禁止中断。
            拥有信号量时,可以休眠,注意:等待该信号量的进程也会休眠。
        2 当唤醒时,不知道休眠了多长时间,不知道都发生了什么事情。
            有可能在休眠时自己的资源被剥夺,所以唤醒后要检查。
        3 除非知道有人会唤醒自己,否则不要休眠。
            可以使用一个等待队列来唤醒休眠。
            被唤醒后,需要检查正在等待的条件是否真的已经为真。

    6.2.2 简单休眠
        1 最简单的是调用宏wait_event()进入休眠,在另一个进程调用wake_up()唤醒休眠的进程。
        2 休眠函数
            wait_event(有几个变形):
            wait_event                      (queue, condition)          //非中断休眠
            wait_event_interruptible        (queue, condition)          //可以被信号中断。(推荐使用)
            wait_event_timeout              (queue, condition, timeout) //时间到时返回0
            wait_event_interruptible_timeout(queue, condition, timeout) //时间到时返回0
                queue :是等待队列头。是一个值,非指针。
                condition:是bool表达式。 休眠后对其求值,若为假则继续休眠。它可能多次求值。

        3 唤醒函数
            以下函数会唤醒等待在给定queue上的进程。
            void wake_up              (wait_queue_head_t *queue);       //与wait_event()对应
            void wake_up_interruptible(wait_queue_head_t *queue);       //与wait_event_interruptible()对应

        4 示例:
            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);
                printk(KERN_DEBUG "    sleepy_read() sleeping ...\n");
                wait_event_interruptible(wq, flag != 0);
                flag = 0;
                printk(KERN_DEBUG "    sleepy_read() awoken\n");
                return 0;
            }

            ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos)
            {
                printk(KERN_DEBUG "    sleepy_write() awokening\n");
                flag = 1;
                wake_up_interruptible(&wq);
                return count;
            }
            解释:我们使用flag来创建应该为真的条件
                    当有2个在等待时,sleepy_write()的执行大多时候会唤醒其中一个,另外一个会继续休眠。因为flag的原因。

    6.2.3 阻塞和非阻塞操作。
        有时候,调用进程通知你他不想阻塞,不管其I/O是否继续.可以使用如下方法达到目的:
        定义:<linux/fcntl.h> 被<linux/fs.h>自动包含
        标志:filp->f_flags 中的 O_NONBLOCK 标志来指示。即非阻塞打开的意思。
              源码使用O_NDELAY与O_NONBLOCK仅名字不同而已。
        用法:
            1 在打开时被指定此标志。默认此标志是被清除的,即按阻塞操作。
            2 此标志仅在open read write 时有用。
            3 若设置了此标志,的行为会如下:
                如果一个进程当没有数据可用时调用read,或者如果当缓冲中没有空间时它调用write时
                会立即返回-EAGAIN(("try it agin")。应用程序可以轮询查询。
                如果open使用此标志,会在初始化开始时返回-EAGAIN。有些OPEN会立即返回成功,不管设备是否被打开。

    6.2.4. 一个阻塞  I/O  的例子
        scullpipe驱动,它是 scull 的一个特殊形式,实现了一个象管道的设备
        我们使用另一个进程来产生数据并唤醒读进程;类似地,读进程被用来唤醒正在等待缓冲空间可用的写者进程.它包含2个等待队列和一个缓冲。
            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)              //没有可读数据
                {
                    up(&dev->sem);                      //释放锁

                    if (filp->f_flags & O_NONBLOCK)     //非阻塞则立即返回
                    {
                        return -EAGAIN;
                    }
                    PDEBUG("\"%s\" reading: going to sleep\n", current->comm);

                    //进入休眠等待,直到dev->rp != dev->wp
                    if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
                    {
                        return -ERESTARTSYS;
                    }
                    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;
            }

    6.2.5 高级休眠
        本节介绍在休眠时,到底发生了什么事。
        6.2.5.1 进程如何休眠
            在<linux/wait.h>有定义wait_queue_head_t数据结构,包含一个自旋锁和一个链表.
            此链表是一个等待队列入口,声明为wait_queue_t.
            第一步:分配和初始化一个wait_queue_t结构,将其添加到正确的等待队列.
            第二步:设置进程的状态为睡眠.在<linux/sched.h>定义状态标志。
                修改状态标志函数是:(通常不需要直接修改)
                    void set_current_state(int new_state);
                老的版本是如下做的:
                    current->state = TASK_INTERRUPTIBLE;
            第三步:检查条件,可能这个条件刚刚被改变
                if (!condition)
                    schedule();     会让出CPU,不会知道schedule返回到你的代码会是多长时间.
            第四步:schedule返回后,必须保证任务状态被重置为TASK_RUNNING.

        6.2.5.2 手工休眠