精华内容
下载资源
问答
  • linux内核等待队列机制: 案例:分析应用程序串口工具操作串口硬件设备的过程。 1.外设的处理速度要远远慢于CPU! 2.应用程序在用户空间没有权利访问硬件设备,只有通过系统调用跑到内核空间才有权限访问硬件设备! ...

    linux内核等待队列机制:
    案例:分析应用程序串口工具操作串口硬件设备的过程。
    1.外设的处理速度要远远慢于CPU!
    2.应用程序在用户空间没有权利访问硬件设备,只有通过系统调用跑到内核空间才有权限访问硬件设备!
    3.一个应用程序读取串口硬件设备采用两种方法:
    轮询方式:相当的耗费CPU的资源,让CPU做大量的无用功!
    中断方式:CPU一旦发现串口设备不可读(没数据),CPU干别的事情,一旦串口接收到数据,给CPU产生一个接收中断信号,让CPU来获取串口数据。问:这个应用程序在做什么呢?
    当串口设备没有接收到数据,应用程序一旦发现,利用内核提供的睡眠机制,应用在内核空间进入休眠状态;一旦串口设备给CPU产生中断信号,中断信号的到来也就代表这数据的到来,这时只需唤醒休眠的应用程序,让应用程序读取串口数据。

    问:如何设备数据没有准备就绪,如果让进程在内核空间进行休眠
    答:linux内核等待队列机制
    本质目的:就是让进程在内核空间进行休眠
    注意:区别于工作队列
    工作队列:是中断底半部的机制,是实现延后执行的一个种手段
    等待队列:是让进程在内核空间进行休眠的
    但是它们针对处理的对象都是进程!

    进程的状态:
    运行:TASK_RUNNING
    准备就绪:TASK_READY
    可中断休眠:TASK_INTERRUPTIBEL
    不可中断休眠:TASK_UNINTERRUPTIBLE
    进程要“休眠":要休眠进程会将CPU资源全部从当前进程中撤下来,将CPU资源给别的任务去使用,比如另外一个进程;
    进程之间的切换:又内核调度器来实现,这个调度器就是用来管理进程的!

    linux内核等待队列机制实现过程:
    老鹰-》调度器:内核已经实现
    鸡妈妈-》等待队列头
    小鸡-》休眠的进程

    内核描述等待队列头涉及的数据类型:
    wait_queue_head_t
    内核描述休眠的进程,装载休眠进程的容器的数据类型:
    wait_queue_t

    等待队列让进程休眠的方法,而不是唤醒:
    方法1:
    步骤:
    1.分配等待队列头
       wait_queue_head_t wq;
    2.初始化等待队列头
       init_waitqueue_head(&wq);
    3.如果一个进程要访问设备,发现设备不可用,进入休眠,此时分配这个进程的休眠的容器
       DECLARE_WAITQUEUE(wait, current);
       wait:表示保存当前进程的容器
       current:它是内核的全局变量,在linux内核中,内核用struct  task_struct结构体来描述每一个进程,那么当进程获取CPU资源是,current就指向当前进程(哪个进程获取CPU资源,current就指向这个进程对应的task_struct结构体对象)
      例如打印出当前进程的pid和name
      printk("current process name is %s pid is %d\n",
            current->comm, current->pid);
      或者:
      wait_queue_t wait;
     init_waitqueue_entry(&wait, current);
    注意:如果有多个休眠的进程,必须为每一个进程分配一个容器,并且current也会分别执行不同的进程!
    4.然后将当前进程添加到休眠的队列中去
       add_wait_queue(&wq, &wait);
       注意:仅仅是将当前进程添加到这个队列中去,但进程还处于运行状态!

    5.设置当前进程的休眠状态(可中断或者不可中断)
    可中断的休眠状态:
    current->state = TASK_INTERRUPTIBLE;
    不可中断的休眠状态:
    current->state = TASK_UNINTERRUPTIBLE;

    6.让进程进入真正的休眠状态
       schedule(); //启动调度器,并且让CPU资源从当前进程撤下来,给别的进程去使用,至此当前进程运行到这个地方就停止不动!一旦被唤醒,这个函数返回,当前进程接着执行

    7.如果进程被设置为可中断的休眠状态,进程被唤醒的方法有两种:
    硬件设备可用,产生中断,由中断来唤醒;
    进程接收到了信号引起的唤醒,所以要判断唤醒的原因:
    判断是否接收到信号引起的唤醒:
    if (signal_pending(current)) {
       printk("当前进程是由于接收到了信号引起的唤醒");
       printk("给用户返回-ERESTARTSYS");
    } else {
       printk("中断引起的唤醒,说明数据可用");
       printk("后续继续获取数据");
    }

    8.不管是硬件中断引起的唤醒还是信号引起的唤醒,重新设置当前进程的状态为运行状态
    current->state = TASK_RUNNING;

    9.将当前进程从休眠队列中移除
      remove_wait_queue(&wq, &wait);

    参考代码:有一个进程读取按键数据:
    wait_queue_head_t wq; //全局变量
    驱动入口函数或者open函数:
    init_waitqueue_head(&wq);
    驱动read函数:
    static ssize_t btn_read(...)
    {
     wait_queue_t wait; //分配一个当前进程的容器
     init_waitqueue_entry(&wait, current);    //把当前进程添加到这个容器中
    add_wait_queue(&wq, &wait);//将当前进程添加到休眠队列中
    current->state = TASK_INTERRUPTIBLE;//设置当前进程的休眠状态
    schedule(); //进入真正的休眠,一旦被唤醒,进程接着执行
    //判断唤醒的原因
    if (signal_pending(current)) {
       printk("接收到了信号引起的唤醒");
       ret = -ERESTARTSYS;
    } else {
       printk("按键有操作,产生中断引起的唤醒");
    }
    current->state = TASK_RUNNING;//设置当前进程的状态为运行
    remove_wait_queue(&wq, &wait);//从休眠队列中移出
    上报按键数据
    return ret;
    }

    唤醒的方法有两种:
    1.接收到信号引起的唤醒
    2.驱动主动唤醒休眠的进程,方法如下:
    wake_up(wait_queue_head_t *queue);
        唤醒由queue指向的等待队列数据链中的所有睡眠类型的等待进程
    wake_up_interruptible(wait_queue_head_t *queue);
        唤醒由queue指向的等待队列数据链中的所有睡眠类型为TASK_INTERRUPTIBLE的等待进程

    案例1:实现读进程唤醒写进程,写进程唤醒读进程
    实验步骤:
    insmod btn_drv.ko
    ./led_test r & //读进程
    ./led_test w & //写进程
    ./led_test r & //读进程
    kill 读进程或者写进程

    案例2:根据以上案例,在底层驱动的read函数能够给用户上报一个按键的信息(键值和按键的状态),提示可以把底层驱动的write函数作为中断来使用。

    案例3:利用等待队列,实现按键驱动,要求按键上报的信息为键值和按键的状态!例如:
    KEY_UP: 0x50
    KEY_DOWN:0x51
    按键状态:按下为1,松开为0
    分析:
    read->fops->cdev->中断->休眠->等待队列

    指定超时时间的休眠:
    把schedule()换成schedule_timeout(5*HZ);
    前者的休眠是永久休眠(没有被驱动主动唤醒或者接收到信号)
    后者的休眠是指定了睡眠的时间,例如5秒,如果没有接收到信号,也没有接收到驱动主动唤醒,一旦5秒到期,此函数也会返回,返回0,否则返回非0(驱动主动唤醒或者接收到了信号)!
    案例:实现按键驱动,指定超时,而是永久休眠!

    方法2:
    1.分配等待队列头
       wait_queue_head_t wq;
    2.初始化等待队列头
       init_waitqueue_head(&wq);
    3.如果进程进入休眠状态:
       wait_event(wq, condition);
       condition为真,立即返回,不休眠
       condition为假,进程进入不可中断的休眠状态,进程被添加   到wq所对应的等待队列头中
       或者
       wait_event_interruptible(wq, condition);
       condition为真,立即返回,不休眠
       condition为假,进程进入可中断的休眠状态,进程被添加      到wq所对应的等待队列头中
       或者
       wait_event_timeout(wq, condition, 5*HZ);
       condition为真,立即返回,不休眠
       condition为假,进程进入不可中断的休眠状态,进程被添加   到wq所对应的等待队列头中,并且超时时间到期,进程也被唤醒;
       或者
       wait_event_interruptible_timeout(wq,             condition,5*HZ);
        condition为真,立即返回,不休眠
       condition为假,进程进入可中断的休眠状态,进程被添加   到wq所对应的等待队列头中,并且超时时间到期,进程也被唤醒;

    总结:以上宏涉及的condition其实就是代表是否是驱动主动唤醒,如果驱动主动唤醒,应该让condition设置为真,否则还是为假!

    案例:利用等待队列编程方法2来实现按键驱动
     

    展开全文
  • Linux内核等待队列

    2014-10-31 13:53:15
    一、等待队列 二、使用

    一、等待队列

            在Linux驱动程序中,可以使用等待队列(waitqueue)来实现阻塞进程的唤醒。

            waitqueue很早就作为一种基本的功能单位出现在Linux内核里了,它以队列位基础数据结构,与进程调度机制紧密结合,能够用于实现内核中异步事件通知机制。

            等待队列可以用来同步对系统资源的访问。(信号量在内核中也依赖等待队列来实现)。

            我的理解:一个进程因为某个条件不满足而阻塞,不继续执行,另一个进程在满足条件后可以唤醒阻塞的进程。

    二、使用示例

    2.1 代码示例

            1. 定义wait_queue_head_t :      static wait_queue_head_t pkt_wq; //step 1 define wait_queue

            2. 初始化wait_queue_head_t :  init_waitqueue_head(&pkt_wq); //step 2 init wait_queue

            3. 一个进程中wait_event  :          wait_event(pkt_wq, udp_pkt_flag == 1 || kthread_should_stop()); //step 3 wait wait_queue and event

                    深入理解wait_eventhttp://blog.csdn.net/djinglan/article/details/8150444

            4. 另一个进程中唤醒wake_up : wake_up(&pkt_wq); //step 4 wake up wait_queue

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/kthread.h>
    #include <linux/err.h>
    #include <linux/netfilter_ipv4.h>
    #include <linux/ip.h>
    #include <linux/udp.h>
    
    static struct task_struct *pkt_thread = NULL;
    
    static wait_queue_head_t pkt_wq;  //step 1 define wait_queue
    static s8 udp_pkt_flag = 0;       //step 1
    
    static int thread_proc(void* arg)
    {
        while (1) 
        {
            wait_event(pkt_wq, udp_pkt_flag == 1 || kthread_should_stop()); //step 3 wait wait_queue and event
            udp_pkt_flag = 0;                                               //step 3
    
            if (kthread_should_stop())
            {
                break;
            }
            else
            {
                printk("a udp packet send from host.\n");
            }
        }
    
        return 0;
    }
    
    unsigned int hook_mark1(unsigned int hooknum, struct sk_buff *skb,
                            const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
    {
        struct iphdr *iph;
        struct udphdr *udph;
    
        u16 dst_port,src_port;
        u32 src_ip,dst_ip;
    
        iph = ip_hdr(skb);
        if (iph->protocol == 17)
        {
            udph = (struct udphdr*)((u_int8_t*)iph + (iph->ihl << 2));
            dst_port = ntohs(udph->dest);
            src_port = ntohs(udph->source);
            src_ip = ntohl(iph->saddr);
            dst_ip = ntohl(iph->daddr);
            
            if (dst_port == 53)
            {
                udp_pkt_flag = 1;   //step 4
                wake_up(&pkt_wq);   //step 4 wake up wait_queue
            }
        }
        return NF_ACCEPT;
    }
    
    
    static struct nf_hook_ops nfho_marker1;
    u8 nfho_marker1_flag = 0;
    
    static int __init init_marker(void)
    {
        printk("init marker.\n");
    
    
    
        //
        init_waitqueue_head(&pkt_wq);  //step 2 init wait_queue
    
        //
        pkt_thread = kthread_create(thread_proc, NULL, "thread");
        if (IS_ERR(pkt_thread))
        {
            printk("create thread error.\n");
            return PTR_ERR(pkt_thread);
        }
        
        wake_up_process(pkt_thread);
    
        //
        nfho_marker1.hook=hook_mark1;
        nfho_marker1.hooknum=NF_INET_POST_ROUTING;
        nfho_marker1.pf=PF_INET;
        nfho_marker1.priority=NF_IP_PRI_LAST;
        nf_register_hook(&nfho_marker1);
        nfho_marker1_flag = 1;
            
        return 0;
    }
    
    static void __exit exit_marker(void)
    {
        //
        if(nfho_marker1_flag == 1)
        {
            nf_unregister_hook(&nfho_marker1);
        }
    
        //
        if (pkt_thread)
        {
            kthread_stop(pkt_thread);
            pkt_thread = NULL;
        }
    
        printk("exit marker.\n");
    }
    
    
    module_init(init_marker);
    module_exit(exit_marker);
    
    
    MODULE_VERSION("1.0.0_0");
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("gwy");
    MODULE_ALIAS("the alias of module name");
    MODULE_DESCRIPTION("the description about the use of the module");
    
    2.2 内核中的使用

    等待队列:

            文件drivers/s390/char/sclp_sdias.c中:

            static wait_queue_head_t sdias_wq;

    线程在内核中的使用:

            文件arch/x86/kernel/apm_32.c                       

            static struct task_struct *kapmd_task;

    Linux内核:kthread_create(线程)、SLEEP_MILLI_SEC

            http://blog.csdn.net/guowenyan001/article/details/39230181


    参考资料

            linux等待队列wait_queue_head_t和wait_queue_t:http://blog.csdn.net/luoqindong/article/details/17840095

            wait_event_interruptible() 和 wake_up()的使用:http://blog.csdn.net/djinglan/article/details/8150444

            linux wait queue:http://blog.csdn.net/zacklin/article/details/7238462

    展开全文
  • 文章目录Linux内核延时概念应用场景Linux内核相关延时函数Linux内核等待队列机制概述等待队列的功能驱动编程实施步骤示例代码(一)示例代码(二)总结 Linux内核延时 概念 延时又称为等待,延时分为两类:忙延时和...


    Linux内核延时

    概念

    延时又称为等待,延时分为两类:忙延时和休眠延时。
    忙延时: 当任务进行忙延时时,任务将会导致所占用的CPU资源进行白白消耗,类似原地空转。
    休眠延时: 进程进入休眠状态,进程会释放所占用的CPU资源给其他进程使用。

    应用场景

    忙延时应用在等待时间极短的场合,进程和中断都可以使用忙延时。
    休眠延时应用在等待时间较长的场合,只能用于进程,不能用于中断。
    注意:CPU资源在进程之间的切换也是需要时间的消耗的 。

    Linux内核相关延时函数

    忙延时相关的延时函数:

    • 如果忙等待时间超过10毫秒,建议还是使用休眠延时。
    ndelay(int n) //纳秒级延时
    ndelay(10);//忙延时10纳秒
      		
    udelay(int n) //微秒级延时
    udelay(10);//忙延时10微秒
      		
    mdelay(int n) //毫秒级延时
    mdelay(5); //忙延时5毫秒,CPU空转5秒
    

    休眠延时相关的延时函数:

    • 执行休眠延时,进程会释放所占用的CPU资源给其他进程。
    msleep(int n); //毫秒级休眠延时
    msleep(100);//休眠延时100毫秒
    
    ssleep(int n); //秒级休眠延时 	 
    ssleep(10);//休眠延时10秒
    
    schedule();//永久休眠
    schedule_timeout(5 * HZ);//休眠延时5秒钟,
    
    • 补充:如果想让进程能够随时随地休眠和唤醒,必须采用Linux内核的等待队列机制。msleep和ssleep本身都是基于等待队列实现的。
      • 消息队列:IPC通信的一种方式。
      • 工作队列:底半部的延后执行的一种方法。
      • 等待队列:进程在内核空间随时随地休眠,随时随地被唤醒的一种机制。

    Linux内核等待队列机制

    概述

    问:进程在内核空间虽然可以调用msleep / ssleep / schedule / schedule_timeout,能够进行随时随地的进入休眠等待状态,但是不能被随时随地被唤醒。如何才能让进程在内核控件进行随时随地的休眠和被唤醒呢?

    • 答:使用等待队列机制,信号量能够使进程休眠本身也是基于等待队列实现的。

    示例应用场景:
    一个进程能够获取按键的操作状态(按下或者松开)的需求分析:进程调用read系统调用函数来获取按键的操作状态(按下或者松开)。由于用户操作按键(按下或者松开)本身就是一个随机过程(开心了按两下,不开心不操作),read读进程为了能够及时获取到用户的按键操作,首先想到采用轮训方式(死等,while(1)),但是这种方式会大量消耗CPU资源,大大降低了CPU的利用率(CPU永远只做一件事),于是乎想到轮训的死对头中断机制也就是说进程调用read系统调用函数来获取按键的操作状态,最终进程调用到底层驱动的read接口,如果进程在驱动的read接口中发现按键无操作(既没有按下也没有松开),干脆让read进程在驱动的read接口中进行休眠等待,等待着按键有操作,一旦read进程在驱动的read接口中进行休眠等待,read进程会释放掉占用的CPU资源给其他进程使用(中断不需要给,它会直接抢占),一旦将来用户对按键进行按下或者松开操作,势必产生按键中断,表示按键有操作,此时只需在按键的中断处理函数中去唤醒休眠的read进程一旦read进程被唤醒,read进程再去读取按键的操作状态即可返回到用户空间,此时此刻,CPU至少做2件事(一个是执行read进程,另一个是其他进程),大大提高了CPU的利用率。

    • 问:在这个过程中,如何让read进程随时随地休眠并且在中断到来时让read进程随时随地被唤醒呢?
      • 答:同样利用等待队列机制,等待队列诞生的根本原因:外设的处理速度远远慢于CPU,所以外设没有准备好数据的时候操作外设的进程就需要进行休眠等待。

    所以,只要用户进程操作外设,外设的数据处理速度远远慢于CPU,将来驱动势必利用等待队列来实现休眠等待外设准备好数据。

    等待队列的功能

    等带队列能让用户进程在内核空间随时随地休眠、随时随地被唤醒。
    等待队列中操作的对象就是进程。
    等待队列=等待+队列 = 要休眠的进程排成一队,形成等待队列,这些休眠的进程要等待某个事件到来,事件没有发生就休眠去等待。

    驱动编程实施步骤

    这里举个类似下图的例子:
    小鸡作为要休眠的进程(驱动完成)、鸡妈妈作为等待队列头(驱动完成)、老鹰作为进程调度器(由内核完成)。
    在这里插入图片描述具体编程步骤:

    1. 定义初始化一个等待队列头(构造一个鸡妈妈)
    wait_queue_head_t wq; //定义等待队列头对象
    init_waitqueue_head(&wq);//初始化等待队列头
    
    1. 定义初始化装载要休眠进程的容器(给每个休眠的进程构造一个小鸡)。注意:一个要休眠的进程对应一个容器wait(小鸡),其中current是一个内核全局指针变量,对应的数据类型为struct task_struct,此数据结构用来描述进程的信息,只要fork一个进程,内核就会用此数据结构定义初始化一个对象来描述fork出来的进程信息,current指针永远指向当前进程对应的struct task_struct对象。“当前进程”指正在获取CPU资源投入运行的进程。
    wait_queue_t wait; //定义装载休眠进程的容器(造小鸡)
    init_waitqueue_entry(&wait, current); //将当前要休眠的进程添加到容器wait中
    
    1. 将休眠的当前进程添加到等待队列中去(将小鸡放在鸡妈妈的后面),注意此时此刻进程还没有正式休眠。
    add_wait_queue(&wq, &wait); 
    
    1. 设置要休眠的当前进程的休眠状态,进程休眠状态的类型分为两类:
      • 可中断的休眠类型(TASK_INTERRUPTIBLE):休眠器件可以立即处理接收的信号,此类休眠进程被唤醒的方式有两种:接收信号唤醒,驱动主动唤醒。
      • 不可中断的休眠类型(TASK_UNINTERRUPTIBLE):休眠器件如果接收到信号,不会立即处理而是在被唤醒以后处理信号,此类休眠进程被唤醒的方法只有一种:驱动主动唤醒。
        注意:此时此刻当前进程还没有正式休眠。
    set_current_state(TASK_INTERRUPTIBLE);//设置为可中断的休眠类型
    
    set_current_state(TASK_UNINTERRUPTIBLE);//设置为不可中断的休眠类型
    
    1. 此时此刻当前进程正式进入休眠状态,此时此刻当前进程会释放所占用的CPU资源给其他进程,此时此刻代码停止不前等待被唤醒,一旦被唤醒,代码继续往下执行。
    schedule();
    //注意:不能单独调用此函数,否则要休眠的当前进程
    //会默认放到内核的默认等待队列中,将来如果要唤醒,做不到随时随地了
    
    1. 一旦休眠的进程被唤醒,设置休眠的进程状态位运行态。
    set_current_state(TASK_RUNNING);
    
    1. 最后将唤醒的休眠进程从等待队列中移除
    remove_wait_queue(&wq, &wait);
    
    1. 一般最好建议判断一下进程被唤醒的原因
    if(signal_pending(current)) 
    {
    	printk("进程由于接收到了信号引起的唤醒!\n");
    	return -ERESTARTSYS;
    } 
    else 
    {
    	printk("进程由于驱动主动引起唤醒!\n");
    	//接下里被唤醒的进程就可以访问硬件
    	//说明硬件数据准备就绪了
    }
    
    1. 驱动主动唤醒休眠进程的方法
    wake_up(&wq);//唤醒wq等待队列中所有的休眠进程
    
    wake_up_interruptible(&wq);//唤醒wq等待队列中所有的休眠类型为可中断的进程
    

    示例代码(一)

    实现一个使read操作的进程休眠,另一个进程操作write唤醒被休眠的进程的驱动。

    • wake_drv.c
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/miscdevice.h>
    #include <linux/gpio.h>
    #include <mach/platform.h>
    #include <linux/uaccess.h>
    #include <linux/irq.h>
    #include <linux/interrupt.h>
    #include <linux/sched.h> //TASK_INTERRUPTIBLE等
    
    //定义一个等待队列头对象(造鸡妈妈)
    static wait_queue_head_t rwq;
    static ssize_t wake_read(struct file *file,
                            char __user *buf,
                            size_t count,
                            loff_t *ppos)
    {
        //1.定义初始化装载休眠进程的容器(构造小鸡)
        //将当前进程添加到容器中
        //一个进程一个容器
        wait_queue_t wait;
        init_waitqueue_entry(&wait, current);
    
        //2.将当前进程添加到等待队列中去
        add_wait_queue(&rwq, &wait);
    
        //3.设置当前进程休眠的状态类型为可中断
        set_current_state(TASK_INTERRUPTIBLE);
    
        //4.当前进程正式进入休眠状态
        //此时代码停止不前
        //当前进程释放CPU资源
        //一旦被唤醒,进程继续往下执行
        printk("读进程[%s][%d]将进入休眠状态...\n",
                        current->comm, current->pid);
        schedule();
    
        //5.一旦被唤醒,设置进程的状态为运行
        set_current_state(TASK_RUNNING);
        
        //6.将被唤醒的进程从队列中移除
        remove_wait_queue(&rwq, &wait);
    
        //7.判断进程被唤醒的原因
        if(signal_pending(current)) {
            printk("读进程[%s][%d]由于接收到了信号引起唤醒\n",
                                current->comm, current->pid);
            return -ERESTARTSYS;
        } else {
            printk("读进程[%s][%d]由于驱动主动引起唤醒.\n",
                                current->comm, current->pid);
        }
        return count;
    }
    
    static ssize_t wake_write(struct file *file,
                            const char __user *buf,
                            size_t count,
                            loff_t *ppos)
    {
        //1.唤醒休眠的读进程
        printk("写进程[%s][%d]唤醒读进程\n",
                            current->comm, current->pid);
        wake_up(&rwq);
        return count;
    }
    
    //定义初始化硬件操作接口对象
    static struct file_operations wake_fops = {
        .owner = THIS_MODULE,
        .read = wake_read,
        .write = wake_write
    };
    
    //定义初始化混杂设备对象
    static struct miscdevice wake_misc = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = "mywake",
        .fops = &btn_fops
    };
    
    static int wake_init(void)
    {
        //注册混杂设备对象
        misc_register(&wake_misc);
        //初始化等待队列头(武装鸡妈妈)
        init_waitqueue_head(&rwq);
        return 0;
    }
    
    static void wake_exit(void)
    {
        //卸载混杂设备对象
        misc_deregister(&wake_misc);
    }
    module_init(wake_init);
    module_exit(wake_exit);
    MODULE_LICENSE("GPL");
    
    
    • Makefile
    obj-m += wake_drv.o
    all:
    	make -C /opt/kernel SUBDIRS=$(PWD) modules
    clean:
    	make -C /opt/kernel SUBDIRS=$(PWD) clean
    
    
    • wake_test.c
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    int main(int argc, char *argv[])
    {
        int fd;
    
        fd = open("/dev/mywake", O_RDWR);
        if (fd < 0) 
            return -1;
    
        if(argc != 2) {
            printf("用法:%s <r|w>\n", argv[0]);
            return -1;
        }
    
        if(!strcmp(argv[1], "r"))
            read(fd, NULL, 0); //启动一个读进程
        else if(!strcmp(argv[1], "w"))
            write(fd, NULL, 0); //启动一个写进程
    
        close(fd);
        return 0;
    }
    
    
    • 执行结果:
      在这里插入图片描述

    示例代码(二)

    实现一个休眠等待读取按键值的驱动,进程任务休眠等待按键,按键按下并唤醒进程读取当前按键的键值。

    • btn_drv.c
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/miscdevice.h>
    #include <linux/gpio.h>
    #include <mach/platform.h>
    #include <linux/uaccess.h>
    #include <linux/irq.h>
    #include <linux/interrupt.h>
    #include <linux/sched.h> //TASK_INTERRUPTIBLE等
    #include <linux/input.h>
    
    //声明描述按键信息的数据结构
    struct btn_resource {
        int gpio; //按键对应的GPIO编号
        char *name;//按键名称
        int code;//按键值
    };
    
    //声明上报按键信息的数据结构
    struct btn_event {
        int state; //上报按键的状态:1:按下;0:松开
        int code;  //上报按键值
    };
    
    //定义初始化四个按键的硬件信息对象
    static struct btn_resource btn_info[] = {
        {
            .gpio = PAD_GPIO_A + 28,
            .name = "KEY_UP",
            .code = KEY_UP
        }
    };
    
    //分配内核缓冲区,记录当前操作的按键信息
    static struct btn_event kbtn;
    
    //定义一个等待队列头对象(造鸡妈妈)
    static wait_queue_head_t rwq;
    static ssize_t btn_read(struct file *file,
                            char __user *buf,
                            size_t count,
                            loff_t *ppos)
    {
        //1.定义初始化装载休眠进程的容器(构造小鸡)
        //将当前进程添加到容器中
        //一个进程一个容器
        wait_queue_t wait;
        init_waitqueue_entry(&wait, current);
    
        //2.将当前进程添加到等待队列中去
        add_wait_queue(&rwq, &wait);
    
        //3.设置当前进程休眠的状态类型为可中断
        set_current_state(TASK_INTERRUPTIBLE);
    
        //4.当前进程正式进入休眠状态
        //此时代码停止不前
        //当前进程释放CPU资源
        //一旦被唤醒,进程继续往下执行
        schedule();
    
        //5.一旦被唤醒,设置进程的状态为运行
        set_current_state(TASK_RUNNING);
        
        //6.将被唤醒的进程从队列中移除
        remove_wait_queue(&rwq, &wait);
    
        //7.判断进程被唤醒的原因
        if(signal_pending(current)) {
            printk("读进程[%s][%d]由于接收到了信号引起唤醒\n",
                                current->comm, current->pid);
            return -ERESTARTSYS;
        } else {
            //此时的kbtn已经被中断处理函数进行赋值操作
            copy_to_user(buf, &kbtn, sizeof(kbtn));
        }
        return count;
    }
    
    //中断处理函数
    static irqreturn_t button_isr(int irq, void *dev)
    {
        //1.获取当前操作的按键的硬件信息
        struct btn_resource *pdata = dev;
        
        //2.获取按键的状态和按键值保存在全局变量中
        kbtn.state = gpio_get_value(pdata->gpio);
        kbtn.code = pdata->code;
    
        //3.一旦有按键操作,硬件上势必产生中断
        //也就说明按键有操作,应该唤醒read进程读取按键的信息
        wake_up(&rwq);
        return IRQ_HANDLED; //中断返回有可能才轮到read进程执行
    }
    
    //定义初始化硬件操作接口对象
    static struct file_operations btn_fops = {
        .owner = THIS_MODULE,
        .read = btn_read,
    };
    
    //定义初始化混杂设备对象
    static struct miscdevice btn_misc = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = "mybtn",
        .fops = &btn_fops
    };
    
    static int btn_init(void)
    {
        int i;
        //注册混杂设备对象
        misc_register(&btn_misc);
        //初始化等待队列头(武装鸡妈妈)
        init_waitqueue_head(&rwq);
        //申请GPIO资源
        //申请中断资源,注册中断处理函数
        for(i = 0; i < ARRAY_SIZE(btn_info); i++) {
            int irq = gpio_to_irq(btn_info[i].gpio);
            gpio_request(btn_info[i].gpio,
                            btn_info[i].name);
            request_irq(irq, button_isr,
                IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                btn_info[i].name, &btn_info[i]);
        }
        return 0;
    }
    
    static void btn_exit(void)
    {
        int i;
        //各种释放
        for(i = 0; i < ARRAY_SIZE(btn_info); i++) {
            int irq = gpio_to_irq(btn_info[i].gpio);
            gpio_free(btn_info[i].gpio);
            free_irq(irq, &btn_info[i]);
        }
        //卸载混杂设备对象
        misc_deregister(&btn_misc);
    }
    module_init(btn_init);
    module_exit(btn_exit);
    MODULE_LICENSE("GPL");
    
    
    • btn_test.c
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    //声明按键信息数据结构
    struct btn_event {
        int state; //按键状态:1:松开;0:按下
        int code; //按键值
    };
    
    int main(int argc, char *argv[])
    {
        int fd;
        struct btn_event btn; //分配用户缓冲区,记录按键的信息
    
        //打开设备
        fd = open("/dev/mybtn", O_RDWR);
        if (fd < 0) {
            printf("打开设备失败!\n");
            return -1;
        }
    
        while(1) {
            read(fd, &btn, sizeof(btn));
            printf("按键[%d]的状态为[%s]\n",
                    btn.code, btn.state ?"松开":"按下");
        }
    
        //关闭设备
        close(fd);
        return 0;
    }
    
    • Makefile
    obj-m += btn_drv.o
    all:
    	make -C /opt/kernel SUBDIRS=$(PWD) modules
    clean:
    	make -C /opt/kernel SUBDIRS=$(PWD) clean
    
    
    • 执行结果:
      在这里插入图片描述

    总结

    使用等待队列的方式能够很好的解决应用层的进程调用外设却需要等待外设准备就绪的问题。

    展开全文
  • Linux内核等待队列详解

    千次阅读 2018-01-25 16:45:34
    内核将因相同原因而阻塞的进程集中在一个队列上,该队列就是等待队列,对于每个需要等待的事件都有一个相应的等待队列等待队列采用链表的方式来存储,各个阻塞进程作为节点存储在链表中。链表头的类型是wait_queue...

    等待队列用于管理应等待某个条件成立或者事件发生而防人之心不可无的进程。进程发出的请求暂时无法满足的时候,需要将进程阻塞直到条件成立。内核将因相同原因而阻塞的进程集中在一个队列上,该队列就是等待队列,对于每个需要等待的事件都有一个相应的等待队列。等待队列采用链表的方式来存储,各个阻塞进程作为节点存储在链表中。链表头的类型是wait_queue_head_t,节点类型为wait_queue_t,如下所示:

    //linux-3.13/include/linux/wait.h
    struct __wait_queue_head {
        spinlock_t      lock;            //自旋锁
        struct list_head    task_list;   //指向队列的链表(成员是wait_queue_t类型)
    };
    typedef struct __wait_queue_head wait_queue_head_t;
    
    typedef struct __wait_queue wait_queue_t;
    struct __wait_queue {
        unsigned int        flags;    //唤醒进程的方式,0表示非互斥方式,1表示互斥方式(一个等待队列中flags标志为1的进程只能一次唤醒一个,即互斥)
    #define WQ_FLAG_EXCLUSIVE   0x01
        void            *private;     //指向阻塞进程的task_struct
        wait_queue_func_t   func;     //唤醒函数(根据唤醒方式的不同而执行不同的唤醒操作)
        struct list_head    task_list;   //构成等待队列的双向链表
    };

    其中默认的唤醒函数wait_queue_func_t是对try_to_wake_up()的简单封装。

    等待队列的使用包括两个步骤,等待和唤醒。当进程需要睡眠时,调用wait_event()将自己加入等待队列,让出CPU,比如在向块设备发出请求之后由于数据不能立即返回,所以需要睡眠,此时就调用wait_event()。当事件到达后,则利用wake_up()等函数唤醒等待队列中的进程。wait_event()宏的代码如下:

    #define wait_event(wq, condition)                   \
    do {                                    \
        if (condition)                          \
            break;                          \
        __wait_event(wq, condition);                    \
    } while (0)
    
    #define __wait_event(wq, condition)                 \
        (void)___wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0,  \
                    schedule())
    
    #define ___wait_event(wq, condition, state, exclusive, ret, cmd)    \
    ({                                  \
        __label__ __out;                        \
        wait_queue_t __wait;                        \
        long __ret = ret;                       \
                                        \
        INIT_LIST_HEAD(&__wait.task_list);              \
        if (exclusive)                          \
            __wait.flags = WQ_FLAG_EXCLUSIVE;           \
        else                                \
            __wait.flags = 0;                   \
                                        \
        for (;;) {                          \                      //循环等待
            long __int = prepare_to_wait_event(&wq, &__wait, state);\     //初始化__wait变量,并将其加入wq中,然后设置进程的运行状态为TASK_UNINTERRUPTIBLE
                                        \
            if (condition)                      \                  //如果条件满足,则跳出循环,否则继续循环下去
                break;                      \
                                        \
            if (___wait_is_interruptible(state) && __int) {     \
                __ret = __int;                  \
                if (exclusive) {                \
                    abort_exclusive_wait(&wq, &__wait,  \
                                 state, NULL);  \
                    goto __out;             \
                }                       \
                break;                      \
            }                           \
                                        \
            cmd;                            \                       //传入的cmd是schedule()
        }                               \
        finish_wait(&wq, &__wait);                  \               //设置进程的运行状态为TASK_RUNNING,并将进程从等待队列wq中删除
    __out:  __ret;                              \
    })

    从代码中可以看出,只要所等待的条件(condition)不满足应付一直循环等待,直到条件满足后执行finish_wait()。其中condition是传入的参数,例如软盘驱动代码中会有wait_event(command_done, command_status >= 2);。在等待队列上的进程可能因为wake_up()而唤醒,wake_up()将会执行__wake_queue->func()唤醒函数,默认是default_wake_function()default_wake_function()调用try_to_wake_up()将进程更改为可运行状态并设置待调度标记。wake_up()的代码如下:

    //linux-3.13/include/linux/wait.h
    #define wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)
    
    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);
    }
    
    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;
    
        //遍历等待队列q
        list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
            unsigned flags = curr->flags;
    
            //执行wait_queue_t变量中注册的唤醒函数
            if (curr->func(curr, mode, wake_flags, key) &&
                    (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                break;
        }
    }

    等待队列的的使用在驱动代码中会有很多,另外,Java中Object.wait()/notify()方法的实现也与等待队列有关。

    展开全文
  • Linux kernel 里有一个数据结构可以帮助我们做到这样的功能。这个数据结构就是本位要为大家介绍的 wait queue。在 kernel 里,wait_queue 的应用很广,举凡 device driver semaphore 等方面都会使用到 wait_queue...
  • Linux内核等待队列探究-wait_queue_t-wait_queue_head_t 【 相关源码版本: LINUX内核源码版本:linux-3.0.86 UBOOT版本:uboot-2010.12. Android系统源码版本:Android-5.0.2】   等待队列是LINUX内核实现阻塞访问...
  • 等待队列1 等待队列2 调度器 CPU调度如下图所示: 等待队列其原理是: cpu会调度就绪队列,或者打断执行线程,运行就绪队列 创建等待队列头和队列,使用wait event,当condition不满足时,当前线程进入等待队列 ...
  • linux内核等待队列

    千次阅读 2013-05-22 11:27:56
    根据内核3.1.6版本源码、书籍和网上资料,对几个函数进行分析  介绍这几个函数,不得不先介绍等待队列wait_queue_head_t与完成量completion。... 完成量机制是基于等待队列的,内核利用该机制等待某一操作的结束。
  • \Linux内核机制之等待队列,消息讲述了wait_queue队列的数据结构和在内核中的实现源码,有助于对如何使用队列更加一目了然。
  • linux内核工作队列

    千次阅读 2017-12-09 12:45:42
    内核工作队列概述工作队列(workqueue)是另外一种将工作推后执行的形式,工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行,最重要的就是工作队列允许被重新调度...
  •  在 Linux 驱动程序设计中,可以使用等待队列来实现进程的阻塞.  等待队列可以看作保存进程的容器,在阻塞进程时,将进程放入等待队列;  当唤醒进程时,从等待队列中取出进程.   等待队列的 定义...
  •  等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程在等待周期中睡眠,当时间发生后由内核自动唤醒。  完成量机制是基于等待队列的,内核利用该机制等待某一操作的结束。这两种经常被
  • Linux内核等待队列[汇编].pdf
  • 嵌入式Linux内核中的等待队列操作.pdf
  • Linux内核等待队列的使用方法总结

    千次阅读 2009-08-02 11:41:00
    如果应用程序准备设计成阻塞方式来存取设备的话,在内核底层驱动设计时需要考虑增加wait_queue_t来实现多进程的存取访问。 struct __wait_queue_head { spinlock_t lock; struct list_head task_list;};typedef ...
  • Linux内核进程管理基础 Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数据和结构,Linux 内核所有涉及到进程和程序的所有算法都是围绕该数据结构建立的,是内核中最重要的数据结构之一。 该...
  • 因此,在中断驱动程序中,引入工作队列实现中断的分层,硬件处理在中断函数中处理,软件及其耗时操作则在Linux内核线程中实现。本小节,将介绍Linux内核队列及其使用方式。 29.2 工作队列特点 (1)工作队列中是...
  •  在 Linux 驱动程序设计中,可以使用等待队列来实现进程的阻塞.  等待队列可以看作保存进程的容器,在阻塞进程时,将进程放入等待队列;  当唤醒进程时,从等待队列中取出进程. 等待队列的 定义...
  • 本文对最新的 Linux-4.19.4 内核源码进行分析,并详细指出内核 IPC 机制中的消息队列的原理。 进程间通信 IPC(进程间通信,InterProcess Communication)是内核提供的系统中进程间进行通信的一种机制。系统中每个...
  • Linux内核等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在中,等待队列在源代码树中,这是一个通过连接的典型双循环链表,如下图所示。  在这个...
  • Linux内核等待队列

    2008-11-30 16:39:00
    Linux内核等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在Linux2.4.21中,等待队列在源代码树include/linux/wait.h中,这是一个通过list_head连接的典型...
  • Linux内核等待队列 和完成量

    千次阅读 2014-05-23 10:30:38
    分类: linux内核技术2012-01-05 00:03 7905人阅读 评论(6) 收藏 举报 wait_eventwake_up等待队列  根据内核3.1.6版本源码、书籍和网上资料,对几个函数进行分析  介绍这几个函数,不得不先介绍等待...
  • 1) 为什么要用等待队列? 假设我们在 kernel 里产生一个 buffer,user 可以经由 read,write 等 system call 来读取或写资料到这个 buffer 里。如果有一个 user 写资料到 buffer 时,此时 buffer 已经满了。那请问...
  •  等待队列用于使得进程等待某一特定事件的发生,无需频繁的轮询,进程在等待周期中睡眠,当时间发生后由内核自动唤醒。 等待队列  (一)数据结构  等待队列结构如下,因为每个等待队列都可以再...
  •  工作队列(workqueue)的Linux内核中的定义的用来处理不是很紧急事件的回调方式处理方法。  以下代码的linux内核版本为2.6.19.2, 源代码文件主要为kernel/workqueue.c.  2. 数据结构  /* include/...
  • 文档介绍了前言,一、等待队列定义,二、等待队列作用,三、字段详解,三、操作,1、定义并初始化等待队列头,2、定义等待队列项,3、(从等待队列头中)添加/移出等待队列项,4、等待事件,5、唤醒队列,6、在等待...
  • Linux 进程控制——等待队列详解

    万次阅读 多人点赞 2016-06-29 21:48:24
    当一个进程被置为睡眠, 它被标识为处于一个特殊的状态并且从调度器的运行队列中去除. 直到发生某些事情改变了那个状态, 这个进程将不被在任何 CPU 上调度, 并且, 因此, 将不会运行. 一个睡着的进程已被搁置到系统的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 91,147
精华内容 36,458
关键字:

linux内核等待队列

linux 订阅