博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linux中断处理与NAPI机制
阅读量:4056 次
发布时间:2019-05-25

本文共 5513 字,大约阅读时间需要 18 分钟。

本文以ast2500evb板子(arm1176jzs)为背景来介绍linux中断服务子程序的工作过程。

在开始前,我们需要解决1个问题:

  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()恢复中断之前的寄存器的值,返回中断前上下文。

至此,我们讲解完了整个网络收包过程的处理。

转载地址:http://ytlci.baihongyu.com/

你可能感兴趣的文章
ubuntu_fastboot
查看>>
ubuntu18.04 Android 7.1.2 编译配置
查看>>
s6e3ha3 amoled屏
查看>>
fts touchscreen
查看>>
gpio pinctrl
查看>>
ubuntu 五笔
查看>>
LVMP 扩容
查看>>
EasyPermissions
查看>>
ubuntu wine
查看>>
开启电量的百分比
查看>>
U9300C 在Linux下的调试
查看>>
Android APP 直接操作内核sysfs
查看>>
ethernet
查看>>
VS2005 wince6.0 environment
查看>>
Wince6.0 目录结构
查看>>
BSP_WINCE_ARM_A8_User_Guide
查看>>
Catalog Item & Build
查看>>
AS提交代码到gitee
查看>>
ov2656
查看>>
android 休眠唤醒
查看>>