热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux学习等待队列

由于学习linux驱动编程,学习到了堵塞型IO读写,等待队列的操作比较的有意思,拿来分析分析,其中的一些代码还是蛮有意思的,感受到了li

由于学习linux驱动编程,学习到了堵塞型IO读写,等待队列的操作比较的有意思,拿来分析分析,其中的一些代码还是蛮有意思的,感受到了linux的美,体会到了艺术家和一般程序员的差别。

我就简要的分析分析等待队列的一些问题,就相当于自己的总结吧。边学驱动,边学内核,还是蛮有意思的。

1、等待队列的定义,包括两个,等待队列头,节点。

    struct __wait_queue_head {

        spinlock_t lock;     /*自旋锁*/

        struct list_head task_list; /*链表头*/

    };

    typedef struct __wait_queue_head wait_queue_head_t;

 ...

 struct __wait_queue {

unsigned int flags;

    #define WQ_FLAG_EXCLUSIVE 0x01

void *private;

wait_queue_func_t func;

struct list_head task_list;

  };

 /*关于等待队列的操作主要是初始化操作*/

#define DECLARE_WAIT_QUEUE_HEAD(name) \

wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

/*就是初始化两个元素*/

#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \

.lock = __SPIN_LOCK_UNLOCKED(name.lock), \

.task_list = { &(name).task_list, &(name).task_list } }

#define init_waitqueue_head(q) \

do { \

static struct lock_class_key __key; \

\

__init_waitqueue_head((q), &__key); \

} while (0)

void __init_waitqueue_head(wait_queue_head_t *q, struct lock_class_key *key)

{

spin_lock_init(&q->lock);

lockdep_set_class(&q->lock, key);

INIT_LIST_HEAD(&q->task_list);

}

从上面的定义可知,实质上等待队列头很简单,只要就是一个链表头,而等待队列的节点主要包含了一个函数指针和对应的参数,以及链表。

我们在驱动过程中主要使用的函数主要包括wait_event(),wait_event_interruptible(),wait_event_killable(),以及唤醒过程中的wait_up(),wait_up_interruptible().

基本的流程就是:

    #define wait_event(wq, condition)                     \

    do {                                    \

        if (condition)

    /*添加满足,则直接跳出*/                             \

            break; 

        /*负责进入等待队列*/                          \

        __wait_event(wq, condition);                    \

    } while (0)

#define __wait_event(wq, condition) \

do { \

/*定义新的等待队列节点*/

       DEFINE_WAIT(__wait); \

\

for (;;) {/*一个循环的过程,可能导致堵塞*/

                /*将添加的节点添加到队列中,并改变进程的运行状态*/ \

prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \

if (condition)/*如果条件合适了,就跳出当前的循环,也就是等待条件获得*/ \

break; \

/*当前进程放弃CPU,进行调度其他的进程,这时的进程进入睡眠状态

                  也就是说在schedule中函数就不在继续执行,只有调用wake_up函数唤

                  醒当前的进程,才会退出schedule函数,然后继续执行下面的函数,也就是继续循环

                  真正的退出循环,只有当条件满足时,如果条件不满足,调用wake_up函数

                  仍然不会满足条件,只会再次调度,再次失去CPU,

                  根据上面的分析可知,只有上面的条件满足了,并调用

                  wake_up函数才能跳出当前的for循环。

                 */

schedule(); \

}

        /*完成等待*/ \

finish_wait(&wq, &__wait); \

} while (0)

#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

#define DEFINE_WAIT_FUNC(name, function) \

wait_queue_t name = { \

.private = current, \

.func = function, \

.task_list = LIST_HEAD_INIT((name).task_list), \

}

void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)

{

unsigned long flags;

        /*改变状态*/

wait->flags &= ~WQ_FLAG_EXCLUSIVE;

spin_lock_irqsave(&q->lock, flags);

        /*如果链表是空,则将当前的这个节点添加进来,这样能避免wait被反复的添加,造成大量的浪费*/

if (list_empty(&wait->task_list))

__add_wait_queue(q, wait);

/*修改当前进程的状态*/

set_current_state(state);

spin_unlock_irqrestore(&q->lock, flags);

}

#define set_current_state(state_value) \

set_mb(current->state, (state_value))

static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)

{

        /*就是将链表添加进来而已*/

list_add(&new->task_list, &head->task_list);

}

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

{

unsigned long flags;

wait->flags &= ~WQ_FLAG_EXCLUSIVE;

spin_lock_irqsave(&q->lock, flags);

__add_wait_queue(q, wait);

spin_unlock_irqrestore(&q->lock, flags);

}

void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)

{

unsigned long flags;

       /*修改当前进程的状态为TASK_RUNNING,因此可以被执行*/

__set_current_state(TASK_RUNNING);

/*

* We can check for list emptiness outside the lock

* IFF:

*  - we use the "careful" check that verifies both

*    the next and prev pointers, so that there cannot

*    be any half-pending updates in progress on other

*    CPU's that we haven't seen yet (and that might

*    still change the stack area.

* and

*  - all other users take the lock (ie we can only

*    have _one_ other CPU that looks at or modifies

*    the list).

*/

        /*删除链表,实质上就是释放*/

if (!list_empty_careful(&wait->task_list)) {

spin_lock_irqsave(&q->lock, flags);

list_del_init(&wait->task_list);

spin_unlock_irqrestore(&q->lock, flags);

}

}

asmlinkage void __sched schedule(void)

{

struct task_struct *prev, *next;

unsigned long *switch_count;

struct rq *rq;

int cpu;

need_resched:

preempt_disable();

cpu = smp_processor_id();

rq = cpu_rq(cpu);

rcu_note_context_switch(cpu);

prev = rq->curr;

release_kernel_lock(prev);

need_resched_nonpreemptible:

schedule_debug(prev);

if (sched_feat(HRTICK))

hrtick_clear(rq);

raw_spin_lock_irq(&rq->lock);

switch_count = &prev->nivcsw;

if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {

if (unlikely(signal_pending_state(prev->state, prev))) {

prev->state = TASK_RUNNING;

} else {

/*

* If a worker is going to sleep, notify and

* ask workqueue whether it wants to wake up a

* task to maintain concurrency.  If so, wake

* up the task.

*/

if (prev->flags & PF_WQ_WORKER) {

struct task_struct *to_wakeup;

to_wakeup = wq_worker_sleeping(prev, cpu);

if (to_wakeup)

try_to_wake_up_local(to_wakeup);

}

deactivate_task(rq, prev, DEQUEUE_SLEEP);

}

switch_count = &prev->nvcsw;

}

pre_schedule(rq, prev);

if (unlikely(!rq->nr_running))

idle_balance(cpu, rq);

put_prev_task(rq, prev);

next = pick_next_task(rq);

clear_tsk_need_resched(prev);

rq->skip_clock_update = 0;

if (likely(prev != next)) {

sched_info_switch(prev, next);

perf_event_task_sched_out(prev, next);

rq->nr_switches++;

rq->curr = next;

++*switch_count;

context_switch(rq, prev, next); /* unlocks the rq */

/*

* The context switch have flipped the stack from under us

* and restored the local variables which were saved when

* this task called schedule() in the past. prev == current

* is still correct, but it can be moved to another cpu/rq.

*/

cpu = smp_processor_id();

rq = cpu_rq(cpu);

} else

raw_spin_unlock_irq(&rq->lock);

post_schedule(rq);

if (unlikely(reacquire_kernel_lock(prev)))

goto need_resched_nonpreemptible;

preempt_enable_no_resched();

if (need_resched())

goto need_resched;

}

根据上面的各个函数,宏定义可知,在wait_event函数中完成了大部分的事情,其中包括等待队列节点的定义,添加,当前进程运行状态的改变,等待条件的满足,跳出等待,函数介绍之前需要完成的任务是修改当前进程的状态为TASK_RUNNING,删除链表,释放一些空间。

其他的函数wait_event_interruptible以及wait_event_killable具有相似的操作,只是对前期修改进程状态存在差别。wait_event_interruptible则不一定只能在条件满足时唤醒,也可以被信号唤醒,而wait_event则在条件满足时被唤醒。




推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 服务器上的操作系统有哪些,如何选择适合的操作系统?
    本文介绍了服务器上常见的操作系统,包括系统盘镜像、数据盘镜像和整机镜像的数量。同时,还介绍了共享镜像的限制和使用方法。此外,还提供了关于华为云服务的帮助中心,其中包括产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题和视频帮助等技术文档。对于裸金属服务器的远程登录,本文介绍了使用密钥对登录的方法,并提供了部分操作系统配置示例。最后,还提到了SUSE云耀云服务器的特点和快速搭建方法。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
author-avatar
義忠仁倫冧沫Bob
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有