Linux驱动开发基础__休眠与唤醒

news2024/9/23 15:32:58

目录

1 适用场景

2 内核函数

2.1 休眠函数

2.2 唤醒函数

3 驱动框架

4 编程

 4.1 gpio_key_drv.c

4.2 button_test.c

 4.3 Makefile


1 适用场景

在前面引入中断时,我们曾经举过一个例子: 

 妈妈怎么知道卧室里小孩醒了? 
休眠-唤醒:进去房间陪小孩一起睡觉,小孩醒了会吵醒她  不累,但是妈妈干不了活了 
当应用程序必须等待某个事件发生,比如必须等待按键被按下时,可以使用“休眠-唤醒”机制: 

  • APP 调用 read 等函数试图读取数据,比如读取按键; 
  • APP 进入内核态,也就是调用驱动中的对应函数,发现有数据则复制到用户空间并马上返回; 
  • 如果 APP 在内核态,也就是在驱动程序中发现没有数据,则 APP 休眠; 
  • 当有数据时,比如当按下按键时,驱动程序的中断服务程序被调用,它会记录数据、唤醒 APP; 
  • APP 继续运行它的内核态代码,也就是驱动程序中的函数,复制数据到用户空间并马上返回。 

 驱动中有数据时,下图中红线就是 APP1 的执行过程,涉及用户态、内核态:

驱动中没有数据时,APP1 在内核态执行到 drv_read 时会休眠。所谓休眠就是把自己的状态改为非 RUNNING,这样内核的调度器就不会让它运行。当按下按键,驱动程序中的中断服务程序被调用,它会记录数据,并唤醒 APP1。所以唤醒就是把程序的状态改为 RUNNING,这样内核的调度器有合适的时间就会让它运行。当 APP1 再次运行时,就会继续执行 drv_read 中剩下的代码,把数据复制回用户空间,返回用户空间。APP1 的执行过程如下图的红色实线所示,它被分成了 2段:

 值得注意的是,上面 2 个图中红线部分都属于 APP1 的“上下文”,或者这样说:红线所涉及的代码,都是 APP1 调用的。但是按键的中断服务程序,不属于APP1 的“上下文”,这是突如其来的,当中断发生时,APP1 正在休眠呢。 
在 APP1 的“上下文”,也就是在 APP1 的执行过程中,它是可以休眠的。 
在中断的处理过程中,也就是 gpio_key_irq 的执行过程中,它不能休眠:“中断”怎么能休眠?“中断”休眠了,谁来调度其他 APP 啊? 
所以,请记住:在中断处理函数中,不能休眠,也就不能调用会导致休眠的函数。 

2 内核函数

2.1 休眠函数

参考内核源码:include\linux\wait.h。 

 比较重要的参数就是: 

 wq:waitqueue,等待队列 休眠时除了把程序状态改为非 RUNNING 之外,还要把进程/进程放入wq 中,以后中断服务程序要从 wq 中把它取出来唤醒。 没有 wq 的话,茫茫人海中,中断服务程序去哪里找到你? 

condition :这可以是一个变量,也可以是任何表达式。表示“一直等待,直到 condition
为真”。 

2.2 唤醒函数

参考内核源码:include\linux\wait.h。 

 3 驱动框架

驱动框架如下:

要休眠的线程,放在 wq 队列里,中断处理函数从 wq 队列里把它取出来唤醒。 
所以,我们要做这几件事: 

  • 初始化 wq 队列 
  • 在驱动的 read 函数中,调用 wait_event_interruptible: 它本身会判断 event 是否为 FALSE,如果为 FASLE 表示无数据,则休眠。 当从 wait_event_interruptible 返回后,把数据复制回用户空间。 
  • 在中断服务程序里: 设置 event 为 TRUE,并调用 wake_up_interruptible 唤醒线程。 

4 编程

 4.1 gpio_key_drv.c

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>


struct gpio_key{
	int gpio;
	struct gpio_desc *gpiod;
	int flag;
	int irq;
} ;

static struct gpio_key *gpio_keys_100ask;

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_key_class;

static int g_key = 0;

static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);

/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	
	wait_event_interruptible(gpio_key_wait, g_key);
	err = copy_to_user(buf, &g_key, 4);
	g_key = 0;
	
	return 4;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_key_drv_read,
};


static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	int val;
	val = gpiod_get_value(gpio_key->gpiod);
	

	printk("key %d %d\n", gpio_key->gpio, val);
	g_key = (gpio_key->gpio << 8) | val;
	wake_up_interruptible(&gpio_key_wait);
	
	return IRQ_HANDLED;
}

/* 1. 从platform_device获得GPIO
 * 2. gpio=>irq
 * 3. request_irq
 */
static int gpio_key_probe(struct platform_device *pdev)
{
	int err;
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;
	enum of_gpio_flags flag;
		
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	count = of_gpio_count(node);
	if (!count)
	{
		printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
		return -1;
	}

	gpio_keys_100ask = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
	for (i = 0; i < count; i++)
	{
		gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag);
		if (gpio_keys_100ask[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}
		gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);
		gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
		gpio_keys_100ask[i].irq  = gpio_to_irq(gpio_keys_100ask[i].gpio);
	}

	for (i = 0; i < count; i++)
	{
		err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpio_keys_100ask[i]);
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_gpio_key", &gpio_key_drv);  /* /dev/100ask_gpio_key */

	gpio_key_class = class_create(THIS_MODULE, "100ask_gpio_key_class");
	if (IS_ERR(gpio_key_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_gpio_key");
		return PTR_ERR(gpio_key_class);
	}

	device_create(gpio_key_class, NULL, MKDEV(major, 0), NULL, "100ask_gpio_key"); /* /dev/100ask_gpio_key */
        
    return 0;
    
}

static int gpio_key_remove(struct platform_device *pdev)
{
	//int err;
	struct device_node *node = pdev->dev.of_node;
	int count;
	int i;

	device_destroy(gpio_key_class, MKDEV(major, 0));
	class_destroy(gpio_key_class);
	unregister_chrdev(major, "100ask_gpio_key");

	count = of_gpio_count(node);
	for (i = 0; i < count; i++)
	{
		free_irq(gpio_keys_100ask[i].irq, &gpio_keys_100ask[i]);
	}
	kfree(gpio_keys_100ask);
    return 0;
}


static const struct of_device_id ask100_keys[] = {
    { .compatible = "100ask,gpio_key" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "100ask_gpio_key",
        .of_match_table = ask100_keys,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init gpio_key_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&gpio_keys_driver); 
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit gpio_key_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(gpio_key_init);
module_exit(gpio_key_exit);

MODULE_LICENSE("GPL");


4.2 button_test.c


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

/*
 * ./button_test /dev/100ask_button0
 *
 */
int main(int argc, char **argv)
{
	int fd;
	int val;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	while (1)
	{
		/* 3. 读文件 */
		read(fd, &val, 4);
		printf("get button : 0x%x\n", val);
	}
	
	close(fd);
	
	return 0;
}


 4.3 Makefile

 


# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = # 板子所用内核源码的目录

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o



obj-m += gpio_key_drv.o

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/181642.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pytorch深度学习案例(一)——手写数学符号识别

文章目录前言简介数据集项目结构utils模块dataLoadermodelsplotShowtrain模块predict模块下载地址前言 在前面的两篇文章中我们介绍了现代计算机视觉中常见的结构化和非结构化的CNN模型&#xff0c;本篇我们将使用这些CNN模型在手写数学符号数据集上进行识别。 CNN模型的介绍请…

2022回顾

2022年回顾 前言 新年和亲朋好友的相聚差不多接近尾声&#xff0c;假期也所剩无几&#xff0c;开始静下心来写作&#xff0c;回顾一下我的2022年&#xff0c;看下自己去年 做得好的和不足&#xff0c;展望下2023&#xff0c;开始新一年的生活。&#xff08;因为是公历2023年…

Grafana 系列文章(一):基于 Grafana 的全栈可观察性 Demo

&#x1f4da;️Reference: https://github.com/grafana/intro-to-mlt 这是关于 Grafana 中可观察性的三个支柱的一系列演讲的配套资源库。 它以一个自我封闭的 Docker 沙盒的形式出现&#xff0c;包括在本地机器上运行和实验所提供的服务所需的所有组件。 Grafana 全栈可观察…

剑指 Offer 第9天 第10天

目录 剑指 Offer 42. 连续子数组的最大和 剑指 Offer 47. 礼物的最大价值 剑指 Offer 46. 把数字翻译成字符串 剑指 Offer 48. 最长不含重复字符的子字符串 剑指 Offer 42. 连续子数组的最大和 输入一个整型数组&#xff0c;数组中的一个或连续多个整数组成一个子数组。求所…

Python self用法详解

在定义类的过程中&#xff0c;无论是显式创建类的构造方法&#xff0c;还是向类中添加实例方法&#xff0c;都要求将 self 参数作为方法的第一个参数。例如&#xff0c;定义一个 Person 类&#xff1a;class Person: def__init__(self): print("正在执行构造方法") #…

大数据项目---电商数仓(三)

目录 1.即席查询_Presto概述 2.即席查询_Presto_Server的部署 3.即席查询_Presto_Server启动 4.即席查询_命令行客户端说明 5.即席查询_LZO说明 6.即席查询_Presto_web端口 ​编辑 7.即席查询_Presto使用注意事项/优化 8.即席查询_Kylin简介 9.即席查询_前置概念 10.即…

数据库系统结构、数据库系统的组成

文章目录一、数据库系统的三级模式结构1.模式&#xff08;逻辑模式&#xff09;2.外模式&#xff08;子模式、用户模式&#xff09;3.内模式&#xff08;存储模式&#xff09;二、数据库的二级映像功能1.外模式/模式映像2.模式/内模式映像3.实际应用三、数据库系统的组成---硬件…

安卓性能优化之内存优化

Java对象生命周期&#xff1a; 创建&#xff1a;为对象分配内存空间&#xff0c;构造对象应用&#xff1a;此时 对象至少被一个强引用持有不可见&#xff1a;未被任何强引用持有&#xff0c;进行可达性分析不可达&#xff1a;可达性分析为不可达&#xff0c;进入下一阶段收集&…

notes

等差&#xff1a;&#xff0c;求 解&#xff1a;、 &#xff0c;则 解&#xff1a; x系数y系数1412 由 得分母 &#xff1b;则分子为&#xff0c; 解&#xff1a;令 已知两边及夹角&#xff0c;可图解 解析几何条件转化 1.平行四边形条件的转化几何性质代数实现(1)对边平行斜…

【IoT】创业:如何找到可以主导的创业市场?

如果你想开始创业&#xff0c;开启一段不一样的旅程&#xff0c;那么你首先要做的就是选赛道&#xff01; 如何选择你的赛道、你的第一个市场呢&#xff1f; 换句话说就是&#xff0c;你如何选择自己的利基市场。 最大的市场&#xff0c;同时&#xff0c;它的需求范围也最广…

全国地级市1999—2020年污染物排放和环境治理相关指标(废水\废气\粉尘等)

工业废水、工业粉尘等污染物是影响居住环境的重要因素&#xff0c;也是在各项研究中常用的数据&#xff01;之前我们基于历年的《中国城市统计年鉴》整理了1999—2020年的人口相关数据和用地相关数据&#xff08;可查看之前推送的文章&#xff09;。在《中国城市统计年鉴》中也…

欧拉回路(模板+外加一些优化)

给定一张图&#xff0c;请你找出欧拉回路&#xff0c;即在图中找一个环使得每条边都在环上出现恰好一次。 输入格式 第一行包含一个整数t, t∈ {1,2}&#xff0c;如果t 1&#xff0c;表示所给图为无向图&#xff0c;如果t2&#xff0c;表示所给图为有向图。 第二行包含两个整数…

【MySQL】日志

https://www.cnblogs.com/myseries/p/10728533.html 在 MySQL 中&#xff0c;有多种不同的日志&#xff0c;包括错误日志、二进制日志、查询日志和慢查询日志&#xff0c;这些日志发挥着不同的作用。另外还有redo日志、undo日志和relay日志。 错误日志 错误日志是 MySQL 中最…

测试篇(四):测试用例的分类、按测试对象划分、按是否查看代码划分、你平时哪种测试方法用的多?、按照开发阶段划分

目录一、按测试对象划分1.1 界面测试1.2 可靠性测试1.3 容测性测试1.4 文档测试1.5 兼容性测试1.6 易用性测试1.7 安装卸载测试1.8 安全测试1.9 性能测试1.10 内存泄露测试1.11 弱网测试二、按是否查看代码划分2.1 黑盒测试2.2 白盒测试2.4 灰盒测试三、面试题&#xff1a;你平…

AcWing 327. 玉米田(状态压缩DP)

AcWing 327. 玉米田&#xff08;状态压缩DP&#xff09;一、问题二、分析1、思路2、状态表示3、状态转移4、循环设计5、初末状态三、代码一、问题 二、分析 1、思路 这道题与之前所讲解的AcWing 1064. 小国王&#xff08;状态压缩DP&#xff09;非常相似&#xff0c;所以如果…

ARM uboot 的源码目录分析

一、uboot的源码目录分析1 1、九鼎官方 uboot 和三星原版 uboot 对比 (1) 以九鼎官方的 uboot 为蓝本来学习的&#xff0c;以三星官方的这份为对照。 (2) 不同版本的 uboot 或者同一版本不同人移植的 uboot&#xff0c;可能目录结构和文件内容都有所不同。将来大家懂了后也可…

剑指 Offer 第8天

目录 剑指 Offer 10- I. 斐波那契数列 剑指 Offer 10- II. 青蛙跳台阶问题 剑指 Offer 63. 股票的最大利润 剑指 Offer 10- I. 斐波那契数列 写一个函数&#xff0c;输入 n &#xff0c;求斐波那契&#xff08;Fibonacci&#xff09;数列的第 n 项&#xff08;即 F(N)&#xf…

java并发编程面试题目及答案2(持续更新)

22、利用原子类手写 CAS 无锁 /** * 利用 cas 手写 锁 */ public class AtomicTryLock {private AtomicLong atomicLongnew AtomicLong(0);private Thread lockCurrentThread; /** * 1 表示锁已经被获取 0 表示锁没有获取 利用 cas 将 0 改为 1 成功则表示获取锁 * return */…

Linux find 命令

Linux find 命令用来在指定目录下查找文件。任何位于参数之前的字符串都将被视为欲查找的目录名。如果使用该命令时&#xff0c;不设置任何参数&#xff0c;则 find 命令将在当前目录下查找子目录与文件。并且将查找到的子目录和文件全部进行显示。语法find path -option [ -pr…

微服务-高并发-思路

一、为什么选择Go语言 Go 语言相对其它语言具有几点天然的优势&#xff1a; 语法简单&#xff0c;上手快性能高&#xff0c;编译快&#xff0c;开发效率也不低原生支持并发&#xff0c;协程模型是非常优秀的服务端模型&#xff0c;同时也适合网络调用部署方便&#xff0c;编译…