一、前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的驱动的时候,如何向Linux Kernel中的中断子系统注册中断处理函数?为了理解注册中断的接口,必须了解一些中断线程化(threaded interrupt handler)的基础知识,这些在第二章描述。第三章主要描述了驱动申请 interrupt line接口API request_threaded_irq的规格。第四章是进入request_threaded_irq的实现细节,分析整个代码的执行过程。 二、和中断相关的linux实时性分析以及中断线程化的背景介绍 1、非抢占式linux内核的实时性 在遥远的过去,linux2.4之前的内核是不支持抢占特性的,具体可以参考下图: 事情的开始源自高优先级任务(橘色block)由于要等待外部事件(例如网络数据)而进入睡眠,调度器调度了某个低优先级的任务(紫色block)执行。该低优先级任务欢畅的执行,直到触发了一次系统调用(例如通过read()文件接口读取磁盘上的文件等)而进入了内核态。仍然是熟悉的配方,仍然是熟悉的味道,低优先级任务正在执行不会变化,只不过从user space切换到了kernel space。外部事件总是在你不想让它来的时候到来,T0时刻,高优先级任务等待的那个中断事件发生了。 中断虽然发生了,但软件不一定立刻响应,可能由于在内核态执行的某些操作不希望被外部事件打断而主动关闭了中断(或是关闭了CPU的中断,或者MASK了该IRQ number),这时候,中断信号没有立刻得到响应,软件仍然在内核态执行低优先级任务系统调用的代码。在T1时刻,内核态代码由于退出临界区而打开中断(注意:上图中的比例是不协调的,一般而言,linux kernel不会有那么长的关中断时间,上面主要是为了表示清楚,同理,从中断触发到具体中断服务程序的执行也没有那么长,都是为了表述清楚),中断一旦打开,立刻跳转到了异常向量地址,interrupt handler抢占了低优先级任务的执行,进入中断上下文(虽然这时候的current task是低优先级任务,但是中断上下文和它没有任何关系)。 从CPU开始处理中断到具体中断服务程序被执行还需要一个分发的过程。这个期间系统要做的主要操作包括确定HW interrupt ID,确定IRQ Number,ack或者mask中断,调用中断服务程序等。T0到T2之间的delay被称为中断延迟(Interrupt Latency),主要包括两部分,一部分是HW造成的delay(硬件的中断系统识别外部的中断事件并signal到CPU),另外一部分是软件原因(内核代码中由于要保护临界区而关闭中断引起的)。 该中断的服务程序执行完毕(在其执行过程中,T3时刻,会唤醒高优先级任务,让它从sleep状态进入runable状态),返回低优先级任务的系统调用现场,这时候并不存在一个抢占点,低优先级任务要完成系统调用之后,在返回用户空间的时候才出现抢占点。漫长的等待之后,T4时刻,调度器调度高优先级任务执行。有一个术语叫做任务响应时间(Task Response Time)用来描述T3到T4之间的delay。 2、抢占式linux内核的实时性 2.6内核和2.4内核显著的不同是提供了一个CONFIG_PREEMPT的选项,打开该选项后,linux kernel就支持了内核代码的抢占(当然不能在临界区),其行为如下: T0到T3的操作都是和上一节的描述一样的,不同的地方是在T4。对于2.4内核,只有返回用户空间的时候才有抢占点出现,但是对于抢占式内核而言,即便是从中断上下文返回内核空间的进程上下文,只要内核代码不在临界区内,就可以发生调度,让最高优先级的任务调度执行。 在非抢占式linux内核中,一个任务的内核态是不可以被其他进程抢占的。这里并不是说kernel space不可以被抢占,只是说进程通过系统调用陷入到内核的时候,不可以被其他的进程抢占。实际上,中断上下文当然可以抢占进程上下文(无论是内核态还是用户态),更进一步,中断上下文是拥有至高无上的权限,它甚至可以抢占其他的中断上下文。引入抢占式内核后,系统的平均任务响应时间会缩短,但是,实时性更关注的是:无论在任何的负载情况下,任务响应时间是确定的。因此,更需要关注的是worst-case的任务响应时间。这里有两个因素会影响worst case latency: (1)为了同步,内核中总有些代码需要持有自旋锁资源,或者显式的调用preempt_disable来禁止抢占,这时候不允许抢占 (2)中断上下文(并非只是中断handler,还包括softirq、timer、tasklet)总是可以抢占进程上下文 因此,即便是打开了PREEMPT的选项,实际上linux系统的任务响应时间仍然是不确定的。一方面内核代码的临界区非常多,我们需要找到,系统中持有锁,或者禁止抢占的最大的时间片。另外一方面,在上图的T4中,能顺利的调度高优先级任务并非易事,这时候可能有触发的软中断,也可能有新来的中断,也可能某些driver的tasklet要执行,只有在没有任何bottom half的任务要执行的时候,调度器才会启动调度。 3、进一步提高linux内核的实时性 通过上一个小节的描述,相信大家都确信中断对linux 实时性的最大的敌人。那么怎么破?我曾经接触过一款RTOS,它的中断handler非常简单,就是发送一个inter-task message到该driver thread,对任何的一个驱动都是如此处理。这样,每个中断上下文都变得非常简短,而且每个中断都是一致的。在这样的设计中,外设中断的处理线程化了,然后,系统设计师要仔细的为每个系统中的task分配优先级,确保整个系统的实时性。 在Linux kernel中,一个外设的中断处理被分成top half和bottom half,top half进行最关键,最基本的处理,而比较耗时的操作被放到bottom half(softirq、tasklet)中延迟执行。虽然bottom half被延迟执行,但始终都是先于进程执行的。为何不让这些耗时的bottom half和普通进程公平竞争呢?因此,linux kernel借鉴了RTOS的某些特性,对那些耗时的驱动interrupt handler进行线程化处理,在内核的抢占点上,让线程(无论是内核线程还是用户空间创建的线程,还是驱动的interrupt thread)在一个舞台上竞争CPU。 三、request_threaded_irq的接口规格 1、输入参数描述
2、输出参数描述 0表示成功执行,负数表示各种错误原因。 3、Interrupt type flags
四、request_threaded_irq代码分析 1、request_threaded_irq主流程
(1)对于那些需要共享的中断,在request irq的时候需要给出dev id,否则会出错退出。为何对于IRQF_SHARED的中断必须要给出dev id呢?实际上,在共享的情况下,一个IRQ number对应若干个irqaction,当操作irqaction的时候,仅仅给出IRQ number就不是非常的足够了,这时候,需要一个ID表示具体的irqaction,这里就是dev_id的作用了。我们举一个例子:
当释放一个IRQ资源的时候,不但要给出IRQ number,还要给出device ID。只有这样,才能精准的把要释放的那个irqaction 从irq action list上移除。dev_id在中断处理中有没有作用呢?我们来看看source code:
linux interrupt framework虽然支持中断共享,但是它并不会协助解决识别问题,它只会遍历该IRQ number上注册的irqaction的callback函数,这样,虽然只是一个外设产生的中断,linux kernel还是把所有共享的那些中断handler都逐个调用执行。为了让系统的performance不受影响,irqaction的callback函数必须在函数的最开始进行判断,是否是自己的硬件设备产生了中断(读取硬件的寄存器),如果不是,尽快的退出。 需要注意的是,这里dev_id并不能在中断触发的时候用来标识需要调用哪一个irqaction的callback函数,通过上面的代码也可以看出,dev_id有些类似一个参数传递的过程,可以把具体driver的一些硬件信息,组合成一个structure,在触发中断的时候可以把这个structure传递给中断处理函数。 (2)通过IRQ number获取对应的中断描述符。在引入CONFIG_SPARSE_IRQ选项后,这个转换变得不是那么简单了。在过去,我们会以IRQ number为index,从irq_desc这个全局数组中直接获取中断描述符。如果配置CONFIG_SPARSE_IRQ选项,则需要从radix tree中搜索。CONFIG_SPARSE_IRQ选项的更详细的介绍请参考IRQ number和中断描述符 (3)并非系统中所有的IRQ number都可以request,有些中断描述符被标记为IRQ_NOREQUEST,标识该IRQ number不能被其他的驱动request。一般而言,这些IRQ number有特殊的作用,例如用于级联的那个IRQ number是不能request。irq_settings_can_request函数就是判断一个IRQ是否可以被request。 irq_settings_is_per_cpu_devid函数用来判断一个中断描述符是否需要传递per cpu的device ID。per cpu的中断上面已经描述的很清楚了,这里不再细述。如果一个中断描述符对应的中断 ID是per cpu的,那么在申请其handler的时候就有两种情况,一种是传递统一的dev_id参数(传入request_threaded_irq的最后一个参数),另外一种情况是针对每个CPU,传递不同的dev_id参数。在这种情况下,我们需要调用request_percpu_irq接口函数而不是request_threaded_irq。 (4)传入request_threaded_irq的primary handler和threaded handler参数有下面四种组合:
(5)这部分的代码很简单,分配struct irqaction,赋值,调用__setup_irq进行实际的注册过程。这里要罗嗦几句的是锁的操作,在内核中,有很多函数,有的是需要调用者自己加锁保护的,有些是不需要加锁保护的。对于这些场景,linux kernel采取了统一的策略:基本函数名字是一样的,只不过需要调用者自己加锁保护的那个函数需要增加__的前缀,例如内核有有下面两个函数:setup_irq和__setup_irq。这里,我们在setup irq的时候已经调用chip_bus_lock进行保护,因此使用lock free的版本__setup_irq。 chip_bus_lock定义如下:
大部分的interrupt controller并没有定义irq_bus_lock这个callback函数,因此chip_bus_lock这个函数对大多数的中断控制器而言是没有实际意义的。但是,有些interrupt controller是连接到慢速总线上的,例如一个i2c接口的IO expander芯片(这种芯片往往也提供若干有中断功能的GPIO,因此也是一个interrupt controller),在访问这种interrupt controller的时候需要lock住那个慢速bus(只能有一个client在使用I2C bus)。 2、注册irqaction (1)nested IRQ的处理代码 在看具体的代码之前,我们首先要理解什么是nested IRQ。nested IRQ不是cascade IRQ,在GIC代码分析中我们有描述过cascade IRQ这个概念,主要用在interrupt controller级联的情况下。为了方便大家理解,我还是给出一个具体的例子吧,具体的HW block请参考下图: 上图是一个两个GIC级联的例子,所有的HW block封装在了一个SOC chip中。为了方便描述,我们先进行编号:Secondary GIC的IRQ number是A,外设1的IRQ number是B,外设2的IRQ number是C。对于上面的硬件,linux kernel处理如下: (a)IRQ A的中断描述符被设定为不能注册irqaction(不能注册specific interrupt handler,或者叫中断服务程序) (b)IRQ A的highlevel irq-events handler(处理interrupt flow control)负责进行IRQ number的映射,在其irq domain上翻译出具体外设的IRQ number,并重新定向到外设IRQ number对应的highlevel irq-events handler。 (c)所有外设驱动的中断正常request irq,可以任意选择线程化的handler,或者只注册primary handler。 需要注意的是,对root GIC和Secondary GIC寄存器的访问非常快,因此ack、mask、EOI等操作也非常快。 我们再看看另外一个interrupt controller级联的情况: IO expander HW block提供了有中断功能的GPIO,因此它也是一个interrupt controller,有它自己的irq domain和irq chip。上图中外设1和外设2使用了IO expander上有中断功能的GPIO,它们有属于自己的IRQ number以及中断描述符。IO expander HW block的IRQ line连接到SOC内部的interrupt controller上,因此,这也是一个interrupt controller级联的情况,对于这种情况,我们是否可以采用和上面GIC级联的处理方式呢? 不行,对于GIC级联的情况,如果secondary GIC上的外设1产生了中断,整个关中断的时间是IRQ A的中断描述符的highlevel irq-events handler处理时间+IRQ B的的中断描述符的highlevel irq-events handler处理时间+外设1的primary handler的处理时间。所幸对root GIC和Secondary GIC寄存器的访问非常快,因此整个关中断的时间也不是非常的长。但是如果是IO expander这个情况,如果采取和上面GIC级联的处理方式一样的话,关中断的时间非常长。我们还是用外设1产生的中断为例子好了。这时候,由于IRQ B的的中断描述符的highlevel irq-events handler处理设计I2C的操作,因此时间非常的长,这时候,对于整个系统的实时性而言是致命的打击。对这种硬件情况,linux kernel处理如下: (a)IRQ A的中断描述符的highlevel irq-events handler根据实际情况进行设定,并且允许注册irqaction。对于连接到IO expander上的外设,它是没有real time的要求的(否则也不会接到IO expander上),因此一般会进行线程化处理。由于threaded handler中涉及I2C操作,因此要设定IRQF_ONESHOT的flag。 (b)在IRQ A的中断描述符的threaded interrupt handler中进行进行IRQ number的映射,在IO expander irq domain上翻译出具体外设的IRQ number,并直接调用handle_nested_irq函数处理该IRQ。 (c)外设对应的中断描述符设定IRQ_NESTED_THREAD的flag,表明这是一个nested IRQ。nested IRQ没有highlevel irq-events handler,也没有primary handler,它的threaded interrupt handler是附着在其parent IRQ的threaded handler上的。 具体的nested IRQ的处理代码如下:
如果一个中断描述符是nested thread type的,说明这个中断描述符应该设定threaded interrupt handler(当然,内核是不会单独创建一个thread的,它是借着其parent IRQ的interrupt thread执行),否则就会出错返回。对于primary handler,它应该没有机会被调用到,当然为了调试,kernel将其设定为irq_nested_primary_handler,以便在调用的时候打印一些信息,让工程师直到发生了什么状况。 (2)forced irq threading处理 具体的forced irq threading的处理代码如下:
forced irq threading其实就是将系统中所有可以被线程化的中断handler全部线程化,即便你在request irq的时候,设定的是primary handler,而不是threaded handler。当然那些不能被线程化的中断(标注了IRQF_NO_THREAD的中断,例如系统timer)还是排除在外的。irq_settings_can_thread函数就是判断一个中断是否可以被线程化,如果可以的话,则调用irq_setup_forced_threading在set irq的时候强制进行线程化。具体代码如下:
(a)系统中有一个强制线程化的选项:CONFIG_IRQ_FORCED_THREADING,如果没有打开该选项,force_irqthreads总是0,因此irq_setup_forced_threading也就没有什么作用,直接return了。如果打开了CONFIG_IRQ_FORCED_THREADING,说明系统支持强制线程化,但是具体是否对所有的中断进行强制线程化处理还是要看命令行参数threadirqs。如果kernel启动的时候没有传入该参数,那么同样的,irq_setup_forced_threading也就没有什么作用,直接return了。只有bootloader向内核传入threadirqs这个命令行参数,内核才真正在启动过程中,进行各个中断的强制线程化的操作。 (b)看到IRQF_NO_THREAD选项你可能会奇怪,前面irq_settings_can_thread函数不是检查过了吗?为何还要重复检查?其实一个中断是否可以进行线程化可以从两个层面看:一个是从底层看,也就是从中断描述符、从实际的中断硬件拓扑等方面看。另外一个是从中断子系统的用户层面看,也就是各个外设在注册自己的handler的时候是否想进行线程化处理。所有的IRQF_XXX都是从用户层面看的flag,因此如果用户通过IRQF_NO_THREAD这个flag告知kernel,该interrupt不能被线程化,那么强制线程化的机制还是尊重用户的选择的。 PER CPU的中断都是一些较为特殊的中断,不是一般意义上的外设中断,因此对PER CPU的中断不强制进行线程化。IRQF_ONESHOT选项说明该中断已经被线程化了(而且是特殊的one shot类型的),因此也是直接返回了。 (c)强制线程化只对那些没有设定thread_fn的中断进行处理,这种中断将全部的处理放在了primary interrupt handler中(当然,如果中断处理比较耗时,那么也可能会采用bottom half的机制),由于primary interrupt handler是全程关闭CPU中断的,因此可能对系统的实时性造成影响,因此考虑将其强制线程化。struct irqaction中的thread_flags是和线程相关的flag,我们给它打上IRQTF_FORCED_THREAD的标签,表明该threaded handler是被强制threaded的。new->thread_fn = new->handler这段代码表示将原来primary handler中的内容全部放到threaded handler中处理,新的primary handler被设定为default handler。 (d)强制线程化是一个和实时性相关的选项,从我的角度来看是一个很不好的设计(个人观点),各个驱动工程师在撰写自己的驱动代码的时候已经安排好了自己的上下文环境。有的是进程上下文,有的是中断上下文,在各自的上下文环境中,驱动工程师又选择了适合的内核同步机制。但是,强制线程化导致原来运行在中断上下文的primary handler现在运行在进程上下文,这有可能导致一些难以跟踪和定位的bug。 当然,作为内核的开发者,既然同意将强制线程化这个特性并入linux kernel,相信他们有他们自己的考虑。我猜测这是和一些旧的驱动代码维护相关的。linux kernel中的中断子系统的API的修改会引起非常大的震动,因为内核中成千上万的驱动都是需要调用旧的接口来申请linux kernel中断子系统的服务,对每一个驱动都进行修改是一个非常耗时的工作,为了让那些旧的驱动代码可以运行在新的中断子系统上,因此,在kernel中,实际上仍然提供了旧的request_irq接口函数,如下:
接口是OK了,但是,新的中断子系统的思路是将中断处理分成primary handler和threaded handler,而旧的驱动代码一般是将中断处理分成top half和bottom half,如何将这部分的不同抹平?linux kernel是这样处理的(这是我个人的理解,不保证是正确的): (d-1)内核为那些被强制线程化的中断handler设定了IRQF_ONESHOT的标识。这是因为在旧的中断处理机制中,top half是不可重入的,强制线程化之后,强制设定IRQF_ONESHOT可以保证threaded handler是不会重入的。 (d-2)在那些被强制线程化的中断线程中,disable bottom half的处理。这是因为在旧的中断处理机制中,botton half是不可能抢占top half的执行,强制线程化之后,应该保持这一点。 (3)创建interrupt线程。代码如下:
(a)调用kthread_create来创建一个内核线程,并调用sched_setscheduler_nocheck来设定这个中断线程的调度策略和调度优先级。这些是和进程管理相关的内容,我们留到下一个专题再详细描述吧。 (b)调用get_task_struct可以为这个threaded handler的task struct增加一次reference count,这样,即便是该thread异常退出也可以保证它的task struct不会被释放掉。这可以保证中断系统的代码不会访问到一些被释放的内存。irqaction的thread 成员被设定为刚刚创建的task,这样,primary handler就知道唤醒哪一个中断线程了。 (c)设定IRQTF_AFFINITY的标志,在threaded handler中会检查该标志并进行IRQ affinity的设定。 (d)分配一个cpu mask的变量的内存,后面会使用到 (e)驱动工程师是撰写具体外设驱动的,他/她未必会了解到底层的一些具体的interrupt controller的信息。有些interrupt controller(例如MSI based interrupt)本质上就是就是one shot的(通过IRQCHIP_ONESHOT_SAFE标记),因此驱动工程师设定的IRQF_ONESHOT其实是画蛇添足,因此可以去掉。 (4)共享中断的检查。代码如下:
(a)old指向注册之前的action list,如果不是NULL,那么说明需要共享interrupt line。但是如果要共享,需要每一个irqaction都同意共享(IRQF_SHARED),每一个irqaction的触发方式相同(都是level trigger或者都是edge trigger),相同的oneshot类型的中断(都是one shot或者都不是),per cpu类型的相同中断(都是per cpu的中断或者都不是)。 (b)将该irqaction挂入队列的尾部。 (5)thread mask的设定。代码如下:
对于one shot类型的中断,我们还需要设定thread mask。如果一个one shot类型的中断只有一个threaded handler(不支持共享),那么事情就很简单(临时变量thread_mask等于0),该irqaction的thread_mask成员总是使用第一个bit来标识该irqaction。但是,如果支持共享的话,事情变得有点复杂。我们假设这个one shot类型的IRQ上有A,B和C三个irqaction,那么A,B和C三个irqaction的thread_mask成员会有不同的bit来标识自己。例如A的thread_mask成员是0x01,B的是0x02,C的是0x04,如果有更多共享的irqaction(必须是oneshot类型),那么其thread_mask成员会依次设定为0x08,0x10…… (a)在上面“共享中断的检查”这个section中,thread_mask变量保存了所有的属于该interrupt line的thread_mask,这时候,如果thread_mask变量如果是全1,那么说明irqaction list上已经有了太多的irq action(大于32或者64,和具体系统和编译器相关)。如果没有满,那么通过ffz函数找到第一个为0的bit作为该irq action的thread bit mask。 (b)irq_default_primary_handler的代码如下:
代码非常的简单,返回IRQ_WAKE_THREAD,让kernel唤醒threaded handler就OK了。使用irq_default_primary_handler虽然简单,但是有一个风险:如果是电平触发的中断,我们需要操作外设的寄存器才可以让那个asserted的电平信号消失,否则它会一直持续。一般,我们都是直接在primary中操作外设寄存器(slow bus类型的interrupt controller不行),尽早的clear interrupt,但是,对于irq_default_primary_handler,它仅仅是wakeup了threaded interrupt handler,并没有clear interrupt,这样,执行完了primary handler,外设中断仍然是asserted,一旦打开CPU中断,立刻触发下一次的中断,然后不断的循环。因此,如果注册中断的时候没有指定primary interrupt handler,并且没有设定IRQF_ONESHOT,那么系统是会报错的。当然,有一种情况可以豁免,当底层的irq chip是one shot safe的(IRQCHIP_ONESHOT_SAFE)。 (6)用户IRQ flag和底层interrupt flag的同步(TODO) 原创文章,转发请注明出处。蜗窝科技。http://www./linux_kenrel/request_threaded_irq.html 评论: 阿孟 2015-03-25 16:42 Hi Linuxer, 请教一下, “新的内核已经不区分slow handler和fast handle,都是fast handler,都是需要关闭CPU中断的,那些需要后续处理的内容推到threaded interrupt handler中去执行。” 如果有的中断比较频繁,会不会可能丢失,如果刚好有其他中断已经关闭了CPU中断。 如果有这样的情况有什么建议吗? linuxer 2015-03-26 09:55 @阿孟:这是和系统(HW and SW)设计有关的,我们假设有硬件A和硬件B,在A的的handler中是关闭中断的,因此,这时候B产生中断只能体现在中断控制器的中断状态寄存器上,如果该寄存器不能反应多次中断,那么在由于A handler而关闭CPU中断的期间,多次B产生的中断只能是合成一次。 如何解决这个问题?我想可能有下面的方法: 1、fast handler就是fast handler,不要做额外的事情,尽量快的处理,减少关闭中断的时间 2、硬件这么快的产生中断是为何呢?是不是硬件的FIFO不够大? RobinHsiang 2015-03-10 21:10 Hi Linuxer, 请教一个问题,ARM外设使用GPIO中断唤醒CPU时,像GPIO配置成输入,和让该GPIO关联系统中断,以及让GPIO作为中断唤醒源等等动作一定要在外设驱动的初始化函数中完成吗? 可以在device tree中编写,然后留一个类似的接口,让驱动只单纯的调用一个函数可以吗? 我其实遇到的问题是,外设(中断源)的驱动是客户写的,但是这些系统相关的他们又不想管。 linuxer 2015-03-11 09:03 @RobinHsiang:你问的问题涉及了三个模块: 1、GPIO子系统模块 2、电源管理子系统模块 3、中断子系统模块 实际上,一个外设驱动当然需要调用内核各个子系统的接口来完成自己的具体功能。因此: GPIO配置、申请中断以及设置wakeup source本身都是驱动功能的一部分。device tree的主要功能是让系统知道硬件的拓扑结构,不可能提供一个一统天下的接口来完成你说的那一系列功能。 如果外设驱动是客户写的,那么你是否在他撰写该驱动的时候仔细的定义了规格?我觉得电源管理也是规格之一的。不过我猜你们和客户其实没有那么正式的合同来约定该驱动的开发,因此,他们只想关注硬件功能的代码而不想涉及其他,如果这样的话,那么该驱动的责任人应该是你们,客户仅仅是提供示例代码而已。 RobinHsiang 2015-03-11 10:16 @linuxer:Thanks..!! 确实是你所说的,这几个驱动没有约定这么详细,当初只是定了大致如何实现功能。 关键是这个客户专门做这几个模块的研发,而且可能牵涉到一些军工和政府项目,不开源给我们。 客户的建议是系统平台部分我们做,他们只open /dev/ttyhsl,至于电源和唤醒部分,我们也提供接口。 最近也讨论了一些方案,比如侦测UART的传输情况来关闭电源和clock啊,或者将系统平台的情况写一些接口出来供他们调用,但是都感觉怪怪的,也怕实现不好。 最终看来,只能是我们将代码写好,让他们做填空题,我们再测试了。 linuxer 2014-09-25 10:16 你可以先用arm-linux-objdump的命令看看是否你编译的异常向量表就是这样的。 指令“EAFFFFFE”应该是被翻译成一个跳转指令,跳转范围是24-bit的立即数,对于“EAFFFFFE”,显然还没有填入这个立即数,一般而言,当你编译出.o文件的时候,异常向量表的反汇编结果就是: __vectors_start(): arch/arm/kernel/entry-armv.S:1134 0: eaffffff b 4 <__vectors_start+0x4> arch/arm/kernel/entry-armv.S:1135 4: eafffffe b 1a0 <vector_und> arch/arm/kernel/entry-armv.S:1136 8: e59ffff0 ldr pc, [pc, #4080] ; 1000 <.vectors+0x1000> arch/arm/kernel/entry-armv.S:1137 c: eafffffe b 120 <vector_pabt> arch/arm/kernel/entry-armv.S:1138 10: eafffffe b a0 <vector_dabt> arch/arm/kernel/entry-armv.S:1139 14: ea000086 b 220 <vector_swi> arch/arm/kernel/entry-armv.S:1140 18: eafffffe b 20 <vector_irq> arch/arm/kernel/entry-armv.S:1141 1c: ea000087 b 224 <vector_fiq_offset> 这时候,还没有link,因此地址还是reallocated。当连接完成,“EAFFFFFE”会被修正,其24-bit的立即数的位置会被填入真正跳转的地址,例如: __vectors_start(): c000e4e4: ef9f0000 svc 0x009f0000 c000e4e8: ea0000dd b c000e864 <early_mem+0x8> c000e4ec: e59ff410 ldr pc, [pc, #1040] ; c000e904 <early_initrd+0x38> c000e4f0: ea0000bb b c000e7e4 <parse_tag_serialnr+0x8> c000e4f4: ea00009a b c000e764 <parse_tag_ramdisk+0x20> c000e4f8: ea0000fa b c000e8e8 <early_initrd+0x1c> c000e4fc: ea000078 b c000e6e4 <parse_tag_videotext+0x20> c000e500: ea0000f7 b c000e8e4 <early_initrd+0x18> 我建议你一步一步的检查: 1、先检查你编译出来的kernel image 2、完成early_trap_init之后,检查看看exception table的copy的对不对 3、是否运行时,有些代码误操作了exception table |
|