Linux内核学习笔记——中断,tasklet和工作队列
对于操作系统来说,中断的实现和裸机代码是不一样的,对于裸机代码来说,它的处理过程更符合下面这个流程,我们在编写裸板驱动代码的时候,一般处理的是硬件中断,而且中断号也是硬件中断号,这个中断号是和CPU寄存器的连线方式直接相关的,只要连线方式一确定,那么外设对应的中断号就确定了。
比如在下面这个图中,中断源可以是TIMER或者BUTTON,对应的中断号是6和16,当这些外设产生中断信号的时候,它首先通过中断控制器的判断将信号传给中断核心层,CPU收到该信号以后会做一些保存现场的工作,然后跳到中断向量表查找对应的中断处理程序并进行处理。
但是操作系统的处理方式并不是这样的,内核对这些驱动的架构都进行了抽象,让编写驱动的时候要符合一定的规则,这样一来,让驱动的编写过程变成了对驱动架构的填充过程,这确实省了不少事,所以这里简单记录一下不同驱动架构中,中断的接收和处理过程是怎么穿梭其中的。
对于内核的驱动架构来说,它不在如过家家似的裸机代码了,首先要对中断号进行映射,也要对外设的数据寄存器以及代码寄存器的地址进行映射。比如寄存器接受到中断号是16的中断信号,当这个信号映射到内核后,这个中断号是160了,当然这两个中断信号其实是同一个信号,只是在不同层之间的不同表示而已,他们是键值对的关系。
linux 驱动中断处理相关数据结构
在Linux内核中有一个表存放中断处理程序叫irq_desc[] ,每个个IRQ中断线,Linux都用一个irq_desc_t数据结构来描述,我们把它叫做 IRQ描述符, NR_IRQS 个IRQ形成一个全局数组 irq_desc[],其定义在 include/linux/irq.h 中:
数据结构irq_desc里有一项叫做 irqaction。它里面就包含着 handler() 函数。irq_desc_t 的 action 成员实际是一个 irqaction 结构的链表,因为多个设备可能共享一个中断源, 每个设备都必须提供一个 irqction 结构挂入 action 链表。
struct irqaction 结构记录当中断发生时具体的处理函数,定义在 include/linux/interrupt.h 文件中。
中断号IRQ的确定
不管对于哪种类型的驱动,都可以在 /proc/interrupts下找到映射完以后的kernel中断号:
第一列是kernel map后的中断号, 倒数第三列是中断的触发方式(上升沿、下降沿),最后一列是注册中断处理子程序是我们分配的描述信息
cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
20: 109111782 1287889318 833179606 1002449638 GIC 20 arch_timer
35: 0 0 0 0 GIC 35 watchdog bark
97: 0 0 30564292 0 GIC 97 edma_eth_tx0
98: 0 0 6 0 GIC 98 edma_eth_tx1
99: 0 0 131107 0 GIC 99 edma_eth_tx2
100: 0 0 4 0 GIC 100 edma_eth_tx3
101: 0 0 0 22339 GIC 101 edma_eth_tx4
102: 0 0 0 0 GIC 102 edma_eth_tx5
103: 0 0 0 205881 GIC 103 edma_eth_tx6
104: 0 0 0 0 GIC 104 edma_eth_tx7
105: 28786218 0 0 0 GIC 105 edma_eth_tx8
106: 4 0 0 0 GIC 106 edma_eth_tx9
107: 99063 0 0 0 GIC 107 edma_eth_tx10
108: 2 0 0 0 GIC 108 edma_eth_tx11
109: 0 29040737 0 0 GIC 109 edma_eth_tx12
110: 0 4 0 0 GIC 110 edma_eth_tx13
111: 0 78467 0 0 GIC 111 edma_eth_tx14
112: 0 8 0 0 GIC 112 edma_eth_tx15
127: 2 0 0 0 GIC 127 78b5000.spi
129: 5171604 0 0 0 GIC 129 i2c-msm-v2-irq
133: 0 0 0 0 GIC 133 sps
139: 238076 0 0 0 GIC 139 msm_serial_hsl0
164: 0 0 0 0 GIC 164 xhci-hcd:usb1
168: 0 0 0 0 GIC 168 xhci-hcd:usb3
173: 0 0 0 0 GIC 173 int_msi
174: 1742 0 0 69415235 GIC 174 wlan_pci
183: 0 0 0 0 GIC 183 int_pls_link_down
200: 1924 0 91612296 0 GIC 200 wlan_ahb
201: 1686 235856973 0 0 GIC 201 wlan_ahb
239: 0 0 0 0 GIC 239 sps
270: 0 0 0 0 GIC 270 sps
272: 41376101 0 0 0 GIC 272 edma_eth_rx0
274: 0 40781979 0 0 GIC 274 edma_eth_rx2
276: 0 0 38000530 0 GIC 276 edma_eth_rx4
278: 0 0 0 41502836 GIC 278 edma_eth_rx6
IPI0: 0 0 0 0 CPU wakeup interrupts
IPI1: 0 0 0 0 Timer broadcast interrupts
IPI2: 72607346 70337041 74354456 73214941 Rescheduling interrupts
IPI3: 14 21 17 20 Function call interrupts
IPI4: 28106763 93631 23136542 20144633 Single function call interrupts
IPI5: 0 0 0 0 CPU stop interrupts
IPI6: 6705108 6830143 5972690 5878126 IRQ work interrupts
IPI7: 0 0 0 0 completion interrupts
Err: 0
当然也可以在/proc/irq下搜索更多信息(find -name *wlan*),这个目录会列出所有内核支持的中断号(注意不是硬件中断),对于那些有绑定驱动的中断号,一般都会在对应的中断号目录下显示对应的名称,比如wlan_pci:
/proc/irq/174# ls
affinity_hint smp_affinity spurious
node smp_affinity_list wlan_pci
对于PCI总线驱动来说,在kernel启动的时候就会对外围设备进行扫描,并将这些外围PCI总线设备添加到链表里面,比如下面的代码,在pci_common_init的时候,在pci_fixup_irqs函数里面指明,当然PCI设备的irq号不是已经在配置空间里明确指定了的,在总线驱动初始化的过程中会从配置空间里面直接读取,如果驱动没有从配置空间里读取,这里也可以保证他不会丢失。
https://blog.csdn.net/eydwyz/article/details/72285858
关于IRQ号的映射关系,这篇文章介绍比较详细:
Linux kernel的中断子系统之(二):IRQ Domain介绍
- void __init pci_common_init(struct hw_pci *hw)
- {
- struct pci_sys_data *sys;
- INIT_LIST_HEAD(&hw->buses);
- if (hw->preinit)
- hw->preinit();
- pcibios_init_hw(hw);
- if (hw->postinit)
- hw->postinit();
- pci_fixup_irqs(pcibios_swizzle, pcibios_map_irq);
当然对于一些字符设备也可以从下面的方式,或者查看对应的DTS文件,染后经过一定的转换
$ grep -rns "IRQ" arch/arm/* | grep "UART" | grep "16"
$ vi arch/arm/plat-s5p/include/plat/irqs.h
关于更多中断子系统的资料可以查看这些文章:
原 Linux中断(interrupt)子系统之一:中断系统基本原理
中断处理函数的注册
当我们确定一个事件的中断号以后,就可以对这个中断号注册中断处理函数,这也是中断处理的核心。
static inline int __must_check
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);
}
extern void free_irq(unsigned int, void *);
request_irq 函数的参数如下:unsigned int irq | // 请求的中断号 |
irqreturn_t (*handler) | // 安装的处理函数指针. . |
unsigned long flags | // 与中断管理相关的选项的位掩码(后面描述). |
const char *dev_name | //这个传递给 request_irq 的字串用在 /proc/interrupts 来显示中断的拥有者(/proc/interupts 最后一列) |
void *dev_id | // 用作共享中断线的指针. 它是一个独特的标识, 用在当释放中断线时置为 NULL, 但是使用这个项指向设备结构不管如何是个好主意. 我们将在"实现一个处理"一节中看到 dev_id 的 一个实际应用. |
其中第一个参数是上面我们说的kernel的中断号
第二个参数是该终端对应的终端处理子函数
第三个参数用来控制中断类型,比如共享中断和快速中断
dev_id用来给共享中断指定处理线程
中断的处理过程
* 中断信号由外部设备发送到中断控制器,中断控制器根据IRQ号转换成相应的中断向量号传给CPU 。
* CPU接收中断后,保存现场,根据中断向量号到IDT中查找相应的处理函数。对于IRQ n的中断,它的处理函数IRQn_interrutp()。
* 调用do_IRQ()函数。该函数完成对中断控制器确认、设置中断源状态等动作, 接着它会根据IRQ号找到描述中断具体动作的irqaction结构变量action,执行如下代码:
处理 irq 事件的函数,主要实现在 kernel/irq/handle.c 中,其中 res = action->handler (irq, action->dev_id);
执行了调用用户注册的 handler() 函数的功能。
irqreturn_t handle_irq_event(struct irq_desc *desc)
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
* 最后do_IRQ()函数要检查是否有软中断,如有则调用do_softirq()执行软中断。
* 跳转到 ret_from_intr退出,恢复中断前的现场。
中断处理我们都知道包括中断处理子程序的上半部分和下半部分,上半部分是需要快速执行并且不能阻塞的,主要的任务交给下半部分处理。我们重点讲讲下半部分,下半部分包括:tastlet,工作队列和软中断。
关于这些buttom half的内容就不赘述了,这些文章有详细分析
linux kernel的中断子系统之(九):tasklet
Concurrency Managed Workqueue之(一):workqueue的基本概念
linux kernel的中断子系统之(八):softirq
关于这些不同BH驱动架构的实例也可以参照这个PDF去练练
ARM驱动开发
更多推荐
所有评论(0)