精华内容
下载资源
问答
  • 在linux中断编程中,需要中断程序分成中断顶部和中断底部两部分,顶部...底部分代码实现可以通过内核共享工作队列实现。 1)核心结构 Workqueue.h include\Linux struct work_struct { atomic_long_t data; ...

    在linux中断编程中,需要中断程序分成中断顶部和中断底部两部分,顶部负责做中断标志,然后耗时的事情在中断底部执行。顶部不能被其他进程中断,而底部可以被其他进程中断,也可以被硬中断事件中断。底部分代码实现可以通过内核共享工作队列实现。


    1)核心结构

    Workqueue.h    include\Linux

    struct work_struct {
    	atomic_long_t data;    
    	struct list_head entry;
    	work_func_t func;      /* 工作函数指针 */
    	#ifdef CONFIG_LOCKDEP
    	struct lockdep_map lockdep_map;
    	#endif
    };

    工作函数原型:

    typedef void (*work_func_t)(struct work_struct *work);

    unc指针所指向的函数:一般是就需要延后执行代码。一般使用编写中断底部代码。

    内核使用这个结构来描述一个工作,一个工作简单理解就是对应于一个函数,可以通过内核调度函数来调用work_struct中func指针所指向的函数。

    work_func_t函数中的参数就是指向struct work_struct  结构本身。

    利用这个特征可以实现在工作函数中访问自定的结构中的其他成员,如:自定义一个数据结构,内部嵌入一个 struct work_struct。

      struct myvar{
        struct work_struct work;   
        int x;
        int y;
      }

    struct myvar  var;

    关键宏:

    INIT_WORK(_work, _func)    :初始化一个work结构

    _work: struct work_struct work结构指针

    _func:用来填充work_struct work结构的fun指针(work的具体代码)

    INIT_WORK(&var.work, work_func);/* 把work放置内核共享工作队列中*/

    工作函数:

    void  work_func (struct work_struct *work)
    {
     	//因为传递进入的work刚刚好是位于 struct myvar 起始位置
     	 struct myvar *p = ( struct myvar*)work;
     
     	p->x = 12;
     	p->y = 23;
    }

     调度宏:

    schedule_work(_work):一个宏,对应一个函数;作用是调度一个一个工作_work。

    _work:要调度工作的结构指针;

    schedule_work(&work);


    使用共享工作队列步骤:

    1.定义一个工作结构变量

    struct work_struct work;

    2.初始化工作结构—重点func成员:

    先编写一个工作函数:

    void work_func(struct work_struct * dat){

    ……

    printk(“%p:”,dat);

    ……

    }

    初始化work:

    INIT_WORK(&work, work_func);

    3.在适当的地方调度工作(如果工作用于中断底部代码,则在中断顶部调度)

            schedule_work(&work);  ――>不是马上执行,而是等待CPU空闲才执行work_func


    内核共享延时工作队列:

    和上面的内核共享工作队列一样,可以实现中断的底部代码功能。和内核共享工作队列惟一区别就是它可以指定一个延时时间,调度工作函数。

    关键数据结构:

    //延时工作队列

    struct delayed_work {

             struct work_struct work; //工作结构

             struct timer_list timer;  //内核定时器结构

    };

    初始化函数:

    INIT_DELAYED_WORK(_work, _func)    

    初始化一个delayed_work结构

    _work: struct delayed_work work结构指针

    _func:用来填充struct delayed_work work结构的fun指针(work的具体代码)

    调度函数:

    int schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)

    dwork要调度的延时工作结构指针;

    delay延后调度的时间,单位是时钟节拍。和内核定时器定时时间单位相同,但是不是到期时间,而是定时时间。

    应用延时工作队列步骤:

    1. 定义一个struct delayed_work结构变量
    2. 初始化struct delayed_work结构
    3. 在适当地方调用工作。

    步骤和前面的共享工作队列完全相同。


    问题:如果结构定义如下:

    struct myvar{
        int x;
        int z ;  
        struct work_struct work;   
        int y;
    }

    上面的方法,就需要对  work- 8 再转换成 struct myvar指针,才可以访问x,y,.(不考虑结构的内存对齐)。如果发后需要增加其他成员,代码又要修改,此法,通用性太差。

    (type*)0 :相当于在0地址定义了一个结构变量 

    ((type *)0)->member:访问结构成员member

    typeof(   ((type *)0)->member)->得成成员类型。typeof :求出变量的类型。如:int a;typeof(a)  --> int

    typeof(   ((type *)0)->member)  *__mptr  = ptr  -- 定义一个__mptr 和成员类型相同 的指针指向成员地址

    offsetof(type,member)   ---offsetof:得到 member 成员在type中为偏移,和变量在内存位置 无关。

    (char *)__mptr - offsetof(type,member)      ---得到具体结构变量首地址--字节地址。

    (type *)( (char *)__mptr - offsetof(type,member) )        --- 转换成结构类型地址。

    内核提供了一个宏,可以通过结构体中任何一个成员地址反推出他所在的变量结构所在的内存首地址。

    如果上面的结构,只要知道 ,x,y,z,work任何一个地址,都可以知道整个结构体变量的内存首地址。

    #define container_of(ptr, type, member)        ({			\
                  const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
                  (type *)( (char *)__mptr - offsetof(type,member) );})

    ptr:是已知的成员指针
    type:结构体类型
    member:就是ptr所指的成员

    使用方法:

    container_of(ptr, type, member) 

    struct myvar{
        int x;
        int z ;  
        struct work_struct work;   
        int y;
    } a,b ;  ---》定义了两个变量
      
    container_of(&a.z, struct myvar, z)    结果  ---- > &a
    container_of(&b.y, struct myvar, y)    结果  ---- > &b     

    /*
     * 使用内核共享工作队列,演示container_of用法
     */
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/workqueue.h>
    
    typedef struct __mydat{
        int x;
        int y;
        struct work_struct mywork;
        int z;
    } mydat_t;
    
    static void work_handler(struct work_struct *data)
    {
        mydat_t *p;
    
        /* 计算第二个参数结构体首地址 */
        p = container_of(data, mydat_t, mywork);
            
        //mydat_t *p = (mydat_t *)data;
        
        //printk(KERN_EMERG "data:%p,\n", data);
        //printk(KERN_EMERG "x:%d,\n", ((mydat_t *)data)->x);
        //printk(KERN_EMERG "y:%d,\n", ((mydat_t *)data)->y);   
        
        printk(KERN_EMERG "x:%d,\n", p->x);
        printk(KERN_EMERG "y:%d,\n", p->y);   
        
        printk(KERN_EMERG "work handler function KKKKKKKKK.\n");
    
        printk(KERN_EMERG "work handler function LLLLLLLLL.\n");
    }
    
    static int __init test_init(void)
    {
        //struct work_struct work;
        static mydat_t work;
     
        work.x = 123;
        work.y = 456;
        //printk(KERN_EMERG "&work:%p,\n", &work);
    
        /* 把work放置内核共享工作队列中*/
        INIT_WORK(&work.mywork, work_handler);
    
        /* 调用工作 */
        schedule_work(&work.mywork);
    
        return 0;
    err:
        return -1;
    }
    
    
    static void __exit test_exit(void)
    {
        //destroy_workqueue(queue);
        printk("%s is call\r\n", __FUNCTION__);
    }
    
    MODULE_LICENSE("GPL");
    module_init(test_init);
    module_exit(test_exit);

     

    展开全文
  • Linux内核的等待队列[转]

    千次阅读 2011-02-24 13:26:00
    Linux内核的等待队列[转] Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在Linux2.4.21中,等待队列在源代码树include/linux/wait.h中,这是一...
    Linux内核的等待队列[转]
     
     
      1. 问题:进程是如何组织起来的?
        我们知道,进程是有很多种状态的:include/linux/sched.h
        #define TASK_RUNNING        0
        #define TASK_INTERRUPTIBLE    1
        #define TASK_UNINTERRUPTIBLE    2
        #define __TASK_STOPPED        4
        #define __TASK_TRACED        8
        /* in tsk->exit_state */
        #define EXIT_ZOMBIE        16
        #define EXIT_DEAD        32
        等等。
        那么,对于不同状态的进程,内核是如何来管理的呢?
      2. 就绪队列:状态为TASK_RUNNING的进程组成的列表;
      • 处于TASK_STOPPED、EXIT_ZOMBIE或者EXIT_DEAD状态的进程是不需要连接进特定链表的。因为对于这些状态的进程而言,父进程只会通过 PID或者子进程链表来进行访问。
      • 而处于TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE状态的进程分为很多种类型,其每个进程对应一种特定事件。在这种情况下,进程的状态信息是不能提供足够的信息去快速的检索所需进程,因此有必要介绍一些其他的链表组织结构。比如等待队列。
    1. 等待队列:
      在内核里面,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。我们主要讨论其在进程同步中的应用。
      有时候,一个进程可能要等待一些事件的发生,如磁盘操作结束、一些系统资源的释放等等。个人理理解:等待队列就是暂时存放等待某些事件发生的进程的集合。如果一个进程要等待一个事件发生,那么该进程便将自身放入相应的等待队列中进入睡眠,而放弃控制权,直到等待事件发生后才会被内核唤醒。
    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内核的 等待队列 使用方法,wait_queue_head_t,进程休眠 收藏
     当你在用户空间需要读写一大片数据的时候,这个就用上了。

    以下来自:http://www.yuanma.org/data/2006/1207/article_1916.htm

    假设我们在 kernel 里产生一个 buffer,user 可以经由 read,write 等 system call 来读取或写资料到这个 buffer 里。如果有一个 user 写资料到 buffer 时,此时 buffer 已经满了。那请问你要如何去处理这种情形呢 ? 第一种,传给 user 一个错误讯息,说 buffer 已经满了,不能再写入。第二种,将 user 的要求 block 住, 等有人将 buffer 内容读走,留出空位时,再让 user 写入资料。但问题来了,你要怎么将 user 的要求 block 住。难道你要用
     while ( is_full );
    write_to_buffer;
    这样的程序代码吗? 想想看,如果你这样做会发生什么事? 第一,kernel会一直在这个 while 里执行。第二个,如果 kernel 一直在这个 while 里执行,表示它没有办法去 maintain系统的运作。那此时系统就相当于当掉了。在这里 is_full 是一个变量,当然,你可以让 is_full 是一个 function,在这个 function里会去做别的事让 kernel 可以运作,那系统就不会当。这是一个方式。还有,你说可以在while里面把buffer里的内容读走,再把is_full的值改了,但是我们会可能把重要的数据在我们不想被读的时候被读走了,那是比较麻烦的,而且很不灵活.如果我们使用 wait_queue 的话,那程序看起来会比较漂亮,而且也比较让人了解,如下所示:
     
    struct wait_queue_head_t wq; /* global variable */
    DECLARE_WAIT_QUEUE_HEAD (wq);
     
    while ( is_full ){
    interruptible_sleep_on( &wq );
    } write_to_buffer();
     
    interruptible_sleep_on( &wq ) 是用来将目前的 process,也就是要求写资料到buffer 的 process放到 wq 这个 wait_queue 里。在 interruptible_sleep_on 里,则是最后会呼叫 schedule() 来做 schedule 的动作,谁调用了schedule谁就趴下,让别人去运行,醒来就原地起来,执行schedule()后的代码。那那个调用了schedule的家伙什么醒过来呢?这时候就需要用到另一个函数了wake_up_interruptible()了。

    以下来自:http://tauruspdj.blog.163.com/blog/static/4312500620090794030998/

    linux中最简单的休眠方式是下面的宏,
    wait_event(queue, condition)  /* 进程将被置于非中断休眠(uninterruptible sleep)*/
    wait_event_interruptible(queue, condition) /*进程可被信号中断休眠,返回非0值表示休眠被信号中断*/
    wait_event_timeout(queue, condition, timeout)    /*等待限定时间jiffy,condition满足其一返回0*/
    wait_event_interruptible_timeout(queue, condition, timeout)
    queue是等待队列头,传值方式
    condition是任意一个布尔表达式,在休眠前后多次对condition求值,为真则唤醒

    唤醒进程的基本函数是wake_up
    void wake_up(wait_queue_head_t *queue);     /*唤醒等待在给定queue上的所有进程*/
    void wake_up_interruptible(wait_queue_head_t *queue);

    实践中,一般是wait_event和 wake_up, wait_event_interruptible和 wake_up_interruptible 成对使用。

    【补充】其实看了那么多,他们也没有给个立即可用的步骤,写blog嘛,就是分享心得。我基于2.6.24总结一下,希望对大家有帮助:

    1、定义:wait_queue_head_t my_queue;

    2、初始化 init_waitqueue_head(&my_queue);

    3、在一个函数里面等待:wait_event(queue, condition) ;(别在中断里面搞)


    4、在另一个函数里面唤醒:wake_up(wait_queue_head_t *queue); (这个可以在中断调用,去唤醒别的进程,特别是dma操作类的)


    有好几个等待和唤醒函数,大家可以慢慢试。

     

    等待队列分析
    等待队列操作分析:
         linux驱动程序中可以用等待队列(wiat queue)来实现阻塞的唤醒
    1)定义等待队列头
    等待队列头结构体的定义:
    struct __wait_queue_head {
    	spinlock_t  lock;          //自旋锁变量,用于在对等待队列头
    //指向的等待队列链表进行操作时锁上,查看哪有对
    lock
    的操作?
    struct list_head task_list; // 指向等待队列的list_head }; typedef struct __wait_queue_head wait_queue_head_t; #define DECLARE_WAIT_QUEUE_HEAD(name) / //声明一个待队列头对象 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 } } lock赋为unlocked,将等待队列头指向的等待队列链表指向name,从而将等待队列头和
    等待队列连起来
    ; 2)等待队列中存放的是在执行设备操作时不能获得资源而挂起的进程 定义等待对列: struct __wait_queue { unsigned int flags; //prepare_to_wait()里有对flags的操作,查看以得出其含义 #define WQ_FLAG_EXCLUSIVE 0x01 //一个常数,在prepare_to_wait()用于修改flags的值
    wait_queue_func_t func; //唤醒阻塞任务的函数 struct list_head task_list; // 阻塞任务链表 }; typedef struct __wait_queue wait_queue_t; #define DECLARE_WAITQUEUE(name, tsk) ///声明一个等待队列并初始化为name wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk) #define __WAITQUEUE_INITIALIZER(name, tsk) { / //等待对列初始化: .private = tsk, / .func = default_wake_function, / .task_list = { NULL, NULL } } 下列两个函数用于对特定的成员进行赋值(当传入不同类型的参数时); static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p) { q->flags = 0; q->private = p; //私有数据指针 q->func = default_wake_function; //使用默认的唤醒函数 } static inline void init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_t func) { q->flags = 0; q->private = NULL; q->func = func; // 自定义的唤醒函数 } 3)对等待队列进行操作 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) /
    { list_add(&new->task_list, &head->task_list); } /增加一个等待队列new到等待
    队列头head指向的等待队列链表中;
    static inline void __add_wait_queue_tail(wait_queue_head_t *head, // wait_queue_t *new) { list_add_tail(&new->task_list, &head->task_list); } 增加一个等待队列到表尾 static inline void __remove_wait_queue /
    (wait_queue_head_t *head,wait_queue_t *old)
    { list_del(&old->task_list); } 删除一个等待队列4)等待事件当等待队列加入到链表中以后,就要等待特定的condition来 唤醒它; #define __wait_event(wq, condition) ///wq:在等待事件的等待队列;condition:等待的条件 do { / DEFINE_WAIT(__wait); /定义并初始化一个wait_queue_t结构 / for (;;) { / prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); / if (condition) / //wait_queue:wq要等的condition是否满足 break; / schedule(); ///condition不成立,放弃cpu重新调度一个task } / finish_wait(&wq, &__wait); / } while (0) 等待condition在成立,否则进程睡眠(TASK_UNINTERRUPTIBLE);condition满足
    后等待结束,跳出循环(后面调用
    wake_up(x)
    进行唤醒)当任何能改变等待条件
    的值的变量发生改变时,要调用
    wake_up()


    wait_event(wq, condition) 			
    __wait_event()的基础上多了一次查询(每次被唤醒的时候)		
    

    #define __wait_event_timeout(wq, condition, ret) /

    condition满足或ret使用完了时进程被唤醒;返回值为:return timeout < 0 ? 0 : timeout

    timeout是一个jiffies类型的变量,当时间用完了,函数返回0,当等待的条件成立了,
    timeout
    还未用完,则将最后的jiffies保留下来。类似的操作还有:
     #define __wait_event_interruptible_timeout(wq, condition, ret)
    /
    可中断,有超时时间的__wait_event(),当timeout长的时间完了后,函数返回0;当时间未完,
    函数被信号中断则返回
    -ERESTARTSYS
    ;如果timeout isn't out,保留jiffies最后的值;
    #define wait_event_interruptible_timeout(wq, condition, timeout) /

    多了一次查询


    #define __wait_event_interruptible_exclusive(wq, condition, ret)	/
    
    #define wait_event_interruptible_exclusive(wq, condition)		/
    
    这几个函数都有用到prepare_to_wait()下面分析一下这个函数;
    prepare_to_wait() 函数

    void fastcall

    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); //获得自旋锁并保存EFLAGS的值

    if (list_empty(&wait->task_list)) //判断是等待队列是否为空:空时返回1;函数为:

    //static inline int list_empty(const struct list_head *head)

    __add_wait_queue(q, wait); //{ return head->next == head;}

    /* 插入等待队列中(为何空时插入,非空时不行?

    * don't alter the task state if this is just going to

    * queue an async wait queue callback

    */

    if (is_sync_wait(wait))

    set_current_state(state); //因为非阻塞进程访问不到设备时并不挂起,所以不改状态

    //set_current_state()->set_mb()->mb() :强制顺序执行(更改状态)

    //函数mb()内存栅头文件中定义的

    spin_unlock_irqrestore(&q->lock, flags);//解锁将EFLAGS的值读回

    }

    prepare_to_wait()的作用是将等待队列插入等待队列链表中,并更改等待队列的状态为state





    inux将进程状态描述为如下五种:

    TASK_RUNNING:可运行状态。处于该状态的进程可以被调度执行而成为当前进程。

    TASK_INTERRUPTIBLE:可中断的睡眠状态。处于该状态的进程在所需资源有效时被唤醒,也可以通过信号或定时中断唤醒。

    TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。处于该状态的进程仅当所需资源有效时被唤醒。

    TASK_ZOMBIE:僵尸状态。表示进程结束且已释放资源,但其task_struct仍未释放。

    TASK_STOPPED:暂停状态。处于该状态的进程通过其他进程的信号才能被唤醒。


    唤醒:

    #define wake_up(x) __wake_up(x, TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1, NULL)


    void fastcall __wake_up(wait_queue_head_t *q, unsigned int mode,

    int nr_exclusive, void *key)

    {

    unsigned long flags;


    spin_lock_irqsave(&q->lock, flags);

    __wake_up_common(q, mode, nr_exclusive, 0, key);

    spin_unlock_irqrestore(&q->lock, flags);

    }

    Wake_up()唤醒等待队列中的进程,其参数含义:

    q:等待队列;

    mode:要唤醒的进程

    nr_exclusive:要唤醒的进程数;

    keyis directly passed to the wakeup function


     

    展开全文
  • 【版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途】... 上一节内核时间管理中讲到内核在始终中断发生执行定时器,定时器作为软中断在下半部上下文中执行。时钟中断
    【版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用
            前面章节说到了把工作推后到除现在以外的时间执行的机制是下半部机制,但是当你需要将工作推后到某个确定的时间段之后执行,使用定时器是很好的选择。
            上一节内核时间管理中讲到内核在始终中断发生执行定时器,定时器作为软中断在下半部上下文中执行。时钟中断处理程序会执行update_process_times函数,在该函数中运行run_local_timers()函数来标记一个软中断去处理所有到期的定时器。如下:
    void update_process_times(int user_tick)
    {
        struct task_struct *p = current;
        int cpu = smp_processor_id();
        /* Note: this timer irq context must be accounted for as well. */
        account_process_tick(p, user_tick);
        run_local_timers();
        rcu_check_callbacks(cpu, user_tick);
        printk_tick();
        scheduler_tick();
        run_posix_cpu_timers(p);
    }
    void run_local_timers(void)
    {
        hrtimer_run_queues();
        raise_softirq(TIMER_SOFTIRQ);
        softlockup_tick();
    }
            在分析定时器的实现之前我们先来看一看使用内核定时器的一个实例,具体使用可查看这篇文章: http://blog.csdn.net/shallnet/article/details/17734571 ,示例如下:
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/version.h>
    #include <linux/timer.h>
    #include <linux/delay.h>
    struct timer_list   sln_timer;
    void sln_timer_do(unsigned long l)
    {
        mod_timer(&sln_timer, jiffies + HZ);
        printk(KERN_ALERT"param: %ld, jiffies: %ld\n", l, jiffies);
    }
    void sln_timer_set(void)
    {
        init_timer(&sln_timer);
        sln_timer.expires = jiffies + HZ;   //1s
        sln_timer.function = sln_timer_do;
        sln_timer.data = 9527;
        add_timer(&sln_timer);
    }
    static int __init sln_init(void)
    {
        printk(KERN_ALERT"===%s===\n", __func__);
        sln_timer_set();
        return 0;
    }
    static void __exit sln_exit(void)
    {
        printk(KERN_ALERT"===%s===\n", __func__);
        del_timer(&sln_timer);
    }
    module_init(sln_init);
    module_exit(sln_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("allen");
    该示例作用是每秒钟打印出当前系统jiffies的值。
            内核定时器由结构timer_list表示,定义在文件<include/linux/timer.h>中。
    struct timer_list {
        struct list_head entry;
        unsigned long expires;
        void (*function)(unsigned long);
        unsigned long data;
        struct tvec_base *base;
    #ifdef CONFIG_TIMER_STATS
        void *start_site;
        char start_comm[16];
        int start_pid;
    #endif
    #ifdef CONFIG_LOCKDEP
        struct lockdep_map lockdep_map;
    #endif
    };
    如示例,内核提供部分操作接口来简化管理定时器,
    第一步、定义一个定时器:
    struct timer_list   sln_timer;
    第二步、初始化定时器数据结构的内部值。
    init_timer(&sln_timer);//初始化定时器

    #define init_timer(timer)\
        init_timer_key((timer), NULL, NULL)
    void init_timer_key(struct timer_list *timer,
                const char *name,
                struct lock_class_key *key)
    {
        debug_init(timer);
        __init_timer(timer, name, key);
    }
    static void __init_timer(struct timer_list *timer,
                 const char *name,
                 struct lock_class_key *key)
    {
        timer->entry.next = NULL;
        timer->base = __raw_get_cpu_var(tvec_bases);
    #ifdef CONFIG_TIMER_STATS
        timer->start_site = NULL;
        timer->start_pid = -1;
        memset(timer->start_comm, 0, TASK_COMM_LEN);
    #endif
        lockdep_init_map(&timer->lockdep_map, name, key, 0);
    }

    第三步、填充timer_list结构中需要的值:
    sln_timer.expires = jiffies + HZ;   //1s后执行  
    sln_timer.function = sln_timer_do;    //执行函数
    sln_timer.data = 9527;

    sln_timer.expires表示超时时间,它以节拍为单位的绝对计数值。如果当前jiffies计数等于或大于sln_timer.expires的值,那么sln_timer.function所指向的处理函数sln_timer_do就会执行,并且该函数还要使用长整型参数sln_timer.dat。
    void sln_timer_do(unsigned long l);
    第四步、激活定时器:
    add_timer(&sln_timer);    //向内核注册定时器
    这样定时器就可以运行了。
    add_timer()的实现如下:
    void add_timer(struct timer_list *timer)
    {
        BUG_ON(timer_pending(timer));
        mod_timer(timer, timer->expires);
    }
    add_timer()调用了mod_timer()。mod_timer()用于修改定时器超时时间。
    mod_timer(&sln_timer, jiffies + HZ);
    由于add_timer()是通过调用mod_timer()来激活定时器,所以也可以直接使用mod_timer()来激活定时器,如果定时器已经初始化但没有激活,mod_timer()也会激活它。
            如果需要在定时器超时前停止定时器,使用del_timer()函数来完成。
    del_timer(&sln_timer);
    该函数实现如下:
    int del_timer(struct timer_list *timer)
    {
        struct tvec_base *base;
        unsigned long flags;
        int ret = 0;
        timer_stats_timer_clear_start_info(timer);
        if (timer_pending(timer)) {
            base = lock_timer_base(timer, &flags);
            if (timer_pending(timer)) {
                detach_timer(timer, 1);
                if (timer->expires == base->next_timer &&
                    !tbase_get_deferrable(timer->base))
                    base->next_timer = base->timer_jiffies;
                ret = 1;
            }
            spin_unlock_irqrestore(&base->lock, flags);
        }
        return ret;
    }
    static inline void detach_timer(struct timer_list *timer,
                    int clear_pending)
    {
        struct list_head *entry = &timer->entry;
        debug_deactivate(timer);
        __list_del(entry->prev, entry->next);
        if (clear_pending)
            entry->next = NULL;
        entry->prev = LIST_POISON2;
    }
            当使用del_timer()返回后,定时器就不会再被激活,但在多处理器机器上定时器上定时器中断可能已经在其他处理器上运行了,所以删除定时器时需要等待可能在其他处理器上运行的定时器处理I程序都退出,这时就要使用del_timer_sync()函数执行删除工作:
    del_timer_sync(&sln_timer);
    该函数不能再中断上下文中使用。
    该函数详细实现如下:
    int del_timer_sync(struct timer_list *timer)
    {
    #ifdef CONFIG_LOCKDEP
        unsigned long flags;
        local_irq_save(flags);
        lock_map_acquire(&timer->lockdep_map);
        lock_map_release(&timer->lockdep_map);
        local_irq_restore(flags);
    #endif
        for (;;) {    //一直循环,直到删除timer成功再退出
            int ret = try_to_del_timer_sync(timer);
            if (ret >= 0)
                return ret;
            cpu_relax();
        }
    }
    int try_to_del_timer_sync(struct timer_list *timer)
    {
        struct tvec_base *base;
        unsigned long flags;
        int ret = -1;
        base = lock_timer_base(timer, &flags);
        if (base->running_timer == timer)
            goto out;
        ret = 0;
        if (timer_pending(timer)) {
            detach_timer(timer, 1);
            if (timer->expires == base->next_timer &&
                !tbase_get_deferrable(timer->base))
                base->next_timer = base->timer_jiffies;
            ret = 1;
        }
    out:
        spin_unlock_irqrestore(&base->lock, flags);
        return ret;
    }
            一般情况下应该使用del_timer_sync()函数代替del_timer()函数,因为无法确定在删除定时器时,他是否在其他处理器上运行。为了防止这种情况的发生,应该调用del_timer_sync()函数而不是del_timer()函数。否则,对定时器执行删除操作后,代码会继续执行,但它有可能会去操作在其它处理器上运行的定时器正在使用的资源,因而造成并发访问,所有优先使用删除定时器的同步方法。
            除了使用定时器来推迟任务到指定时间段运行之外,还有其他的方法处理延时请求。有的方法会在延迟任务时挂起处理器,有的却不会。实际上也没有方法能够保证实际的延迟时间刚好等于指定的延迟时间。
            1. 最简单的 延迟方法是忙等待,该方法实现起来很简单,只需要在循环中不断旋转直到希望的时钟节拍数耗尽。比如:
    unsigned long delay = jiffies+10;   //延迟10个节拍
    while(time_before(jiffies,delay))
            ;
            这种方法当代码等待时,处理器只能在原地旋转等待,它不会去处理其他任何任务。最好在任务等待时,允许内核重新调度其它任务执行。将上面代码修改如下:
    unsigned long delay = jiffies+10;   //10个节拍
    while(time_before(jiffies,delay))
        cond_resched();
    看一下cond_resched()函数具体实现代码:
    #define cond_resched() ({           \
        __might_sleep(__FILE__, __LINE__, 0);   \
        _cond_resched();            \
    })
    
    int __sched _cond_resched(void)
    {
        if (should_resched()) {
            __cond_resched();
            return 1;
        }
        return 0;
    }
    
    static void __cond_resched(void)                                                                 
    {
        add_preempt_count(PREEMPT_ACTIVE);
        schedule(); //最终还是调用schedule()函数来重新调度其它程序运行
        sub_preempt_count(PREEMPT_ACTIVE);
    }
    函数cond_resched()将重新调度一个新程序投入运行,但它只有在设置完need_resched标志后才能生效。换句话说,就是系统中存在更重要的任务需要运行。再由于该方法需要调用调度程序,所以它不能在中断上下文中使用----只能在进程上下文中使用。事实上,所有延迟方法在进程上下文中使用,因为中断处理程序都应该尽可能快的执行。另外,延迟执行不管在哪种情况下都不应该在持有锁时或者禁止中断时发生。
            2. 有时内核需要更短的延迟,甚至比节拍间隔还要短。这时可以使用内核提供的ms、ns、us级别的延迟函数。
    void udelay(unsigned long usecs);    //arch/x86/include/asm/delay.h
    void ndelay(unsigned long nsecs);    //arch/x86/include/asm/delay.h
    void mdelay(unsigned long msecs);    
    udelay()使用忙循环将任务延迟指定的ms后执行,其依靠执行数次循环达到延迟效果,mdelay()函数是通过udelay()函数实现,如下:
    #define mdelay(n) (\ 
        (__builtin_constant_p(n) && (n)<=MAX_UDELAY_MS) ? udelay((n)*1000) : \
        ({unsigned long __ms=(n); while (__ms--) udelay(1000);}))
    #endif
            udelay()函数仅能在要求的延迟时间很短的情况下执行,而在高速机器中时间很长的延迟会造成溢出。对于较长的延迟,mdelay()工作良好。
            3. schedule_timeout()函数是更理想的延迟执行方法。该方法会让需要延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行。但该方法也不能保证睡眠时间正好等于指定的延迟时间,只能尽量是睡眠时间接近指定的延迟时间。当指定的时间到期后,内核唤醒被延迟的任务并将其重新放回运行队列。用法如下:
        set_current_state(TASK_INTERRUPTIBLE);    //将任务设置为可中断睡眠状态
        schedule_timeout(s*HZ);    //小睡一会儿,“s”秒后唤醒

            唯一的参数是延迟的相对时间,单位是jiffies,上例中将相应的任务推入可中断睡眠队列,睡眠s秒。在调用函数schedule_timeout之前,不要要将任务设置成可中断或不和中断的一种,否则任务不会休眠。这个函数需要调用调度程序,所以调用它的代码必须保证能够睡眠,简而言之,调用代码必须处于进程上下文中,并且不能持有锁。
            事实上schedule_timeout()函数的实现就是内核定时器的一个简单应用。
    signed long __sched schedule_timeout(signed long timeout)
    {
        struct timer_list timer;
        unsigned long expire;
    
        switch (timeout)
        {
        case MAX_SCHEDULE_TIMEOUT:
            /*
             * These two special cases are useful to be comfortable
             * in the caller. Nothing more. We could take
             * MAX_SCHEDULE_TIMEOUT from one of the negative value
             * but I' d like to return a valid offset (>=0) to allow
             * the caller to do everything it want with the retval.
             */
            schedule();
            goto out;
        default:
            /*
             * Another bit of PARANOID. Note that the retval will be
             * 0 since no piece of kernel is supposed to do a check
             * for a negative retval of schedule_timeout() (since it
             * should never happens anyway). You just have the printk()
             * that will tell you if something is gone wrong and where.
             */
            if (timeout < 0) {
                printk(KERN_ERR "schedule_timeout: wrong timeout "
                    "value %lx\n", timeout);
                dump_stack();
                current->state = TASK_RUNNING;
                goto out;
            }
        }
    
        expire = timeout + jiffies;
    
    //下一行代码设置了超时执行函数process_timeout()。
        setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
        __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);    //激活定时器
        schedule();    //调度其他新任务
        del_singleshot_timer_sync(&timer);
    
        /* Remove the timer from the object tracker */
        destroy_timer_on_stack(&timer);
    
        timeout = expire - jiffies;
    
     out:
        return timeout < 0 ? 0 : timeout;
    }
    当定时器超时时,process_timeout()函数被调用:
    static void process_timeout(unsigned long __data) 
    {
        wake_up_process((struct task_struct *)__data);
    }

            当任务被重新调度时,将返回代码进入睡眠前的位置继续执行,位置正好在schedule()处。
            进程上下文的代码为了等待特定时间发生,可以将自己放入等待队列。但是,等待队列上的某个任务可能既在等待一个特定事件到来,又在等待一个特定时间到期,就看谁来得更快。这种情况下,代码可以简单的使用scedule_timeout()函数代替schedule()函数,这样一来,当希望指定时间到期后,任务都会被唤醒,当然,代码需要检查被唤醒的原因,有可能是被事件唤醒,也有可能是因为延迟的时间到期,还可能是因为接收到了信号,然后执行相应的操作。
    本文源码下载:

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

    千次阅读 2014-05-23 10:30:38
    inux内核中等待队列 (函数wait_event与wake_up) 分类: linux内核技术2012-01-05 00:03 7905人阅读 评论(6) 收藏 举报 wait_eventwake_up等待队列  根据内核3.1.6版本源码、书籍和网上资料,对...

    inux内核中等待队列 (函数wait_event与wake_up)


     

    等待队列(一)

    分类: C语言 驱动程序 内核 数据结构   87人阅读  评论(0)  收藏  举报

    在Linux内核中等待队列有很多用途,可用于中断处理、进程同步及定时。我们在这里只说,进程经常必须等待某些事件的发生。等待队列实现了在事件上的条件等待: 希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制全。因此,等待队列表示一组睡眠的进程,当某一条件为真时,由内核唤醒它们。

    等待队列由循环链表实现,其元素包括指向进程描述符的指针。每个等待队列都有一个等待队列头(wait queue head),等待队列头是一个类型为wait_queue_head_t的数据结构

    (1)定义等待队列头(相关内容可以在linux/include/wait.h中找到)

    等待队列头结构体的定义:

    struct __wait_queue_head {

      spinlock_t  lock;          //自旋锁变量,用于在对等待队列头          

      struct list_head task_list;  // 指向等待队列的list_head

    }; 

    typedef struct __wait_queue_head  wait_queue_head_t;

    使用等待队列时首先需要定义一个wait_queue_head,这可以通过DECLARE_WAIT_QUEUE_HEAD宏来完成,这是静态定义的方法。该宏会定义一个wait_queue_head,并且初始化结构中的锁以及等待队列。当然,动态初始化的方法也很简单,初始化一下锁及队列就可以了。

    #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 } }

    将lock赋为unlocked, 将等待队列头指向的等待队列链表指向name,从而将等待队列头和等待队列连起来;

     一般在写程序的时候将DECLARE_WAIT_QUEUE_HEAD分成两步来完成:

    声明:

    wait_queue_head_t  wait_que;

    初始化:

    init_waitqueue_head( &wait_que);

     

         Linux中等待队列的实现思想如下图所示,当一个任务需要在某个wait_queue_head上睡眠时,将自己的进程控制块信息封装到wait_queue中,然后挂载到wait_queue的链表中,执行调度睡眠。当某些事件发生后,另一个任务(进程)会唤醒wait_queue_head上的某个或者所有任务,唤醒工作也就是将等待队列中的任务设置为可调度的状态,并且从队列中删除。

     

    (2)等待队列中存放的是在执行设备操作时不能获得资源而挂起的进程

    定义等待对列:

    struct __wait_queue {

         unsigned int flags;  //prepare_to_wait()里有对flags的操作,查看以得出其含义

              #define WQ_FLAG_EXCLUSIVE        0x01 //一个常数,在prepare_to_wait()用于修改flags的值

                                                                                                 

              void * private          //通常指向当前任务控制块

              wait_queue_func_t func;    //唤醒阻塞任务的函数 ,决定了唤醒的方式

         struct list_head task_list;    // 阻塞任务链表

    };

    typedef struct __wait_queue          wait_queue_t;

     //声明一个等待队列并初始化为name

    #define DECLARE_WAITQUEUE(name, tsk)        \

    wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

     

    #define __WAITQUEUE_INITIALIZER(name, tsk) {        \ 

    .private        = tsk,                                                \                       

    .func                = default_wake_function,                        \      

    .task_list        = { NULL, NULL } }

     

    //下列两个函数用于对特定的成员进行赋值(当传入不同类型的参数时);

    static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)

    {

      q->flags = 0;

      q->private = p;          //私有数据指针

      q->func = default_wake_function;  //使用默认的唤醒函数

    }

     

    static inline void init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_t func)

    {

      q->flags = 0;

      q->private = NULL;

      q->func = func;       // 自定义的唤醒函数

    }

     

    (3)对等待队列进行操作

      

    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)  /

    {

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

    }

    //增加一个等待队列new到等待队列头head指向的等待队列链表中;

     

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

    {

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

    }

    增加一个等待队列到表尾

     

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

    {

      list_del(&old->task_list);

    }

     

     

     //wq: 在等待事件的等待队列,condition: 等待的条件

    #define __wait_event(wq, condition) \                                                                                 

    do {                                                                        \

    DEFINE_WAIT(__wait);                \  //定义并初始化一个wait_queue_t结构        

    for (;;) {                                                        \

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

      if (condition)        \  //看wait_queue:wq要等的condition是否满足

        break;                                                \

      schedule();        \     //condition不成立,放弃cpu重新调度一个task

    }                                                                \

    finish_wait(&wq, &__wait);                                        \

    } while (0)

     上面程序的执行过程:

    1.用当前的进程描述块(PCB)初始化一个wait_queue描述的等待任务。

    2.在等待队列锁资源的保护下,将等待任务加入等待队列。

    3.判断等待条件是否满足,如果满足,那么将等待任务从队列中移出,退出函数。

    4.如果条件不满足,那么任务调度,将CPU资源交与其它任务。

    5.当睡眠任务被唤醒之后,需要重复(2)、(3)步骤,如果确认条件满足,退出等待事件函数。

     我在一个程序中因为使用使用wait_event_interruptible()遇到了很大的麻烦,原因就是不知道condition在函数中具体起个什么作用,通过分析源码终于搞清楚。(后面会有专门的文章来介绍那个问题。)

     

    等待队列编程接口:

    wait_event(wq, condition)

    这是一个宏,让当前任务处于等待事件状态。输入参数如下:

    @wq:等待队列

    @conditions:等待条件

     

    wait_event_timeout(wq, condition, timeout)

    功能与wait_event类似,多了一个超时机制。参数中多了一项超时时间。

     

    wait_event_interruptible(wq, condition)

    这是一个宏,与前两个宏相比,该宏定义的等待能够被消息唤醒。如果被消息唤醒,那么返回- ERESTARTSYS。输入参数如下:

    @wq:等待队列

    @condition:等待条件

    @rt:返回值

     

    wait_event_interruptible_timeout(wq, condition, timeout)

    与上一个相比,多了超时机制

     

    wake_up(x)

    唤醒等待队列中的一个任务

     

    wake_up_interruptible(x)

    用于唤醒wake_event_interruptible()睡眠的进程

     

    wake_up_all(x)

    唤醒等待队列中的所有任务

     

    Linux将进程状态描述为如下五种:

    TASK_RUNNING:可运行状态。处于该状态的进程可以被调度执行而成为当前进程。

    TASK_INTERRUPTIBLE:可中断的睡眠状态。处于该状态的进程在所需资源有效时被唤醒,也可以通过信号定时中断唤醒(因为有signal_pending()函数)。

    TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。处于该状态的进程仅当所需资源有效时被唤醒。

    TASK_ZOMBIE:僵尸状态。表示进程结束且已释放资源,但其task_struct仍未释放。

    TASK_STOPPED:暂停状态。处于该状态的进程通过其他进程的信号才能被唤醒。

    通过 前一篇文章 的介绍我们队等待队列有了一个比较具体的认识,本文将来分析一下等待队列是如何睡眠一个进程和如何唤醒一个进程的。

    使用等待队列前通常先定义一个等待队列头:static wait_queue_head_t wq ,然后调用wait_event_*函数将等待某条件condition的当前进程插入到等待队列wq中并睡眠,一直等到condition条件满足后,内核再将睡眠在等待队列wq上的某一进程或所有进程唤醒。

    这里我们来分析一下唤醒的过程,举比较常用的wait_event_interruptible来分析:

    复制代码
    /**
    
     * wait_event_interruptible - sleep until a condition gets true
    
     * @wq: the waitqueue to wait on
    
     * @condition: a C expression for the event to wait for
    
     *
    
     * The process is put to sleep (TASK_INTERRUPTIBLE) until the
    
     * @condition evaluates to true or a signal is received.
    
     * The @condition is checked each time the waitqueue @wq is woken up.
    
     *
    
     * wake_up() has to be called after changing any variable that could
    
     * change the result of the wait condition.
    
     *
    
     * The function will return -ERESTARTSYS if it was interrupted by a
    
     * signal and 0 if @condition evaluated to true.
    
     */
    
    #define wait_event_interruptible(wq, condition)                            \
    
    ({                                                             \
    
           int __ret = 0;                                                \
    
           if (!(condition))                                     \
    
                  __wait_event_interruptible(wq, condition, __ret);     \
    
           __ret;                                                     \
    
    })
    复制代码

     

    这里很简单,判断一下condition条件是否满足,如果不满足则调用__wait_event_interruptible函数

    复制代码
    #define __wait_event_interruptible_timeout(wq, condition, ret)        \
    
    do {                                                               \
    
           DEFINE_WAIT(__wait);                                          \
    
                                                                   \
    
           for (;;) {                                            \
    
                  prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);  \
    
                  if (condition)                                         \
    
                         break;                                      \
    
                  if (!signal_pending(current)) {                         \
    
                         ret = schedule_timeout(ret);                     \
    
                         if (!ret)                               \
    
                                break;                               \
    
                         continue;                                \
    
                  }                                                \
    
                  ret = -ERESTARTSYS;                             \
    
                  break;                                             \
    
           }                                                       \
    
           finish_wait(&wq, &__wait);                             \
    
    } while (0) 
    复制代码

     

    __wait_event_interruptible首先定义了一个wait_queue_t类型的等待队列项__wait:

     

    复制代码
    #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),    \
    
           }
    复制代码

     

    可以发现,这里__wait的private成员(通常用来存放进程的描述符)已经被初始化为current, 表示该等待队列项对应为当前进程。func成员为该等待队列项对应的唤醒函数,该进程被唤醒后会执行它,已经被初始化为默认的autoremove_wake_function函数。

     

    然后在一个for (;;) 循环内调用prepare_to_wait函数:

    复制代码
    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);
    
      if (list_empty(&wait->task_list))
    
      __add_wait_queue(q, wait);
    
      set_current_state(state);
    
      spin_unlock_irqrestore(&q->lock, flags);
    
    }
    复制代码

     

     

    prepare_to_wait做如下两件事,将先前定义的等待队列项__wait插入到等待队列头wq,然后将当前进程设为TASK_INTERRUPTIBLE状态。prepare_to_wait执行完后立马再检查一下condition有没有满足,如果此时碰巧满足了则不必要在睡眠了。如果还没有满足,则准备睡眠。

     

    睡眠是通过调用schedule()函数实现的,由于之前已经将当前进程设置为TASK_INTERRUPTIBLE状态,因而这里再执行schedule()进行进程切换的话,之后就永远不会再调度到该进程运行的,直到该进程被唤醒(即更改为TASK_RUNNING状态)。

     

    这里在执行schedule()切换进程前会先判断一下有没signal过来,如果有则立即返回ERESTARTSYS。没有的话则执行schedule()睡眠去了。

    for (;;) 循环的作用是让进程被唤醒后再一次去检查一下condition是否满足。主要是为了防止等待队列上的多个进程被同时唤醒后有可能其他进程已经抢先把资源占有过去造成资源又变为不可用,因此最好再判断一下。(当然,内核也提供了仅唤醒一个或多个进程(独占等待进程)的方式,有兴趣的可以参考相关资料)

    进程被唤醒后最后一步是调用finish_wait(&wq, &__wait)函数进行清理工作。finish_wait将进程的状态再次设为TASK_RUNNING并从等待队列中删除该进程。

    复制代码
    void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
    
    {
    
      unsigned long flags;
    
      __set_current_state(TASK_RUNNING);
    
      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);
    
      }
    
    }
    复制代码

     

    再往后就是返回你先前调用wait_event_interruptible(wq, condition)被阻塞的地方继续往下执行。

     

    3. 等待队列的唤醒过程

     

    直到这里我们明白等待队列是如何睡眠的,下面我们分析等待队列的唤醒过程。

    使用等待队列有个前提,必须得有人唤醒它,如果没人唤醒它,那么同眠在该等待队列上的所有进程岂不是变成“僵尸进程”了。

    对于设备驱动来讲,通常是在中断处理函数内唤醒该设备的等待队列。驱动程序通常会提供一组自己的读写等待队列以实现上层(user level)所需的BLOCK和O_NONBLOCK操作。当设备资源可用时,如果驱动发现有进程睡眠在自己的读写等待队列上便会唤醒该等待队列。

    唤醒一个等待队列是通过wake_up_*函数实现的。这里我们举对应的wake_up_interruptible作为例子分析。定义如下:

    #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

    这里的参数x即要唤醒的等待队列对应的等待队列头。唤醒TASK_INTERRUPTIBLE类型的进程并且默认唤醒该队列上所有非独占等待进程和一个独占等待进程。

    __wake_up定义如下:

    复制代码
    /**
    
    * __wake_up - wake up threads blocked on a waitqueue.
    
    * @q: the waitqueue
    
    * @mode: which threads
    
    * @nr_exclusive: how many wake-one or wake-many threads to wake up
    
    * @key: is directly passed to the wakeup function
    
    */
    
    void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key)
    
    {
    
           unsigned long flags;
    
     
    
           spin_lock_irqsave(&q->lock, flags);
    
           __wake_up_common(q, mode, nr_exclusive, 0, key);
    
           spin_unlock_irqrestore(&q->lock, flags);
    
    }
    
    __wake_up 简单的调用__wake_up_common进行实际唤醒工作。
    
    __wake_up_common定义如下:
    
    /*
    
     * 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 continuing to scan the queue.
    
     */
    
    static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
    
                         int nr_exclusive, int wake_flags, void *key)
    
    {
    
           wait_queue_t *curr, *next;
    
     
    
           list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
    
                  unsigned flags = curr->flags;
    
     
    
                  if (curr->func(curr, mode, wake_flags, key) &&
    
                                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
    
                         break;
    
           }
    
    }
    复制代码

     

    __wake_up_common循环遍历等待队列内的所有元素,分别执行其对应的唤醒函数。这里的唤醒函数即先前定义等待队列项DEFINE_WAIT(__wait)时默认初始化的autoremove_wake_function函数。autoremove_wake_function最终会调用try_to_wake_up函数将进程置为TASK_RUNNING状态。这样后面的进程调度便会调度到该进程,从而唤醒该进程继续执行。


    分类: linux内核技术   7905人阅读  评论(6)  收藏  举报

            根据内核3.1.6版本源码、书籍和网上资料,对几个函数进行分析

           介绍这几个函数,不得不先介绍等待队列wait_queue_head_t与完成量completion。

           等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程在等待周期中睡眠,当时间发生后由内核自动唤醒。

           完成量机制是基于等待队列的,内核利用该机制等待某一操作的结束。这两种经常被使用。

    一、等待队列

           (一)数据结构

           等待队列结构如下,因为每个等待队列都可以再中断时被修改,因此,在操作等待队列之前必须获得一个自旋锁。

    1. struct __wait_queue_head {  
    2.         spinlock_t lock;  
    3.         struct list_head task_list;  
    4. };  
    5. typedef struct__wait_queue_head wait_queue_head_t;  
           等待队列是通过task_list双链表来实现,其数据成员是以下数据结构:

    1. typedef struct__wait_queue wait_queue_t;  
    2. struct __wait_queue {  
    3.         unsigned int flags;            
    4. #defineWQ_FLAG_EXCLUSIVE      0x01  /* 表示等待进程想要被独占地唤醒  */  
    5.         void *private;               /* 指向等待进程的task_struct实例 */  
    6.         wait_queue_func_t func;      /* 用于唤醒等待进程              */  
    7.         struct list_head task_list;  /* 用于链表元素,将wait_queue_t链接到wait_queue_head_t */  
    8. };  
     其图如下:


           等待队列如何使用哪?分两步

           1. 为了使得等待进程在一个等待队列中睡眠,需要调用函数wait_event()函数。进程进入睡眠,将控制权释放给调度器。

           2. 在内核中另一处,调用wake_up()函数唤醒等待队列中的睡眠进程。

    注:使用wait_event()函数使得进程睡眠;而在内核另一处有一个对应的wake_up()函数被调用。

            (二)初始化等待队列元素

            有两种方法初始化队列:

            1. 动态初始化init_waitqueue_entry()

    1. static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)  
    2. {  
    3.     q->flags = 0;  
    4.     q->private = p;  
    5.     q->func = default_wake_function;  
    6. }  

            2. 静态初始化DEFINE_WAIT()

    1. #define DEFINE_WAIT_FUNC(name, function)                \  
    2.     wait_queue_t name = {                       \  
    3.         .private    = current,              \  
    4.         .func       = function,             \  
    5.         .task_list  = LIST_HEAD_INIT((name).task_list), \  
    6.     }  
    7.   
    8. #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)  

           其中函数autoremove_wake_function()是用来唤醒进程的,该函数不经调用default_wake_function(),还将所属等待队列成员从等待队列删除。   

           (三)进程睡眠

            1. 通过add_wait_queue()函数将一个进程添加到等待队列,首先获得自旋锁,然后调用__add_wait_queue()实现将新的等待进程添加等待队列(添加到等待队列的头部),然后解锁;代码如下:

    1. static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)  
    2. {  
    3.     list_add(&new->task_list, &head->task_list);  
    4. }  

            另一个函数add_wait_queue_exclusive()的含义与add_wait_queue()函数类似,但是将等待进程添加到等待队列的尾部,并设置WQ_EXCLUSIXE标志。

            使得进程在等待队列上睡眠的另一种方法是:prepare_to_wait(),除了有add_wait_queue()函数的参数外,还要设置进程的状态。

            另一个函数prepare_to_wait_exclusive()语义类似。        

            通常情况下,add_wait_queue()函数不会直接使用,而是调用wait_evnet()函数

    1. /** 
    2.  * wait_event - sleep until a condition gets true 
    3.  * @wq: the waitqueue to wait on 
    4.  * @condition: a C expression for the event to wait for 
    5.  * 
    6.  * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the 
    7.  * @condition evaluates to true. The @condition is checked each time 
    8.  * the waitqueue @wq is woken up. 
    9.  * 
    10.  * wake_up() has to be called after changing any variable that could 
    11.  * change the result of the wait condition. 
    12.  */  
    13. #define wait_event(wq, condition)                   \  
    14. do {                                    \  
    15.     if (condition)                          \  
    16.         break;                          \  
    17.     __wait_event(wq, condition);                    \  
    18. while (0)  
    函数__wait_event()

    1. #define __wait_event(wq, condition)                     \  
    2. do {                                    \  
    3.     DEFINE_WAIT(__wait);                        \  
    4.                                     \  
    5.     for (;;) {                          \  
    6.         prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \  
    7.         if (condition)                      \  
    8.             break;                      \  
    9.         schedule();                     \  
    10.     }                               \  
    11.     finish_wait(&wq, &__wait);                  \  
    12. while (0)  

             其中wq是等待进程需要加入的等待队列,而condition是通过与所等待时间有关的一个C表达式形式给出。表示,条件满足时,可以立即停止处理。主要工作由__wait_event()来完成。

           分析__wait_event()函数,

           (1) 调用DEFINE_WAIT宏建立等待队列成员;

           (2) 使用一个无线循环,在循环体内,

                    (a) 调用prepare_to_wait()使得进程在等待队列上等待;

                    (b) 当进程被唤醒时,检查指定的条件condition是否满足,如果满足则跳出循环,否则将控制权交给调度器,然后进程继续睡眠。

            (3) 调用函数finish_wait()将进程状态设置为TASK_RUNNING,并从等待队列的链表中移除对应的成员。

           其他与wait_event类似的函数:

           1. wait_event_timeout()函数 ,使得进程处于TASK_INTERRUPTIBLE状态,从而睡眠进程可以通过接收信号被唤醒;

           2. wait_event_timeout()函数,等待满足指定的条件,但是如果等待时间超过指定的超时限制则停止睡眠,可以防止进程永远睡眠;

           3. wait_event_interruptible_timeout() 使得进程睡眠,但是可以通过接收信号被唤醒,也具有超时限制。

           (四)进程睡眠

           内核中虽然定义了很多唤醒等待队列中进程的函数,但是最终调用的都是__wake_up()

    1. #define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)  
    2. #define wake_up_nr(x, nr)       __wake_up(x, TASK_NORMAL, nr, NULL)  
    3. #define wake_up_all(x)          __wake_up(x, TASK_NORMAL, 0, NULL)  
    4. #define wake_up_locked(x)       __wake_up_locked((x), TASK_NORMAL)  
    5.   
    6. #define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)  
    7. #define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)  
    8. #define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)  
    9. #define wake_up_interruptible_sync(x)   __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)  

           而__wake_up()函数在加锁之后调用的是__wake_up_common()

    [html]  view plain copy print ?
    1. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
    2.             int nr_exclusive, int wake_flags, void *key)  
    3. {  
    4.     wait_queue_t *curr, *next;  
    5.   
    6.     list_for_each_entry_safe(curr, next, &q->task_list, task_list) {  
    7.         unsigned flags = curr->flags;  
    8.   
    9.         if (curr->func(curr, mode, wake_flags, key) &&  
    10.                 (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)  
    11.             break;  
    12.     }  
    13. }  

            其中:q是等待队列,mode指定进程的状态,用于控制唤醒进程的条件,nr_exclusive表示将要唤醒的设置了WQ_FLAG_EXCLUSIVE标志的进程的数目。 

            然后扫描链表,调用func,直至没有更多的进程被唤醒,或者被唤醒的的独占进程数目已经达到规定数目。

    完成量,下一次在学习。

    参考资料:

          (1) http://blog.chinaunix.net/space.php?uid=20565550&do=blog&id=303575

          (2) 《深入linuc内核架构》

     

    完成量基础知识 ( completion )

    分类: linux内核技术   1874人阅读  评论(1)  收藏  举报

            完成量机制是基于等待队列的,内核使用该机制等待某一操作的完成。其有两个参与者:一是等待某操作完成;另一是在操作完成时发出声明。当然可以有“任意数目”个进程等待操作完成。

            完成量的数据描述如下:

    1. struct completion {  
    2.     unsigned int done;       /* 用于处理“在进程开始等待之前,事件或操作已经完成” */  
    3.     wait_queue_head_t wait;  /* 地等待队列 */  
    4. };  
             一、完成量的初始化

            初始化一个动态分配的completion完成量结构体,

    1. static inline void init_completion(struct completion *x)  
    2. {  
    3.     x->done = 0;  
    4.     init_waitqueue_head(&x->wait);  
    5. }  
            初始化一个静态completion完成量结构体。

    1. #define DECLARE_COMPLETION(work) \  
    2.     struct completion work = COMPLETION_INITIALIZER(work)  
             二、添加到等待队列

            进程可以通用一下函数添加到等待队列

    1. void __sched wait_for_completion(struct completion *x)  
    2. unsigned long __sched wait_for_completion_timeout(struct completion *x, unsigned long timeout)  
    3. int __sched wait_for_completion_interruptible(struct completion *x)  
    4. long __sched wait_for_completion_interruptible_timeout(struct completion *x, unsigned long timeout)  
    5. int __sched wait_for_completion_killable(struct completion *x)  
    6. long __sched wait_for_completion_killable_timeout(struct completion *x, unsigned long timeout)  
            (1) 进程在等待完成时处于不可中断状态,若使用wait_for_completion_interruptible表示可中断,如果进程被中断,则返回-ERESTARTSYS,否则返回0;

            (2) wait_for_completion_timeout表示等待事件的发生,并且提供超时设置,如果超过了这一设置,则取消等待,可防止无限等待;如果在超时之前完成则返回剩余时间,否则返回0。

            (3) wait_for_completion_killable表示可以由kill信号中断。

            其他函数均是这三者的变种。

           三、唤醒进程

            进程唤醒,可以通过以下函数实现:

    1. extern void complete(struct completion *);  
    2. extern void complete_all(struct completion *);  
            (1) complete调用每次只能从等待队列中移除一个进程。如果等待队列有N个进程,则需要执行N次、
            (2) complete_all唤醒所有的等待线程。

                    

            done的解释:

            每次调用complete,done计数器都会+1,仅当done=0时,wait_for系列函数才会使得调用进程睡眠。

    后续会进行complete编程练习......



    展开全文
  • 工作队列(workqueue)的Linux内核中的定义的用来处理不是很紧急事件的回调方式处理方法.  以下代码的linux内核版本为2.6.19.2, 源代码文件主要为kernel/workqueue.c.  2. 数据结构  /* include/linux/workqueue...
  • 工作队列(workqueue)的Linux内核中的定义的用来处理不是很紧急事件的回调方式处理方法. 以下代码的linux内核版本为2.6.19.2, 源代码文件主要为kernel/workqueue.c. 2. 数据结构 /* include/linux/workqueue.h ...
  • 本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于... 前言 工作队列(workqueue)的Linux内核中的定义的用来处理不是很紧急事件的回调方式处理方法. 以下代码的Lin
  • 本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业...工作队列(workqueue)的Linux内核中的定义的用来处理不是很紧急事件的回调方式处理方法. 以下代...
  •  工作队列(workqueue)的Linux内核中的定义的用来处理不是很紧急事件的回调方式处理方法。  以下代码的linux内核版本为2.6.19.2, 源代码文件主要为kernel/workqueue.c.  2. 数据结构  /* include/...
  • Linux操作系统 内核工作队列的操作模式1. 前言 工作队列(workqueue)的Linux内核中的定义的用来处理不是很紧急事件的回调方式处理方法. 以下代码的linux内核版本为2.6.19.2, 源代码文件主要为kernel/workqueue.c. 2. ...
  • Linux内核机制之等待队列

    千次阅读 2014-10-30 12:06:47
    等待队列在linux内核中有着举足轻重的作用,很多linux驱动都或多或少涉及到了等待队列。因此,对于linux内核及驱动开发者来说,掌握等待队列是必须课之一。 Linux内核的等待队列是以双循环链表为基础数据结构,与...
  • linux 内核定时器、linux 内核共享工作队列、linux 内核tasklet机制 以上的3中方法都可以吧工作推后执行。下面介绍linux内核定时器: 内核定时器本质是使用一个硬件定时器实现。相当于裸机程序编写的一个定时器...
  • 1. tasklet只可以在一个CPU上同步地执行,不同的tasklet可以在不同地CPU上同步地执行. 2. tasklet的实现是建立在两个软件中断的基础之上的,即HI_SOFTIRQ和TASKLET_SOFTIRQ,本质上没有什么区别,只不过HI_SOFTIRQ...
  • 本文是由字节跳动系统部 STE 团队出品的“kernel trace tools”系列文章之一,以介绍团队自研多类延迟问题追踪工具。在实际工作中,会遇到由于内核态不调度而引发的高网络延迟...
  • 一: 每隔30s执行一次,将字符串写入文件 // 30秒执行一次 ignore_user_abort(true); //即使Client断开(如关掉浏览器),PHP脚本也可以继续执行. set_time_limit(0); // 执行时间为无限制,php默认执行时间是30秒...
  • 下面简述一些基本概念,以及对内核代码做最初步的了解; 一 Linux内核进程管理基础 Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数据和结构,Linux 内核所有涉及到进程和程序的所有算法都是...
  • Linux内核中的定时

    千次阅读 2012-12-16 11:23:17
    系统定时器频率(节拍率)是通过静态预处理器定义的,也就是HZ,编写内核代码时,不要以为HZ值是固定不变的值。连续两次时钟的间隔时间叫做节拍,它等于节拍率分之一秒。高HZ有利于提供诸如poll和select函数运行的...
  • Linux 内核必须完成两种主要的定时测量: 保存当前的时间和日期,可由 time()、ftime()、gettimeofday() 返回给用户程序,也可由内核本身把当前时间作为文件和网络包的时间戳。 维持定时器,告诉内核或用户程序...
  • 等待队列(一)

    2013-07-31 14:33:38
    在Linux内核中等待队列有很多用途,可用于中断处理、进程同步及定时。我们在这里只说,进程经常必须等待某些事件的发生。等待队列实现了在事件上的条件等待: 希望等待特定事件的进程把自己放进合适的等待队列,并...
  • 1:内核定时器: 在内核中有系统自带的定时器,达到一定的时间就可以进行一个操作,这个和单片机中的定时器中断一样。 包含头文件是 Timer.h (\include\linux)结构体是timer_list struct timer_list{ /* * All ...
  • Linux内核进程调度函数schedule()的触发和执行时机

    千次阅读 多人点赞 2018-01-22 20:07:42
    内核的调度操作分为触发和执行两个部分,触发时仅仅设置一下当前进程的TIF_NEED_RESCHED标志,执行的时候则是通过schedule()函数来完成进程的选择和切换。当前进程的thread_info->flags中TIF_NEED_RESCHED位表示需要...
  • 等待队列

    千次阅读 2014-10-11 12:18:32
    在Linux内核中等待队列有很多用途,可用于中断处理、进程同步及定时。我们在这里只说,进程经常必须等待某些事件的发生。等待队列实现了在事件上的条件等待: 希望等待特定事件的进程把自己放进合适的等待队列,并...
  • 定时器有时也称为动态定时器或内核定时器,是管理内核时间的基础。定时器的使用很简单。你只需要执行一些初始化工作,设置一个超时时间,指定超时发生后执行的函数,然后激活定时器就可以了。 注意,定时器并不是...
  • linux工作队列

    2017-07-02 18:43:17
    共享工作队列: linux内核自带的用于开销量比较小的工作队列,用户不能长时间独占该队列 自定义工作队列: ...共享工作队列与自定义工作队列只要调用调度函数后会立即执行,延时工作队列可以定时启动处理任务
  • 提示:本文基于开源鸿蒙内核分析,官方源码【kernel_liteos_a】,官方文档【docs】 本文作者:鸿蒙内核发烧友,将持续研究鸿蒙内核,更新博文,敬请关注...鸿蒙内核源码分析(调度队列篇) 以便对本文任务调度机制的理解
  • Windows内核对象 - 通过异步程序调用(APC)实现的定时功能
  • tasklet 一、用途  中断管理 二、特性  1. 与内核定时器相比:  相同点:中断期间运行(在... 不同点:不能在某个给定的时间定时执行  2. 可被禁止或重新调用;只有启动和禁止的次数相同时tasklet才会t被执行

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 22,307
精华内容 8,922
关键字:

内核定时执行队列