Linux设备驱动第八天(等待队列)

回顾:
linux内核的并发和竞态:
概念:
并发:多个执行单元同时发生
竞态:
共享资源:
互斥访问:
临界区:
原子性:
内核执行路径:

产生竞态的几种情况:
多核:
进程与线程
中断与进程
中断与中断

linux内核避免竞态的方法:
中断屏蔽
原子操作
自旋锁
信号量:睡眠锁


linux内核等待队列机制:
等待队列机制:
目的:就是让进程在内核空间进行休眠操作!
问:进程在用户空间如何显示进行休眠?
答:sleep();
问:为什么需要让用户进程在内核空间进行休眠呢?
答:
进程没有权利和资格在空间进行访问硬件设备,必须利用系统调用陷入内核空间才能访问硬件设备;一旦进程陷入内核空间访问硬件设备(串口、按键),进程发现硬件设备不可用(串口没有接收到数据或者按键没有人为操作),那么内核就让用户进程在内核空间进行休眠,等待硬件设备可用;
一旦硬件设备可用了(通过中断判断硬件设备可用)唤醒之前休眠的进程,进程再次对设备进行访问操作!
特点:如果一个进程需要在内核空间休眠,等待队列机制就将这个进程放入一个等待队列(休眠队列)中去,一旦进程被唤醒,进程也会从队列(休眠队列)中进行移除!

比如信号量机制就是利用等待队列机制来实现进程休眠的!

问:等待队列和工作队列的区别?
答:工作队列的本质是延后执行,底半部的一个机制
等待队列的本质就是让进程进行休眠!

调度器:属于linux内核进程管理子系统的重要部件,负责进程之间的切换、调度、抢占,也负责进程之间CPU资源的转换工作!
等待队列机制让用户进程由运行进入休眠,由休眠进入运行,整个过程就需要调度器参与!

等待队列机制如何使用?
调度器<->老鹰
等待队列头<->鸡妈妈
休眠的进程<->鸡宝宝

游戏规则:如果一个进程要休眠,只需给这个休眠的进程分配一个容器,将此进程填充到容器中,然后把这个容器再添加到以鸡妈妈代表的休眠队列中去,进行休眠!一旦这个进程被唤醒,调度器(老鹰)将这个进程从休眠队列中移除,让这个进程投入运行!

linux内核等待队列的实现过程:
等待队列头(鸡妈妈)的数据结构:wait_queue_head_t
存放休眠进程的容器的数据结构:wait_queue_t

让进程在内核空间休眠的步骤:
1,分配等待队列头
wait_queue_head_t wq;
2,初始化等待队列头对象
init_waitqueue_head(&wq)
3,分配存放休眠进程的容器(一个进程一个容器)
wait_queue_t wait;
4,初始化存放进程的容器:
init_waitqueut_entry(&wait,current);
注意:current是内核全局变量,代表当前进程(正在使用CPU资源的进程),指向当前的struct task struct 结构体(linux系统对于每一个进程或者线程是否分配一个struct task_struct结构体对象来描述当前进程或者线程的属性)

例如:利用current打印当前进程的信息:
printk(“当前进程名%s,当前进程pid:%d \n”,current->pid,current->comm);

5,将要休眠的进程添加到等待队列头所在休眠队列中去。
add_wait_queue(&wq,&wait);
注意:当前进程还没有正式休眠!

6,设置当前进程的状态为休眠状态
set_currnet_state(TASK_INTERRUPTIBLE);//设置为可中断的休眠状态
7,让当前进程进入真正的休眠状态(当前进程释放CPU资源)
scedhule();//才算是完成进程的休眠
注意:进程一旦调用此函数进行休眠,代码就执行到此处不再往下执行了!
注意:这个函数的休眠是永久休眠!
如果这个进程被唤醒,这个函数立即返回
8,等待进程被唤醒,进程被唤醒的方法途径有两种:
驱动主动唤醒(设备可用产生中断,有中断来唤醒)等待队列中休眠的进程;
给休眠的进程发送信号(kill)唤醒等待队列中休眠的进程
问:驱动如何主动唤醒wq等待队列中休眠的进程呢?
9,一旦进程被唤醒以后,将当前进程从等待队列中进行移除!
remove_wait_queut(&wq,&wait);
10,设置当前进程的状态为运行状态,进程正式投入运行!
set current_state(TASK_RUNNING);
11,判断进程被唤醒的原因,如果是驱动主动唤醒 ,进程就应该合理的,正常的,有理由的继续访问设备(比如读取串口设备);如果是接收到kill信号引起的唤醒,进程就应该处理这个信号,而不是继续访问这个设备!

    if(signal_pending(current)){
        printk("进程接收到信号引起的唤醒");
        return ERETARTSYS;
    }else{
        printk("进程由驱动主动唤醒");
        //继续访问设备
    }

用schedule_timeout函数让进程在内核空间休眠的步骤:
1,分配等待队列头
wait_queue_head_t wq;
2,初始化等待队列头对象
init_waitqueue_head(&wq)
3,分配存放休眠进程的容器(一个进程一个容器)
wait_queue_t wait;
4,初始化存放进程的容器:
init_waitqueut_entry(&wait,current);
注意:current是内核全局变量,代表当前进程(正在使用CPU资源的进程),指向当前的struct task struct 结构体(linux系统对于每一个进程或者线程是否分配一个struct task_struct结构体对象来描述当前进程或者线程的属性)

例如:利用current打印当前进程的信息:
printk(“当前进程名%s,当前进程pid:%d \n”,current->pid,current->comm);

5,将要休眠的进程添加到等待队列头所在休眠队列中去。
add_wait_queue(&wq,&wait);
注意:当前进程还没有正式休眠!

6,设置当前进程的状态为休眠状态
set_currnet_state(TASK_INTERRUPTIBLE);//设置为可中断的休眠状态
7,让当前进程进入真正的休眠状态(当前进程释放CPU资源)
schedhule_timeout();//才算是完成进程的休眠
注意:进程一旦调用此函数进行休眠,代码就执行到此处不再往下执行了!
注意:这个函数的休眠不是永久休眠,而是指定了休眠的时间!
schedhule_timeout(5*HZ)//休眠5秒
如果这个进程被唤醒,这个函数立即返回
8,等待进程被唤醒,进程被唤醒的方法途径有两种:
驱动主动唤醒(设备可用产生中断,有中断来唤醒)等待队列中休眠的进程;
给休眠的进程发送信号(kill)唤醒等待队列中休眠的进程
如果超时时间到期(5秒到期),那么这个函数主动返回,进程主动被唤醒(由称超时唤醒)!如果是超时唤醒, schedhule_timeout返回为0,否则返回为非0!
9,一旦进程被唤醒以后,将当前进程从等待队列中进行移除!
remove_wait_queut(&wq,&wait);
10,设置当前进程的状态为运行状态,进程正式投入运行!
set current_state(TASK_RUNNING);
11,判断进程被唤醒的原因,如果是驱动主动唤醒 ,进程就应该合理的,正常的,有理由的继续访问设备(比如读取串口设备);如果是接收到kill信号引起的唤醒,进程就应该处理这个信号,而不是继续访问这个设备!如果以上的唤醒都是不成立,则就是超时唤醒

    if("如果是超时唤醒"){
        printk("进程是由超时引起的唤醒");
        return -EAGAIN;
    }else{//驱动唤醒和接收信号唤醒
        if(signal_pending(current)){
            printk("进程接收到信号引起的唤醒");
            return ERETARTSYS;
        }else{
             printk("进程由驱动主动唤醒");
             //继续访问设备
         }
    }

问:驱动如何主动唤醒休眠的进程呢?
答:“主动唤醒”:主动唤醒的前提是设备可用;
“如何判断设备可用”:设备可用,必然产生中断,通过中断处理函数的是否调用来判断设备是否可用!

案例:回顾之前的按键驱动
仅仅实现了一个中断处理函数!
案例:按键驱动,要求写进程唤醒读进程!

btn_drv.c

#include <linux/init.h>
#include <linux/moudule.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/input.h>//标准的键值KEY_UP,KEY_DOWN....
#include <linux/sched.h>//TASK_*
#include <linux/wait.h>//add_wait_queue....

//1,分配读进程的等待队列头对象
static wait_queue_head_t rwq;//里面保存休眠的读进程

//当应用程序调用read的时候,通过软中断到这里
//让读进程进入休眠状态(这里用schedule函数休眠)
static ssize_t btn_read(struct file *file,char __user *buf,size_t count,lofft_t *ppos){
    printk("读进程[%d]:PID[%d],将进入休眠状态\n",current->comm,current->pid);
    //1,分配保存当前进程的容器
    wait_queue_t wait;
    //2,初始化保存进程的容器,将当前进程填充到这个容器中
    init_waitqueue_entry(&wait,current);
    //3,将当前进程添加到睡眠队列中去
    add_wait_queue(&rwq,&wait);
    //4,设置当前进程的状态为可中断的休眠状态
    set_current_state(TASK_INTERRUPTIBLE);
    //5,当前进程进入真正的休眠状态
    schedule();//代码停止不动!静静的等待着被唤醒
    //6,一旦被唤醒 ,进程从schedule函数返回,设置当前进程的状态为运行
    set_current_state(TASK_RUNNING);
    //7,一旦被唤醒,进程从睡眠队列中进行移除
    remove_wait_queue(&rwq,&wait);
    //8,判断是哪种原因引起的进程唤醒
    if(signal_pending(current)){//判断当前进程是否接收到信号
        printk("读进程[%s]:PID[%d]接收到的信号\n",current->comm,current->pid);
        return -ERSETARTSYS;
    }
    //9,如果8不成立,说明是驱动主动唤醒
    printk("读进程[%s]:PID[%d]被写进程主动唤醒 \n",current->comm,current->pid);
    return count;
}

static ssize_t btn_write(struct file *file,char __user *buf,size_t count,lofft_t *ppos){
    printk("写进程[%s]:PID[%d]将唤醒读进程 \n",current->comm,current->pid);
    //唤醒读等待队列中休眠的读进程
    wake_up_interruptible(&rwq);
    return count;
}

//分配硬件操作集合
static struct file_opeartons  btn_fops={
    .owner = THIS_MODULE,
    .read = btn_read,
    .write = btn_write
}

//分配初始化混杂设备对象
static struct miscdevice btn_misc = {
     .minor = MISC_DYNAMDIC_MINOR,//动态分配次设备号
     .name = "mybtn",//   /dev/mybtn
     .fops = &btn_fops
}

static int btn_init(void){
     //注册混杂设备
     misc_register(&btn_misc);
     //2,初始化读进程的等待队列头
     init_waitqueue_head(&rwq);
     return 0;
}

static void  btn_exit(void){
     misc_deregister(&led_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>

int main(int argc,char *argv[]){
    int fd;
    int data;
    if(argc<2){
        printf("Usage \n %s <r|w> \n",argv[0]);
        return -1;
    }
    fd = open("/dev/mybtn",O_RDWR);
    if(fd<0){
       printf("open fail \n");
       return -1;
    }

    if(!strcmp(argv[1],"r")){//制造一个读进程
       //读进程,会休眠
       read(fd,&data,4);
    }eles if(!strcmp(argv[1],"w")){
       //写进程,不会休眠
       write(fd,"hello",6);       
    }
    close(fd);
    return 0;
}

案例:按键驱动,要求写进程唤醒读进程,读进程获取某个按键的按键值和按键的状态:

#include <linux/init.h>
#include <linux/moudule.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/input.h>//标准的键值KEY_UP,KEY_DOWN....
#include <linux/sched.h>//TASK_*
#include <linux/wait.h>//add_wait_queue....

//上报按键信息的数据结构
struct btn_event{
   int code;//上报的按钮的值
   int state;//上报的按键状态
}

//分配上报的按键信息
static struct btn_event g_data;

//1,分配读进程的等待队列头对象
static wait_queue_head_t rwq;//里面保存休眠的读进程

//当应用程序调用read的时候,通过软中断到这里
//让读进程进入休眠状态(这里用schedule函数休眠)
static ssize_t btn_read(struct file *file,char __user *buf,size_t count,lofft_t *ppos){
    printk("读进程[%d]:PID[%d],将进入休眠状态\n",current->comm,current->pid);
    //1,分配保存当前进程的容器
    wait_queue_t wait;
    //2,初始化保存进程的容器,将当前进程填充到这个容器中
    init_waitqueue_entry(&wait,current);
    //3,将当前进程添加到睡眠队列中去
    add_wait_queue(&rwq,&wait);
    //4,设置当前进程的状态为可中断的休眠状态
    set_current_state(TASK_INTERRUPTIBLE);
    //5,当前进程进入真正的休眠状态
    schedule();//代码停止不动!静静的等待着被唤醒
    //6,一旦被唤醒 ,进程从schedule函数返回,设置当前进程的状态为运行
    set_current_state(TASK_RUNNING);
    //7,一旦被唤醒,进程从睡眠队列中进行移除
    remove_wait_queue(&rwq,&wait);
    //8,判断是哪种原因引起的进程唤醒
    if(signal_pending(current)){//判断当前进程是否接收到信号
        printk("读进程[%s]:PID[%d]接收到的信号\n",current->comm,current->pid);
        return -ERSETARTSYS;
    }
    //9,如果8不成立,说明是驱动主动唤醒
    printk("读进程[%s]:PID[%d]被写进程主动唤醒 \n",current->comm,current->pid);

    //10,上报按键信息
    copy_to_user(buf,&g_data,sizeof(g_data));

    return count;
}

static ssize_t btn_write(struct file *file,char __user *buf,size_t count,lofft_t *ppos){
    printk("写进程[%s]:PID[%d]将唤醒读进程 \n",current->comm,current->pid);

    //赋值的代码要放到唤醒代码的前面
    g_data.code = KEY_UP;
    g_data.state = 1;//假如1表示按下

    //唤醒读等待队列中休眠的读进程
    wake_up_interruptible(&rwq);
    return count;
}

//分配硬件操作集合
static struct file_opeartons  btn_fops={
    .owner = THIS_MODULE,
    .read = btn_read,
    .write = btn_write
}

//分配初始化混杂设备对象
static struct miscdevice btn_misc = {
     .minor = MISC_DYNAMDIC_MINOR,//动态分配次设备号
     .name = "mybtn",//   /dev/mybtn
     .fops = &btn_fops
}

static int btn_init(void){
     //注册混杂设备
     misc_register(&btn_misc);
     //2,初始化读进程的等待队列头
     init_waitqueue_head(&rwq);
     return 0;
}

static void  btn_exit(void){
     misc_deregister(&led_misc);//卸载混杂设备
}

module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

struct btn_event{
    int code;
    int state;
};

int main(int argc,char *argv[]){
    int fd;
    struct btn_event data;
    if(argc<2){
        printf("Usage \n %s <r|w> \n",argv[0]);
        return -1;
    }
    fd = open("/dev/mybtn",O_RDWR);
    if(fd<0){
       printf("open fail \n");
       return -1;
    }

    if(!strcmp(argv[1],"r")){//制造一个读进程
       //读进程,会休眠
       read(fd,&data,sizeof(data));//由驱动上报的按键值
       printf("code = %d ,state = %d",data.code,data.state);
    }eles if(!strcmp(argv[1],"w")){
       //写进程,不会休眠
       write(fd,"hello",6);       
    }
    close(fd);
    return 0;
}

应用程序测试代码:

案例:把以上两个案例的驱动schedule换成schedule_timout(5*HZ)

案例:实现按键驱动,每当用户进行按键操作进,驱动能够上报按键对应的键值和按键状态;
提示:btn_write换成中断处理函数进行唤醒

#include <linux/init.h>
#include <linux/moudule.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/input.h>//标准的键值KEY_UP,KEY_DOWN....
#include <linux/sched.h>//TASK_*
#include <linux/wait.h>//add_wait_queue....

//上报按键信息的数据结构
struct btn_event{
   int code;//上报的按钮的值
   int state;//上报的按键状态
}
//声明按键硬件相关的数据结构
struct btn_resource{
    int irq;//中断号
    int gpio;//gpio
    int code;//键值
    char *name;//按键名称
};

//分配初始化按键信息
static struct btn_resource btn_info[] = {
    [0] = {
       .irq = IRQ_EINT(0),
       .gpio = S5PV210_GPH0(0),
       .code = KEY_UP,
       .name = "KEY_UP"
    },
    [1] = {
       .irq = IRQ_EINT(1),
       .gpio = S5PV210_GPH0(),
       .code = KEY_DOWN,
       .name = "KEY_DOWN"
    }
};

//分配上报的按键信息
static struct btn_event g_data;

//1,分配读进程的等待队列头对象
static wait_queue_head_t rwq;//里面保存休眠的读进程

//当应用程序调用read的时候,通过软中断到这里
//让读进程进入休眠状态(这里用schedule函数休眠)
static ssize_t btn_read(struct file *file,char __user *buf,size_t count,lofft_t *ppos){
    printk("读进程[%d]:PID[%d],将进入休眠状态\n",current->comm,current->pid);
    //1,分配保存当前进程的容器
    wait_queue_t wait;
    //2,初始化保存进程的容器,将当前进程填充到这个容器中
    init_waitqueue_entry(&wait,current);
    //3,将当前进程添加到睡眠队列中去
    add_wait_queue(&rwq,&wait);
    //4,设置当前进程的状态为可中断的休眠状态
    set_current_state(TASK_INTERRUPTIBLE);
    //5,当前进程进入真正的休眠状态
    schedule();//代码停止不动!静静的等待着被唤醒
    //6,一旦被唤醒 ,进程从schedule函数返回,设置当前进程的状态为运行
    set_current_state(TASK_RUNNING);
    //7,一旦被唤醒,进程从睡眠队列中进行移除
    remove_wait_queue(&rwq,&wait);
    //8,判断是哪种原因引起的进程唤醒
    if(signal_pending(current)){//判断当前进程是否接收到信号
        printk("读进程[%s]:PID[%d]接收到的信号\n",current->comm,current->pid);
        return -ERSETARTSYS;
    }
    //9,如果8不成立,说明是驱动主动唤醒
    printk("读进程[%s]:PID[%d]被写进程主动唤醒 \n",current->comm,current->pid);

    //10,上报按键信息
    copy_to_user(buf,&g_data,sizeof(g_data));

    return count;
}

static irqreturn_r button_isr(int irq,void *dev_id){
    //1,获取对应按键的结构体的首地址
    struct btn_resource *pdata = (struct btn_resource *)dev_id

    g_data.code = pdata->code;
    g_data.state = !gpio_get_value(pdata->gpio);//返回:按下是1,松开是0

    //唤醒读等待队列中休眠的读进程
    wake_up_interruptible(&rwq);
    return IRQ_HANDLE;
}

//分配硬件操作集合
static struct file_opeartons  btn_fops={
    .owner = THIS_MODULE,
    .read = btn_read,
    .write = btn_write
}

//分配初始化混杂设备对象
static struct miscdevice btn_misc = {
     .minor = MISC_DYNAMDIC_MINOR,//动态分配次设备号
     .name = "mybtn",//   /dev/mybtn
     .fops = &btn_fops
}

static int btn_init(void){
     int i;
     //注册混杂设备
     misc_register(&btn_misc);
     //2,初始化读进程的等待队列头
     init_waitqueue_head(&rwq);

     //注册中断处理函数和申请GPIO资源
     for(i=0;i<ARRAY_SIZE(btn_info);i++){
          gpio_request(btn_info[i].gpio,btn_info[i].name);
             request_irq(btn_info[i].irq,
                         button_isr,
                         IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                        &btn_info[i]);

     }
     return 0;
}

static void  btn_exit(void){

     //释放资源
     int i;
     for(i=0;i<ARRAY_SIZE(btn_info);i++){
          gpio_free(btn_info[i].gpio);
          free_irq(btn_info[i].irq,&btn_info[i]);     
     }
     misc_deregister(&led_misc);//卸载混杂设备
}

module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");

对应的测试程序为:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

struct btn_event{
    int code;
    int state;
};

int main(int argc,char *argv[]){
    int fd;
    struct btn_event data;
    if(argc<2){
        printf("Usage \n %s <r|w> \n",argv[0]);
        return -1;
    }
    fd = open("/dev/mybtn",O_RDWR);
    if(fd<0){
       printf("open fail \n");
       return -1;
    }
    while(1){
       read(fd,&data,sizeof(data));//读的时候会休眠,
       printf("code = %d ,state = %d",data.code,data.state);
    }
    close(fd);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值