概述
本文以ast2500evb板子(arm1176jzs)为背景来介绍linux中断服务子程序的工作过程。
在开始前,我们需要解决1个问题:
- 中断服务子程序的地址如何告知cpu
对于这个问题,我们可以从arm1176 手册可以获得:
cpu开启了high vectors(默认),则中断向量表的地址为0xFFFF0000。
我们再来看代码,代码中的中断向量表定义在哪?
Linux/arch/arm/kernel/entry-armv.S
从上图中可知,我们的中断服务代码放在__vectors_start符号处,从这个代码片段可知,中断向量表,每个表项都是跳转指令,跳转到对应的中断服务子程序。
这个中断向量表的表项安排如下图示:
最开始处,我们得知了中断向量表的地址必须是0Xffff0000. 而这个__vectors_start是什么地址呢?
Linux/arch/arm/kernel/vmlinux.lds
显然,__vectors_start并不是在cpu要求的地址。要使得cpu可以使用这张软件定义的中断向量表,自然地,就需要将这张表拷贝到地址0xffff0000处。
下面的函数调用过程描述了内核拷贝中断向量表到地址0xffff0000的过程:
start_kernel() -> setup_arch() -> paging_init() -> devicemaps_init()
linux/init/main.c
Linux/arch/arm/kernel/setup.c
Linux/arch/arm/mm/mmu.c
Linux/arch/arm/mm/mmu.c
Kernel在devicemaps_init()中首先申请一页中断向量表的内存(1270行), 然后调用early_trap_init()将中断向量表__vectors_start拷贝到这块内存中,接着设置这块内存的虚拟地址为0xffff0000, 并将申请的这块中断向量表内存页映射到0xffff0000地址上。
Linux/arch/arm/kernel/traps.c
至此,我们解决了如何将软件的中断向量表地址”告知”cpu的问题。
自然地,我们下面要来分析,当发生一个外部中断irq时(比如网卡中断),程序怎么处理的。
从中断向量表中,我们直到,irq发生后跳转到vector_irq处理。
vector_irq定义在linux/arch/arm/kernel/entry-armv.S
Vector_irq首先将lr-4(968行),并将r0, lr, spsr保存到栈上。
989行:lr中存储的是上一个状态寄存器的值(976行),对后4位做位与操作,即取spsr mode位(bit0-bit4),最高位bit4舍弃。与操作后,相当于拿到了中断前cpu所处的模式,然后用这个值来索引对应的跳转位置。
在arm中,pc = 当前指令地址+8
这里将1016行宏展开来分析就很清晰:
即,当中断前,cpu处于用户模式usr,则中断进入__irq_usr处理
当中断前,cpu处理svc模式,则中断进入__irq_svc处理。
来看看这两个符号处的代码:
Linux/arch/arm/kernel/entry-armv.S
可见,它们最终都进入irq_handler处理。
Linux/arch/arm/kernel/entry-armv.S
irq_handler进入arch_irq_handler_default处理。
Linux/arch/arm/include/asm/entry-macro-multi.S
arch_irq_handler_default会跳转到asm_do_IRQ处理,带两个参数: irqno, pt
asm_do_IRQ()调用handle_IRQ()
linux/arch/arm/kernel/irq.c
handle_IRQ()又调用generic_handle_irq()来处理。
Linux/arch/arm/kernel/irq.c
我们接着来分析generic_handle_irq()来分析
Linux/arch/arm/kernel/irq/irqdesc.c
generic_handle_irq()首先调用irq_to_desc()来获取中断描述符。
Linux/arch/arm/kernel/irq/irqdesc.c
这里,irq_desc[irqno]是通过在驱动中调用request_irq()来注册的。比如
Linux/drivers/net/ethernet/aspeed/ast_eth.c
Linux/include/linux/interrupt.h
Linux/kernel/irq/manage.c
这里__setup_irq()会创建proc目录:/proc/irq/<irqno>, /proc/irq/<irqno>/<devname>。
Linux/kernel/irq/manage.c
我们回到generic_handle_irq(),generic_handle_irq()在调用irq_to_desc()获取中断描述符后,调用generic_handle_irq_desc().
这里的desc->handle_irq()哪里定义的?
我们来看下面的代码:
start_kernel() -> setup_arch() -> setup_machine_tags()
linux/arch/arm/kernel/setup.c
Linux/arch/arm/kernel/atags_parse.c
setup_machine_tags()调用setup_machine_tags(), 而setup_machine_tags()通过for_each_machine_desc()来获取所有定义的machine_desc,找出匹配的machine_desc,并记录到machine_desc变量(886行)。
对于ast2500evb板子,这个machine_desc定义在
Linux/arch/arm/machine-astevb/setup.c
start_kernel() ->init_IRQ()
Linux/arch/arm/kernel/irq.c
这里调用了额machine_desc->init_irq(),对于ast2500evb板子来说,这个machine_desc->init_irq就是ast_init_irq.
Linux/arch/arm/soc-ast/ast-irq.c
Ast_init_irq()通过irq_set_chip_and_handler() -> irq_set_chip_and_handler_name() -> __irq_set_handler()来设置desc->handle_irq为handle_level_irq()。
Linux/include/linux/irq.h
Linux/kernel/irq/chip.c
故,对于ast2500evb板子来说,desc->handle_irq就是handle_level_irq.
所以generic_handle_irq_desc()是调用了handle_level_irq()来处理中断。
Linux/kernel/irq/chip.c
这里mask_ack_irq()调用chip的irq_mask取屏蔽当前的中断源再次报告中断。
Linux/kernel/irq/chip.c
Linux/arch/arm/soc-ast/ast-irq.c
handle_level_irq() -> handle_irq_event() -> handle_irq_event_percpu(),通过这个调用栈,中断处理程序调用了驱动程序通过request_irq()注册的handler来处理。
Linux/kernel/irq/handle.c
对于ast2500evb板子的网卡驱动来说,就是ast_ether_irq_handler()。
Linux/drivers/net/ethernet/aspeed/ast_ether.c
至此,我们大致讲完了中断发生后的函数调用过程。
下面我们接着将,下面部分涉及napi的机制和调用过程。
上面讲到了当网卡发生中断后,屏蔽对应的中断源后,调用网卡驱动程序中注册的中断处理函数ast_ether_irq_handler。我们来看一下这个注册的中断处理函数做些什么。
Linux/drivers/net/ethernet/aspeed/ast_ether.c
1499-1501行取MAC00芯片上的中断状态寄存器的值和中断使能寄存器使能位,并做与操作,来获取MAC00芯片上的状态数据,保存到status中,然后清零状态寄存器。
1507行,ISR_RPKT2B表示MAC00已经将收到的包拷贝到了rx buffer中了,即驱动可以到buffer中取出数据包。
1509行,清除这个ISR_RPKT2B。本文以NAPI的方式来介绍(CONFIG_ASTMAC100_NAPI=y)
1511行,__napi_schedule()
Linux/net/core/dev.c
__napi_schedule()先disable irq中断, 然后通过____napi_schedule()来raise一个软中断NET_RX_SOFTIRQ, 之后enable irq中断。4237行disable中断,是因为如果不disable,那么会竞争操作__get_cpu_var(softnet_data).
1511行后面的,处理MAC00的其他状态,然后返回。这里不处理具体的网络包,以便快速响应硬件中断处理。
我们回到handle_level_irq()。
Linux/kernel/irq/chip.c
驱动注册的中断处理函数执行完后,从handle_irq_event()返回,然后调用cond_unmask_irq()。
Linux/kernel/irq/chip.c
cond_unmask_irq()又调用了unmask_irq。
Linux/kernel/irq/chip.c
unmask_irq()通过chip->irq_unmask()使能之前设置的禁止这个irqno的中断源再次发生中断请求。
这样,这个中断源又可以申请中断了。
Linux/arch/arm/soc-ast/ast-irq.c
Irq unmask之后,我们从handle_level_irq()返回,一级级返回到handle_IRQ().
Linux/arch/arm/kernel/irq.c
在enable了irq中断源中断后,调用irq_exit()。
Linux/kernel/softirq.c
Irq_exit()判断当前存在pending的softirq(比如我们在网卡驱动中raise的NET_RX_SOFTIRQ中断),调用invoke_softirq()处理。
Linux/kernel/softirq.c
Linux/include/linux/interrupt.h
invoke_softirq()通过do_softirq_own_stack()又调用了__do_softirq()来处理pending的软中断.
Linux/kernel/softirq.c
__do_softirq()取出所有pending的软中断号,以中断号为softirq_vec[]的索引,调用其对应的action来处理(270行)。
这里软中断的action是通过open_softirq()来注册的。
Linux/kernel/softirq.c
网卡软中断NET_RX_SOFTIRQ的注册是在linux/net/core/dev.c中注册。
Linux/net/core/dev.c
所以,对于网卡收包的软中断处理action就是net_rx_action.
Linux/net/cor/dev.c
net_rx_action()循环取出所有的napi,调用对应的poll函数,poll函数是在irq enable状态下跑的。这个napi->poll()函数就是我们驱动中通过netif_napi_add()注册的函数,weight参数为注册时指定的权重参数。
比如:
Linux/drivers/net/ethernet/aspeed/ast_ether.c
Linux/net/core/dev.c
ast_ether的napi->poll函数ast_ether_poll就是到硬件收包的buffer中拿包,然后通过netif_receive_skb()递交给上层协议处理。
Linux/drivers/net/ethernet/aspeed/ast_ether.c
所以,对于收包后,L2, L3, L4协议层对包的处理都是在软中断的action上下文中处理的,直到将包存放到对应的socket中或被丢弃。
包处理完后,handle_IRQ()通过set_irq_regs()恢复中断之前的寄存器的值,返回中断前上下文。
至此,我们讲解完了整个网络收包过程的处理。
最后
以上就是单纯魔镜为你收集整理的linux中断处理与NAPI机制的全部内容,希望文章能够帮你解决linux中断处理与NAPI机制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复