Linux驱动开发——内核延时操作和内核等待队列

简介: 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,将来驱动势必利用等待队列来实现休眠等待外设准备好数据。


等待队列的功能

等带队列能让用户进程在内核空间随时随地休眠、随时随地被唤醒。

等待队列中操作的对象就是进程。

等待队列=等待+队列 = 要休眠的进程排成一队,形成等待队列,这些休眠的进程要等待某个事件到来,事件没有发生就休眠去等待。


驱动编程实施步骤

这里举个类似下图的例子:

小鸡作为要休眠的进程(驱动完成)、鸡妈妈作为等待队列头(驱动完成)、老鹰作为进程调度器(由内核完成)。


20200101210020723.png

20200101210020723.png

具体编程步骤:


  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. 设置要休眠的当前进程的休眠状态,进程休眠状态的类型分为两类:
  2. 可中断的休眠类型(TASK_INTERRUPTIBLE):休眠器件可以立即处理接收的信号,此类休眠进程被唤醒的方式有两种:接收信号唤醒,驱动主动唤醒。
  3. 不可中断的休眠类型(TASK_UNINTERRUPTIBLE):休眠器件如果接收到信号,不会立即处理而是在被唤醒以后处理信号,此类休眠进程被唤醒的方法只有一种:驱动主动唤醒。
  4. 注意:此时此刻当前进程还没有正式休眠。
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;
}


  • 执行结果:

20200101220015662.png

示例代码(二)

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


  • 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


  • 执行结果:

20200101222006916.png

总结

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

相关文章
|
14天前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
14天前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
15天前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
15天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
17天前
|
负载均衡 算法 Linux
深入探索Linux内核调度机制:公平与效率的平衡####
本文旨在剖析Linux操作系统内核中的进程调度机制,特别是其如何通过CFS(完全公平调度器)算法实现多任务环境下资源分配的公平性与系统响应速度之间的微妙平衡。不同于传统摘要的概览性质,本文摘要将直接聚焦于CFS的核心原理、设计目标及面临的挑战,为读者揭开Linux高效调度的秘密。 ####
32 3
|
20天前
|
负载均衡 算法 Linux
深入探索Linux内核调度器:公平与效率的平衡####
本文通过剖析Linux内核调度器的工作机制,揭示了其在多任务处理环境中如何实现时间片轮转、优先级调整及完全公平调度算法(CFS),以达到既公平又高效地分配CPU资源的目标。通过对比FIFO和RR等传统调度策略,本文展示了Linux调度器如何在复杂的计算场景下优化性能,为系统设计师和开发者提供了宝贵的设计思路。 ####
32 6
|
20天前
|
消息中间件 安全 Linux
深入探索Linux操作系统的内核机制
本文旨在为读者提供一个关于Linux操作系统内核机制的全面解析。通过探讨Linux内核的设计哲学、核心组件、以及其如何高效地管理硬件资源和系统操作,本文揭示了Linux之所以成为众多开发者和组织首选操作系统的原因。不同于常规摘要,此处我们不涉及具体代码或技术细节,而是从宏观的角度审视Linux内核的架构和功能,为对Linux感兴趣的读者提供一个高层次的理解框架。
|
1月前
|
Linux 网络安全 数据安全/隐私保护
Linux 超级强大的十六进制 dump 工具:XXD 命令,我教你应该如何使用!
在 Linux 系统中,xxd 命令是一个强大的十六进制 dump 工具,可以将文件或数据以十六进制和 ASCII 字符形式显示,帮助用户深入了解和分析数据。本文详细介绍了 xxd 命令的基本用法、高级功能及实际应用案例,包括查看文件内容、指定输出格式、写入文件、数据比较、数据提取、数据转换和数据加密解密等。通过掌握这些技巧,用户可以更高效地处理各种数据问题。
92 8
|
1月前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
251 6
下一篇
DataWorks