历史上的今天
返回首页

历史上的今天

今天是:2025年04月16日(星期三)

正在发生

2021年04月16日 | ARM Linux中断源码分析(1)——中断初始化与注册

2021-04-16 来源:eefocus

1.中断和异常

中断可分为同步(synchronous)中断和异步(asynchronous)中断:


(1)同步中断又称为异常,是当指令执行时由CPU控制单元产生,之所以称为同步,是因为只有在一条指令执行完成后CPU才会发出中断。典型的异常有缺页和除0。


(2)异步中断是指由其他硬件设备依照CPU时钟信号随机产生。

1.1 ARM异常与向量表

当一个异常或中断发生时,处理器会把pc设置为一个特定的存储器地址。这个地址放在一个被称为向量表(vector table)的特定的地址范围内。向量表的入口是一些跳转指令,跳转到专门处理某个异常或中断的子程序。


存储器映射地址0x00000000是为向量表保留的。在有些处理器中,向量表可以选择定位在存储空间的更高地址(从偏移量0xffff0000开始)。操作系统,如Linux和Microsoft的嵌入式操作系统,就可以利用这一特性。


当一个异常或中断发生时,处理器挂起正常的执行转而从向量表(见表1-1)装载指令。每一个向量表入口包含一条指向一个特定子程序的跳转指令。


表1-1 ARM异常向量表


复位向量是处理器上电后执行的第一条指令的位置。这条指令使处理器转到初始化代码处。 未定义指令向量是在处理器不能对一条指令译码时使用的。 软件中断向量是执行SWI指令时被调用的。SWI指令经常被用作调用一个操作系统例程的机制。 预取指中止向量是发生在处理器试图从一个未获得正确访问权限的地址去取指时,实际上中止发生在“译码”阶段。 数据中止向量是与预取指中止类似,发生在一条指令试图访问未获得正确访问权限的数据存储器时。 中断请求向量是用于外部硬件中断处理器的正常执行流。只有当cpsr中的IRQ位未被屏蔽时才能发生。 快速中断请求向量类似于中断请求,是为要求更短的中断响应时间的硬件保留的。只有当cpsr中的FIQ位未被屏蔽时才能发生。


2.相关数据结构


与中断相关的主要数据结构有三个,分别是:irq_desc、irq_chip和irqaction。下面将介绍一下这三个数据结构的实现以及它们之间的关系。


内核通过irq_desc来描述一个中断,irq_desc结构体在include/linux/irq.h中定义:


struct irq_desc{


unsignedintirq;/*中断号*/ …… irq_flow_handler_t handle_irq;/*电流层中断处理函数*/ struct irq_chip*chip;/*包含处理器相关的处理函数*/ struct msi_desc*msi_desc; void*handler_data; void*chip_data; struct irqaction*action;/*IRQ操作链表*/ unsignedintstatus;/*IRQ状态*/ unsignedintdepth; …… constchar*name; }____cacheline_internodealigned_in_smp;


其中,handle_irq指向电流层中断处理函数,主要用于对不同触发方式(电平、边沿)的处理。handler_data可以指向任何数据,供handle_irq函数使用。每当发生中断时,体系结构相关的代码都会调用handle_irq,该函数负责使用chip中提供的处理器相关的方法,来完成处理中断所必需的一些底层操作。用于不同中断类型的默认函数由内核提供,例如handle_level_irq和handle_edge_irq等。


中断控制器相关操作被封装到irq_chip,该结构体在include/linux/irq.h中定义:


struct irq_chip{


constchar*name; unsignedint(*startup)(unsignedintirq); void(*shutdown)(unsignedintirq); void(*enable)(unsignedintirq); void(*disable)(unsignedintirq); void(*ack)(unsignedintirq); void(*mask)(unsignedintirq); void(*mask_ack)(unsignedintirq); void(*unmask)(unsignedintirq); void(*eoi)(unsignedintirq); void(*end)(unsignedintirq); int(*set_affinity)(unsignedintirq,conststruct cpumask*dest); int(*retrigger)(unsignedintirq); int(*set_type)(unsignedintirq,unsignedintflow_type); int(*set_wake)(unsignedintirq,unsignedinton); void(*bus_lock)(unsignedintirq); void(*bus_sync_unlock)(unsignedintirq); …… constchar*typename; };


action指向一个操作链表,在中断发生时执行。由中断通知设备驱动程序,可以将与之相关的处理函数放置在此处。irqaction结构体在include/linux/interrupt.h中定义:


struct irqaction{


irq_handler_t handler; unsigned long flags; constchar*name; void*dev_id; struct irqaction*next; intirq; struct proc_dir_entry*dir; irq_handler_t thread_fn; struct task_struct*thread; unsigned long thread_flags; };


每个处理函数都对应该结构体的一个实例irqaction,该结构体中最重要的成员是handler,这是个函数指针。该函数指针就是通过request_irq来初始化的,在后续小节中将详细介绍。当系统产生中断,内核将调用该函数指针所指向的处理函数。name和dev_id唯一地标识一个irqaction实例,name用于标识设备名,dev_id用于指向设备的数据结构实例。


请注意irq_desc中的handle_irq不同于irqaction中的handler,这两个函数指针分别定义为:


typedefvoid(*irq_flow_handler_t)(unsignedintirq,struct irq_desc*desc);


typedef irqreturn_t(*irq_handler_t)(int,void*);


内核通过维护一个irq_desc的全局数组来管理所有的中断。该数组在kernel/irq/handle.c中定义:


struct irq_desc irq_desc[NR_IRQS]__cacheline_aligned_in_smp={


[0...NR_IRQS-1]={ .status=IRQ_DISABLED, .chip=&no_irq_chip, .handle_irq=handle_bad_irq, .depth=1, .lock=__RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock), } };


其中NR_IRQS表示支持中断的最大数,一般在arch/arm/mach-XXX/include/mach/irqs.h文件中定义。

图2-1描述了以上三个数据结构之间的关系。所有irq_desc的chip成员都指向同一irq_chip实例。


图2-1 IRQ相关数据结构关系


3.中断初始化


ARM Linux中断初始化主要通过三个函数完成:early_trap_init、early_irq_init和init_IRQ。


整个Linux中断初始化流程如图3-1所示:



图3-1 Linux中断初始化流程


early_trap_init函数定义在arch/arm/kernel/traps.c文件中,在setup_arch函数最后调用的,主要完成ARM异常向量表和异常处理程序的重定位。


void __init early_trap_init(void)


{ unsigned long vectors=CONFIG_VECTORS_BASE; extern char __stubs_start[],__stubs_end[]; extern char __vectors_start[],__vectors_end[]; extern char __kuser_helper_start[],__kuser_helper_end[]; intkuser_sz=__kuser_helper_end-__kuser_helper_start; /* *拷贝异常向量表到vectors地址处,通常是0xffff0000。 *拷贝异常处理程序到vectors+0x200地址处。 */ memcpy((void*)vectors,__vectors_start,__vectors_end-__vectors_start); memcpy((void*)vectors+0x200,__stubs_start,__stubs_end-__stubs_start); memcpy((void*)vectors+0x1000-kuser_sz,__kuser_helper_start,kuser_sz); /* *拷贝信号处理函数 */ memcpy((void*)KERN_SIGRETURN_CODE,sigreturn_codes, sizeof(sigreturn_codes)); /* *拷贝信号处理函数 */ flush_icache_range(vectors,vectors+PAGE_SIZE); modify_domain(DOMAIN_USER,DOMAIN_CLIENT); }


这个函数把定义在arch/arm/kernel/entry-armv.S中的异常向量表和异常处理程序的stub进行重定位:异常向量表拷贝到0xFFFF_0000,异常向量处理程序的stub拷贝到0xFFFF_0200。然后调用modify_domain()修改了异常向量表所占据的页面的访问权限,这使得用户态无法访问该页,只有核心态才可以访问。arm处理器发生异常时总会跳转到0xFFFF_0000(设为“高端向量配置”时)处的异常向量表,所以要进行这个重定位工作。


early_irq_init函数定义在kernel/irq/handle.c文件中:


struct irq_desc irq_desc[NR_IRQS]__cacheline_aligned_in_smp={


[0...NR_IRQS-1]={ .status=IRQ_DISABLED, .chip=&no_irq_chip, .handle_irq=handle_bad_irq, .depth=1, .lock=__SPIN_LOCK_UNLOCKED(irq_desc->lock), } }; static unsignedintkstat_irqs_all[NR_IRQS][NR_CPUS]; int__init early_irq_init(void) { struct irq_desc*desc; intcount; inti; init_irq_default_affinity(); printk(KERN_INFO"NR_IRQS:%dn",NR_IRQS); desc=irq_desc; count=ARRAY_SIZE(irq_desc); for(i=0;i


kernel/irq/handle.c文件中,根据内核的配置选项CONFIG_SPARSE_IRQ,可以选择两个不同版本的early_irq_init()中的一个进行编译。CONFIG_SPARSE_IRQ配置项,用于支持稀疏irq号,对于发行版的内核很有用,它允许定义一个高CONFIG_NR_CPUS值,但仍然不希望消耗太多内存的情况。通常情况下,我们并不配置该选项。


early_irq_init的主要工作是初始化用于管理中断的irq_desc[NR_IRQS]数组的每个元素(NR_IRQS表示中断的总数,在irqs.h文件中定义),它主要设置数组中每一个成员的中断号,使得数组中每一个元素的kstat_irqs字段(irq stats per cpu),指向定义的二维数组kstat_irqs_all中的对应的行。alloc_desc_masks(&desc[i], 0, true)和init_desc_masks(&desc[i])函数在非SMP平台上为空函数。arch_early_irq_init()在主要用于x86平台和PPC平台,其他平台上为空函数。


init_IRQ函数定义在arch/arm/kernel/irq.c文件中:


void __init init_IRQ(void)


{ intirq; for(irq=0;irq


init_IRQ函数首先遍历irq_desc中断描述符表,并初始化每一个中断的状态为IRQ_NOREQUEST和IRQ_NOPROBE。然后调用板级相关函数init_arch_irq,该函数在setup_arch函数中被初始化为mdesc->init_irq,而mdesc->init_irq通常在arch/arm/mach-xxx目录下的板级文件中定义的。init_arch_irq函数完成对cpu中断控制器初始化,通常还会通过以下for循环来初始化irq_desc数组:


for(irq=IRQ_TIMER0;irq<=IRQ_TIMER4;irq++){


set_irq_chip(irq,&arch_chip);//将中断控制器操作结构体irq_chip的实例注册到irq_desc[irq].chip上 set_irq_handler(irq,handle_level_irq); set_irq_flags(irq,IRQF_VALID); }

4.注册中断处理程序


本节将主要讲述如何注册中断处理程序。Linux中用于注册中断处理程序的函数是request_irq,该函数的实现为:


static inlineint__must_check


request_irq(unsignedintirq,irq_handler_t handler,unsigned long flags, constchar*name,void*dev) { return request_threaded_irq(irq,handler,NULL,flags,name,dev); } intrequest_threaded_irq(unsignedintirq,irq_handler_t handler, irq_handler_t thread_fn,unsigned long irqflags, constchar*devname,void*dev_id) { struct irqaction*action; struct irq_desc*desc; intretval; /* *handle_IRQ_event()always ignores IRQF_DISABLED exceptfor *the _first_ irqaction(sigh).That can cause oopsing,but *the behaviorisclassified as"will not fix"so we needto *start nudging drivers away from using that idiom. */ if((irqflags&(IRQF_SHARED|IRQF_DISABLED))== (IRQF_SHARED|IRQF_DISABLED)){ pr_warning( "IRQ %d/%s: IRQF_DISABLED is not guaranteed on shared IRQsn", irq,devname); } #ifdef CONFIG_LOCKDEP /* *Lockdep wants atomic interrupt handlers: */ irqflags|=IRQF_DISABLED; #endif /* *Sanity-check:shared interrupts must passina real dev-ID, *otherwise we'll have trouble later tryingtofigure out *which interruptiswhich(messes up the interrupt freeing *logic etc). */ if((irqflags&IRQF_SHARED)&&!dev_id) return-EINVAL; desc=irq_to_desc(irq); if(!desc) return-EINVAL; if(desc->status&IRQ_NOREQUEST) return-EINVAL; if(!handler){ if(!thread_fn) return-EINVAL; handler=irq_default_primary_handler; } action=kzalloc(sizeof(struct irqaction),GFP_KERNEL); if(!action) return-ENOMEM; action->handler=handler; action->thread_fn=thread_fn; action->flags=irqflags; action->name=devname; action->dev_id=dev_id; chip_bus_lock(irq,desc); retval=__setup_irq(irq,desc,action); chip_bus_sync_unlock(irq,desc); if(retval) kfree(action); return retval; }


图3-1给出了request_irq代码流程图。


图3-1 request_irq代码流程图


内核首先生成一个新的irqaction实例,然后用函数参数填充其内容。


所有进一步的工作都委托给__setup_irq函数,它将执行以下步骤:


(1)如果设置了IRQF_SAMPLE_RANDOM,则该中断将对内核熵池有所贡献,熵池用于随机数发生器/dev/random。


(2)由request_irq生成的irqaction实例被添加到所属IRQ编号对应的链表尾部,该链表表头为desc->action。在处理共享中断时,内核就通过这种方式来确保中断发生时调用处理程序的顺序与其注册顺序相同。


(3)register_irq_proc在proc文件系统中建立目录/proc/irq/NUM。而register_handler_proc生成/proc/irq/NUM/name。


参考资料:


[1] Andrew N.Sloss, Dominic Symes, Chris Wright. ARM嵌入式系统开发——软件设计与优化.沈建华,译.


[2] Daniel P.Bovet, Marco Cesati.深入理解Linux内核(第三版).陈莉君,张琼声,张宏伟,译.


[3] Pobert Love. Linux内核设计与实现.陈莉君,康华,张波,译.


[4] Wolfgang Mauerer.深入Linux内核架构.郭旭,译.


[5]陈学松.深入Linux设备驱动程序内核机制.


[6] ARM Architecture Reference Manual


[7] The ARM-THUMB Procedure Call Standard


推荐阅读

史海拾趣

Engineered Components Co公司的发展小趣事

为了确保产品质量和客户满意度,ECC建立了完善的质量管理体系。他们从原材料采购到生产、检测、包装等各个环节都制定了严格的质量标准。ECC还引入了先进的质量检测设备和方法,确保每一件产品都符合高标准的质量要求。此外,ECC还定期对员工进行质量培训,提高员工的质量意识和操作技能。这些措施使得ECC的产品在市场上赢得了良好的口碑和信誉。

Dean Technology公司的发展小趣事

近年来,随着电子行业的快速发展和市场竞争的加剧,Dean Technology公司也面临着前所未有的挑战。为了应对这些挑战,公司积极调整战略方向,加强内部管理和团队建设。同时,他们还密切关注行业趋势和市场需求的变化,及时调整产品结构和市场策略。这些努力使得Dean Technology在应对行业变革和挑战时更加从容和自信。

请注意,以上故事是基于对Dean Technology公司一般情况的了解而编写的,可能不完全符合公司的实际发展历程。如需更详细的信息,请查阅相关资料或联系公司官方渠道。

EMMICRO公司的发展小趣事

作为一家具有社会责任感的企业,EMMICRO公司始终关注环保和社会公益事业。公司积极参与环保活动和公益捐赠,推动企业的可持续发展和社会进步。同时,公司还积极承担对员工的培训和发展责任,为员工提供良好的职业发展平台和福利待遇。这种对社会责任的担当也赢得了社会的广泛赞誉和尊重。

BRIGHT公司的发展小趣事

近年来,BRIGHT公司积极投身于太阳能领域的发展。他们推出了一种创新的商业模式,即帮助用户免费安装太阳能面板,并随后收取服务费。这一模式类似于有线电视的收费方式,有效降低了用户安装太阳能系统的门槛。通过与私人投资者的合作,BRIGHT公司成功承担了安装成本,并致力于向全球提供优质的屋顶太阳能解决方案。这一突破性的举措使得BRIGHT公司在太阳能领域取得了显著的成绩。

Gumstix公司的发展小趣事
检查压敏元件是否老化或受到污染,必要时更换新的压敏元件。同时,也可以考虑调整电路参数以提高灵敏度。
Conxall公司的发展小趣事

为了进一步提升公司的竞争力,Conxall公司积极寻求与其他企业的合作。公司与多家知名电子企业签订了战略合作协议,共同研发新技术、新产品。通过合作,Conxall公司不仅获得了更多的技术资源和市场支持,还提升了自身的品牌形象和知名度。

问答坊 | AI 解惑

吐血力作:TI系列硬件设计电路参考

我收藏的一些TI各个系列的电路,包括2000,5000,6000。不是很全,但对于一般的系统设计会够用的。 …

查看全部问答>

出租车计费器又闹内鬼了?

多地出租车计价器集体故障 日期成2012年 http://news.163.com/10/0102/02/5S07U1DH0001124J.html 核心提示:1月1日,沈阳、锦州数千辆出租车的计价器集体故障,屏幕出现“归零”问题,打印出来的发票日期大多为2012年1月1日,而且无论行驶多远价 ...…

查看全部问答>

bootloader是否需要传递启动参数

我自己谢了一个bootloader 已经拷贝NK至sdram 我想是否需要可以直接跳到sdram-NK的地址直接,启动还是需要....比如参数传递等等 thank u so much.…

查看全部问答>

FL2440如何加密?

我做了个Linux的应用程序放在FL2440的核心板,想对它进行加密。目前我想到的方法有:1. 绑定板上的电子器件    a. 绑定CPU、内存、flash:  可是发现S3C2440 没有唯一序列号,内存、flash也没找到序列号。    b. 绑定网 ...…

查看全部问答>

TI LM3S系列在TFT屏上显示波形或者柱状图的例程哪位大哥有?

求助:   TI LM3S系列在TFT屏上显示波形或者柱状图的例程哪位大哥有? 听说TI有的系列中有波形显示的程序,但我用的9B92中没有,哪位有这个例程的共享下,谢谢! 邮箱:zhangyao1213@126.com…

查看全部问答>

最小Linux系统制作例程

一,什么是BabyLinux   BabyLinux不是一个完整的发行版,他是利用原有的一套完整的linux系统的内核原代码和编译工具,利用busybox内建的强大功能,在一张软盘上做的一个很小的linux系统.他具备一个linux系统的基本特征,支持linux系统最常用的一百多 ...…

查看全部问答>

求图形驱动库安装程序

网上找了半天,大多都是快速安装版的。。各位大侠谁能提供安装包啊?…

查看全部问答>

说说大家都是在哪里怎么得到MSP430单片机芯片的呢?

在TI官网直接购买?代理商?阿里巴巴?淘宝?拍拍?我也要买,用于研发的,样片也不好申请。希望各位大侠或者有货源的商家们SOS呢 ^0^!…

查看全部问答>