精华内容
下载资源
问答
  • linux内核消息队列实现

    千次阅读 2010-04-18 12:48:00
    Linux内核的同步机制(三):等待队列Linux内核中的等待队列Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在Linux2.4.21中,等待队列在源代码树...

    Linux内核的同步机制(三):等待队列

    Linux内核中的等待队列
    Linux 内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在Linux2.4.21中,等待队列在源 代码树include/linux/wait.h中,这是一个通过list_head连接的典型双循环链表,如下图所示。

    在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队 列头和等待队列项中都包含一个list_head类型的域作为"连接件"。由于我们只需要对队列进行添加和删除操作,并不会修改其中的对象(等待队列 项),因此,我们只需要提供一把保护整个基础设施和所有对象的锁,这把锁保存在等待队列头中,为wq_lock_t类型。在实现中,可以支持读写锁 (rwlock)或自旋锁(spinlock)两种类型,通过一个宏定义来切换。如果使用读写锁,将wq_lock_t定义为rwlock_t类型;如果 是自旋锁,将wq_lock_t定义为spinlock_t类型。无论哪种情况,分别相应设置wq_read_lock、wq_read_unlock、 wq_read_lock_irqsave、wq_read_unlock_irqrestore、wq_write_lock_irq、 wq_write_unlock、wq_write_lock_irqsave和wq_write_unlock_irqrestore等宏。
    等待队列头
    struct __wait_queue_head {
     wq_lock_t lock;
     struct list_head task_list;
    };
    typedef struct __wait_queue_head wait_queue_head_t;
    前面已经说过,等待队列的主体是进程,这反映在每个等待队列项中,是一个任务结构指针(struct task_struct * task)。flags为该进程的等待标志,当前只支持互斥。
    等待队列项
    struct __wait_queue {
     unsigned int flags;
    #define WQ_FLAG_EXCLUSIVE 0x01
     struct task_struct * task;
     struct list_head task_list;
    };
    typedef struct __wait_queue wait_queue_t;
    声明和初始化
    #define DECLARE_WAITQUEUE(name, tsk)     /
     wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
    #define __WAITQUEUE_INITIALIZER(name, tsk) {    /
     task:  tsk,      /
     task_list: { NULL, NULL },     /
        __WAITQUEUE_DEBUG_INIT(name)}
    通过DECLARE_WAITQUEUE宏将等待队列项初始化成对应的任务结构,并且用于连接的相关指针均设置为空。其中加入了调试相关代码。
    #define DECLARE_WAIT_QUEUE_HEAD(name) /
     wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
    #define __WAIT_QUEUE_HEAD_INITIALIZER(name) {    /
     lock:  WAITQUEUE_RW_LOCK_UNLOCKED,   /
     task_list: { &(name).task_list, &(name).task_list }, /
       __WAITQUEUE_HEAD_DEBUG_INIT(name)}
    通过DECLARE_WAIT_QUEUE_HEAD宏初始化一个等待队列头,使得其所在链表为空,并设置链表为"未上锁"状态。其中加入了调试相关代码。
    static inline void init_waitqueue_head(wait_queue_head_t *q)
    该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prev和next指向它自身。
    {
        q->lock = WAITQUEUE_RW_LOCK_UNLOCKED;
        INIT_LIST_HEAD(&q->task_list);
    }
    static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
    该函数初始化一个已经存在的等待队列项,它设置对应的任务结构,同时将标志位清0。
    {
        q->flags = 0;
        q->task = p;
    }
    static inline int waitqueue_active(wait_queue_head_t *q)
    该函数检查等待队列是否为空。
    {
        return !list_empty(&q->task_list);
    }
    static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
    将指定的等待队列项new添加到等待队列头head所在的链表头部,该函数假设已经获得锁。
    {
        list_add(&new->task_list, &head->task_list);
    }
    static inline void __add_wait_queue_tail(wait_queue_head_t *head, wait_queue_t *new)
    将指定的等待队列项new添加到等待队列头head所在的链表尾部,该函数假设已经获得锁。
    {
        list_add_tail(&new->task_list, &head->task_list);
    }
    static inline void __remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)
    将函数从等待队列头head所在的链表中删除指定等待队列项old,该函数假设已经获得锁,并且old在head所在链表中。
    {
        list_del(&old->task_list);
    }
    睡眠和唤醒操作
    对 等待队列的操作包括睡眠和唤醒(相关函数保存在源代码树的/kernel/sched.c和include/linux/sched.h中)。思想是更改 当前进程(CURRENT)的任务状态,并要求重新调度,因为这时这个进程的状态已经改变,不再在调度表的就绪队列中,因此无法再获得执行机会,进入"睡 眠"状态,直至被"唤醒",即其任务状态重新被修改回就绪态。
    常用的睡眠操作有interruptible_sleep_on和 sleep_on。两个函数类似,只不过前者将进程的状态从就绪态(TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发 送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何 singal。以interruptible_sleep_on为例,其展开后的代码是:
    void interruptible_sleep_on(wait_queue_head_t *q)
    {
        unsigned long flags;
        wait_queue_t wait;
        /* 构造当前进程对应的等待队列项 */
        init_waitqueue_entry(&wait, current);
        /* 将当前进程的状态从TASK_RUNNING改为TASK_INTERRUPTIBLE */
        current->state = TASK_INTERRUPTIBLE;
        /* 将等待队列项添加到指定链表中 */
        wq_write_lock_irqsave(&q->lock,flags);
        __add_wait_queue(q, &wait); 
        wq_write_unlock(&q->lock);
        /* 进程重新调度,放弃执行权 */
        schedule();
        /* 本进程被唤醒,重新获得执行权,首要之事是将等待队列项从链表中删除 */
        wq_write_lock_irq(&q->lock);
        __remove_wait_queue(q, &wait);
        wq_write_unlock_irqrestore(&q->lock,flags);
        /* 至此,等待过程结束,本进程可以正常执行下面的逻辑 */
    }
    对 应的唤醒操作包括wake_up_interruptible和wake_up。wake_up函数不仅可以唤醒状态为 TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。 wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。这两个宏的定义如下:
    #define wake_up(x)   __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1)
    #define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)
    __wake_up函数主要是获取队列操作的锁,具体工作是调用__wake_up_common完成的。
    void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)
    {
        if (q) {
            unsigned long flags;
            wq_read_lock_irqsave(&q->lock, flags);
            __wake_up_common(q, mode, nr, 0);
            wq_read_unlock_irqrestore(&q->lock, flags);
        }
    }
    /* The core wakeup function.  Non-exclusive wakeups (nr_exclusive == 0) just wake everything up.  If it's an exclusive wakeup (nr_exclusive == small +ve number) then we wake all the non-exclusive tasks and one exclusive task.
    There are circumstances in which we can try to wake a task which has already started to run but is not in state TASK_RUNNING.  try_to_wake_up() returns zero in this (rare) case, and we handle it by contonuing to scan the queue. */
    static inline void __wake_up_common (wait_queue_head_t *q, unsigned int mode, int nr_exclusive, const int sync)
    参数q表示要操作的等待队列,mode表示要唤醒任务的状态,如TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE等。nr_exclusive是要唤醒的互斥进程数目,在这之前遇到的非互斥进程将被无条件唤醒。sync表示???
    {
        struct list_head *tmp;
        struct task_struct *p;
        CHECK_MAGIC_WQHEAD(q);
        WQ_CHECK_LIST_HEAD(&q->task_list);
        /* 遍历等待队列 */
        list_for_each(tmp,&q->task_list) {
            unsigned int state;
            /* 获得当前等待队列项 */
            wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
            CHECK_MAGIC(curr->__magic);
            /* 获得对应的进程 */
            p = curr->task;
            state = p->state;
            /* 如果我们需要处理这种状态的进程 */
            if (state & mode) {
                WQ_NOTE_WAKER(curr);
                if (try_to_wake_up(p, sync) && (curr->flags&WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                    break;
            }
        }
    }
    /* 唤醒一个进程,将它放到运行队列中,如果它还不在运行队列的话。"当前"进程总是在运行队列中的(except when the actual re-schedule is in progress),and as such you're allowed to do the simpler "current->state = TASK_RUNNING" to mark yourself runnable without the overhead of this. */
    static inline int try_to_wake_up(struct task_struct * p, int synchronous)
    {
        unsigned long flags;
        int success = 0;
        /* 由于我们需要操作运行队列,必须获得对应的锁 */
        spin_lock_irqsave(&runqueue_lock, flags);
        /* 将进程状态设置为TASK_RUNNING */
        p->state = TASK_RUNNING;
        /* 如果进程已经在运行队列中,释放锁退出 */
        if (task_on_runqueue(p))
            goto out;
        /* 否则将进程添加到运行队列中 */
        add_to_runqueue(p);
        /* 如果设置了同步标志 */
        if (!synchronous || !(p->cpus_allowed & (1UL << smp_processor_id())))
            reschedule_idle(p);
        /* 唤醒成功,释放锁退出 */
        success = 1;
    out:
        spin_unlock_irqrestore(&runqueue_lock, flags);
        return success;
    }
    等待队列应用模式
    等待队列的的应用涉及两个进程,假设为A和B。A是资源的消费者,B是资源的生产者。A在消费的时候必须确保资源已经生产出来,为此定义一个资源等待队列。这个队列同时要被进程A和进程B使用,我们可以将它定义为一个全局变量。
    DECLARE_WAIT_QUEUE_HEAD(rsc_queue); /* 全局变量 */
    在进程A中,执行逻辑如下:
    while (resource is unavaiable) {
        interruptible_sleep_on( &wq );
    }
    consume_resource();
    在进程B中,执行逻辑如下:
    produce_resource();
    wake_up_interruptible( &wq )
    展开全文
  • Linux内核的等待队列[汇编].pdf
  • Linux内核新旧工作队列机制的剖析和比较.pdf
  • Linux内核的等待队列

    千次阅读 2008-04-23 15:56:00
    Linux内核的同步机制:等待队列Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在Linux2.4.21中,等待队列在源代码树include/linux/wait.h中,这...

    Linux内核的同步机制:等待队列
    Linux
    内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在Linux2.4.21中,等待队列在源代码树include/linux/wait.h中,这是一个通过list_head连接的典型双循环链表,如下图所示。

    在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head类型的域作为"连接件"。由于我们只需要对队列进行添加和删除操作,并不会修改其中的对象(等待队列项),因此,我们只需要提供一把保护整个基础设施和所有对象的锁,这把锁保存在等待队列头中,为wq_lock_t类型。在实现中,可以支持读写锁(rwlock)或自旋锁(spinlock)两种类型,通过一个宏定义来切换。如果使用读写锁,将wq_lock_t定义为rwlock_t类型;如果是自旋锁,将wq_lock_t定义为spinlock_t类型。无论哪种情况,分别相应设置wq_read_lockwq_read_unlockwq_read_lock_irqsavewq_read_unlock_irqrestorewq_write_lock_irqwq_write_unlockwq_write_lock_irqsavewq_write_unlock_irqrestore等宏。


    等待队列头
    struct  __wait_queue_head {
     wq_lock_t  lock;
     struct  list_head  task_list;
    };
    typedef  struct __wait_queue_head  wait_queue_head_t;
    前面已经说过,等待队列的主体是进程,这反映在每个等待队列项中,是一个指向进程task_struct结构的指针指针(struct task_struct  *task)。flags为该进程的等待标志指示当前的进程状态。此等待进程的状态必须说明成是INTERRUPTIBLE还是UNINTERRUPTIBLE

     

    声明和初始化
    #define DECLARE_WAITQUEUE(name, tsk)     /
     wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
    #define __WAITQUEUE_INITIALIZER(name, tsk) {    /
     task:  tsk,      /
     task_list: { NULL, NULL },     /
        __WAITQUEUE_DEBUG_INIT(name)}
    通过DECLARE_WAITQUEUE宏将等待队列项初始化成对应的进程结构task,并且用于连接的相关指针均设置为空。其中加入了调试相关代码。

    #define DECLARE_WAIT_QUEUE_HEAD(name) /
     wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
    #define __WAIT_QUEUE_HEAD_INITIALIZER(name) {    /
     lock:  WAITQUEUE_RW_LOCK_UNLOCKED,   /
     task_list: { &(name).task_list, &(name).task_list }, /  //
    前驱、后继指针都指向自己
       __WAITQUEUE_HEAD_DEBUG_INIT(name)}
    通过DECLARE_WAIT_QUEUE_HEAD宏初始化一个等待队列头,使得其所在链表为空(指等待队列没有等待队列项),并设置链表为"未上锁"状态。其中加入了调试相关代码。

     

    static inline void init_waitqueue_head(wait_queue_head_t *q)
    该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prevnext指向它自身。
    {
        q->lock = WAITQUEUE_RW_LOCK_UNLOCKED;
        INIT_LIST_HEAD(&q->task_list);
    }

     

    static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
    该函数初始化一个已经存在的等待队列项,它设置对应的任务结构(进程),同时将标志位清0
    {
        q->flags = 0;
        q->task = p;
    }

     

    static inline int waitqueue_active(wait_queue_head_t *q)
    该函数检查等待队列是否为空。
    {
        return  !list_empty(&q->task_list);
    }

     

    static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)
    将指定的等待队列项new添加到等待队列头head所在的链表头部,该函数假设已经获得锁。
    {
        list_add(&new->task_list, &head->task_list);
    }

     

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

    将指定的等待队列项new添加到等待队列头head所在的链表尾部,该函数假设已经获得锁。

    {

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

    }

     

    static inline void  __remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)

    将函数从等待队列头head所在的链表中删除指定等待队列项old,该函数假设已经获得锁,并且oldhead所在链表中。

    {

        list_del(&old->task_list);

    }

     

    睡眠和唤醒操作
    对等待队列的操作包括睡眠和唤醒(相关函数保存在源代码树的/kernel/sched.cinclude/linux/sched.h中)。思想是更改当前进程(CURRENT)的任务状态,并要求重新调度,因为这时这个进程的状态已经改变,不再在调度表的就绪队列中,因此无法再获得执行机会,进入"睡眠"状态,直至被"唤醒",即其任务状态重新被修改回就绪态。
    常用的睡眠操作有interruptible_sleep_onsleep_on。两个函数类似,只不过前者将进程的状态从就绪态(TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal。以interruptible_sleep_on为例,其展开后的代码是:

    void  interruptible_sleep_on(wait_queue_head_t  *q)
    {
        unsigned long flags;
        wait_queue_t wait;
        /*
    构造当前进程对应的等待队列项 */
        init_waitqueue_entry(&wait, current);

        /* 将当前进程的状态从TASK_RUNNING改为TASK_INTERRUPTIBLE */
        current->state = TASK_INTERRUPTIBLE;

        /* 将等待队列项添加到指定链表中 */
        wq_write_lock_irqsave(&q->lock,flags);   //
    获得锁,关中断并保留状态字,保留在flags
        __add_wait_queue(q, &wait); 
        wq_write_unlock(&q->lock);

        /* 进程重新调度,放弃执行权 */
        schedule();

        /* 本进程被唤醒,重新获得执行权,首要之事是将等待队列项从链表中删除 */
        wq_write_lock_irq(&q->lock);
        __remove_wait_queue(q, &wait);
        wq_write_unlock_irqrestore(&q->lock,flags);  //
    释放锁,开中断并从flags中恢复状态字
        /*
    至此,等待过程结束,本进程可以正常执行下面的逻辑 */
    }

     

    对应的唤醒操作包括wake_up_interruptiblewake_upwake_up函数不仅可以唤醒状态为TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。这两个宏的定义如下:
    #define wake_up(x)   __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1)
    #define wake_up_interruptible(x)  __wake_up((x),TASK_INTERRUPTIBLE, 1)
    __wake_up
    函数主要是获取队列操作的锁,具体工作是调用__wake_up_common完成的。
    void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)
    {
        if (q) {
            unsigned long flags;
            wq_read_lock_irqsave(&q->lock, flags);
            __wake_up_common(q, mode, nr, 0);
            wq_read_unlock_irqrestore(&q->lock, flags);
        }
    }

     

    static inline  void  __wake_up_common (wait_queue_head_t *q, unsigned int mode, int nr_exclusive, const int sync)
    参数q表示要操作的等待队列,mode表示要唤醒任务的状态,如TASK_UNINTERRUPTIBLETASK_INTERRUPTIBLE等。nr_exclusive是要唤醒的互斥进程数目,在这之前遇到的非互斥进程将被无条件唤醒。sync表示?
    {
        struct list_head *tmp;
        struct task_struct *p;

        CHECK_MAGIC_WQHEAD(q);
        WQ_CHECK_LIST_HEAD(&q->task_list);

        /* 遍历等待队列 */
        list_for_each(tmp,&q->task_list) {
            unsigned int state;
            /*
    获得当前等待队列项 */
            wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);

            CHECK_MAGIC(curr->__magic);
            /*
    获得对应的进程 */
            p = curr->task;
            state = p->state;

            /* 如果我们需要处理这种状态的进程 */
            if (state & mode) {
                WQ_NOTE_WAKER(curr);
                if (try_to_wake_up(p, sync) && (curr->flags&WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                    break;
            }
        }
    }

     

     

    /* 唤醒一个进程,将它放到运行队列中,如果它还不在运行队列的话。"当前"进程总是在运行队列中的(except when the actual re-schedule is in progress)and as such you're allowed to do the simpler "current->state = TASK_RUNNING" to mark yourself runnable without the overhead of this. */
    static inline int try_to_wake_up(struct task_struct * p, int synchronous)
    {
        unsigned long flags;
        int success = 0;

        /* 由于我们需要操作运行队列,必须获得对应的锁 */
        spin_lock_irqsave(&runqueue_lock, flags);
        /*
    将进程状态设置为TASK_RUNNING */
        p->state = TASK_RUNNING;
        /*
    如果进程已经在运行队列中,释放锁退出 */
        if (task_on_runqueue(p))
            goto out;
        /*
    否则将进程添加到运行队列中 */
        add_to_runqueue(p);

        /* 如果设置了同步标志 */
        if (!synchronous || !(p->cpus_allowed & (1UL << smp_processor_id())))
            reschedule_idle(p);
        /*
    唤醒成功,释放锁退出 */
        success = 1;
    out:
        spin_unlock_irqrestore(&runqueue_lock, flags);
        return success;
    }

     

    等待队列应用模式
    等待队列的的应用涉及两个进程,假设为ABA是资源的消费者,B是资源的生产者。A在消费的时候必须确保资源已经生产出来,为此定义一个资源等待队列。这个队列同时要被进程A和进程B使用,我们可以将它定义为一个全局变量。
    DECLARE_WAIT_QUEUE_HEAD(rsc_queue); /*
    全局变量 */
    在进程A中,执行逻辑如下:
    while (resource is unavaiable) {
        interruptible_sleep_on( &wq );
    }
    consume_resource();
    在进程B中,执行逻辑如下:
    produce_resource();
    wake_up_interruptible( &wq );

     

    展开全文
  • linux内核之工作队列

    2018-10-23 15:45:45
    在我的文章Linux内核:软中断、tasklet中,我们已经了解了中断底半部的两种实现方式,即软中断和tasklet微线程。但是这两种方式归根结底都是采用软中断机制的,其根本上还是在中断的上下文中执行,所以这也就要求了...

    版权声明:本文为博主原创文章,未经博主允许不得转载。
    https://blog.csdn.net/huangweiqing80/article/details/83306220

    在我的文章Linux内核:软中断、tasklet中,我们已经了解了中断底半部的两种实现方式,即软中断和tasklet微线程。但是这两种方式归根结底都是采用软中断机制的,其根本上还是在中断的上下文中执行,所以这也就要求了采用这两种方式编写中断底半部,不能出现一些可能导致程序休眠或者是延迟的函数(虽然当发生中断嵌套时会生成Ksoftirq线程,但这个是不确定的,所以我们在编写程序时,还是不能采用具有休眠或者延时的函数)。因为这样一种缺陷,所以我们的Linux设计师发明了一种新的将操作延迟的方法,那就是工作队列(workqueue)。由于工作队列是工作在一个内核线程上,因此其工作环境为进程的上下文,从而工作函数可以休眠任意时间。

    那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。

    对于每一个工作队列,内核都会为其创建一个新的内核守护线程,也就是说,每一个工作队列都有唯一的一个内核线程与其对应。工作时,该内核线程就会轮询地执行这个工作队列上所有的工作节点上对应的处理函数(这一点有点像tasklet,只不过现在是在一个线程上执行该工作队列),工作队列由一个workqueue_struct数据结构体描述。

    首先要注意本文的两个概念:(1)使用内核提供的工作队列, (2)自己创建工作队列

    /*
     * The externally visible workqueue abstraction is an array of
     * per-CPU workqueues:
     */
    struct workqueue_struct {
    	unsigned int		flags;		/* I: WQ_* flags */
     
    //这个共用体表示该workqueue_struct属于哪个CPU的队列。
    	union {
    		struct cpu_workqueue_struct __percpu	*pcpu;
    		struct cpu_workqueue_struct		*single;
    		unsigned long				v;
    	} cpu_wq;				/* I: cwq's */
    	struct list_head	list;		/* W: list of all workqueues */
    //用来连接work_struct的队列头
    	struct mutex		flush_mutex;	/* protects wq flushing */
    	int			work_color;	/* F: current work color */
    	int			flush_color;	/* F: current flush color */
    	atomic_t		nr_cwqs_to_flush; /* flush in progress */
    	struct wq_flusher	*first_flusher;	/* F: first flusher */
    	struct list_head	flusher_queue;	/* F: flush waiters */
    	struct list_head	flusher_overflow; /* F: flush overflow list */
     
    	mayday_mask_t		mayday_mask;	/* cpus requesting rescue */
    	struct worker		*rescuer;	/* I: rescue worker */
     
    	int			saved_max_active; /* W: saved cwq max_active */
    	const char		*name;		/* I: workqueue name */
    #ifdef CONFIG_LOCKDEP
    	struct lockdep_map	lockdep_map;
    #endif
    };
    
    

    workqueue_struct结构体比较复杂,一般没有必要了解所有成员的含义。在workqueue_struct涉及到一个cpu_workqueue_struct结构体,该结构体有什么用呢?
    与原来的tasklet一样,一个工作队列也是只能工作在一个CPU上面的,即每一个CPU都有一个工作队列。而cpu_workqueue_sruct就是描述该CPU的工作队列的结构体。

    /*
     * The per-CPU workqueue.  The lower WORK_STRUCT_FLAG_BITS of
     * work_struct->data are used for flags and thus cwqs need to be
     * aligned at two's power of the number of flag bits.
     */
    struct cpu_workqueue_struct {
    	struct global_cwq	*gcwq;		/* I: the associated gcwq */
    	struct workqueue_struct *wq;		/* I: the owning workqueue ,指向属于该CPU的workqueue_struct结构体*/
    	int			work_color;	/* L: current color */
    	int			flush_color;	/* L: flushing color */
    	int			nr_in_flight[WORK_NR_COLORS];
    						/* L: nr of in_flight works */
    	int			nr_active;	/* L: nr of active works */
    	int			max_active;	/* L: max active works */
    	struct list_head	delayed_works;	/* L: delayed works */
    };
    
    

    了解了上面两个结构体之后,我们因该能够大致了解工作队列的工作机制,大体上与tasklet差不多。下面我们就来看一下工作队列最为重要的成员----工作,work_struct。work_struct是工作队列里面的成员,里面会定义该work_struct的处理函数。

    struct work_struct {
    	atomic_long_t data;
    	struct list_head entry;  //指向与其相邻的前后两个work_struct
    	work_func_t func;  //该work_struct节点的处理函数。
    #ifdef CONFIG_LOCKDEP
    	struct lockdep_map lockdep_map;
    #endif
    };
    
    

    这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。

    实现原理

    工作队列的组织结构
    即workqueue_struct、cpu_workqueue_struct与work_struct的关系。
    一个工作队列对应一个work_queue_struct,工作队列中每cpu的工作队列由cpu_workqueue_struct表示,而work_struct为其上的具体工作。
    关系如下图所示:
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    相关API

    创建一个队列就会有一个内核线程,一般不要轻易创建队列
    位于进程上下文--->可以睡眠
    定义:
    	struct work_struct work;
     
    初始化:
    	INIT_WORK(struct work_struct *work, void (*func)(struct work_struct *work));
     
    定义并初始化:
    	DECLARE_WORK(name, void (*func)(struct work_struct *work));
     
    ===========================================================
    共享队列: 
    调度:
    	int schedule_work(struct work_struct *work);
    	返回1成功, 0已经添加在队列上
     
    延迟调度:
    	int schedule_delayed_work(struct work_struct *work, unsigned long delay);
     
    ===========================================================
    自定义队列: 
    创建新队列和新工作者线程:
    	struct workqueue_struct *create_workqueue(const char *name);
     
    调度指定队列:
    	int queue_work(struct workqueue_struct *wq, struct work_struct *work);
     
    延迟调度指定队列:
    	int queue_delayed_work(struct workqueue_struct *wq, 
    			struct work_struct *work, unsigned long delay);
    销毁队列:
    	void destroy_workqueue(struct workqueue_struct *wq);
    
    

    通常我们如果要在驱动程序中使用工作队列时,一共有两种方式:

    采用共享工作队列
    自定义工作队列

    采用共享工作队列

    在Linux系统中,内核为了方便用户编程,已经默认实现了一个所有进程都可以使用的工作队列(其对应的内核线程是kevent线程,其在Linux启动时创建,该线程被创建之后就处于sleep状态,当我们使用schedule_work函数时,才会唤醒该线程,当工作队列上的所有节点被执行完毕,该线程又会处于休眠状态,知道schedule_work再次被调用)。因此采用共享工作队列,在用户的实现上是非常简单的。
    第一步:编写自己的 work_struct 工作函数。
    第二步:定义自己的 work_struct 结构体。
    第三步:初始化work_struct结构体,使工作函数地址指向work_struct ->func
    第四步:可以在适当位置使用schedule_work函数完成向系统工作队列添加自己的work_struct

    采用自定义工作队列

    采用共享工作队列会有一个弊端,因为毕竟共享队列采用的是kevent线程,系统里面的其它工作也会使用到该共享队列。如果我们在该工作队列加入太多耗时的程序,无疑会降低系统性能,因此一般在驱动程序中,我们会偏向于使用自定义工作队列,采用自定义工作队列也比较简单,相对于共享工作队列,这里多了一个创建自定义工作的函数,即:create_queue函数(注意这个函数会在每一个CPU上都创建一个一个工作队列和相应的线程,这未免太过于消耗资源,因此我们还可以采用在某一指定的CPU上创建一个工作队列,例如采用create_singlethread_workqueue函数,就会在编号为第一个的CPU上创建内核线程和工作队列。)对于自定义的工作队列,在这里我们不能使用schedule_struct函数将work_struct添加进工作队列了,这是因为schedule_work函数只能往共享工作队列上添加工作节点(work_struct),所以我们必须要采用queue_work 函数。

    接下来,我们来实现这个demo:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/platform_device.h>
    #include <linux/fb.h>
    #include <linux/backlight.h>
    #include <linux/err.h>
    #include <linux/pwm.h>
    #include <linux/slab.h>
    #include <linux/miscdevice.h>
    #include <linux/delay.h>
    #include <linux/gpio.h>
    #include <mach/gpio.h>
    #include <plat/gpio-cfg.h>
    #include <linux/timer.h>  /*timer*/
    #include <asm/uaccess.h>  /*jiffies*/
    #include <linux/delay.h>
    #include <linux/interrupt.h>
    #include <linux/workqueue.h>
    struct tasklet_struct task_t ; 
    struct workqueue_struct *mywork ;
    //定义一个工作队列结构体
    struct work_struct work;
    static void task_fuc(unsigned long data)
    {
    	if(in_interrupt()){
                 printk("%s in interrupt handle!\n",__FUNCTION__);
            }
    }
    //工作队列处理函数
    static void mywork_fuc(struct work_struct *work)
    {
    	if(in_interrupt()){
                 printk("%s in interrupt handle!\n",__FUNCTION__);
            }
    	msleep(2);
    	printk("%s in process handle!\n",__FUNCTION__);
    }
     
    static irqreturn_t irq_fuction(int irq, void *dev_id)
    {	
    	tasklet_schedule(&task_t);
    	//调度工作
    	schedule_work(&work);
    	if(in_interrupt()){
    	     printk("%s in interrupt handle!\n",__FUNCTION__);
    	}
    	printk("key_irq:%d\n",irq);
    	return IRQ_HANDLED ;
    }
    	
    static int __init tiny4412_Key_irq_test_init(void) 
    {
    	int err = 0 ;
    	int irq_num1 ;
    	int data_t = 100 ;
    	//创建新队列和新工作者线程
    	mywork = create_workqueue("my work");
    	//初始化
    	INIT_WORK(&work,mywork_fuc);
    	//调度指定队列
    	queue_work(mywork,&work);
    	tasklet_init(&task_t,task_fuc,data_t);
    	printk("irq_key init\n");
    	irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
    	err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,"tiny4412_key1",(void *)"key1");
    	if(err != 0){
    		free_irq(irq_num1,(void *)"key1");
    		return -1 ;
    	}
    	return 0 ;
    }
     
    static void __exit tiny4412_Key_irq_test_exit(void) 
    {
    	int irq_num1 ;
    	printk("irq_key exit\n");
    	irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
    	//销毁一条工作队列
    	destroy_workqueue(mywork);
    	free_irq(irq_num1,(void *)"key1");
    }
     
    module_init(tiny4412_Key_irq_test_init);
    module_exit(tiny4412_Key_irq_test_exit);
     
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("YYX");
    MODULE_DESCRIPTION("Exynos4 KEY Driver");
    
    

    工作队列是一种将工作推后执行的形式,交由一个内核线程去执行在进程上下文执行,其不能访问用户空间。最重要特点的就是工作队列允许重新调度甚至是睡眠。工作队列子系统提供了一个默认的工作者线程来处理这些工作。默认的工作者线程叫做events/n,这里n是处理器的编号,每个处理器对应一个线程,也可以自己创建工作者线程。

    展开全文
  • Linux内核:工作队列

    千次阅读 2015-10-02 10:26:21
    在我的上一篇文章Linux内核:中断、软中断、tasklet中,我们已经了解了中断底半部的两种实现方式,即软中断和tasklet微线程。但是这两种方式归根结底都是采用软中断机制的,其根本上还是在中断的上下文中执行,所以...

    在我的上一篇文章Linux内核:中断、软中断、tasklet中,我们已经了解了中断底半部的两种实现方式,即软中断和tasklet微线程。但是这两种方式归根结底都是采用软中断机制的,其根本上还是在中断的上下文中执行,所以这也就要求了采用这两种方式编写中断底半部,不能出现一些可能导致程序休眠或者是延迟的函数(虽然当发生中断嵌套时会生成Ksoftirq线程,但这个是不确定的,所以我们在编写程序时,还是不能采用具有休眠或者延时的函数)。因为这样一种缺陷,所以我们的Linux设计师发明了一种新的将操作延迟的方法,那就是工作队列(workqueue)。由于工作队列是工作在一个内核线程上,因此其工作环境为进程的上下文,从而工作函数可以休眠任意时间。

    对于每一个通过队列,内核都会为其创建一个新的内核守护线程,也就是说,每一个工作队列都有唯一的一个内核线程与其对应。工作时,该内核线程就会轮询地执行这个工作队列上所有的工作节点上对应的处理函数(这一点有点像tasklet,只不过现在是在一个线程上执行该工作队列),工作队列由一个workqueue_struct数据结构体描述。

    /*
     * The externally visible workqueue abstraction is an array of
     * per-CPU workqueues:
     */
    struct workqueue_struct {
    	unsigned int		flags;		/* I: WQ_* flags */
    
    //这个共用体表示该workqueue_struct属于哪个CPU的队列。
    	union {
    		struct cpu_workqueue_struct __percpu	*pcpu;
    		struct cpu_workqueue_struct		*single;
    		unsigned long				v;
    	} cpu_wq;				/* I: cwq's */
    	struct list_head	list;		/* W: list of all workqueues */
    //用来连接work_struct的队列头
    	struct mutex		flush_mutex;	/* protects wq flushing */
    	int			work_color;	/* F: current work color */
    	int			flush_color;	/* F: current flush color */
    	atomic_t		nr_cwqs_to_flush; /* flush in progress */
    	struct wq_flusher	*first_flusher;	/* F: first flusher */
    	struct list_head	flusher_queue;	/* F: flush waiters */
    	struct list_head	flusher_overflow; /* F: flush overflow list */
    
    	mayday_mask_t		mayday_mask;	/* cpus requesting rescue */
    	struct worker		*rescuer;	/* I: rescue worker */
    
    	int			saved_max_active; /* W: saved cwq max_active */
    	const char		*name;		/* I: workqueue name */
    #ifdef CONFIG_LOCKDEP
    	struct lockdep_map	lockdep_map;
    #endif
    };
    
    workqueue_struct结构体比较复杂,一般没有必要了解所有成员的含义。在workqueue_struct涉及到一个cpu_workqueue_struct结构体,该结构体有什么用呢?

    与原来的tasklet一样,一个工作队列也是只能工作在一个CPU上面的,即每一个CPU都有一个工作队列。而cpu_workqueue_sruct就是描述该CPU的工作队列的结构体。

    /*
     * The per-CPU workqueue.  The lower WORK_STRUCT_FLAG_BITS of
     * work_struct->data are used for flags and thus cwqs need to be
     * aligned at two's power of the number of flag bits.
     */
    struct cpu_workqueue_struct {
    	struct global_cwq	*gcwq;		/* I: the associated gcwq */
    	struct workqueue_struct *wq;		/* I: the owning workqueue ,指向属于该CPU的workqueue_struct结构体*/
    	int			work_color;	/* L: current color */
    	int			flush_color;	/* L: flushing color */
    	int			nr_in_flight[WORK_NR_COLORS];
    						/* L: nr of in_flight works */
    	int			nr_active;	/* L: nr of active works */
    	int			max_active;	/* L: max active works */
    	struct list_head	delayed_works;	/* L: delayed works */
    };
    
    了解了上面两个结构体之后,我们因该能够大致了解工作队列的工作机制,大体上与tasklet差不多。下面我们就来看一下工作队列最为重要的成员,work_struct。work_struct是工作队列里面的成员,里面会定义该work_struct的处理函数。

    struct work_struct {
    	atomic_long_t data;
    	struct list_head entry;  //指向与其相邻的前后两个work_struct
    	work_func_t func;  //该work_struct节点的处理函数。
    #ifdef CONFIG_LOCKDEP
    	struct lockdep_map lockdep_map;
    #endif
    };
    
    下面我们再来看看工作队列的调度函数schedule_work(类似于 tasklet 的 schedule_tasklet 吧)。

    /**
     * schedule_work - put work task in global workqueue
     * @work: job to be done
     *
     * Returns zero if @work was already on the kernel-global workqueue and
     * non-zero otherwise.
     *
     * This puts a job in the kernel-global workqueue if it was not already
     * queued and leaves it in the same position on the kernel-global
     * workqueue otherwise.
     */
    int schedule_work(struct work_struct *work)
    {
    	return queue_work(system_wq, work); //将一个新的work_struct添加进workqueue队列
    }
    
    可以看到schedule_work的工作内容很简单,即将一个新的work_struct加入我们的工作队列(这里的工作队列是已经被创建的系统工作队列)上,这个也与我们之前的tasklet有些相似,只不过tasklet多了一个raise函数(将tasklet 软中断类型挂起)。工作队列由于只有这一个队列,所以是不需要有挂起函数这个操作的。由于该函数只有一句代码,所以我们在实际的使用过程中也可以直接使用queue_work函数。(一般自己创建的工作队列,在往工作队列加入work_struct时,需要使用到该queue_work函数,因为schedule_work函数默认是将work_struct加入系统队列中。)

    了解完工作队列的基本操作方法之后呢?下面我要开始讲解如何执行一个工作队列了。前面我们了解到工作队列是由一个内核线程执行的,同时这个内核线程是具有CPU属性的,也就是说每一个CPU都有独属于自己的内核工作线程(当然这个线程需要我们自己创建的,在创建的时候我们也可以指定该内核线程属于哪个CPU。创建的方式后面会讲到)。创建完内核线程之后(这个时候内核线程其实已经被唤醒了,因为在创建内核线程的代码里面有唤醒该线程的函数),当然要执行该内核线程的内核函数,这个函数就是woker_thread,在该函数里面呢,woker_thread主要会调用run_workqueue函数,因为该函数负责执行工作队列里面的work_struct关联的函数。



    通常我们如果要在驱动程序中使用工作队列时,一共有两种方式:

    1. 采用共享工作队列
    2. 自定义工作队列

    采用共享工作队列

    在Linux系统中,内核为了方便用户编程,已经默认实现了一个所有进程都可以使用的工作队列(其对应的内核线程是kevent线程,其在Linux启动时创建,该线程被创建之后就处于sleep状态,当我们使用schedule_work函数时,才会唤醒该线程,当工作队列上的所有节点被执行完毕,该线程又会处于休眠状态,知道schedule_work再次被调用)。因此采用共享工作队列,在用户的实现上是非常简单的。
    第一步:编写自己的 work_struct 工作函数。
    第二步:定义自己的 work_struct 结构体。
    第三步:初始化work_struct结构体,使工作函数地址指向work_struct ->func
    第四步:可以在适当位置使用schedule_work函数完成向系统工作队列添加自己的work_struct 


    采用自定义工作队列

    采用共享工作队列会有一个弊端,因为毕竟共享队列采用的是kevent线程,系统里面的其它工作也会使用到该共享队列。如果我们在该工作队列加入太多耗时的程序,无疑会降低系统性能,因此一般在驱动程序中,我们会偏向于使用自定义工作队列,采用自定义工作队列也比较简单,相对于共享工作队列,这里多了一个创建自定义工作的函数,即:create_queue函数(注意这个函数会在每一个CPU上都创建一个一个工作队列和相应的线程,这未免太过于消耗资源,因此我们还可以采用在某一指定的CPU上创建一个工作队列,例如采用create_singlethread_workqueue函数,就会在编号为第一个的CPU上创建内核线程和工作队列。)对于自定义的工作队列,在这里我们不能使用schedule_struct函数将work_struct添加进工作队列了,这是因为schedule_struct函数只能往共享工作队列上添加工作节点(work_struct),所以我们必须要采用queue_work 函数。


    展开全文
  • Linux内核中等待队列 和完成量

    千次阅读 2014-05-23 10:30:38
    分类: linux内核技术2012-01-05 00:03 7905人阅读 评论(6) 收藏 举报 wait_eventwake_up等待队列  根据内核3.1.6版本源码、书籍和网上资料,对几个函数进行分析  介绍这几个函数,不得不先介绍等待...
  • linux内核工作队列demo

    2016-07-01 18:08:59
    很多博客都有深入分析内核队列的工作原理,但是少有能够拿来直接运行的demo,这个demo亲测可用!
  • linux内核工作队列

    千次阅读 2017-12-09 12:45:42
    内核工作队列概述工作队列(workqueue)是另外一种将工作推后执行的形式,工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行,最重要的就是工作队列允许被重新调度...
  • Linux内核中等待队列的几种用法

    千次阅读 2012-10-17 12:47:47
    Linux内核里的等待队列机制在做驱动开发时用的非常多,多用来实现阻塞式访问,下面简单总结了等待队列的四种用法,希望对读者有所帮助。 1. 睡眠等待某个条件发生(条件为假时睡眠):  睡眠方式:wait_event, wait...
  • Linux内核中的无锁队列 - kfifo
  • 本文对最新的 Linux-4.19.4 内核源码进行分析,并详细指出内核 IPC 机制中的消息队列的原理。 进程间通信 IPC(进程间通信,InterProcess Communication)是内核提供的系统中进程间进行通信的一种机制。系统中每个...
  • linux内核态和用户

    千次阅读 2016-05-05 13:20:32
    linux系统内核空间与用户空间通信的实现与分析: http://www.ibm.com/developerworks/cn/linux/l-netlink/ 进程上下文VS中断上下文: http://www.2cto.com/os/201212/177668.html 内核态和用户的最大区别...
  • \Linux内核机制之等待队列,消息讲述了wait_queue队列的数据结构和在内核中的实现源码,有助于对如何使用队列更加一目了然。
  • LinuxLinux消息队列

    千次阅读 2018-08-13 19:14:09
    消息队列亦称报文队列,也叫做...为在进程与内核之间传递消息,无论发送进程还是接收进程,都需要在进程空间中用消息缓冲区来暂存消息。该消息缓冲区的结构定义如下: struct msgbuf { long mtype; /* 消息的类型...
  • Linux内核等待队列详解

    千次阅读 2018-01-25 16:45:34
    内核将因相同原因而阻塞的进程集中在一个队列上,该队列就是等待队列,对于每个需要等待的事件都有一个相应的等待队列。等待队列采用链表的方式来存储,各个阻塞进程作为节点存储在链表中。链表头的类型是wait_queue...
  • Linux内核通用队列实现 Kfifo位置:kernel/kififo.c使用需要包含头文件#include 1、创建队列(动态创建)int kfifo_alloc(struct kififo *fifo , unsigned int size , gfp_t gfp_mask);该函数会创建并初始化一个大小为...
  • 嵌入式Linux内核中的等待队列操作.pdf
  •  等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程在等待周期中睡眠,当时间发生后由内核自动唤醒。  完成量机制是基于等待队列的,内核利用该机制等待某一操作的结束。这两种经常被
  • Linux内核态抢占机制分析

    千次阅读 2011-11-12 20:44:20
    接着分析Linux下有两种抢占:用户抢占(User Preemption)、内核态抢占(Kernel Preemption)。然后分析了在内核态下:如何判断能否抢占内核(什么是可抢占的条件);何时触发重新调度(何时设置可抢占条件);抢占发生的...
  • 消息队列: 1.每次msgrcv一个消息,1.那个消息会在内核中移除 2.每次msgrcv都只会给一个消息出来,不管你rcv用多大的buf来接收,都是可以的。如果msgrcv的bufSize小于实际的该消息的大小,那么可以设置一个标志:...
  • 消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息...消息队列是随内核持续的
  • linux内核态与用户通信方式

    万次阅读 2014-01-06 16:50:42
    下面对linux内核态与用户通信方式中的procfs进行讲解。 /proc主要存放内核的一些控制信息,所以这些信息大部分的逻辑位置位于内核控制的内存,在/proc下使用ls -l你会发现大部分的文件或者文件夹的大小都是0,...
  • 初始化工作队列调度工作队列取消工作队列[cpp] view plain copy#include &lt;linux/module.h&gt; #include &lt;linux/sched.h&gt; #include &lt;linux/kthread.h&gt; #include &...
  • linux IPC-消息队列内核限制

    千次阅读 2010-09-07 14:19:00
    几乎所有的 Linux 发行版本都包含 ipcs 命令,该命令可以提供当前加载到系统上的 IPC 资源信息。通过 ipcs 可以确定系统的当前 IPC 限制,还可以检查系统当前使用的上述 三类IPC资源的状态。例如,...
  • Linux消息队列

    千次阅读 2018-06-02 11:59:24
    Linux中的消息队列是进程间通信的一种方式,通过创建一个消息队列可以完成一个或者多个进程的信息交汇。因此,首先我们要了解消息队列是什么。 消息队列的定义 消息队列的本质其实是一个内核提供的链表,内核基于...
  • Linux下用户空间与内核空间数据交换的方式一  本系列文章包括两篇,它们文详细地介绍了 Linux 系统下用户空间与内核空间数据交换的九种方式,包括内核启动参数、模块参数与sysfs、sysctl、系统调用、netlink、...
  • 本文介绍了Linux操作系统内核中工作队列的操作。
  • 运行在用户上下文环境中的代码是可以阻塞的,这样,便可以使用消息队列和 UNIX 域套接字来实现内核态与用户的通信。但这些方法的数据传输效率较低,Linux 内核提供 copy_from_user()/copy_to_user() 函数来实现...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 138,975
精华内容 55,590
关键字:

linux内核态消息队列

linux 订阅