17_Linux中断

Linux中断

1.什么是中断?

CPU在正常运行期间,由外部或者内部引起的事件,让CPU停下当前正在运行的程序,转而去执行触发他的中断所对应的程序,这个就是中断。

举例:

我现在正在厨房做饭,突然电话响了,然后我关火去接电话,接完电话在回去开火继续做饭,这个过程就是中断的一个过程。

在这个看似简单的过程中,却涉及到了中断的几个过程,我们一起来看一下:

<1>电话铃声响了:中断请求

<2>我要去接电话:中断响应

<3>我关掉火:保护现场

<4>我接电话的过程:中断处理

<5>接完电话回到厨房开火:恢复现场

<6>继续做饭:中断返回

<7>如果我不接电话:中断屏蔽

2.什么是中断上下文,为什么会有中断上下文?

中断的存在可以极大的提高CPU的运行效率,但是中断会打断内核进程中的正常调度和运行,所以为保证系统实时性,中断服务程序必须足够简短,但实际应用中某些时候发生中断时必须处理大量的事物,这时候如果都在中断服务程序中完成,则会严重降低中断的实时性,基于这个原因,linux 系统提出了一个概念:把中断服务程序分为两部分:中断上文和中断下文。

中断上文:完成尽可能少且比较急的任务,中断上文的特点就是响应速度快。

中断下文:处理中断剩余的大量比较耗时间的任务,而且可以被新的中断打断。

举例:

我现在正在厨房做饭,突然电话响了,然后我关火去接电话,快递员打电话让我下楼去拿快递,接完电话叫我女朋友去下楼拿快递,然后我在回去开火继续做饭,这个过程就是中断上下文。

分析:

快递员打电话让我下去拿快递,这个事情很紧急,所以要快速处理,这个就是要在中断上文中完成。但是下楼拿快递这个过程非常耗时间,所以叫女朋友去拿快递,这个就是中断下文。下楼拿快递很耗时间,如果我不叫女朋友去帮我拿而是自己拿,等我拿完饭回来我锅里的菜是不是就凉了呀,同理,如果你在中断里面做很耗时间的时间,系统就会崩溃。

如果女朋友在去拿快递的过程中,突然口渴了,要去超市买水,所以,中断下半部分是可以被中断打断的。

总之:

中断上文越快越好,中断下文可以做比较耗时间的事情,但是你不能死循环。

没有比较耗时的事情,可以只写中断上文不写中断下文

3.Linux中断可以嵌套吗?

以前是可以,现在不可以。

一.设备树中的中断节点。

如果一个设备需要用到中断功能,开发人员就需要在设备树中配置好中断属性信息,因为设备树是用来描述硬件信息的,然后Linux内核通过设备树配置的中断属性来配置中断功能。

设备树中断的参考绑定文档:

Documentation/devicetree/bindings/arm/gic.txt

中断实际上是非常复杂的,但是作为开发人员,我们只需要关系怎么在设备树中指定中断,怎么在代码中获得中断就可以。其他的事情,比如设备树中的中断控制器,这些都是由原厂的BSP工程师帮我们写好了,我们不需要来修改他。

比如,在imx6ull.dtsi文件,其中的inc节点就是imx6ull的中断控制器节点,如下图所示:

img

比如,对于GPIO来说,GPIO的节点也可以作为中断控制器,在imx6ull.dtsi文件中GPIO1的节点内容如下图所示:

IMG_256

这些工作都是由原厂的BSP工程师来帮我们写好的,并不需要我们来写。除非将来你有机会去原厂工作,否则我们不会从头开始写一个设备树文件的。分工是非常明确的,我们需要关注的点是怎么在设备树里面描述一个外设的中断节点,我们来看一个例子。

key {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "key";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_key>;
	key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
	interrupt-parent = <&gpio1>;
	interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
	status = "okay"; 
}

在这个例子中,我们先使用pinctrl和gpio子系统把这个引脚设置为了gpio功能,因为我们在使用中断的时候需要把引脚设置成输入。然后使用interrupt-parent和interrupts属性来描述中断。interrupt-parent的属性值是gpio1,也就是他的要使用gpio1这个中断控制器,为什么是gpio1呢,因为我们的引脚使用的是gpio1里面的io18,所以我们使用的是gpio1这个中断控制器。interrupts属性设置的是中断源,为什么里面是俩个cells呢,因为我们在gpio1这个中断控制器里面#interrupt-cells的值为2,如下图所示:

img

例子中的第一个 cells 的 18 表示 GPIO1 组的 18 号 IO。IRQ_TYPE_EDGE_BOTH 表示上升沿和下降沿同时有效。

​ IRQ_TYPE_EDGE_BOTH 定义在文件 include/linux/irq.h 中, 定义如下

img

所以我们在设备树里面配置中断的时候只需要俩个步骤即可,第一个步骤是把管脚设置为gpio功能。第二个步骤是使用interrupt-parent和interrupts属性来描述中断。

如果使用 GPIO 的话,可以不在设备树文件里写interrupt-parent和interrupts属性,而使用 gpio_to_irq 函数来获取 gpio 对应的中断号

二.中断相关函数

头文件

#include <linux/interrupt.h>
#include <linux/of_irq.h>

<1>获取中断号相关函数

编写驱动的时候需要用到中断号,每一个中断都有中断号,我们用到中断号,中断信息已经写到了设备树里面,因此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,函数原型如下:

unsigned int irq_of_parse_and_map(struct device_node *dev,int index);

参数:

dev: 设备节点。

index:索引号, interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。

返回值:中断号。

如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下:

int gpio_to_irq(unsigned int gpio);

参数:

gpio: 要获取的 GPIO 编号。

返回值: GPIO 对应的中断号。如果某个GPIO引脚不支持中断,那么gpio_to_irq()函数会返回一个负值,表示出错。

<2>申请中断函数

同GPIO一样,在Linux内核里面,如果我们要使用某个中断也是需要申请的,申请中断我们使用的函数是 request_irq

函数原型:

int request_irq( unsigned int irq,
			  	irq_handler_t handler,
				unsigned long flags,
				const char *name,
				void *dev);

参数:

irq:要申请中断的中断号。

handler:中断处理函数,当中断发生以后就会执行此中断处理函数。

flags:中断标志。

中断标识可以在文件 include/linux/interrupt.h 里面查看所有的中断标志,这里我们介绍几个常用的中断标志,如下图所示:

img

name: 中断名字, 设置以后可以在/proc/interrupts 文件中看到对应的中断名字。

dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断, 一般情况下将 dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。

返回值:

0 中断申请成功, 其他负值 中断申请失败, 如果返回-EBUSY 的话表示中断已经被申请了。

<3>中断处理函数

使用 request_irq 函数申请中断的时候需要设置中断处理函数, 中断处理函数格式如下所示:

 irqreturn_t (*irq_handler_t) (int irq, void *args)

第一个参数是要中断处理函数要相应的中断号。

第二个参数是一个指向 void 的指针, 也就是个通用指针, 需要与 request_irq 函数的 dev 参数保持一致。 用于区分共享中断的不同设备, dev 也可以指向设备数据结构。

返回值为 irqreturn_t 类型, irqreturn_t 类型定义如下所示

enum irqreturn {
	IRQ_NONE = (0 << 0), //这个中断不是本驱动的中断, 不处理
	IRQ_HANDLED = (1 << 0), //正常处理
	IRQ_WAKE_THREAD = (1 << 1), //使用中断下文处理
};
typedef enum irqreturn irqreturn_t;

可以看出 irqreturn_t 是个枚举类型, 一共有三种返回值。

一般中断服务函数返回值使用如下形式:return IRQ_RETVAL(IRQ_HANDLED)

<4>free_irq 函数

中断使用完成以后就要通过 free_irq 函数释放掉相应的中断。 如果中断不是共享的, 那么 free_irq 会删除中断处理函数并且禁止中断。 free_irq 函数原型如下所示:

void free_irq(unsigned int irq,void *dev);

参数:

irq: 要释放的中断。

dev: 如果中断设置为共享(IRQF_SHARED)的话, 此参数用来区分具体的中断。

注意: 中断申请函数request_irq()与中断释放函数free_irq()的最后一个参数(void *dev 设备结构体)要保持一致,必须是同一个指针,不然中断会无法释放掉

共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

返回值: 无。

按键中断实验

设备树文件里不写interrupt-parent和interrupts属性

imx6ull-14x14-evk.dts

test_key{
	compatible = "keys";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_gpio_keys>;
	gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
};
pinctrl_gpio_keys: gpio-keys {
	fsl,pins = <
		MX6UL_PAD_UART1_CTS_B__GPIO1_IO18       0x80000000
	>;
};

driver.c

#include <linux/init.h>            //包含宏定义的头文件
#include <linux/module.h>          //包含初始化加载模块的头文件
#include <linux/platform_device.h> //平台设备所需要的头文件
#include <linux/of.h>              //of函数
#include <linux/of_address.h>      //of_iomap函数
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>

struct device_node *test_device_node; // 节点
int gpio_nu;                          // GPIO 编号
int irq;                              // GPIO 对应的中断号

IRQ_RETVAL(irqreturn_t test_key(int irq, void *args) // 中断处理函数
{
    char *str = args;
    printk("test_key is %s\n", str);
    return IRQ_RETVAL(IRQ_HANDLED); // 正常处理
}

int beep_probe(struct platform_device *pdev)
{
    int ret = 0;

    printk("beep_probe\n");
    test_device_node = of_find_node_by_path("/test_key"); // 查找根节点下的test_key节点
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error\n");
        return -1;
    }
    printk("test_device_node name is %s\n", test_device_node->name);

    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); // 获取GPIO标号
    if (gpio_nu < 0)
    {
        printk("of_get_named_gpio is error\n");
        return -1;
    }
    printk("of_get_named_gpio is ok\n");
    ret =  gpio_request(gpio_nu, "key0"); //申请一个 GPIO 管脚
    if(ret != 0)
    {
        printk("gpio_request is error\n");
        return -1;
    }
    printk("gpio_request is ok\n");
    ret = gpio_direction_input(gpio_nu); // 设置GPIO为输入
    if (ret < 0)
    {
        printk("gpio_direction_input is error\n");
        return -1;
    }
    printk("gpio_direction_input is ok\n");
    irq = gpio_to_irq(gpio_nu); // 获取 gpio 对应的中断号
    if (irq < 0)
    {
        printk("gpio_to_irq is error\n");
        return -1;
    }
    printk("irq is %d\n", irq);
    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", "test_args"); // 申请中断
    if (ret < 0)
    {
        printk("request_irq is error\n");
        return -1;
    }
    printk("request_irq is ok\n");
    return 0;
}

int beep_remove(struct platform_device *platform_device)
{
    printk("beep_remove\n");
    return 0;
}

struct platform_device_id beep_id_table = {
    .name = "123"};

struct of_device_id of_match_table_test[] = { // 与设备树的 compatible 匹配
    {
        .compatible = "keys"},
    {

    }};

/* platform 驱动结构体 */
struct platform_driver beep_platform_driver = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "beep_test",                   // 匹配优先级3
        .of_match_table = of_match_table_test, // 中的.compatible匹配优先级1
    },
    .id_table = &beep_id_table // 中的.name匹配优先级2
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("hello world\n");
    ret = platform_driver_register(&beep_platform_driver); // 注册平台驱动
    if (ret < 0)
    {
        printk("platform_driver_register is error\n");
        return ret;
    }
    return 0;
}

static void beep_driver_exit(void)
{
    printk("byby\n");
    gpio_free(gpio_nu); //释放GPIO
    free_irq(irq, NULL); //释放中断
    platform_driver_unregister(&beep_platform_driver); // 卸载 platform 驱动
}

module_init(beep_driver_init); // 驱动模块的入口
module_exit(beep_driver_exit); // 驱动模块的出口

MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证

Makefile

obj-m +=driver.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga 
PWD?=$(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

运行结果

image-20240426150556367

查看申请的中断

cat /proc/interrupts

image-20240426144343101

查看中断发生次数

cat /proc/irq/47/spurious

image-20240426150111265

设备树文件里写interrupt-parent和interrupts属性

test_key{
	compatible = "keys";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_gpio_keys>;
	gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
	
	interrupt-parent = <&gpio1>;
	interrupts = <18 IRQ_TYPE_EDGE_RISING>;
};

pinctrl_gpio_keys: gpio-keys {
	fsl,pins = <
		MX6UL_PAD_UART1_CTS_B__GPIO1_IO18       0x80000000
	>;
};

driver.c

#include <linux/init.h>            //包含宏定义的头文件
#include <linux/module.h>          //包含初始化加载模块的头文件
#include <linux/platform_device.h> //平台设备所需要的头文件
#include <linux/of.h>              //of函数
#include <linux/of_address.h>      //of_iomap函数
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>

struct device_node *test_device_node; // 节点
int gpio_nu;                          // GPIO 编号
int irq;                              // GPIO 对应的中断号

irqreturn_t test_key(int irq, void *args) // 中断处理函数
{
    printk("test_key is ok\n");
    return IRQ_RETVAL(IRQ_HANDLED); // 正常处理
}

int beep_probe(struct platform_device *pdev)
{
    int ret = 0;

    printk("beep_probe\n");
    test_device_node = of_find_node_by_path("/test_key"); // 查找根节点下的test_key节点
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error\n");
        return -1;
    }
    printk("test_device_node name is %s\n", test_device_node->name);

    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); // 获取GPIO标号
    if (gpio_nu < 0)
    {
        printk("of_get_named_gpio is error\n");
        return -1;
    }
    printk("of_get_named_gpio is ok\n");
    ret =  gpio_request(gpio_nu, "key0"); //申请一个 GPIO 管脚
    if(ret != 0)
    {
        printk("gpio_request is error\n");
        return -1;
    }
    printk("gpio_request is ok\n");
    ret = gpio_direction_input(gpio_nu); // 设置GPIO为输入
    if (ret < 0)
    {
        printk("gpio_direction_input is error\n");
        return -1;
    }
    printk("gpio_direction_input is ok\n");

    // irq = gpio_to_irq(gpio_nu); // 获取 gpio 对应的中断号
    irq = irq_of_parse_and_map(test_device_node, 0); //从 interupts 属性中提取到对应的设备号
    if (irq < 0)
    {
        printk("gpio_to_irq is error\n");
        return -1;
    }
    printk("irq is %d\n", irq);

    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL); // 申请中断
    if (ret < 0)
    {
        printk("request_irq is error\n");
        return -1;
    }
    printk("request_irq is ok\n");
    return 0;
}

int beep_remove(struct platform_device *platform_device)
{
    printk("beep_remove\n");
    return 0;
}

struct platform_device_id beep_id_table = {
    .name = "123"};

struct of_device_id of_match_table_test[] = { // 与设备树的 compatible 匹配
    {
        .compatible = "keys"},
    {

    }};

/* platform 驱动结构体 */
struct platform_driver beep_platform_driver = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "beep_test",                   // 匹配优先级3
        .of_match_table = of_match_table_test, // 中的.compatible匹配优先级1
    },
    .id_table = &beep_id_table // 中的.name匹配优先级2
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("hello world\n");
    ret = platform_driver_register(&beep_platform_driver); // 注册平台驱动
    if (ret < 0)
    {
        printk("platform_driver_register is error\n");
        return ret;
    }
    return 0;
}

static void beep_driver_exit(void)
{
    printk("byby\n");
    gpio_free(gpio_nu); //释放GPIO
    free_irq(irq, NULL);                               // 释放中断
    platform_driver_unregister(&beep_platform_driver); // 卸载 platform 驱动
}

module_init(beep_driver_init); // 驱动模块的入口
module_exit(beep_driver_exit); // 驱动模块的出口

MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证

Makefile

obj-m +=driver.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga 
PWD?=$(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

查看设备树节点和属性

image-20240426155906820

运行结果

image-20240426161216130

中断下文之tasklet

一.tasklet相关知识点

1. 什么是tasklet?

​ tasklet是中断处理中断下文常用的一种方法,tasklet是一种特殊的软中断。处理中断下文的机制还有工作队列和软中断。

2. 怎么使用tasklet来设计中断下文?

框图:

img

Linux把中断分成俩个部分,一个是上半部分,一个是下半部分,在上半部分我们只处理紧急的事情,同时可以调用tasklet来启动中断下文,比较耗时间的就要放到下文来处理,调用tasklet以后,tasklet绑定的函数并不会立马执行,而是出中断以后,经过一个很短的不确定时间在来执行。

3. tasklet定义

tasklet由tasklet_struct结构表示,每个结构体单独代表一个tasklet,在<linux/interrupt.h>中定义为:

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

1.next:链表中的下一个tasklet,方便管理和设置tasklet;

2.state: tasklet的状态。

3.count:表示tasklet是否出在激活状态,如果是0,就处在激活状态,如果非0,就处在非激活状态

4.void (*func)(unsigned long):结构体中的func成员是tasklet的绑定函数,data是它唯一的参数。

5.date:函数执行的时候传递的参数,另外可以区别是哪个tasklet_struct执行的函数

二.tasklet相关函数

头文件

#include <linux/interrupt.h>

<1>tasklet_schedule函数

作用:调度tasklet

函数原型:

void tasklet_schedule(struct tasklet_struct *t);

参数:指向tasklet_struct结构的指针。

<2>tasklet_init函数

作用:动态初始化tasklet

函数原型:

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

参数:

*t:指向tasklet_struct结构的指针。

func:tasklet绑定的函数。

data:函数执行的时候传递的参数。

<3>tasklet_kill函数

功能:删除一个tasklet

函数原型:

tasklet_kill(struct tasklet_struct *t);

参数:指向tasklet_struct结构的指针

注意:这个函数会等待tasklet执行完毕,然后再将它移除。该函数可能会引起休眠,所以要禁止在中断上下文中使用。

三.使用tasklet设计中断下文步骤

步骤一:定义一个tasklet结构体

步骤二:动态初始化tasklet

步骤三:编写tasklet绑定的函数

步骤四:在中断上文调用tasklet

步骤五:卸载模块的时候删除tasklet

示例: 实现按一下 key0 按键, 打印 99-0

driver.c

#include <linux/init.h>            //包含宏定义的头文件
#include <linux/module.h>          //包含初始化加载模块的头文件
#include <linux/platform_device.h> //平台设备所需要的头文件
#include <linux/of.h>              //of函数
#include <linux/of_address.h>      //of_iomap函数
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>

struct device_node *test_device_node; // 节点
int gpio_nu;                          // GPIO 编号
int irq;                              // GPIO 对应的中断号
char *args = "test_args"; //中断处理函数的参数

struct tasklet_struct key_tasklet;

void test(unsigned long data)
{
    int i = data;
    while (i--)
    {
        printk("test i is %d\n", i);
    }
}

irqreturn_t test_key(int irq, void *args) // 中断处理函数
{
    printk("start\n");
    tasklet_schedule(&key_tasklet); // 调度tasklet
    printk("end\n");
    return IRQ_RETVAL(IRQ_WAKE_THREAD); // 使用中断下文处理
}

int beep_probe(struct platform_device *pdev)
{
    int ret = 0;

    printk("beep_probe\n");
    test_device_node = of_find_node_by_path("/test_key"); // 查找根节点下的test_key节点
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error\n");
        return -1;
    }
    printk("test_device_node name is %s\n", test_device_node->name);

    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); // 获取GPIO标号
    if (gpio_nu < 0)
    {
        printk("of_get_named_gpio is error\n");
        return -1;
    }
    printk("of_get_named_gpio is ok\n");
    ret =  gpio_request(gpio_nu, "key0"); //申请一个 GPIO 管脚
    if(ret != 0)
    {
        printk("gpio_request is error\n");
        return -1;
    }
    printk("gpio_request is ok\n");
    ret = gpio_direction_input(gpio_nu); // 设置GPIO为输入
    if (ret < 0)
    {
        printk("gpio_direction_input is error\n");
        return -1;
    }
    printk("gpio_direction_input is ok\n");

    // irq = gpio_to_irq(gpio_nu); // 获取 gpio 对应的中断号
    irq = irq_of_parse_and_map(test_device_node, 0); // 从 interupts 属性中提取到对应的设备号
    if (irq < 0)
    {
        printk("gpio_to_irq is error\n");
        return -1;
    }
    printk("irq is %d\n", irq);

    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", args); // 申请中断
    if (ret < 0)
    {
        printk("request_irq is error\n");
        return -1;
    }
    printk("request_irq is ok\n");

    tasklet_init(&key_tasklet, test, 100); // 动态初始化tasklet
    return 0;
}

int beep_remove(struct platform_device *platform_device)
{
    printk("beep_remove\n");
    return 0;
}

struct platform_device_id beep_id_table = {
    .name = "123",
};

struct of_device_id of_match_table_test[] = {
    // 与设备树的 compatible 匹配
    {
        .compatible = "keys",
    },
    {

    },
};

/* platform 驱动结构体 */
struct platform_driver beep_platform_driver = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "beep_test",                   // 匹配优先级3
        .of_match_table = of_match_table_test, // 中的.compatible匹配优先级1
    },
    .id_table = &beep_id_table // 中的.name匹配优先级2
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("hello world\n");
    ret = platform_driver_register(&beep_platform_driver); // 注册平台驱动
    if (ret < 0)
    {
        printk("platform_driver_register is error\n");
        return ret;
    }
    return 0;
}

static void beep_driver_exit(void)
{
    printk("byby\n");
    gpio_free(gpio_nu); //释放GPIO
    free_irq(irq, args);                               // 释放中断,要和request_irq参数一致
    tasklet_kill(&key_tasklet);                        // 删除一个tasklet
    platform_driver_unregister(&beep_platform_driver); // 卸载 platform 驱动
}

module_init(beep_driver_init); // 驱动模块的入口
module_exit(beep_driver_exit); // 驱动模块的出口

MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证

Makefile

obj-m +=driver.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga 
PWD?=$(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

运行结果

image-20240426180423861

等待队列

一.阻塞与非阻塞的概念

​ 阻塞:当前设备如果不可读或不可写时,也就是不能获得资源的时候,那么当前进程程会被挂起。只有当设备满足条件的时候才可以返回。默认情况下,文件都是以这种方式打开。

​ 非阻塞:当前设备不可读或不可写时,该函数不会阻塞当前进程,要么放弃,要么不停的查询,或者直到可以操作为止。

读写函数是否阻塞可以通过参数来指定:

fd = open(filepath,O_RDWR);            //默认阻塞打开
fd = open(filepath,O_RDWR|O_NONBLOCK); //非阻塞方式打开

二.等待队列基础知识

​ 当我们进程去访问设备的时候,经常需要等待有特定事件发生以后在继续往下运行,这个时候就需要在驱动里面实现当条件不满足的时候进程休眠,当条件满足的时候在由内核唤醒进程。那么等待队列就实现了在事件上的条件等待。

<1>等待队列头

​ 等待队列头就是一个等待队列的头部, 每个访问设备的进程都是一个队列项, 当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。

​ 等待队列头使用结构体wait_queue_head_t来表示,这个结构体定义在文件include/linux/wait.h里面,结构体内容如下:

struct __wait_queue_head {
spinlock_t lock; //自旋锁
 	struct list_head task_list; //链表头
};
typedef struct __wait_queue_head wait_queue_head_t;

类型名是wait_queue_head_t,只需要记住这个即可。

定义一个等待队列头:

wait_queue_head_t  test_wq; //定义一个等待队列的头

定义等待队列头以后需要初始化,可以使用init_waitqueue_head函数初始化等待队列头, 函数原型如下:

void init_waitqueue_head(wait_queue_head_t *q)

也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义和初始化。

DECLARE_WAIT_QUEUE_HEAD (wait_queue_head_t *q);

#define DECLARE_WAIT_QUEUE_HEAD(name) \
	wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

三.等待队列相关函数

头文件
#include <linux/wait.h>
#include <linux/sched.h>

<1>init_waitqueue_head宏

原型:

void init_waitqueue_head(wait_queue_head_t *q);

作用:动态初始化等待队列头结构

参数:

q是wait_queue_head_t 指针

<2>wait_event宏

原型:

wait_event(wq, condition);

功能:不可中断的阻塞等待,让调用进程进入不可中断的睡眠状态,在等待队列里面睡眠直到condition变成真,被内核唤醒。

参数:

wq :wait_queue_head_t 类型变量。

​ condition 为等待的条件,为假时才可以进入休眠

注意:调用的时要确认condition 值是真还是假,如果调用condition为真,则不会休眠。

<3>wait_event_interruptible宏

原型:

wait_event_interruptible(wq, condition);

功能:可中断的阻塞等待,让调用进程进入可中断的睡眠状态,直到condition变成真被内核唤醒或被信号打断唤醒。

参数:

wq :wait_queue_head_t 类型变量。

condition: 为等待的条件,为假时才可以进入休眠

返回:判断condition是否为真,如果为真则返回,否则检查如果进程是被信号唤醒,会返回-ERESTARTSYS错误码.如果是 condition 为真,则 返回 0

<4>wake_up宏

原型:

void wake_up(wait_queue_head_t *q);

功能:唤醒所有休眠进程,需要等待条件为真

参数:

x : 等待队列头结构指针。

<5>wake_up_interruptible宏

原型:

void wake_up_interruptible(wait_queue_head_t *q);

功能:唤醒可中断的休眠进程,需要等待条件为真

参数:

​ x : 等待队列头结构指针。

等待队列示例

当按键按下的时候, 再去读取 value 的信息

driver.c

#include <linux/init.h>            //包含宏定义的头文件
#include <linux/module.h>          //包含初始化加载模块的头文件
#include <linux/platform_device.h> //平台设备所需要的头文件
#include <linux/of.h>              //of函数
#include <linux/of_address.h>      //of_iomap函数
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched.h>

struct device_node *test_device_node; // 节点
int gpio_nu;                          // GPIO 编号
int irq;                              // GPIO 对应的中断号
char *args = "test_args"; //中断处理函数的参数
int value = 0; //用来模拟管脚的状态
DECLARE_WAIT_QUEUE_HEAD (key_wq); //等待队列头的定义和初始化
int wq_flags = 0; //等待的条件,为真时唤醒

irqreturn_t test_key(int irq, void *args) // 中断处理函数
{
    value = !value; //取反
    wq_flags = 1; //设置等待条件为真
    wake_up_interruptible(&key_wq); //唤醒可中断的休眠进程,需要等待条件为真
    return IRQ_RETVAL(IRQ_HANDLED); // 正常处理
}

int misc_open(struct inode *node, struct file *file)
{
	printk("hello misc_open \n");
	return 0;
}

int misc_release(struct inode *node, struct file *file)
{
	printk("hello misc_release bye bye\n");
	return 0;
}

ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    wait_event_interruptible(key_wq, wq_flags); //可中断的阻塞等待
	if (copy_to_user(ubuf, &value, sizeof(value)) != 0)
	{
		printk("copy_to_user error\n");
		return -1;
	}
    wq_flags = 0; //重置等待条件
	return 0;
}

//文件操作集
struct file_operations misc_fops = {
	.owner = THIS_MODULE,
	.open = misc_open,
	.release = misc_release,
	.read = misc_read};

struct miscdevice misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "test_wq",
	.fops = &misc_fops,
};

int beep_probe(struct platform_device *pdev)
{
    int ret = 0;

    printk("beep_probe\n");
    test_device_node = of_find_node_by_path("/test_key"); // 查找根节点下的test_key节点
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error\n");
        return -1;
    }
    printk("of_find_node_by_path is ok\n");
    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); // 获取GPIO标号
    if (gpio_nu < 0)
    {
        printk("of_get_named_gpio is error\n");
        return -1;
    }
    printk("of_get_named_gpio is ok\n");
    ret =  gpio_request(gpio_nu, "key0"); //申请一个 GPIO 管脚
    if(ret != 0)
    {
        printk("gpio_request is error\n");
        return -1;
    }
    printk("gpio_request is ok\n");
    ret = gpio_direction_input(gpio_nu); // 设置GPIO为输入
    if (ret < 0)
    {
        printk("gpio_direction_input is error\n");
        return -1;
    }
    printk("gpio_direction_input is ok\n");
    
    // irq = gpio_to_irq(gpio_nu); // 获取 gpio 对应的中断号
    irq = irq_of_parse_and_map(test_device_node, 0); // 从 interupts 属性中提取到对应的设备号
    if (irq < 0)
    {
        printk("gpio_to_irq is error\n");
        return -1;
    }
    printk("irq is %d\n", irq);

    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", args); // 申请中断
    if (ret < 0)
    {
        printk("request_irq is error\n");
        return -1;
    }
    printk("request_irq is ok\n");

    ret = misc_register(&misc_dev); //注册杂项设备
	if (ret < 0)
	{
		printk("misc_register is error\n");
		return -1;
	}
	printk("misc_register is ok\n");
    return 0;
}

int beep_remove(struct platform_device *platform_device)
{
    printk("beep_remove\n");
    return 0;
}

struct platform_device_id beep_id_table = {
    .name = "123",
};

struct of_device_id of_match_table_test[] = {
    // 与设备树的 compatible 匹配
    {
        .compatible = "keys",
    },
    {

    },
};

/* platform 驱动结构体 */
struct platform_driver beep_platform_driver = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "beep_test",                   // 匹配优先级3
        .of_match_table = of_match_table_test, // 中的.compatible匹配优先级1
    },
    .id_table = &beep_id_table // 中的.name匹配优先级2
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("hello world\n");
    ret = platform_driver_register(&beep_platform_driver); // 注册平台驱动
    if (ret < 0)
    {
        printk("platform_driver_register is error\n");
        return ret;
    }
    return 0;
}

static void beep_driver_exit(void)
{
    printk("byby\n");
    gpio_free(gpio_nu); //释放GPIO
    free_irq(irq, args);                               // 释放中断,要和request_irq参数一致
    misc_deregister(&misc_dev);
    platform_driver_unregister(&beep_platform_driver); // 卸载 platform 驱动
}

module_init(beep_driver_init); // 驱动模块的入口
module_exit(beep_driver_exit); // 驱动模块的出口

MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
	int fd;
	int value;
	// 打开设备节点
	fd = open("/dev/test_wq", O_RDWR);
	if (fd < 0)
	{
		// 打开设备节点失败
		perror("open error \n");
		return fd;
	}
	while (1)
	{
		read(fd, &value, sizeof(value));
		printf("value is %d \n", value);
	}
	close(fd);
	return 0;
}

Makefile

obj-m +=driver.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga 
PWD?=$(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

运行结果

image-20240427145419549

工作队列

一.工作队列的基础概念

1. 什么是工作队列?

工作队列(workqueue)是实现中断下文的机制之一,是一种将工作推后执行的形式。那工作队列和我们之前学的tasklet机制有什么不同呢?tasklet也是实现中断下文的机制。他们俩个最主要的区别是tasklet不能休眠,而工作队列是可以休眠的。所以,tasklet可以用来处理比较耗时间的事情,而工作队列可以处理非常复杂并且更耗时间的事情。

2.工作队列(workqueue)的工作原理

Linux系统在启动期间会创建内核线程,该线程创建以后就处于sleep状态,然后这个线程会一直去队列里面读,看看有没有任务,如果有就执行,如果没有就休眠。工作队列的实现机制实际上是非常复杂的,初学阶段只需要了解这些基本概念接口。

类比理解:

img

​ 流水线上的机械:Linux系统自动会创建一个。多种不同的物料使用同一个流水线机械,那么这个就是共享工作队列的概念。

​ 如果当前的流水线机械不能满足我们加工的物料,我们是不是就需要重新定制一台流水线机器呢,这个就是自定义工作队列的概念。

​ 共享工作队列有什么有缺点呢?

不需要自己创建,但是如果前面的工作比较耗时间,就会影响后面的工作。

​ 自定义工作队列有什么优缺点呢?

需要自己创建,系统开销大。优点是不会受到其他工作的影响。(因为这个流水线就是专门加工这一种零件的。)

二.工作队列相关API

头文件

#include <linux/workqueue.h>

尽管工作队列的实现机制非常复杂,但是我们使用工作队列其实就是在这个流水线上添加自己的物料,然后等待执行即可。

一个具体的工作(类比就是流水线上的物料)我们使用结构体work_struct来描述的,定义在Linux\work\queue.h里面。

所以我们使用工作队列的第一步就是先定义个工作队列。

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t   func;
};

在这个结构体里面我们只需要关注func这个成员就可以了,他是是一个函数指针,因为我们要把我们需要完成的工作写在这个函数里面。

<1>宏DECLARE_WORK

原型:

#define DECLARE_WORK(n, f) 

作用:静态定义并且初始化工作队列。

示例:

static void ft5x06_func(struct work_struct *data)
{
    
}
DECLARE_WORK(ft5x06_work, ft5x06_func);

<2>宏INIT_WORK

原型:

#define INIT_WORK(_work, _func)

作用:动态定义并初始化工作队列

参数:

work:工作队列地址;

func :工作函数

void test(struct work_struct *data)
{
    
}

举例:

struct work_struct test;    
在模块的初始化函数中:
INIT_WORK(&test, func);
相当于:
DECLARE_WORK(test, func);

<3>schedule_work

原型:

int schedule_work(struct work_struct *work);

作用:调度工作,把work_struct挂到CPU相关的工作结构队列链表上,等待工作者线程处理。

参数:_work 工作队列地址;

需要注意的是,如果调度完工作,并不会马上执行,只是加到了共享的工作队列里面去,等轮到他才会执行。

如果我们多次调用相同的任务,假如上一次的任务还没有处理完成,那么多次调度相同的任务是无效的

工作队列举例

driver.c

#include <linux/init.h>            //包含宏定义的头文件
#include <linux/module.h>          //包含初始化加载模块的头文件
#include <linux/platform_device.h> //平台设备所需要的头文件
#include <linux/of.h>              //of函数
#include <linux/of_address.h>      //of_iomap函数
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/workqueue.h> //工作队列相关头文件

struct device_node *test_device_node; // 节点
int gpio_nu;                          // GPIO 编号
int irq;                              // GPIO 对应的中断号
char *args = "test_args"; //中断处理函数的参数

struct work_struct key_test; //工作队列

void test(struct work_struct *data) //工作函数
{
    int i = 100;
    while (i--)
    {
        printk("test i is %d\n", i);
    }
}

irqreturn_t test_key(int irq, void *args) // 中断处理函数
{
    printk("start\n");
    schedule_work(&key_test); //调度工作,把work_struct挂到CPU相关的工作结构队列链表上,等待工作者线程处理。
    printk("end\n");
    return IRQ_RETVAL(IRQ_WAKE_THREAD); // 使用中断下文处理
}

int beep_probe(struct platform_device *pdev)
{
    int ret = 0;

    printk("beep_probe\n");
    test_device_node = of_find_node_by_path("/test_key"); // 查找根节点下的test_key节点
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error\n");
        return -1;
    }
    printk("test_device_node name is %s\n", test_device_node->name);

    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); // 获取GPIO标号
    if (gpio_nu < 0)
    {
        printk("of_get_named_gpio is error\n");
        return -1;
    }
    printk("of_get_named_gpio is ok\n");
    ret =  gpio_request(gpio_nu, "key0"); //申请一个 GPIO 管脚
    if(ret != 0)
    {
        printk("gpio_request is error\n");
        return -1;
    }
    ret = gpio_direction_input(gpio_nu); // 设置GPIO为输入
    if (ret < 0)
    {
        printk("gpio_direction_input is error\n");
        return -1;
    }
    printk("gpio_direction_input is ok\n");

    // irq = gpio_to_irq(gpio_nu); // 获取 gpio 对应的中断号
    irq = irq_of_parse_and_map(test_device_node, 0); // 从 interupts 属性中提取到对应的设备号
    if (irq < 0)
    {
        printk("gpio_to_irq is error\n");
        return -1;
    }
    printk("irq is %d\n", irq);

    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", args); // 申请中断
    if (ret < 0)
    {
        printk("request_irq is error\n");
        return -1;
    }
    printk("request_irq is ok\n");

    INIT_WORK(&key_test, test); //动态定义并初始化工作队列
    return 0;
}

int beep_remove(struct platform_device *platform_device)
{
    printk("beep_remove\n");
    return 0;
}

struct platform_device_id beep_id_table = {
    .name = "123",
};

struct of_device_id of_match_table_test[] = {
    // 与设备树的 compatible 匹配
    {
        .compatible = "keys",
    },
    {

    },
};

/* platform 驱动结构体 */
struct platform_driver beep_platform_driver = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "beep_test",                   // 匹配优先级3
        .of_match_table = of_match_table_test, // 中的.compatible匹配优先级1
    },
    .id_table = &beep_id_table // 中的.name匹配优先级2
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("hello world\n");
    ret = platform_driver_register(&beep_platform_driver); // 注册平台驱动
    if (ret < 0)
    {
        printk("platform_driver_register is error\n");
        return ret;
    }
    return 0;
}

static void beep_driver_exit(void)
{
    printk("byby\n");
    gpio_free(gpio_nu); //释放GPIO
    free_irq(irq, args);                               // 释放中断,要和request_irq参数一致
    platform_driver_unregister(&beep_platform_driver); // 卸载 platform 驱动
}

module_init(beep_driver_init); // 驱动模块的入口
module_exit(beep_driver_exit); // 驱动模块的出口

MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证

Makefile

obj-m +=driver.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga 
PWD?=$(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules
posted @ 2024-04-30 15:29  爱吃冰激凌的黄某某  阅读(22)  评论(0编辑  收藏  举报