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

总结

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

相关文章
|
4天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
19 9
|
2天前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
18 6
|
3天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
18 5
|
1天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
14 4
|
4天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
4天前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。
|
3天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
17 2
|
网络协议 NoSQL Linux
阿里云 Linux 内核优化实战(sysctl.conf 和 ulimits )
一、sysctl.conf优化Linux系统内核参数的配置文件为 /etc/sysctl.conf 和 /etc/sysctl.d/ 目录。其读取顺序为: /etc/sysctl.d/ 下面的文件按照字母排序;然后读取 /etc/sysctl.conf 。
8594 1
|
6天前
|
机器学习/深度学习 负载均衡 算法
深入探索Linux内核调度机制的优化策略###
本文旨在为读者揭开Linux操作系统中至关重要的一环——CPU调度机制的神秘面纱。通过深入浅出地解析其工作原理,并探讨一系列创新优化策略,本文不仅增强了技术爱好者的理论知识,更为系统管理员和软件开发者提供了实用的性能调优指南,旨在促进系统的高效运行与资源利用最大化。 ###
|
5天前
|
监控 网络协议 算法
Linux内核优化:提升系统性能与稳定性的策略####
本文深入探讨了Linux操作系统内核的优化策略,旨在通过一系列技术手段和最佳实践,显著提升系统的性能、响应速度及稳定性。文章首先概述了Linux内核的核心组件及其在系统中的作用,随后详细阐述了内存管理、进程调度、文件系统优化、网络栈调整及并发控制等关键领域的优化方法。通过实际案例分析,展示了这些优化措施如何有效减少延迟、提高吞吐量,并增强系统的整体健壮性。最终,文章强调了持续监控、定期更新及合理配置对于维持Linux系统长期高效运行的重要性。 ####