设为首页 收藏本站
查看: 848|回复: 0

[经验分享] 【Linux 驱动】第六章 高级字符驱动程序操作 ----阻塞型I/O

[复制链接]

尚未签到

发表于 2016-4-1 10:16:38 | 显示全部楼层 |阅读模式
  
    序言:试想如果在驱动方法中的read/write中,当数据不可用时,用户可能调用read,当输出缓冲区满时,设备并未准备好接受数据,这种情况下驱动程序可以阻塞该进程,并且置入休眠状态直到满足条件。
  

  一,休眠

     当一个进程休眠时,它会被标记为一种特殊状态并从调度器的运行队列中移走,直到某些情况修改了这个状态,进程才会在任意cpu上调度,即运行该进程。
在linux下,为了让进程安全的进入休眠状态,有两条规则需要牢记:

      1)永远不要在原子上下文中休眠。
         说明:原子上下文:在执行多个步骤时,不能有任何并发访问。不能再拥有自旋锁,seqlock,rcu锁时休眠,进程1在拥有信号量时休眠是合法的,但是要确保进程1拥有信号量不会阻塞唤醒我们的那个进程2。

    2)对唤醒之后的状态不能做任何假定,必须检查以确保我们等待的条件为真。
     
     初始化一个等待队列有两种方法:

    1)静态
        DECLARE_WAIT_QUEUE_HEAD(my_queue);
    2)动态
       wait_queue_head_t  my_queue;
         init_waitqueue_head(&my_queue);
  

  一个等待队列由一个wait_queue_head_t 结构体来管理,其定义在<linux/wait.h>

  struct __wait_queue_head {spinlock_t lock;struct list_head task_list;};typedef struct __wait_queue_head wait_queue_head_t;



二, 简单休眠

  等待队列:一个进程链表,包含了等待某个特定事件的所有进程
  在linux中,一个等待队列通过一个"wait queue head"来管理,类型为 wait_queue_head_t,在<linux/wait.h>中定义。


休眠宏: wait_event(queue,condition); //queue是等待队列头 condition是boolea类型
           wait_event_interruptible(queue,condition); //可以中断休眠
           wait_event_timeout(queue,condition,timeout);//等待限定的时间
           wait_event_interruptible_timeout(queue,condition,timeout);


  上述宏的调用如果condition条件不满足将引起调用进程的阻塞,并且宏会在休眠前后对condition求值,从名字可以看出带interruptible的宏用户可以中断休眠,返回非0值表示被中断,此时驱动程序要返回   -ERESTARTSYS.而带timeout的宏表示在给定的时间到期时,宏都会返回0.


唤醒函数:


         void  wake_up(wait_queue_head_t  *queue);
         void  wake_up_interruptible(wait_queue_head_t  *queue);

wake_up会唤醒在queue上的所有进程,而wake_up_interruptible只唤醒那些可中断的进程。


三,高级休眠


进程如何休眠?

   step1:分配初始化一个wait_queue_t 结构,然后加入到对应的等待队列。

     step2:通过函数set_current_state(int new_state)设置进程状态。
             驱动程序关心的进程状态主要是TASK_RUNNING(可运行),TASK_INTERRUPTIBLE(可中断休眠),TASK_UNINTERRPUTIBLE(不可中断休眠)。
     step3:调用schedule(),记住在调用之前,必须先检查进入休眠的条件。如果不做检查会引入竞态: 如果在忙于上面的这个过程时有其他的线程刚刚试图唤醒你,你可能错过
   唤醒且长时间休眠,经典的检查代码如下:

      if(!condition)

              schedule();
  四,手工休眠 <linux/sched.h>中包含必须的定义
     (1)创建和初始化一个等待队列。常由宏定义完成:
             DEFINE_WAIT(my_wait);  //name 是等待队列入口项的名字.
         

  方法二:
  wait_queue_t  my_wait;
init_wait(&my_wait);

  常用的做法是放一个 DEFINE_WAIT 在循环的顶部,来实现休眠。


(2)添加等待队列入口到队列,并设置进程状态:
void  prepare_to_wait(wait_queue_head_t   *queue, wait_queue_t  *wait, int  state);
queue  等待队列头

  wait 进程入口
  state  进程的新状态:TASK_INTERRUPTIBLE(可中断休眠,推荐)或TASK_UNINTERRUPTIBLE(不可中断休眠,不推荐)。


(3)在检查确认仍然需要休眠之后调用 schedule
schedule();

  (4)schedule 返回,就到了清理时间:

  void   finish_wait(wait_queue_head_t   *queue, wait_queue_t  *wait);

  认真地看简单休眠中的 wait_event(queue, condition) 和 wait_event_interruptible(queue, condition) 底层源码会发现,其实他们只是手工休眠中的函数的组合。所以怕麻烦的话还是用wait_event比较好。
  
  
  
  五, 独占等待
当一个进程调用 wake_up 在等待队列上,所有的在这个队列上等待的进程被置为可运行的。 这在许多情况下是正确的做法。但有时,可能只有一个被唤醒的进程将成功获得需要的资源,而其余的将再次休眠。这时如果等待队列中的进程数目大,这可能严重降低系统性能。为此,内核开发者增加了一个“独占等待”选项

独占等待进程与普通休眠进程的不同:

1)等待队列入口设置了WQ_FLAG_EXCLUSIVE标志,并且添加到等待队列的尾部,没有这个标志的进程被添加到等待队列的头部。

2)在某个等待队列上调用wake_up时,它会唤醒第一个具有WQ_FLAG_EXCLUSIVE标志的进程之后再唤醒其他进程。

什么时候采用独占等待进程呢?满足以下两个条件可以考虑:

1)对某个资源存在严重竞争。

  2)唤醒单个进程就能完整消耗该资源
  六,唤醒的相关函数
很少会需要调用wake_up_interruptible 之外的唤醒函数,但为完整起见,这里是整个集合:
wake_up(wait_queue_head_t *queue); //唤醒队列中的每个非独占等待进程和一个独占等待进程
wake_up_interruptible(wait_queue_head_t *queue); //过处于不可中断休眠的进程。它们在返回之前, 使一个或多个进程被唤醒、被调度(如果它们被从一个原子上下文调用, 这就不会发生)

wake_up_nr(wait_queue_head_t *queue, int nr);
wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);

  这些函数类似 wake_up, 除了它们能够唤醒多达 nr 个独占等待者, 而不只是一个. 注意传递 0 被解释为请求所有的互斥等待者都被唤醒

wake_up_all(wait_queue_head_t *queue);
wake_up_interruptible_all(wait_queue_head_t *queue);  

  这种 wake_up 唤醒所有的进程, 不管它们是否进行独占等待(可中断的类型仍然跳过在做不可中断等待的进程)

wake_up_interruptible_sync(wait_queue_head_t *queue);
一个被唤醒的进程可能抢占当前进程, 并且在 wake_up 返回之前被调度到处理器。 但是, 如果你需要不要被调度出处理器时,可以使用      wake_up_interruptible 的"同步"变体. 这个函数最常用在调用者首先要完成剩下的少量工作,且不希望被调度出处理器时。



七,阻塞和非阻塞操作


设置非阻塞I/O


显式的非阻塞I/O由filp->f_flags中的O_NONBLOCK标志决定,为了保持和System V代码兼容,标志O_NDELAY和O_NONBLOCK是一个意思,非阻塞io的read和write会返回-EAGAIN.


应用程序有两种方式制定非阻塞IO:


1)在open的时候指定,用于在open调用可能会阻塞很长的时间。


2)调用fcntl函数。具体使用方法可在网上查哦这里就不列出了。


阻塞操作的标准语义

  如果一个进程调用了read但是没有数据可读,进程阻塞,数据到达时进程被唤醒,并把数据返回给调用者,即使数据少于count;如果一个进程调用了write但是缓冲区满,该进程必须阻塞,休眠在与read进程不同的等待队列上,当缓冲区有空闲时,进程被唤醒,写入数据成功,即使写入了小于count的字节数。

一个阻塞IO的例子scullp

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 */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;}

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-198305-1-1.html 上篇帖子: Boost下载安装编译配置使用指南(含Windows和Linux) 下篇帖子: Linux Centos6挂载本地ISO光驱镜像
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表