• 韦东山嵌入式Liunx入门驱动开发四



    本人学习完韦老师的视频,因此来复习巩固,写以笔记记之。
    韦老师的课比较难,第一遍不知道在说什么,但是坚持看完一遍,再来复习,基本上就水到渠成了。
    看完视频复习的同学观看最佳!
    基于 IMX6ULL-PRO
    参考视频 Linux快速入门到精通视频
    参考资料:01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板.pdf

    一、异常与中断的概念及处理流程

    1-1 中断的引入

    首先中断属于一种异常
    中断需要保存现场(各类寄存器)、处理异常、恢复现场。
    1、初始化:①设置中断源,让它可以产生中断;②设置中断控制器 (可以屏蔽某个中断,优先级);③设置 CPU总开关 (使能中断 )
    2、执行其他程序 正常进行
    3、产生中断 :比如 按下按键 —>中断控制器 —>CPU
    4、CPU 每执行完一条指令都会检查有无中断 /异常产生;
    5、CPU发现有中断 /异常产生,开始处理。
    对于不同的异常,跳去不同的地址执行程序。这地址是异常向量表,只是一条跳转指令,跳去执行某个函数,
    “ldr pc,_irq”:cpu首先跳转到0x18偏移地址,然后将_irq的地址传入pc指针,再跳转了_irq的地址去执行某个函数
    在这里插入图片描述6、某个函数做什么事情
    ① 保护现场(各种寄存器的值)
    ② 处理异常(中断):判断中断源,在调用对应的处理函数(如按键、网卡、USB等)
    ③ 恢复现场

    1-2 栈
    (1) CPU实现a = a+b的过程

    ARM芯片属于精简指令集计算机 (RISC Reduced Instruction Set
    Computing),它所用的指令比较简单,有如下特点:
    ① 对内存只有读、写指令
    ② 对于数据的运算是在CPU内部实现
    ③ 使用RISC指令的CPU复杂度小一点,易于设计
    实现a = a+b
    CPU 运行时,先去取得指令,再执行指令:
    ① 把内存a 的值读入CPU 寄存器R0
    ② 把内存b 的值读入CPU 寄存器R1
    ③ 把R0、R1 累加,存入R0
    ④ 把R0 的值写入内存a
    在这里插入图片描述

    (2) 进程与线程

    在Linux 中:资源分配的单位是进程,调度的单位是线程。
    进程

    • 进程是程序的一次执行过程,是操作系统进行资源分配和调度的基本单位。每个进程都拥有独立的内存空间,包括代码、数据和堆栈等。
    • 每个进程都由操作系统分配唯一的进程标识符(PID),并且拥有自己的地址空间、文件描述符、系统资源等。
    • 进程之间通常是相互独立的,彼此不会直接影响到对方的运行状态(除非通过特定的IPC机制进行通信)。
    • 操作系统通过调度算法来管理进程的执行顺序,以及为每个进程分配处理器时间。

    线程

    • 线程是进程中的实际执行单位,一个进程可以包含多个线程,它们共享同一个进程的地址空间和系统资源,但是每个线程有独立的栈空间
    • 线程之间可以方便地共享数据,因为它们处于同一个进程的上下文中。
    • 多线程程序可以充分利用多核处理器的并行性能,提高程序的执行效率。
    • 线程的切换开销通常比进程小,因为它们共享了很多资源,如内存空间等。

    多线程编程,读取按键是一个线程,播放音乐是另一个线程,它们之间可以通过全局变量传递数据,示意代码如下:

    int g_key;
    void key_thread_fn()
    {
    	while(1){
    		if(g_key != -1){
    			switch(g_key){
    				case NEXT:
    					select_next_music(); /*在GUI选中下一首歌*/
    					break;
    			}
    		}
    	}
    }
    
    void music_fn()
    {
    	while(1){
    		if(g_key == STOP)
    			stop_music();
    		else{
    			send_music();
    		}
    	}
    }
    
    int main(int argc, char **argv)
    {
    	int key;
    	create_threads(key_tehread_fn);
    	create_threads(music_fn);
    
    	while(1){
    		sleep(10);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    1-3 Linux系统对中断处理的演进

    Linux系统中断
    在Linux 中,中断处理程序的执行也可能会影响进程的调度情况,例如通过唤醒等待中的进程,或者改变进程的优先级等。
    中断耗时处理方法
    1、分为上下半部分进行执行。上半部:处理紧急事情,无法被打断;下半部:可以被打断
    2、用内核线程处理中断
    系统中有硬件中断,也有软件中断。对硬件中断的处理有 2 个原则:不能嵌套,越快越好。
    硬件中断:每个硬件中断都有对应的处理函数,比如按键中断、网卡中断的处理函数肯定不一样。
    简单的认为硬件中断的处理是用数组来实现的,数组里存放的是函数指针。当发生A中断时,对应的irq_function_A 函数被调用。硬件导致该函数被调用。
    在这里插入图片描述
    软件中断:由软件决定,对于X号软件中断,只需要把它的flag 设置为1就表示发生了该中断。在处理完硬件中断后,再去处理软件中断。
    在这里插入图片描述
    在Linux 系统中使用中断,可以使用request_irq函数为某个中断irq注册中断处理函数handler,handler运行中断的上半部分,并且触发软中断或者把工作放入工作队列,使用线程化来处理中断下半部分。

    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    	    const char *name, void *dev)
    {
    	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    中断下半部的实现有很多种方法,2种主要的:tasklet、work queue(工作队列)。
    当下半部比较耗时但是能忍受,并且它的处理比较简单时,可以用tasklet来处理下半部。tasklet是使用软件中断来实现。
    在Linux中,中断是不会被嵌套执行的,但是中断处理程序中可能会触发延迟处理机制,导致在下半部处理中处理其他中断请求。

    enum
    {
    	HI_SOFTIRQ=0,
    	TIMER_SOFTIRQ,
    	NET_TX_SOFTIRQ,
    	NET_RX_SOFTIRQ,
    	BLOCK_SOFTIRQ,
    	IRQ_POLL_SOFTIRQ,
    	TASKLET_SOFTIRQ, /*通过软中断机制实现的,允许将一些需要延迟处理的任务放入任务队列中,在适当的时机执行。*/
    	SCHED_SOFTIRQ,
    	HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the
    			    numbering. Sigh! */
    	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
    
    	NR_SOFTIRQS
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    中断要做的事情实在太耗时,应该用内核线程来做:在中断上半部唤醒内核线程。

    最新的中断处理方法
    在这里插入图片描述

    request_threaded_irq(unsigned int irq, irq_handler_t handler,
    		     irq_handler_t thread_fn,
    		     unsigned long flags, const char *name, void *dev);
    
    • 1
    • 2
    • 3

    这个函数通常用于注册一个中断处理程序,当中断发生时,会调用指定的中断处理函数来处理中断,而线程中断处理函数则会在一个独立的线程中运行,以避免中断处理函数执行时间过长导致系统性能下降。
    unsigned int irq: 要请求的中断号。
    irq_handler_t handler: 中断处理函数,当中断发生时会被调用。
    irq_handler_t thread_fn: 线程中断处理函数,会在一个独立的线程中执行。
    unsigned long flags: 用于指定中断处理的标志,比如中断类型等。
    const char *name: 中断处理函数的名称。
    void *dev: 传递给中断处理函数的设备数据指针。

    1-4 Linux 中断系统中的重要数据结构
    (1) irq_desc 结构体

    在Linux 内核中有一个中断数组,对于每一个硬件中断,都有一个数组项,这个数组就是irq_desc 数组。irq_desc 结构体在include/linux/irqdesc.h 中定义。
    每一个irq_desc 数组项中都有一个函数:handle_irq,还有一个action链表。
    在这里插入图片描述

    (2) irqaction 结构体

    irqaction 结构体在include/linux/interrupt.h中定义。
    当调用request_irq 、request_threaded_irq 注册中断处理函数时,内核就会构造一个 irqaction 结构体。在里面保存 name、dev_id 等,最重要的是 handler 、thread_fn 、thread 。
    handler是中断处理的上半部函数,用来处理紧急的事情。
    thread_fn对应一个内核线程thread ,当handler 执行完毕,Linux内核会唤醒对应的内核线程。在内核线程里,会调用 thread_fn函数。

    在这里插入图片描述

    (3) irq_data 结构体

    irq_descirq_data结构体在 include/linux/irq.h 中定义,它就是个中转站,里面有irq_chip 指针 irq_domain 指针,都是指向别的结构体。
    在这里插入图片描述

    (4) irq_domain 结构体

    irq为软件中断号,hwirq为硬件中断号。 irq_domain会把本地的hwirq映射为全局的irq。
    比如GPIO控制器里有第1号中断,UART模块里也有第1号中断,这两个第1号中断是不一样的,它们属于不同的“域”── irq_domain
    设备树的中断通过irq_domain结构体的irq_domain_ops 结构体的一系列函数转换为request_irq(irq, handler)中的irq。
    irq_domain结构体在 include/linux/irqdomain.h 中定义
    在这里插入图片描述
    在这里插入图片描述

    (5) irq_domain 结构体

    irq_chip结构体在include/linux/irq.h 中定义
    在这里插入图片描述
    我们在request_irq 后,并不需要手工去使能中断,原因就是系统调用对应的irq_chip 里的函数帮我们使能了中断。
    我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是
    系统帮我们调用irq_chip 中的相关函数。

    1-5 设备树中的中断
    (1) 设备树里的中断控制器和使用中断

    在设备树中,中断控制器节点中必须有一个属性:interrupt-controller,表明它是中断控制器。还必须有一个属性:#interrupt-cells,表明引用这个中断控制器的话需要多少个cell。
    一个外设的设备树里需要有这连个属性interrupt parent=<&XXXX>:你要用哪一个中断控制器里的中
    断?interrupts:你要用哪一个中断?

    #interrupt cells=<2>
    
    • 1

    别的节点要使用这个中断控制器时,需要一个cell来表明使用哪一个中断;还需要另一个cell来描述中断,一般是表明触发类型。
    第 2个cell的 bits[3:0] 用来表示中断触发类型 trigger type and level flags)
    1 = low to high edge triggered ,上升沿触发
    2 = high to low edge triggered ,下降沿触发
    4 = active high level sensitive ,高电平触发
    8 = active low level sensitive ,低电平触发

    一个interrupts extended 属性就可以既指定 interrupt parent也指定interrupt

    interrupts extended = <&intc1 5 1>, <&intc2 1 0>;
    
    • 1
    (2) 在代码中获得中断

    platform_device
    一个节点能被转换为platform_device ,如果它的设备树里指定了中断属
    性,那么可以从 platform_device中获得中断资源,可以使用下列函数获得 IORESOURCE_IRQ 资源,即中断号。

    /**
     * platform_get_resource - get a resource for a device
     * @dev: platform device
     * @type: resource type
     * @num: resource index
     */
    struct resource *platform_get_resource(struct platform_device *dev,
    				       unsigned int type, unsigned int num)
    {
    	int i;
    
    	for (i = 0; i < dev->num_resources; i++) {
    		struct resource *r = &dev->resource[i];
    
    		if (type == resource_type(r) && num-- == 0)
    			return r;
    	}
    	return NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    对于I2C设备、SPI设备
    对于I2C 设备节点,I2C总线驱动在处理设备树里的I2C子节点时,也会处
    理其中的中断信息。一个I2C设备会被转换为一个i2c_client 结构体,中断号会保存在i2c_client的irq成员里。代码:drivers/i2c/i2c core.c
    在这里插入图片描述
    在这里插入图片描述
    对于SPI设备节点,SPI总线驱动在处理设备树里的 SPI 子节点时,也会处
    理其中的中断信息。一个SPI设备会被转换为一个spi_device结构体,中断号会保存在spi_device的的irq成员里,代码如下drivers/spi/spi.c
    在这里插入图片描述
    在这里插入图片描述

    ③ 调用of_irq_get 获得中断号
    在驱动程序中自行调用 o f_irq_get 函数去解析设备树,得到中断号。
    ④ 对于GPIO
    参考:drivers/input/keyboard/gpio_keys.c

    gpio keys {
    	compatible = "gpio keys";
    	pinctrl names = "default";
    	
    	user {
    		label = "User Button";
    		gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
    		gpio-key,wakeup;
    		linux,code = <KEY_1>;
    		};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    使用下面函数获取引脚和flag

    button -->gpio = of_get_gpio_flags(pp, 0, &flags);
    bdata -->gpiod = gpio_to_desc(button->gpio);
    
    • 1
    • 2

    再使用gpiod_to_irq获取中断号

    irq = gpiod_to_irq(bdata->gpiod);
    
    • 1
    1-6 编写使用中断的按键驱动程序

    设备树编写
    节点信息
    在这里插入图片描述
    pinctrl子系统(实验结果显示不添加也可以,因为这两个GPIO引脚默认工作于GPIO模式)
    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    汇川PLC学习Day3:轴控代码编写、用户程序结构说明与任务配置示例、用户变量空间与编址
    Koa 6 响应(Response)
    手写编程语言-实现运算符重载
    WPF 按键图标转动效果示例
    C语言:练习题三
    新 Nsight Graph、Nsight Aftermath 版本中的性能提升和增强功能
    2022年计算机专业毕业设计课题推荐
    利用SSH将本地仓库文件推送至远程仓库(GitHub)
    【Apache Kafka3.2】KafkaProducer发送消息源码分析
    Promise异步编程
  • 原文地址:https://blog.csdn.net/qq_45009309/article/details/136346400