Wait_Queue------linux内核等待队列机制

案例:当串口设备不可读的时候(没有数据可读),那么应用程序应该怎么办? 

案例:当按键设备没有操作时(按键数据不可读),那么应用程序应该怎么办? 

答:应用程序对设备的这种状态(数据不可用的状态),应用程序要不就轮询读取设备的数据直到读到有效的数据,当然这种方法相当的糟糕,这种操作方式其实也是一种忙等待。 

当然还可以通过睡眠的等待,就是当设备数据不可用时,由底层驱动来检测,检查,识别设备数据可用不可用,如果不可用,底层设备驱动就让应用程序进入休眠状态(结果让当前进程的CPU资源撤下来给别的任务去使用),并且底层驱动能够检查设备可用不可用,如果一旦检查到设备数据可用,再次唤醒休眠的进程(休眠的进程一旦被唤醒,就会获取CPU的资源),然后去读取数据即可。 


问:如何实现一个应用程序在设备驱动程序中进行休眠和唤醒呢? 

答:要实现这种机制,根本上需要驱动程序具备能够检查,检测到设备可用不可用的功能! 


linux内核等待队列实现进程休眠和唤醒的方法和步骤: 

1.分配等待队列头 

wait_queue_head_t wq; 


2.初始化等待队列头 

init_waitqueue_head(&wq); 


//宏名用于定义并初始化,相当于"快捷方式"

DECLARE_WAIT_QUEUE_HEAD (my_queue);


/*定义并初始化一个名为name的等待队列 ,注意此处是定义一个wait_queue_t类型的变量name,并将其private设置为tsk*/

DECLARE_WAITQUEUE(name,tsk);


3.分配等待队列 

wait_queue_t wait; 


4.初始化等待队列,将当前进程添加到这个容器中 

init_waitqueue_entry(&wait, current); 

说明:current是内核的一个全局变量,用来记录当前进程,内核对于每一个进程,在内核空间都有一个对应的结构体struct task_struct,而current指针就指向当前运行的那个进程的task_struct结构体,你可以通过current指针来获取当前进程的pid和进程的名字(current->pid, current->comm) 


5.将当前进程添加到等待队列头中(并没有真正的休眠) 

add_wait_queue(&wq, &wait); 


6.设置当前进程为可中断的休眠状态(还没有真正的休眠) 

set_current_state(TASK_INTERRUPTIBLE);//能够接收处理信号 

说明:设置状态之前,进程是TASK_RUNNING状态! 


7.调用schedule()完成真正的休眠工作

当调用此函数时,会将当前进程占用的CPU资源让出来给别的任务,并且让当前进程进入真正的休眠状态,一旦进程被唤醒,schedule()函数就返回,代码继续往下执行。 


8.一旦被唤醒以后,要判断是什么原因使当前进程唤醒

唤醒进程的原因:1.数据可用的唤醒,2.接收到了信号 


9.调用signal_pending(current)来判断是否是因为接收到信号引起的唤醒

如果此函数返回非0,表明是接收到了信号,如果返回0,表明没有接收到信号,那说明这个唤醒是由于数据可用引起的唤醒操作。如果是接收到信号的唤醒,一般就不要在操作硬件设备了 


10.如果是设备数据可用引起的唤醒,一旦唤醒,调用: 

current->state = TASK_RUNNING; //设置当前进程为运行状态 

remove_wait_queue(&state->wait_queue, &wait);//将唤醒的进程从等待队列头所在的数据连中移除。 


11.进程读取或者操作设备即可。 


参考代码:

假设串口没有数据到来,应用程序调用read读->驱动uart_read:

wait_queue_head_t rwq; //分配一个读的等待队列头, 全局变量

init_waitqueue_head(&wq); //在驱动入口函数初始化

uart_read()

{

        wait_queue_t wait; //分配等待队列

        init_waitqueue_entry(&wait, current); //将当前进程添加到容器中

        add_wait_queue(&rwq, &wait); //将当前进程添加到队列头中

        set_current_state(TASK_INTERRUPTIBLE);//设置当前进程的状态

        schedule(); //进入真正的休眠状态(CPU资源让给别的任务)

        set_current_state(TASK_RUNNING);

        remove_wait_queue(&rwq, &wait);

        //一旦被唤醒,要判断是哪个原因引起的唤醒

        if(signal_pending(current))

        {

                printk("RECV SIN!\n");

               return -ERESTARTSYS; //返回用户空间的read

       }  else {  

                //由于数据可用引起的唤醒读取串口数据

                copy_to_user(...); //上报数据

        }

}


编程实现方法2: 

案例:如果串口没有数据到来,应用程序调用read->uart_read 

1.分配等待队列头 

wait_queue_heat_t rwq; 


2.初始化等待队列头 

init_waitqueue_head(&rwq); 


3.在uart_read函数中,直接调用 

wait_event/wait_evnet_timeout/wait_event_interruptible_timeout 

wait_event_interruptible(&rwq, condition); //如果数据可用,condition为真,如果数据不可用,condition为假,当前进程就会进入休眠。 

4.一旦被唤醒,当前进程直接去操作设备即可


进程通过执行下面几个步骤将自己加入到一个等待队列中

-------------------------------------------------------------------------------

调用宏 DEFINE_WAIT() 创建一个等待队列的项。

调用 add_wait_queue() 把自己加入到队列中(链表操作)。该队列会在进程等待的条件满足时唤醒它。当然我们必须在其他地方撰写相关代码,在事件发生时,对等待队列执行 wake_up() 操作

调用 prepare_to_wait() 方法将进程的状态变更为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 。而且该函数会在必要的情况下将进程加回到等待队列,这是在接下来的循环遍历中所需要的。

如果状态被设置为 TASK_INTERRUPTIBLE ,则信号唤醒进程。这就是所谓的伪唤醒(唤醒不是因为事件的发生),因此检查并处理信号。

当进程被唤醒的时候,它会再次检查条件是否为真。如果是,它就退出循环;如果不是,它再次调用 schedule() 并一直重复这步操作。

当条件满足后,进程将自己设置为 TASK_RUNNING 并调用 finish_wait() 方法把自己移出等待队列。


/* 'q' 是我们希望休眠的等待队列 */  

DEFINE_WAIT(wait);  

add_wait_queue(q, &wait);  

while (!condition)   /* 'condition' 是我们在等待的事件 */  

{   

       {

              prepare_to_wait(&q, &wait, TASK_INTERRUPTIBLE);  

        }

if (signal_pending(current))  

       {

              /* 处理信号 */  

              schedule();  

       }  

}

finish_wait(&q, &wait); 


唤醒

唤醒操作通过函数 wake_up() 进行,它会唤醒指定的等待队列上的所有进程。它调用函数 try_to_wake_up() ,该函数负责将进程设置为 TASK_RUNNING 状态,调用 enqueue_task() 将此进程放入红黑树中,如果被唤醒的进程优先级比当前执行的进程优先级高,还要设置 need_resched 标志。通常哪段代码促使等待条件达成,它就要负责随后调用 wake_up() 函数 。举例来说,当磁盘数据到来时,VFS 就要负责对等待队列调用 wake_up() ,以便唤醒队列中等待这些数据的进程。

关于休眠有一点需要注意,存在虚假的唤醒(信号)。有时候进程被唤醒并不是因为它所等待的条件达成了,所以需要用一个循环处理来保证它等待的条件真正达成。下图描述了每个调度程序状态之间的关系


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容