linux r队列,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() ,以便唤醒队列中等待这些数据的进程。

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

a73b4c6727dd?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值