Linux驱动开发基础(中断)

 所学来自百问网

目录

1. 嵌入式中断系统

2. 中断处理流程

3. 异常向量表

4. Linux系统对中断的处理

4.1 ARM 处理器程序运行的过程

4.2 保护现场

5. Linux 系统对中断处理的演进

5.1 硬件中断和软件中断

5.2 中断拆分(上半部和下半部)

5.2.1 tasklet

5.2.2 工作队列

5.2.3 新技术 threaded irq

6. Linux中断系统中的重要数据结构

6.1 irq_desc 数组

6.2 irqaction 结构体

6.3 irq_data 结构体

6.4 irq_domain 结构体

6.5 irq_chip 结构体

7. 设备树中的中断语法

8. 驱动程序基石

8.1 休眠函数

8.2 唤醒函数

8.3 休眠与唤醒

8.4 poll机制

8.5 异步通知

8.6 阻塞与非阻塞


1. 嵌入式中断系统

CPU 在运行的过程中,也会被各种“异常”打断。这些“异常”有:

1.指令未定义

2.指令、数据访问有问题

3.SWI(软中断)

4.快中断

5.中断

中断也属于一种“异常”,导致中断发生的称为“中断源“,这些“中断源”很多,比如:按键、UART发送完数据、收到数据等等

这些众多的“中断源”,汇集到“中断控制器“,由“中断控制器”选择优先级最高的中断并通知CPU。

2. 中断处理流程

1.初始化:

a) 设置中断源,让它可以产生中断

b) 设置中断控制器(可以屏蔽某个中断,优先级)

c) 设置CPU总开关(使能中断)

2.执行其他程序:正常程序

3.产生中断:比如按下按键--->中断控制器--->CPU

4.CPU 每执行完一条指令都会检查有无中断/异常产生

5.CPU发现有中断/异常产生,开始处理。

对于不同的异常,跳去不同的地址执行程序。

这地址上,只是一条跳转指令,跳去执行某个函数(地址),这个就是异常向量。

6.函数执行

a) 保存现场(各种寄存器)

b) 处理异常(中断):分辨中断源,再调用不同的处理函数

c) 恢复现场

其中345是硬件负责实现,6是软件负责实现

3. 异常向量表

u-boot 或是Linux内核,都有类似如下的代码:

_start: b reset   // 产生复位时执行ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq  //发生中断时,CPU跳到这个地址执行该指令 **假设地址为0x18** ldr pc, _fiq 

这就是异常向量表,每一条指令对应一种异常。

这些指令存放的位置是固定的,比如对于ARM9芯片中断向量的地址是0x18。当发生中断时,CPU就强制跳去执行0x18处的代码。

在向量表里,一般都是放置一条跳转指令,发生该异常时,CPU就会执行向量表中的跳转指令,去调用更复杂的函数。

当然,向量表的位置并不总是从0地址开始,很多芯片可以设置某个vector base寄存器,指定向量表在其他位置,比如设置vector base为0x80000000, 指定为DDR的某个地址。但是表中的各个异常向量的偏移地址,是固定的,如复位向量偏移地址是0,中断是0x18。

4. Linux系统对中断的处理

4.1 ARM 处理器程序运行的过程

ARM 芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:

1.对内存只有读、写指令

2.对于数据的运算是在CPU内部实现

3.使用RISC指令的CPU复杂度小一点,易于设计

对于a = a + b的处理流程如下:

CPU 运行时,先去取得指令,再执行指令:

1.把内存a的值读入CPU寄存器R0

2.把内存b的值读入CPU寄存器R1

3.把R0、R1累加,存入R0

4.把R0的值写入内存a

由此可知:CPU处理数据是需要经过寄存器,寄存器可以去保存数据

4.2 保护现场

从上图可知,CPU内部的寄存器很重要,如果要暂停一个程序,中断一个程序,就需要把这些寄存器的值保存下来:这就称为保存现场。

这些寄存器的值会被保存在内存中,也就是栈,程序要继续执行,就先从栈中恢复那些CPU内部寄存器的值。

这个场景并不局限于中断,下图可以概括程序A、B的切换过程,其他情况是类似的:

函数调用:

a) 在函数A里调用函数B,实际就是中断函数A的执行。

b) 那么需要把函数A调用B之前瞬间的CPU寄存器的值,保存到栈里;

c) 再去执行函数B;

d) 函数B返回之后,就从栈中恢复函数A对应的CPU寄存器值,继续执行。

中断处理:

a) 进程A正在执行,这时候发生了中断。

b) CPU强制跳到中断异常向量地址去执行。

c) 这时就需要保存进程A被中断瞬间的CPU寄存器值。

d) 可以保存在进程A的内核态栈,也可以保存在进程A的内核结构体中。

e) 中断处理完毕,要继续运行进程A之前,恢复这些值。

进程切换:

a) 在所谓的多任务操作系统中,我们以为多个程序是同时运行的。

b) 如果我们能感知微秒、纳秒级的事件,可以发现操作系统时让这些程序依次执行一小段时间,进程A的时间用完了,就切换到进程B。

c) 切换过程是发生在内核态里的,跟中断的处理类似。

d) 进程A的被切换瞬间的CPU寄存器值保存在某个地方;

e) 恢复进程B之前保存的CPU寄存器值,这样就可以运行进程B了

所以,在中断处理的过程中,伴存着进程的保存现场、恢复现场。进程的调度也是使用栈来保存、恢复现场:

5. Linux 系统对中断处理的演进

Linux 系统中有硬件中断,也有软件中断。对硬件中断的处理有2个原则: 不能嵌套,越快越好。

5.1 硬件中断和软件中断

Linux 系统把中断的意义扩展了,对于按键中断等硬件产生的中断,称之为 “硬件中断”(hard irq)。每个硬件中断都有对应的处理函数,比如按键中断、 网卡中断的处理函数肯定不一样。 

为方便理解,你可以先认为对硬件中断的处理是用数组来实现的,数组里存放的是函数指针:

当发生A中断时,对应的irq_function_A函数被调用。硬件导致该函数被调用。相对的,还可以人为地制造中断:软件中断(soft irq),如下图所示:

注意:以上两图是简化的

对于硬件中断:CPU会优先处理硬件中断事件,在硬件中断中有两条原则,不能嵌套和越快越好

1.不能嵌套

中断A正在处理的过程中,假设又发生了中断B,那么在栈里要保存A的现场,然后处理B,在处理B的过程中又发生了中断C,那么在栈里要保存B的现场,然后处理C。以此类推,如果中断嵌套突然暴发,那么栈将越来越大,栈终将耗尽。

为了防止这种情况,在Linux系统上中断无法嵌套:即当前中断A没处理完之前,不会响应另一个中断B(即使它的优先级更高)。

2.越快越好

在单芯片系统中,假设中断处理很慢,那应用程序在这段时间内就无法执行:系统显得很迟顿。

在SMP系统中,假设中断处理很慢,那么正在处理这个中断的CPU上的其他线程也无法执行。

在中断的处理过程中,该CPU是不能进行进程调度的,所以中断的处理要越快越好,尽早让其他中断能被处理──进程调度靠定时器中断来实现。

在Linux 系统中使用中断是挺简单的,为某个中断irq注册中断处理函数 handler,可以使用request_irq函数:

对于软件中断:中断由软件决定,对于X号软件中断,只需要把它的flag设置为1就表示发生了该中断。在处理完硬件中断后,再去处理软件中断

在include/linux/interrupt.h中,有以下软件中断:

enum
{HI_SOFTIRQ=0,               //高优先级软中断TIMER_SOFTIRQ,              //定时器软中断NET_TX_SOFTIRQ,             //网络发送软中断NET_RX_SOFTIRQ,             //网络接收软中断BLOCK_SOFTIRQ,              //块设备软中断IRQ_POLL_SOFTIRQ,           //IRQ轮询软中断TASKLET_SOFTIRQ,            //tasklet软中断SCHED_SOFTIRQ,              //调度软中断HRTIMER_SOFTIRQ,            //高精度定时器软中断     RCU_SOFTIRQ,                //RCU(Read-Copy Update)软中断,RCU是一种用于同步对共享数据的读取和更新的技术NR_SOFTIRQS                 //软中断类型的总数
};

5.2 中断拆分(上半部和下半部)

对于硬件中断来说,秉承着中断不能嵌套和越快越好的原则,虽然解决了爆栈的问题,但是处理某个中断要做的事情就是很多,没办法加快。比如对于按键中断,我们需要等待几十毫秒消除机械抖动。非紧急的情况就没必要在handler函数里处理,由此将中断拆分为中断上半部和中断下半部

对于中断下半部实现的方式很多,以下主要对:tasklet(小任务)、work queue(工作队列)进行分析

5.2.1 tasklet

tasklet适合用于比较耗时但是能忍受,并且处理比较简单的程序

如下:

使用流程图简化一下:

preempt_count表示中断数

以下使用情景化分析:

  • 硬件中断A处理过程中,没有其他中断发生

按流程从上到下执行一遍

  • 硬件中断A处理过程中,又再次发生了中断A

程序会先正常执行到第六步,这时preempt_count = 1,当中断打开后,程序会从头执行,preempt_count 加1减1,执行到第四步,这时preempt_count = 1,程序直接完成处理后,处理完中断,程序继续执行中断下半部直至结束

由此可知:同一个中断的上半部、下半部,在执行时是多对一的关系。

  • 硬件中断A处理过程中,又再次发生了中断B

程序A会先正常执行到第六步,这时preempt_count = 1,当中断打开后,程序B执行,preempt_count 加1减1,执行到第四步,这时preempt_count = 1,程序B直接完成处理后(这里指的是处理完程序B的上半部),处理完中断,程序会继续执行A、B中断下半部直至结束

由此可知:多个中断的下半部,是汇集在一起处理的

总结:

1.中断的处理可以分为上半部,下半部

2.中断上半部,用来处理紧急的事,它是在关中断的状态下执行的

3.中断下半部,用来处理耗时的、不那么紧急的事,它是在开中断的状态下执行的

4.中断下半部执行时,有可能会被多次打断,有可能会再次发生同一个中断

5.中断上半部执行完后,触发中断下半部的处理

6.中断上半部、下半部的执行过程中,不能休眠

5.2.2 工作队列

工作队列适用于下半部要做的事情太多并且很复杂十分耗时的程序,这是在内核线程中执行程序,这个内核线程是系统帮我们创建的,一般是kworker线程

使用步骤

1.创建work

你得先写出一个函数,然后用这个函数填充一个work结构体。比如:

2.把work提交给work_queue

上述函数会把work提供给系统默认的work_queue:system_wq,它是一个队列。在中断场景中,可以在中断上半部调用schedule_work函数。schedule_work 函数不仅仅是把 work 放入队列,还会把 kworker 线程唤醒。此线程抢到时间运行时,它就会从队列中取出 work,执行里面的函数。

总结:

1.很耗时的中断处理,应该放到线程里去,对应的函数可以休眠

2.在中断上半部调用schedule_work函数,触发work的处理

5.2.3 新技术 threaded irq

你可以只提供thread_fn,系统会为这个函数创建一个内核线程。发生中断时,内核线程就会执行这个函数。

新技术threaded irq,为每一个中断都创建一个内核线程;多个中断的内核线程可以分配到多个CPU上执行,这提高了效率。

6. Linux中断系统中的重要数据结构

6.1 irq_desc 数组

irq_desc 结构体在include/linux/irqdesc.h 中定义,主要内容如下图:

每一个irq_desc数组项中都有一个函数:handle_irq,还有一个action 链表。要理解它们,需要先看中断结构图:

外部设备 1、外部设备 n 共享一个 GPIO 中断 B,多个 GPIO 中断汇聚到 GIC(通用中断控制器)的A号中断,GIC再去中断CPU。那么软件处理时就是反过来,先读取GIC获得中断号A,再细分出GPIO中断B,最后判断是哪一个外部芯片发生了中断。

所以,中断的处理函数来源有三:

1.GIC的处理函数:

假设irq_desc[A].handle_irq 是 XXX_gpio_irq_handler(XXX 指厂家), 这个函数需要读取芯片的GPIO控制器,细分发生的是哪一个GPIO中断(假设是 B),再去调用irq_desc[B]. handle_irq。

显然中断A是CPU感受到的顶层的中断,GIC中断CPU时,CPU读取GIC状态得到中断A。

注意:irq_desc[A].handle_irq 细分出中断后B,调用对应的 irq_desc[B].handle_irq。

2.模块的中断处理函数:

比如对于GPIO模块向 GIC 发出的中断 B,它的处理函数是 irq_desc[B].handle_irq。

BSP 开发人员会设置对应的处理函数,一般是 handle_level_irq 或 handle_edge_irq,从名字上看是用来处理电平触发的中断、边沿触发的中断。

注意:导致GPIO中断B发生的原因很多,可能是外部设备1,可能是外部设备 n,可能只是某一个设备,也可能是多个设备。所以irq_desc[B].handle_irq 会调用某个链表里的函数,这些函数由外部设备提供。这些函数自行判断该中断是否自己产生,若是则处理。

3.外部设备提供的处理函数:

这里说的“外部设备”可能是芯片,也可能是简单的按键。它们的处理函数由自己驱动程序提供,它知道如何判断设备是否发生了中断,如何处理中断。

对于共享中断,比如GPIO中断B,它的中断来源可能有多个,每个中断源对应一个中断处理函数。所以irq_desc[B]中应该有一个链表,存放着多个中断源的处理函数。 一旦程序确定发生了GPIO 中断B,那么就会从链表里把那些函数取出来, 一一执行。这个链表就是action链表。

6.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函数。

  • 可以提供handler而不提供thread_fn,就退化为一般的request_irq函数。

  • 可以不提供handler只提供thread_fn,完全由内核线程来处理中断。

  • 也可以既提供handler也提供thread_fn,这就是中断上半部、下半部。

在reqeust_irq 时可以传入dev_id,主要作用如下:

1.中断处理函数执行时,可以使用dev_id

2.卸载中断时要传入dev_id,这样才能在action链表中根据dev_id找到对应项

所以在共享中断中必须提供dev_id,非共享中断可以不提供。

6.3 irq_data 结构体

irq_data 结构体在include/linux/irq.h 中定义,主要内容如下图:

它就是个中转站,里面有irq_chip指针 irq_domain指针,都是指向别的结构体。 其中irq、hwirq,irq 是软件中断号,hwirq 是硬件中断号。 比如上面我们举的例子,在GPIO中断B是软件中断号,可以找到irq_desc[B] 这个数组项;GPIO里的第x号中断,这就是hwirq。

irq_domain会把本地的hwirq映射为全局的irq,比如GPIO控制器里有第1号中断,UART 模块里也有第1号中断,这两个“第1号中断”是不一样的,它们属于不同的“域”──irq_domain。

6.4 irq_domain 结构体

irq_domain 结构体在include/linux/irqdomain.h 中定义,主要内容如下图:

irq_domain 结构体代表了一个中断域(IRQ domain),它是一个或多个中断控制器的逻辑分组。每个中断域都可以管理一组特定的中断号,并且这些中断号通常是连续分配的。中断域还负责将硬件中断线(hardware interrupt lines)映射到内核使用的中断号上。

在设备树中你会看到这样的属性:

interrupt-parent = <&gpio1>; 
interrupts = <5 IRQ_TYPE_EDGE_RISING>; 

它表示要使用gpio1里的第5号中断,hwirq就是5。

我们在驱动中会使用request_irq(irq, handler)这样的函数来注册中断,irq是软件中断号,gpio1中的irq_domain将硬件中断转化为软件中断号

irq_domain 结构体中有一个irq_domain_ops结构体,里面有各种操作函数,

主要是:

  • xlate: 用来解析设备树的中断属性,提取出hwirq、type等信息。

  • map: 把hwirq转换为irq。

6.5 irq_chip 结构体

irq_chip 结构体在include/linux/irq.h 中定义,主要内容如下图:

这个结构体跟“chip”即芯片相关,里面各成员的作用在头文件中也列得很清楚,摘录部分如下:

* @irq_startup: start up the interrupt (defaults to ->enable if NULL) 
* @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL) 
* @irq_enable:  enable the interrupt (defaults to chip->unmask if NULL) 
* @irq_disable: disable the interrupt 
* @irq_ack:  start of a new interrupt 
* @irq_mask:  mask an interrupt source 
* @irq_mask_ack: ack and mask an interrupt source 
* @irq_unmask:  unmask an interrupt source 
* @irq_eoi:  end of interrupt

我们在request_irq 后,并不需要手工去使能中断,原因就是系统调用对应的irq_chip里的函数帮我们使能了中断。

我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是系统帮我们调用irq_chip中的相关函数。

但是对于外部设备相关的清中断操作,还是需要我们自己做的。

就像上面图里的“外部设备1“、“外部设备n”,外设备千变万化,内核里可没有对应的清除中断操作。

7. 设备树中的中断语法

中断的硬件框图如下:

在硬件上,“中断控制器”只有GIC这一个,而在软件层面,可以将GPIO等其他模块称为“中断控制器”,当将汇聚的起来的中断源向GIC发出中断,一个中断就涉及到GIC的一个hwirq(硬件中断号)

在设备树中,中断控制器节点中必须有一个属性:interrupt-controller, 表明它是“中断控制器”,还必须有一个属性:#interrupt-cells,表明引用这个中断控制器的话需要多少个cell。当#interrupt-cells=<1>时,表示只需要一个cell来表明使用“哪一个中断“,当#interrupt-cells=<2>时,表示使用两个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,低电平触发 

设备树中设置中断示例如下:

vic: intc@10140000 { compatible = "arm,versatile-vic"; interrupt-controller;      // 中断控制器#interrupt-cells = <1>;    // 表示”哪一个中断“reg = <0x10140000 0x1000>; 
}; 

当中断控制器有级联关系,下级中断控制器需要设置“interrupt-parent”和“interrupts”

“interrupt-parent”表示定义中断父节点,在可以产生中断的设备节点中,其中断信号连接到了哪个中断控制器

“interrupts”表示用于描述一个设备的一个或多个中断源,这个属性的值包含了中断号、触发方式(如边缘触发或电平触发)等信息

注意:Interrupts里要用几个cell,由interrupt-parent对应的中断控制器决定。在中断控制器里有“#interrupt-cells”属性,它指明了要用几个cell 来描述中断。

示例:

i2c@7000c000 { gpioext: gpio-adnp@41 { compatible = "ad,gpio-adnp"; interrupt-parent = <&gpio>;   interrupts = <160 1>;  // 表明中断号为160,使用1号触发方式gpio-controller; #gpio-cells = <1>; interrupt-controller; #interrupt-cells = <2>; }; 
}; 

interrupts-extended

一个“interrupts-extended”属性就可以既指定“interrupt-parent”, 也指定“interrupts”,比如:

interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;

以100ASK_IMX6ULL开发板为例,在arch/arm/boot/dts目录下可以看到2个文件:imx6ull.dtsi、100ask_imx6ull-14x14.dts,把里面有关中断的部分内容抽取出来。

8. 驱动程序基石

8.1 休眠函数

在Linux内核中,wait_eventwait_event_interruptible及其带有超时参数的版本wait_event_interruptible_timeoutwait_event_timeout是用于进程同步和等待特定条件成立的宏。这些宏通常用于等待某个条件变为真,同时允许内核线程或进程在条件不满足时进入睡眠状态。

  • 使当前进程或线程在指定的等待队列wq上等待,直到condition变为真。在等待期间,进程会被置于不可中断的睡眠状态。

    • void wait_event(wait_queue_head_t *wq, int condition)

    • wq:指向等待队列的指针,该队列用于管理等待该条件的所有进程。

    • condition:一个表达式,用于检查是否满足唤醒条件。这个条件在每次唤醒时都会重新检查。

  • wait_event类似,但进程在睡眠期间可以被信号中断。如果进程被信号中断,则返回-EINTR

    • int wait_event_interruptible(wait_queue_head_t *wq, int condition)

    • wq:指向等待队列的指针,该队列用于管理等待该条件的所有进程。

    • condition:一个表达式,用于检查是否满足唤醒条件。这个条件在每次唤醒时都会重新检查。

  • 在指定的timeout时间内等待condition变为真。

    • long wait_event_interruptible_timeout(wait_queue_head_t *wq, int condition, long timeout)

    • wq:指向等待队列的指针。

    • condition:条件表达式。

    • timeout:等待的最长时间,以jiffies为单位。

    • 返回值:如果条件在超时前变为真,则返回剩余的超时时间,如果进程被信号中断,则返回-EINTR

  • wait_event_interruptible_timeout类似,但等待期间不能被信号中断

    • long wait_event_timeout(wait_queue_head_t *wq, int condition, long timeout)

    • wq:指向等待队列的指针。

    • condition:条件表达式。

    • timeout:等待的最长时间,以jiffies为单位。

    • 返回值:如果条件在超时前变为真,则返回剩余的超时时间;如果超时发生且条件仍未满足,则返回0。

8.2 唤醒函数

在Linux内核中,wake_up_interruptiblewake_up_interruptible_nrwake_up_interruptible_allwake_upwake_up_nrwake_up_all是用于唤醒等待队列中进程或线程的宏。这些宏用于在特定条件得到满足时,唤醒那些在该等待队列上等待该条件的进程或线程。

  • 唤醒在给定等待队列x上等待且处于可中断睡眠状态的所有进程。

    • wake_up_interruptible(x)

    • x 是指向等待队列的指针。

  • wake_up_interruptible类似,但它尝试唤醒指定数量的(nr)在给定等待队列x上等待且处于可中断睡眠状态的进程。

    • wake_up_interruptible_nr(x, nr)

    • x 是指向等待队列的指针。

    • nr 是希望唤醒的进程数量。

  • 在给定等待队列x上等待且处于可中断睡眠状态的所有进程都被唤醒。

    • wake_up_interruptible_all(x)

    • x 是指向等待队列的指针。

  • 唤醒在给定等待队列x上等待的所有进程,无论它们是否处于可中断睡眠状态。这包括那些处于不可中断睡眠状态的进程。

    • wake_up(x)

    • x 是指向等待队列的指针。

  • 尝试唤醒在给定等待队列x上等待的指定数量(nr)的进程。与wake_up_interruptible_nr不同,这个宏不区分进程是否处于可中断睡眠状态。

    • wake_up_nr(x, nr)

    • x 是指向等待队列的指针。

    • nr 是希望唤醒的进程数量。

  • 确保在给定等待队列x上等待的所有进程都被唤醒,无论它们是否处于可中断睡眠状态。这个宏用于在条件满足时唤醒所有等待的进程。

    • wake_up_all(x)

    • x 是指向等待队列的指针。

8.3 休眠与唤醒

图解:当APP调用read去读取驱动数据的时候,没有数据则休眠,有数据则将数据记录并唤醒APP

驱动框架

要休眠的线程,放在 wq 队列里,中断处理函数从 wq 队列里把它取出来唤醒。

步骤:

1.初始化wq队列

2.在驱动的read函数中,调用wait_event_interruptible:

它本身会判断event是否为FALSE,如果为FASLE表示无数据,则休眠。

当从wait_event_interruptible 返回后,把数据复制回用户空间。

3.在中断服务程序里: 设置event为TRUE,并调用wake_up_interruptible唤醒线程。

8.4 poll机制

使用休眠-唤醒的方式等待某个事件发生时,有一个缺点:等待的时间可能很久。我们可以加上一个超时时间,这时就可以使用poll机制。

图解:APP不知道驱动程序中是否有数据,可以先调用poll函数查询一下,poll函数可以传入超时时间;当没有数据时则会休眠,若休眠事件超过超时事件则唤醒,当有数据时则会记录数据返回

注意:

1.drv_poll 要把线程挂入队列 wq,但是并不是在 drv_poll 中进入休眠,而是在调用drv_poll之后休眠

2.drv_poll要返回数据状态,判断是获取到数据还是超时,有数据时再去调用read 函数。

3.APP调用一次poll,有可能会导致drv_poll被调用2次

4.线程被唤醒的原因有2:中断发生了或超时时间到了

编程

使用 poll 机制时,驱动程序的核心就是提供对应的 drv_poll 函数。在 drv_poll 函数中要做2件事:

1.把当前线程挂入队列wq:poll_wait

这边使用内核函数poll_wait把线程挂入队列可防止drv_poll被调用2次

2.返回设备状态:

APP 调用poll函数时,有可能是查询“有没有数据可以读”:POLLIN,也有可能是查询“你有没有空间给我写数据”:POLLOUT。所以drv_poll要返回自己的当前状态:(POLLIN | POLLRDNORM) 或 (POLLOUT | POLLWRNORM)。

a) POLLRDNORM等同于POLLIN,为了兼容某些APP把它们一起返回。 b) POLLWRNORM等同于POLLOUT ,为了兼容某些APP把它们一起返 回。

APP 调用 poll 后,很有可能会休眠。对应的,在按键驱动的中断服务程序中,也要有唤醒操作。

3.APP可以调用poll或select函数,这2个函数的作用是一样的。

poll/select 函数可以监测多个文件,可以监测多种事件:

事件类型说明
POLLIN有数据可读
POLLRDNORM等同于POLLIN
POLLPRI高优先级数据可读
POLLOUT可以写数据
POLLWRNORM等同于POLLOUT
POLLERR发生了错误
POLLHUP挂起
POLLNVAL无效的请求,一般是fd未open

8.5 异步通知

② APP给SIGIO这个信号注册信号处理函数func,以后APP收到SIGIO信号时,这个函数会被自动调用;

③ 把APP的PID(进程ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录PID;

④ 读取驱动程序文件Flag;

⑤ 设置Flag里面的FASYNC位为1:当FASYNC位发生变化时,会导致驱动程序的fasync被调用;

⑥⑦ 调用faync_helper,它会根据 FAYSNC 的值决定是否设置 button_async->fa_file = 驱动文件 filp:驱动文件filp结构体里面含有之前设置的PID。

⑧ APP可以做其他事;

⑨⑩ 按下按键,发生中断,驱动程序的中断服务程序被调用,里面调用 kill_fasync 发信号;

⑪⑫⑬ APP收到信号后,它的信号处理函数被自动调用,可以在里面调用 read 函数读取按键。

涉及函数

  • 用于向所有通过 fp 指向的 fasync_struct 链表注册了异步通知的进程发送信号 sig

    • void kill_fasync(struct fasync_struct **fp, int sig, int band)

    • struct fasync_struct **fp:这是一个指向指针的指针,指向 fasync_struct 结构体的链表头部。fasync_struct 结构体用于在内核中维护一个或多个进程对特定文件描述符的异步通知请求。

    • int sig:这是要发送给所有注册了异步通知的进程的信号编号。如SIGIOSIGURG

    • int band:具体结合上下文

  • 用于启用或禁用对指定文件描述符 fd 的异步通知

    • int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp)

    • int fd:这是要启用或禁用异步通知的文件描述符。

    • struct file *filp:这是与 fd 关联的文件对象。它包含了文件的状态和操作函数等信息。

    • int on:这是一个控制标志。如果 on 非零,则启用异步通知;如果 on 为零,则禁用异步通知。

    • struct fasync_struct **fapp:这是一个指向指针的指针,指向 fasync_struct 结构体链表的头部(或驱动内部维护的类似结构)。这个函数将使用这个指针来更新链表,以添加或删除对 fd 的异步通知请求。

8.6 阻塞与非阻塞

所谓阻塞,就是等待某件事情发生。比如调用read读取按键时,如果没有按键数据则read函数不会返回,它会让线程休眠等待。

使用poll时,如果传入的超时时间不为0,这种访问方法也是阻塞的。

使用poll时,可以设置超时时间为0,这样即使没有数据它也会立刻返回, 这就是非阻塞方式。

APP 调用open函数时,传入O_NONBLOCK,就表示要使用非阻塞方式;默认是阻塞方式。

注意:

对于普通文件、块设备文件,O_NONBLOCK不起作用。

对于字符设备文件,O_NONBLOCK 起作用的前提是驱动程序针对 O_NONBLOCK 做了处理。

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

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

相关文章

git笔记:git常用命令备忘录

1、工作区域和文件状态 1.1、工作区域 git的数据管理分为四个区域&#xff1a; 工作区&#xff08;Working Directory&#xff09; 本地工作目录&#xff0c;是我们电脑上的目录&#xff0c;是我们实际编写代码的区域&#xff0c;修改完工作区的文件后可以使用git add命令将…

72 华为资源库

1 报文格式 https://info.support.huawei.com/info-finder/tool/zh/enterprise/packetformat 2 华为IP网络电子书 资源可以下载 https://e.huawei.com/cn/topic/enterprise-network/ip-ebook 3 华为产品文档 https://support.huawei.com/enterprise/zh/doc/index.html 4 华为…

Facebook的AI助手:如何提升用户社交体验的智能化

在现代社交媒体平台中&#xff0c;人工智能&#xff08;AI&#xff09;的应用正逐渐改变人们的社交体验。Facebook作为全球最大的社交媒体平台之一&#xff0c;已在AI技术的开发与应用上投入了大量资源&#xff0c;并通过其AI助手为用户提供了更加个性化、智能化的互动体验。这…

vagrant 创建虚拟机

创建一个名为 “Vagrantfile” 的文件&#xff0c;修改如下内容&#xff1a; Vagrant.configure("2") do |config|(1..3).each do |i|config.vm.define "k8s-node#{i}" do |node|# 设置虚拟机的Boxnode.vm.box "centos/7"# 设置虚拟机的主机名…

逆向中的游戏-入土为安的第二十五天

逆向中的游戏 CE的介绍 Cheat Engine &#xff0c;简称CE&#xff0c;是逆向工程师常用的几大神器之一&#xff0c;也是游戏汉化、破解以及外挂编写中常用的工具&#xff0c;其功能包括&#xff1a;内存扫描、十六进制编辑器、调试工具&#xff0c;可以进行反汇编调试、断点跟…

代码随想录算法训练营_day28

题目信息 122. 买卖股票的最佳时机 II 题目链接: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/题目描述: 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你…

matlab 计算复共轭

目录 一、概述1、算法概述2、主要函数二、代码示例1、求复数的复共轭2、求矩阵中复数值的复共轭三、参考链接本文由CSDN点云侠翻译,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的抄袭狗。 一、概述 1、算法概述 2、主要函数 Zc = conj(Z)返回 Z …

【python】Python中小巧的异步web框架Sanic快速上手实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Git在IDEA中的集成操作(附步骤图)

1.先做适配操作&#xff0c;将安装的Git软件关联到IDEA中 点击Test之后若成功会显示出Git版本&#xff1a; 2.创建版本仓库 3.创建新的版本 3.1将文件提交到暂存区(不重要) 第一种方式&#xff1a;菜单栏提交 第二种方式&#xff1a;项目右键提交 4.查看历史版本信息 目…

整合sentinel遇到的小问题

1.运行jar包 &#xff0c;端口为默认8080 正确命令 java -Dserver.port8090 -Dcsp.sentinel.dashboard.server127.0.0.1:8090 -Dproject.namesentinel-dashboard -jar sentinel-dashboard-1.8.6.jar -D这些指令要在 -jar前面 在宝塔部署时&#xff0c;直接复制到运行命令后…

Sparse Kernel Canonical Correlation Analysis

论文链接&#xff1a;https://arxiv.org/pdf/1701.04207 看这篇论文终于看懂核函数了。。谢谢作者

基于无人机边沿相关 ------- IBUS、SBUS协议和PPM信号

文章目录 一、IBUS协议二、SBUS协议三、PPM信号 一、IBUS协议 IBUS&#xff08;Intelligent Bus&#xff09;是一种用于电子设备之间通信的协议&#xff0c;采用串行通信方式&#xff0c;允许多设备通过单一数据线通信&#xff0c;较低延迟&#xff0c;支持多主机和从机结构&a…

SpringBoot集成kafka-监听器注解

SpringBoot集成kafka-监听器注解 1、application.yml2、生产者3、消费者4、测试类5、测试 1、application.yml #自定义配置 kafka:topic:name: helloTopicconsumer:group: helloGroup2、生产者 package com.power.producer;import com.power.model.User; import com.power.uti…

C++11详解 (右值引用、可变参数模板、emplace_back、lambda表达式、function、bind)

目录 简介 左值引用与右值引用 左值引用与右值引用是什么 左值引用与右值引用的比较 右值引用的使用场景与C11中STL的新变化 完美转发 新的类功能 可变参数模板 可变参数模板的应用——emplace_back lambda表达式 包装器 function bind 结语 简介 在过往&#xf…

基于vue框架的毕业设计选题系统bqx47(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,指导老师,课题信息,类型,选题信息 开题报告内容 基于Vue框架的毕业设计选题系统 开题报告 一、引言 毕业设计选题是高等教育中极为关键的一环&#xff0c;它不仅关乎学生未来研究的方向与深度&#xff0c;也是培养其创新思维和实…

SSRF漏洞与redis未授权访问的共同利用

1.利用靶场Pikachu来认识SSRF漏洞 1.什么是SSRF SSRF漏洞允许攻击者通过向服务器发起请求来伪造请求。这种漏洞的核心在于攻击者能够控制服务器向任意目标地址发起请求&#xff0c;而这些请求通常是攻击者无法直接从客户端发起的。 简单来说&#xff0c;假设你的网站有一个功能…

rk3568 npu opencv 怎么联系起来

问题&#xff1a; 客户一直再问 关于 3568 npu opencv 的编译内容。 大致了解一些 &#xff0c;这方面的内容。 网上的资料。 也许这个基本上就是他的逻辑了&#xff0c; 首先界面使用QT来写。 然后&#xff0c;使用 opencv 去读取摄像头。 然后拿到一帧图像之后&#xff…

IO进程线程8月26ri

1&#xff0c;思维导图 2&#xff0c;用两个进程分别复制文件的上下两部分到另一个文件 #include<myhead.h> int main(int argc, const char *argv[]) {int fpopen("./1.txt",O_RDONLY);if(fp-1){perror("open");return -1;}int countlseek(fp,0,SE…

电脑U口管理软件分享|U口管理软件哪个好?

电脑U口&#xff08;即USB端口&#xff09;管理软件是保护电脑安全、防止数据泄露和恶意软件入侵的重要工具。 在选择U口管理软件时&#xff0c;需要考虑其功能、易用性、安全性以及是否满足个人或企业的具体需求。以下是一些值得推荐的电脑U口管理软件及其特点&#xff1a; 1…

基本数据类型及命令

String String 是Redis最基本的类型&#xff0c;Redis所有的数据结构都是以唯一的key字符串作为名称&#xff0c;然后通过这个唯一的key值获取相应的value数据。不同的类型的数据结构差异就在于value的结构不同。 String类型是二进制安全的。意思是string可以包含任何数据&…