Linux中断子系统(三)之GIC中断处理过程

news/2024/11/29 18:21:18/

Linux中断子系统(三)之GIC中断处理过程

备注:
  1. Kernel版本:5.4
  2. 使用工具:Source Insight 4.0
  3. 参考博客:
Linux中断子系统(一)中断控制器及驱动分析
Linux中断子系统(二)-通用框架处理
吐血整理 | 肝翻Linux中断所有知识点
Linux kernel的中断子系统之(六):ARM中断处理过程

文章目录

  • Linux中断子系统(三)之GIC中断处理过程
    • 中断处理的准备过程
      • 中断模式的stack准备
      • SVC模式的stack准备
      • 异常向量表的准备
    • 中断处理的执行过程
      • vectot_irq的定义
      • vector_stub宏定义
      • 当发生中断的时候,代码运行在用户空间
      • 当发生中断的时候,代码运行在内核空间
      • handle_arch_irq执行过程
    • 中断退出过程

中断处理的准备过程

中断模式的stack准备

  ARM处理器有多种processor mode,例如user mode(用户空间的AP所处于的模式)、supervisor mode(即SVC mode,大部分的内核态代码都处于这种mode)、IRQ mode(发生中断后,处理器会切入到该mode)等。对于linux kernel,其中断处理处理过程中,ARM 处理器大部分都是处于SVC mode。但是,实际上产生中断的时候,ARM处理器实际上是进入IRQ mode,因此在进入真正的IRQ异常处理之前会有一小段IRQ mode的操作,之后会进入SVC mode进行真正的IRQ异常处理。由于IRQ mode只是一个过度,因此IRQ mode的栈很小,只有12个字节,具体如下:

//源码:arch/arm/kernel/setup.c
struct stack {u32 irq[3];u32 abt[3];u32 und[3];u32 fiq[3];
} ____cacheline_aligned;#ifndef CONFIG_CPU_V7M
static struct stack stacks[NR_CPUS];
#endif

  除了irq mode,linux kernel在处理abt mode(当发生data abort exception或者prefetch abort exception的时候进入的模式)和und mode(处理器遇到一个未定义的指令的时候进入的异常模式)的时候也是采用了相同的策略。也就是经过一个简短的abt或者und mode之后,stack切换到svc mode的栈上,这个栈就是发生异常那个时间点current thread的内核栈。anyway,在irq mode和svc mode之间总是需要一个stack保存数据,这就是中断模式的stack,系统初始化的时候,cpu_init函数中会进行中断模式stack的设定:

/** cpu_init - initialise one CPU.** cpu_init sets up the per-CPU stacks.*/
void notrace cpu_init(void)
{
#ifndef CONFIG_CPU_V7Munsigned int cpu = smp_processor_id();	//获取CPU IDstruct stack *stk = &stacks[cpu];		//获取该CPU对于的irq abt和und的stack指针if (cpu >= NR_CPUS) {pr_crit("CPU%u: bad primary CPU number\n", cpu);BUG();}/** This only works on resume and secondary cores. For booting on the* boot cpu, smp_prepare_boot_cpu is called after percpu area setup.*/set_my_cpu_offset(per_cpu_offset(cpu));cpu_proc_init();/** Define the placement constraint for the inline asm directive below.* In Thumb-2, msr with an immediate value is not allowed.*/
#ifdef CONFIG_THUMB2_KERNEL
#define PLC	"r"	//Thumb-2下,msr指令不允许使用立即数,只能使用寄存器
#else
#define PLC	"I"
#endif/** setup stacks for re-entrant exception handlers*/__asm__ ("msr	cpsr_c, %1\n\t"		//让CPU进入IRQ mode"add	r14, %0, %2\n\t"	//r14寄存器保存stk->irq"mov	sp, r14\n\t"		//设定IRQ mode的stack为stk->irq"msr	cpsr_c, %3\n\t""add	r14, %0, %4\n\t""mov	sp, r14\n\t"		//设定abt mode的stack为stk->abt"msr	cpsr_c, %5\n\t""add	r14, %0, %6\n\t""mov	sp, r14\n\t""msr	cpsr_c, %7\n\t"		//设定und mode的stack为stk->und"add	r14, %0, %8\n\t""mov	sp, r14\n\t""msr	cpsr_c, %9"			//设定fiq mode的stack为stk->fiq:: "r" (stk),PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),"I" (offsetof(struct stack, irq[0])),PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),"I" (offsetof(struct stack, abt[0])),PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),"I" (offsetof(struct stack, und[0])),PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE),"I" (offsetof(struct stack, fiq[0])),PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE): "r14");
#endif
}

  对于SMP,bootstrap CPU会在系统初始化的时候执行cpu_init函数,进行本CPU的irq、abt、und和fiq四种模式的内核栈的设定,具体调用序列是:start_kernel—>setup_arch—>setup_processor—>cpu_init
  对于系统中其他的CPU,bootstrap CPU会在系统初始化的最后,对每一个online的CPU进行初始化,具体的调用序列是:

start_kernel--->rest_init--->kernel_init--->kernel_init_freeable--->kernel_init_freeable
--->smp_init--->cpu_up--->_cpu_up--->__cpu_up。

  __cpu_up函数是和CPU architecture相关的。对于ARM,其调用序列是

__cpu_up--->boot_secondary--->smp_ops.smp_boot_secondary(SOC相关代码)--->secondary_startup--->__secondary_switched--->secondary_start_kernel--->cpu_init。

  除了初始化,系统电源管理也需要irq、abt和und stack的设定。如果我们设定的电源管理状态在进入sleep的时候,CPU会丢失irq、abt和und stack point寄存器的值,那么在CPU resume的过程中,要调用cpu_init来重新设定这些值。

SVC模式的stack准备

  我们经常说进程的用户空间和内核空间,对于一个应用程序而言,可以运行在用户空间,也可以通过系统调用进入内核空间。在用户空间,使用的是用户栈,也就是我们软件工程师编写用户空间程序的时候,保存局部变量的stack。陷入内核后,当然不能用用户栈了,这时候就需要使用到内核栈。所谓内核栈其实就是处于SVC mode时候使用的栈。在linux最开始启动的时候,系统只有一个进程(更准确的说是kernel thread),就是PID等于0的那个进程,叫做swapper进程(或者叫做idle进程)。该进程的内核栈是静态定义的,如下:

源码:init/init_task.c
/** Set up the first task table, touch at your own risk!. Base=0,* limit=0x1fffff (=2MB)*/
struct task_struct init_task
#ifdef CONFIG_ARCH_TASK_STRUCT_ON_STACK__init_task_data
#endif
= {
#ifdef CONFIG_THREAD_INFO_IN_TASK.thread_info	= INIT_THREAD_INFO(init_task),.stack_refcount	= REFCOUNT_INIT(1),
#endif.state		= 0,.stack		= init_stack,.usage		= REFCOUNT_INIT(2),.flags		= PF_KTHREAD,.prio		= MAX_PRIO - 20,.static_prio	= MAX_PRIO - 20,.normal_prio	= MAX_PRIO - 20,.policy		= SCHED_NORMAL,.cpus_ptr	= &init_task.cpus_mask,.cpus_mask	= CPU_MASK_ALL,.nr_cpus_allowed= NR_CPUS,.mm		= NULL,.active_mm	= &init_mm,.restart_block	= {.fn = do_no_restart_syscall,},.se		= {.group_node 	= LIST_HEAD_INIT(init_task.se.group_node),},.rt		= {.run_list	= LIST_HEAD_INIT(init_task.rt.run_list),.time_slice	= RR_TIMESLICE,},.tasks		= LIST_HEAD_INIT(init_task.tasks),
#ifdef CONFIG_SMP.pushable_tasks	= PLIST_NODE_INIT(init_task.pushable_tasks, MAX_PRIO),
#endif
#ifdef CONFIG_CGROUP_SCHED.sched_task_group = &root_task_group,
#endif.ptraced	= LIST_HEAD_INIT(init_task.ptraced),.ptrace_entry	= LIST_HEAD_INIT(init_task.ptrace_entry),.real_parent	= &init_task,.parent		= &init_task,.children	= LIST_HEAD_INIT(init_task.children),.sibling	= LIST_HEAD_INIT(init_task.sibling),.group_leader	= &init_task,RCU_POINTER_INITIALIZER(real_cred, &init_cred),RCU_POINTER_INITIALIZER(cred, &init_cred),.comm		= INIT_TASK_COMM,.thread		= INIT_THREAD,.fs		= &init_fs,.files		= &init_files,.signal		= &init_signals,.sighand	= &init_sighand,.nsproxy	= &init_nsproxy,.pending	= {.list = LIST_HEAD_INIT(init_task.pending.list),.signal = {{0}}},.blocked	= {{0}},.alloc_lock	= __SPIN_LOCK_UNLOCKED(init_task.alloc_lock),.journal_info	= NULL,INIT_CPU_TIMERS(init_task).pi_lock	= __RAW_SPIN_LOCK_UNLOCKED(init_task.pi_lock),.timer_slack_ns = 50000, /* 50 usec default slack */.thread_pid	= &init_struct_pid,.thread_group	= LIST_HEAD_INIT(init_task.thread_group),.thread_node	= LIST_HEAD_INIT(init_signals.thread_head),
#ifdef CONFIG_AUDIT.loginuid	= INVALID_UID,.sessionid	= AUDIT_SID_UNSET,
#endif
#ifdef CONFIG_PERF_EVENTS.perf_event_mutex = __MUTEX_INITIALIZER(init_task.perf_event_mutex),.perf_event_list = LIST_HEAD_INIT(init_task.perf_event_list),
#endif
#ifdef CONFIG_PREEMPT_RCU.rcu_read_lock_nesting = 0,.rcu_read_unlock_special.s = 0,.rcu_node_entry = LIST_HEAD_INIT(init_task.rcu_node_entry),.rcu_blocked_node = NULL,
#endif
#ifdef CONFIG_TASKS_RCU.rcu_tasks_holdout = false,.rcu_tasks_holdout_list = LIST_HEAD_INIT(init_task.rcu_tasks_holdout_list),.rcu_tasks_idle_cpu = -1,
#endif
#ifdef CONFIG_CPUSETS.mems_allowed_seq = SEQCNT_ZERO(init_task.mems_allowed_seq),
#endif
#ifdef CONFIG_RT_MUTEXES.pi_waiters	= RB_ROOT_CACHED,.pi_top_task	= NULL,
#endifINIT_PREV_CPUTIME(init_task)
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN.vtime.seqcount	= SEQCNT_ZERO(init_task.vtime_seqcount),.vtime.starttime = 0,.vtime.state	= VTIME_SYS,
#endif
#ifdef CONFIG_NUMA_BALANCING.numa_preferred_nid = NUMA_NO_NODE,.numa_group	= NULL,.numa_faults	= NULL,
#endif
#ifdef CONFIG_KASAN.kasan_depth	= 1,
#endif
#ifdef CONFIG_TRACE_IRQFLAGS.softirqs_enabled = 1,
#endif
#ifdef CONFIG_LOCKDEP.lockdep_depth = 0, /* no locks held yet */.curr_chain_key = INITIAL_CHAIN_KEY,.lockdep_recursion = 0,
#endif
#ifdef CONFIG_FUNCTION_GRAPH_TRACER.ret_stack	= NULL,
#endif
#if defined(CONFIG_TRACING) && defined(CONFIG_PREEMPTION).trace_recursion = 0,
#endif
#ifdef CONFIG_LIVEPATCH.patch_state	= KLP_UNDEFINED,
#endif
#ifdef CONFIG_SECURITY.security	= NULL,
#endif
};
EXPORT_SYMBOL(init_task);
#define THREAD_SIZE_ORDER	1
#define THREAD_SIZE		(PAGE_SIZE << THREAD_SIZE_ORDER)
#define THREAD_START_SP		(THREAD_SIZE - 8)extern unsigned long init_stack[THREAD_SIZE / sizeof(unsigned long)];

  对于ARM平台,THREAD_SIZE是8192个byte,因此占据两个page frame。随着初始化的进行,Linux kernel会创建若干的内核线程,而在进入用户空间后,user space的进程也会创建进程或者线程。Linux kernel在创建进程(包括用户进程和内核线程)的时候都会分配一个(或者两个,和配置相关)page frame,具体代码如下:

static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{struct task_struct *tsk;unsigned long *stack;struct vm_struct *stack_vm_area __maybe_unused;int err;if (node == NUMA_NO_NODE)node = tsk_fork_get_node(orig);tsk = alloc_task_struct_node(node);if (!tsk)return NULL;stack = alloc_thread_stack_node(tsk, node);if (!stack)goto free_tsk;if (memcg_charge_kernel_stack(tsk))goto free_stack;stack_vm_area = task_stack_vm_area(tsk);err = arch_dup_task_struct(tsk, orig);/** arch_dup_task_struct() clobbers the stack-related fields.  Make* sure they're properly initialized before using any stack-related* functions again.*/tsk->stack = stack;
#ifdef CONFIG_VMAP_STACKtsk->stack_vm_area = stack_vm_area;
#endif
#ifdef CONFIG_THREAD_INFO_IN_TASKrefcount_set(&tsk->stack_refcount, 1);
#endifif (err)goto free_stack;#ifdef CONFIG_SECCOMP/** We must handle setting up seccomp filters once we're under* the sighand lock in case orig has changed between now and* then. Until then, filter must be NULL to avoid messing up* the usage counts on the error path calling free_task.*/tsk->seccomp.filter = NULL;
#endifsetup_thread_stack(tsk, orig);clear_user_return_notifier(tsk);clear_tsk_need_resched(tsk);set_task_stack_end_magic(tsk);#ifdef CONFIG_STACKPROTECTORtsk->stack_canary = get_random_canary();
#endifif (orig->cpus_ptr == &orig->cpus_mask)tsk->cpus_ptr = &tsk->cpus_mask;/** One for the user space visible state that goes away when reaped.* One for the scheduler.*/refcount_set(&tsk->rcu_users, 2);/* One for the rcu users */refcount_set(&tsk->usage, 1);
#ifdef CONFIG_BLK_DEV_IO_TRACEtsk->btrace_seq = 0;
#endiftsk->splice_pipe = NULL;tsk->task_frag.page = NULL;tsk->wake_q.next = NULL;account_kernel_stack(tsk, 1);kcov_task_init(tsk);#ifdef CONFIG_FAULT_INJECTIONtsk->fail_nth = 0;
#endif#ifdef CONFIG_BLK_CGROUPtsk->throttle_queue = NULL;tsk->use_memdelay = 0;
#endif#ifdef CONFIG_MEMCGtsk->active_memcg = NULL;
#endifreturn tsk;free_stack:free_thread_stack(tsk);
free_tsk:free_task_struct(tsk);return NULL;
}

  底部是struct thread_info数据结构,顶部(高地址)就是该进程的内核栈。当进程切换的时候,整个硬件和软件的上下文都会进行切换,这里就包括了svc mode的sp寄存器的值被切换到调度算法选定的新的进程的内核栈上来。

异常向量表的准备

  对于ARM处理器而言,当发生异常的时候,处理器会暂停当前指令的执行,保存现场,转而去执行对应的异常向量处的指令,当处理完该异常的时候,恢复现场,回到原来的那点去继续执行程序。系统所有的异常向量(共计8个)组成了异常向量表。向量表(vector table)的代码如下:

//源码:arch/arm/kernel/entry-armv.S.section .vectors, "ax", %progbits
.L__vectors_start:W(b)	vector_rstW(b)	vector_undW(ldr)	pc, .L__vectors_start + 0x1000W(b)	vector_pabtW(b)	vector_dabtW(b)	vector_addrexcptnW(b)	vector_irqW(b)	vector_fiq

  对于本文而言,我们重点关注vector_irq这个exception vector。异常向量表可能被安放在两个位置上:
(1)异常向量表位于0x0的地址。这种设置叫做Normal vectors或者Low vectors。
(2)异常向量表位于0xffff0000的地址。这种设置叫做high vectors具体是low vectors还是high vectors是由ARM的一个叫做的SCTLR寄存器的第13个bit (vector bit)控制的。
  对于启用MMU的ARM Linux而言,系统使用了high vectors。为什么不用low vector呢?
  对于linux而言,0~3G的空间是用户空间,如果使用low vector,那么异常向量表在0地址,那么则是用户空间的位置,因此linux选用high vector。当然,使用Low vector也可以,这样Low vector所在的空间则属于kernel space了(也就是说,3G~4G的空间加上Low vector所占的空间属于kernel space),不过这时候要注意一点,因为所有的进程共享kernel space,而用户空间的程序经常会发生空指针访问,这时候,内存保护机制应该可以捕获这种错误(大部分的MMU都可以做到,例如:禁止userspace访问kernel space的地址空间),防止vector table被访问到。对于内核中由于程序错误导致的空指针访问,内存保护机制也需要控制vector table被修改,因此vector table所在的空间被设置成read only的。

  在使用了MMU之后,具体异常向量表放在那个物理地址已经不重要了,重要的是把它映射到0xffff0000的虚拟地址就OK了,具体代码如下:

//源码:arch/arm/mm/mmu.c
/** Set up the device mappings.  Since we clear out the page tables for all* mappings above VMALLOC_START, except early fixmap, we might remove debug* device mappings.  This means earlycon can be used to debug this function* Any other function or debugging method which may touch any device _will_* crash the kernel.*/
static void __init devicemaps_init(const struct machine_desc *mdesc)
{struct map_desc map;unsigned long addr;void *vectors;/** Allocate the vector page early.*/vectors = early_alloc(PAGE_SIZE * 2);//分配两个page的物理页帧early_trap_init(vectors);//copy向量表以及相关help function到该区域......../** Create a mapping for the machine vectors at the high-vectors* location (0xffff0000).  If we aren't using high-vectors, also* create a mapping at the low-vectors virtual address.*/map.pfn = __phys_to_pfn(virt_to_phys(vectors));map.virtual = 0xffff0000;map.length = PAGE_SIZE;
#ifdef CONFIG_KUSER_HELPERSmap.type = MT_HIGH_VECTORS;
#elsemap.type = MT_LOW_VECTORS;
#endifcreate_mapping(&map);//映射0xffff0000的那个page frame//如果SCTLR.V的值设定为low vectors,那么还要映射0地址开始的memoryif (!vectors_high()) {map.virtual = 0;map.length = PAGE_SIZE * 2;map.type = MT_LOW_VECTORS;create_mapping(&map);}/* Now create a kernel read-only mapping */map.pfn += 1;map.virtual = 0xffff0000 + PAGE_SIZE;map.length = PAGE_SIZE;map.type = MT_LOW_VECTORS;create_mapping(&map);	//映射high vecotr开始的第二个page frame/** Ask the machine support to map in the statically mapped devices.*/if (mdesc->map_io)mdesc->map_io();elsedebug_ll_io_init();fill_pmd_gaps();/* Reserve fixed i/o space in VMALLOC region */pci_reserve_io();/** Finally flush the caches and tlb to ensure that we're in a* consistent state wrt the writebuffer.  This also ensures that* any write-allocated cache lines in the vector page are written* back.  After this point, we can start to touch devices again.*/local_flush_tlb_all();flush_cache_all();/* Enable asynchronous aborts */early_abt_enable();
}

  为什么要分配两个page frame呢?
  这里vectors table和kuser helper函数(内核空间提供的函数,但是用户空间使用)占用了一个page frame,另外异常处理的stub函数占用了另外一个page frame。为什么会有stub函数呢?稍后会讲到。在early_trap_init函数中会初始化异常向量表,具体代码如下:

void __init early_trap_init(void *vectors_base)
{
#ifndef CONFIG_CPU_V7Munsigned long vectors = (unsigned long)vectors_base;extern char __stubs_start[], __stubs_end[];extern char __vectors_start[], __vectors_end[];unsigned i;vectors_page = vectors_base;/** Poison the vectors page with an undefined instruction.  This* instruction is chosen to be undefined for both ARM and Thumb* ISAs.  The Thumb version is an undefined instruction with a* branch back to the undefined instruction.*/// 将整个vector table那个page frame填充成未定义的指令。// 起始vector table加上kuser helper函数并不能完全的充满这个page,有些缝隙。// 如果不这么处理,当极端情况下(程序错误或者HW的issue),// CPU可能从这些缝隙中取指执行,从而导致不可知的后果。// 如果将这些缝隙填充未定义指令,那么CPU可以捕获这种异常。for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)((u32 *)vectors_base)[i] = 0xe7fddef1;/** Copy the vectors, stubs and kuser helpers (in entry-armv.S)* into the vector page, mapped at 0xffff0000, and ensure these* are visible to the instruction stream.*/// 拷贝vector table,拷贝stub functionmemcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);kuser_init(vectors_base);flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
#else /* ifndef CONFIG_CPU_V7M *//** on V7-M there is no need to copy the vector table to a dedicated* memory area. The address is configurable and so a table in the kernel* image can be used.*/
#endif
}

  一旦涉及代码的拷贝,我们就需要关心其编译连接时地址(link-time address)和运行时地址(run-time address)。在kernel完成链接后,__vectors_start有了其link-time address,如果link-time address和run-time address一致,那么这段代码运行时毫无压力。

  但是,目前对于vector table而言,其被copy到其他的地址上(对于High vector,这是地址就是0xffff00000),也就是说,link-time address和run-time address不一样了,如果仍然想要这些代码可以正确运行,那么需要这些代码是位置无关的代码。

  对于vector table而言,必须要位置无关。B这个branch instruction本身就是位置无关的,它可以跳转到一个当前位置的offset。不过并非所有的vector都是使用了branch instruction,对于软中断,其vector地址上指令是“W(ldr) pc, __vectors_start + 0x1000 ”,这条指令被编译器编译成ldr pc, [pc, #4080],这种情况下,该指令也是位置无关的,但是有个限制,offset必须在4K的范围内,这也是为何存在stub section的原因了。

中断处理的执行过程

vectot_irq的定义

//源码:arch/arm/kernel/entry-armv.S
/** Interrupt dispatcher*/vector_stub	irq, IRQ_MODE, 4 @ 减去4,确保返回发生中断之后的那条指令.long	__irq_usr			@  0  (USR_26 / USR_32).long	__irq_invalid			@  1  (FIQ_26 / FIQ_32).long	__irq_invalid			@  2  (IRQ_26 / IRQ_32).long	__irq_svc			@  3  (SVC_26 / SVC_32).long	__irq_invalid			@  4.long	__irq_invalid			@  5.long	__irq_invalid			@  6.long	__irq_invalid			@  7.long	__irq_invalid			@  8.long	__irq_invalid			@  9.long	__irq_invalid			@  a.long	__irq_invalid			@  b.long	__irq_invalid			@  c.long	__irq_invalid			@  d.long	__irq_invalid			@  e.long	__irq_invalid			@  f

vector_stub宏定义

THUMB(	.thumb	)/** Vector stubs.** This code is copied to 0xffff1000 so we can use branches in the* vectors, rather than ldr's.  Note that this code must not exceed* a page size.** Common stub entry macro:*   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC** SP points to a minimal amount of processor-private memory, the address* of which is copied into r0 for the mode specific abort handler.*/.macro	vector_stub, name, mode, correction=0.align	5vector_\name:@ 异常的时候,lr中保存了发生中断的PC+4,@ 如果减去4的话,得到的就是发生中断那一点的PC值.if \correctionsub	lr, lr, #\correction	.endif@@ Save r0, lr_<exception> (parent PC) and spsr_<exception>@ (parent CPSR)@@ 依次保存lr、pc、spsr的值(硬件已经帮我们保存了CPSR到SPSR中)stmia	sp, {r0, lr}		@ save r0, lrmrs	lr, spsr@ 因为随后的代码要使用r0寄存器,因此我们要把r0放到栈上,@ 只有这样才能完完全全恢复硬件现场。str	lr, [sp, #8]		@ save spsr@@ Prepare for SVC32 mode.  IRQs remain disabled.@@ 准备将ARM推送到SVC mode@ 其实就是修改SPSR的值,SPSR不是CPSR,@ 不会引起processor mode的切换mrs	r0, cpsreor	r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)msr	spsr_cxsf, r0@@ the branch table must immediately follow this code@and	lr, lr, #0x0f @ lr保存了发生异常时候的CPSR,通过and操作,可以获取CPSR.M[3:0]的值THUMB(	adr	r0, 1f			) @ 根据当前PC值,获取lable 1的地址THUMB(	ldr	lr, [r0, lr, lsl #2]	) @ lr根据当前mode,要么是__irq_usr的地址 ,要么是__irq_svc的地址mov	r0, sp	@将irq mode的stack point通过r0传递给即将跳转的函数ARM(	ldr	lr, [pc, lr, lsl #2]	)movs	pc, lr			@ branch to handler in SVC mode
ENDPROC(vector_\name).align	2@ handler addresses follow this label
1:.endm

当发生中断的时候,代码运行在用户空间

Interrupt dispatcher的代码如下:

源码:arch/arm/kernel/entry-armv.S
/** Interrupt dispatcher*/vector_stub	irq, IRQ_MODE, 4.long	__irq_usr			@  0  (USR_26 / USR_32).long	__irq_invalid			@  1  (FIQ_26 / FIQ_32).long	__irq_invalid			@  2  (IRQ_26 / IRQ_32).long	__irq_svc			@  3  (SVC_26 / SVC_32).long	__irq_invalid			@  4.long	__irq_invalid			@  5.long	__irq_invalid			@  6.long	__irq_invalid			@  7.long	__irq_invalid			@  8.long	__irq_invalid			@  9.long	__irq_invalid			@  a.long	__irq_invalid			@  b.long	__irq_invalid			@  c.long	__irq_invalid			@  d.long	__irq_invalid			@  e.long	__irq_invalid			@  f

  这其实就是一个lookup table,根据CPSR.M[3:0]的值进行跳转。因此,该lookup table共设定了16个入口,当然只有两项有效,分别对应user mode和svc mode的跳转地址。其他入口的__irq_invalid也是非常关键的,这保证了在其模式下发生了中断,系统可以捕获到这样的错误,为debug提供有用的信息。


__irq_usr函数定义:

//源码:arch/arm/kernel/entry-armv.S.align	5
__irq_usr:usr_entry				@ 保存用户现场kuser_cmpxchg_checkirq_handler				@ 中断处理函数get_thread_info tsk		@ tsk是r9,指向当前的thread info数据结构mov	why, #0				@ why是r8b	ret_to_user_from_irq @ 中断返回UNWIND(.fnend		)
ENDPROC(__irq_usr)

(1)保存发生中断时候的现场。所谓保存现场其实就是把发生中断那一刻的硬件上下文(各个寄存器)保存在了SVC mode的stack上。
usr_entry函数定义:

//源码:arch/arm/kernel/entry-armv.S
.macro	usr_entry, trace=1, uaccess=1UNWIND(.fnstart	)UNWIND(.cantunwind	)	@ don't unwind the user spacesub	sp, sp, #PT_REGS_SIZE         --------------AARM(	stmib	sp, {r1 - r12}	)  --------------BTHUMB(	stmia	sp, {r0 - r12}	)ATRAP(	mrc	p15, 0, r7, c1, c0, 0)ATRAP(	ldr	r8, .LCcralign)ldmia	r0, {r3 - r5}                   --------------Cadd	r0, sp, #S_PC		@ here for interlock avoidance  --Dmov	r6, #-1			@  ""  ""     ""        ""str	r3, [sp]		@ save the "real" r0 copied@ from the exception stackATRAP(	ldr	r8, [r8, #0])@@ We are now ready to fill in the remaining blanks on the stack:@@  r4 - lr_<exception>, already fixed up for correct return/restart@  r5 - spsr_<exception>@  r6 - orig_r0 (see pt_regs definition in ptrace.h)@@ Also, separately save sp_usr and lr_usr@stmia	r0, {r4 - r6}            --------------EARM(	stmdb	r0, {sp, lr}^			) -------FTHUMB(	store_user_sp_lr r0, r1, S_SP - S_PC	).if \uaccessuaccess_disable ip.endif@ Enable the alignment trap while in kernel modeATRAP(	teq	r8, r7)ATRAP( mcrne	p15, 0, r8, c1, c0, 0)@@ Clear FP to mark the first stack frame@zero_fp.if	\trace
#ifdef CONFIG_TRACE_IRQFLAGSbl	trace_hardirqs_off
#endifct_user_exit save = 0.endif.endm

  A:代码执行到这里的时候,ARM处理已经切换到了SVC mode。一旦进入SVC mode,ARM处理器看到的寄存器已经发生变化,这里的sp已经变成了sp_svc了。因此,后续的压栈操作都是压入了发生中断那一刻的进程的(或者内核线程)内核栈(svc mode栈)。具体保存多少个寄存器值?S_FRAME_SIZE已经给出了答案,这个值是18个寄存器。r0~r15再加上CPSR也只有17个而已。先保留这个疑问,我们稍后回答。

  B:压栈首先压入了r1~r12,这里为何不处理r0?因为r0在irq mode切到svc mode的时候被污染了,不过,原始的r0被保存的irq mode的stack上了。r13(sp)和r14(lr)需要保存吗,当然需要,稍后再保存。执行到这里,内核栈的布局如下图所示:
请添加图片描述

  stmib中的ib表示increment before,因此,在压入R1的时候,stack pointer会先增加4,重要是预留r0的位置。stmib sp, {r1 - r12}指令中的sp没有“!”的修饰符,表示压栈完成后并不会真正更新stack pointer,因此sp保持原来的值。

  C:注意,这里r0指向了irq stack,因此,r3是中断时候的r0值,r4是中断现场的PC值,r5是中断现场的CPSR值。

  D:把r0赋值为S_PC的值。根据struct pt_regs的定义(这个数据结构反应了内核栈上的保存的寄存器的排列信息),从低地址到高地址依次为:

ARM_r0
ARM_r1
ARM_r2
ARM_r3
ARM_r4
ARM_r5
ARM_r6
ARM_r7
ARM_r8
ARM_r9
ARM_r10
ARM_fp
ARM_ip
ARM_sp 
ARM_lr
ARM_pc<---------add    r0, sp, #S_PC指令使得r0指向了这个位置
ARM_cpsr
ARM_ORIG_r0

为什么要给r0赋值?因此kernel不想修改sp的值,保持sp指向栈顶。

  E:在内核栈上保存剩余的寄存器的值,根据代码,依次是r0,PC,CPSR和orig r0。执行到这里,内核栈的布局如下图所示
请添加图片描述

R0,PC和CPSR来自IRQ mode的stack。实际上这段操作就是从irq stack就中断现场搬移到内核栈上。

  F:内核栈上还有两个寄存器没有保持,分别是发生中断时候sp和lr这两个寄存器。这时候,r0指向了保存PC寄存器那个地址(add r0, sp, #S_PC),stmdb r0, {sp, lr}^中的“db”是decrement before,因此,将sp和lr压入stack中的剩余的两个位置。需要注意的是,我们保存的是发生中断那一刻(对于本节,这是当时user mode的sp和lr),指令中的“^”符号表示访问user mode的寄存器。


(2)核心处理irq_handler
  irq_handler的处理有两种配置。一种是配置了CONFIG_MULTI_IRQ_HANDLER。这种情况下,linux kernel允许run time设定irq handler。如果我们需要一个linux kernel image支持多个平台,这是就需要配置这个选项。另外一种是传统的linux的做法,irq_handler实际上就是arch_irq_handler_default,具体代码如下:

//源码:arch/arm/kernel/entry-armv.S.macro	irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLERldr	r1, =handle_arch_irqmov	r0, sp			@设定传递给machine定义的handle_arch_irq的参数badr	lr, 9997f	@设定返回地址ldr	pc, [r1]
#elsearch_irq_handler_default
#endif
9997:.endm

  对于情况一,machine相关代码需要设定handle_arch_irq函数指针,这里的汇编指令只需要调用这个machine代码提供的irq handler即可(当然,要准备好参数传递和返回地址设定)。

  情况二要稍微复杂一些(而且,看起来kernel中使用的越来越少),代码如下:

//源码:arch/arm/include/asm/entry-macro-multi.S.macro	arch_irq_handler_defaultget_irqnr_preamble r6, lr
1:	get_irqnr_and_base r0, r2, r6, lrmovne	r1, sp@@ routine called with r0 = irq number, r1 = struct pt_regs *@ 需要两个参数,一个是 irq number(保存在r0)@ 另一个是 struct pt_regs *(保存在r1中)@ 返回地址设定为符号1,也就是说要不断的解析irq状态寄存器@的内容,得到IRQ number,直到所有的irq number处理完毕badrne	lr, 1b      bne	asm_do_IRQ#ifdef CONFIG_SMP/** XXX** this macro assumes that irqstat (r2) and base (r6) are* preserved from get_irqnr_and_base above*/ALT_SMP(test_for_ipi r0, r2, r6, lr)ALT_UP_B(9997f)movne	r1, spbadrne	lr, 1bbne	do_IPI
#endif
9997:.endm

  这里的代码已经是和machine相关的代码了,我们这里只是简短描述一下。所谓machine相关也就是说和系统中的中断控制器相关了。get_irqnr_preamble是为中断处理做准备,有些平台根本不需要这个步骤,直接定义为空即可。get_irqnr_and_base 有四个参数,分别是:r0保存了本次解析的irq number,r2是irq状态寄存器的值,r6是irq controller的base address,lr是scratch register。

  对于ARM平台而言,我们推荐使用第一种方法,因为从逻辑上讲,中断处理就是需要根据当前的硬件中断系统的状态,转换成一个IRQ number,然后调用该IRQ number的处理函数即可。通过get_irqnr_and_base这样的宏定义来获取IRQ是旧的ARM SOC系统使用的方法,它是假设SOC上有一个中断控制器,硬件状态和IRQ number之间的关系非常简单。但是实际上,ARM平台上的硬件中断系统已经是越来越复杂了,需要引入interrupt controller级联,irq domain等等概念,因此,使用第一种方法优点更多,详见下文:handle_arch_irq执行过程。


当发生中断的时候,代码运行在内核空间

如果中断发生在内核空间,代码会跳转到__irq_svc处执行:

	.align	5
__irq_svc:svc_entry		@ 保存发生中断那一刻的现场保存在内核栈上irq_handler		@ 具体的中断处理,同user mode的处理#ifdef CONFIG_PREEMPT	@ 和preempt相关的处理ldr	r8, [tsk, #TI_PREEMPT]		@ get preempt countldr	r0, [tsk, #TI_FLAGS]		@ get flagsteq	r8, #0				@ if preempt count != 0movne	r0, #0				@ force flags to 0tst	r0, #_TIF_NEED_RESCHEDblne	svc_preempt
#endifsvc_exit r5, irq = 1			@ return from exceptionUNWIND(.fnend		)
ENDPROC(__irq_svc)

  保存现场的代码和user mode下的现场保存是类似的,因此这里不再详细描述,只是在下面的代码中内嵌一些注释。

//源码:arch/arm/kernel/entry-armv.S.macro	svc_entry, stack_hole=0, trace=1, uaccess=1UNWIND(.fnstart		)UNWIND(.save {r0 - pc}		)sub	sp, sp, #(SVC_REGS_SIZE + \stack_hole - 4) @ sp指向struct pt_regs中r1的位置
#ifdef CONFIG_THUMB2_KERNELSPFIX(	str	r0, [sp]	)	@ temporarily savedSPFIX(	mov	r0, sp		)SPFIX(	tst	r0, #4		)	@ test original stack alignmentSPFIX(	ldr	r0, [sp]	)	@ restored
#elseSPFIX(	tst	sp, #4		)
#endifSPFIX(	subeq	sp, sp, #4	)stmia	sp, {r1 - r12}	@ 寄存器入栈ldmia	r0, {r3 - r5}add	r7, sp, #S_SP - 4	@ here for interlock avoidance @ r7指向struct pt_regs中r12的位置mov	r6, #-1			@  ""  ""      ""       "" @ orig r0设为-1add	r2, sp, #(SVC_REGS_SIZE + \stack_hole - 4) @ r2是发现中断那一刻stack的现场SPFIX(	addeq	r2, r2, #4	)str	r3, [sp, #-4]!		@ save the "real" r0 copied @ 保存r0,注意有一个!,sp会加上4,这时候sp就指向栈顶的r0位置了@ from the exception stackmov	r3, lr @ 保存svc mode的lr到r3@@ We are now ready to fill in the remaining blanks on the stack:@@  r2 - sp_svc@  r3 - lr_svc@  r4 - lr_<exception>, already fixed up for correct return/restart@  r5 - spsr_<exception>@  r6 - orig_r0 (see pt_regs definition in ptrace.h)@stmia	r7, {r2 - r6}get_thread_info tskuaccess_entry tsk, r0, r1, r2, \uaccess.if \trace
#ifdef CONFIG_TRACE_IRQFLAGSbl	trace_hardirqs_off
#endif.endif.endm

  至此,在内核栈上保存了完整的硬件上下文。实际上不但完整,而且还有些冗余,因为其中有一个orig_r0的成员。所谓original r0就是发生中断那一刻的r0值,按理说,ARM_r0和ARM_ORIG_r0都应该是用户空间的那个r0。
  为何要保存两个r0值呢?为何中断将-1保存到了ARM_ORIG_r0位置呢?理解这个问题需要跳脱中断处理这个主题,我们来看ARM的系统调用。对于系统调用,它 和中断处理虽然都是cpu异常处理范畴,但是一个明显的不同是系统调用需要传递参数,返回结果。
  如果进行这样的参数传递呢?对于ARM,当然是寄存器了, 特别是返回结果,保存在了r0中。对于ARM,r0~r7是各种cpu mode都相同的,用于传递参数还是很方便的。因此,进入系统调用的时候,在内核栈上保存了发生系统调用现场的所有寄存器,一方面保存了hardware context,另外一方面,也就是获取了系统调用的参数。返回的时候,将返回值放到r0就OK了。

  根据上面的描述,r0有两个作用,传递参数,返回结果。当把系统调用的结果放到r0的时候,通过r0传递的参数值就被覆盖了。本来,这也没有什么,但是有些场合是需要需要这两个值的:
  1、ptrace (和debugger相关,这里就不再详细描述了)
  2、system call restart (和signal相关,这里就不再详细描述了)
  正因为如此,硬件上下文的寄存器中r0有两份,ARM_r0是传递的参数,并复制一份到ARM_ORIG_r0,当系统调用返回的时候,ARM_r0是系统调用的返回值。
  OK,我们再回到中断这个主题,其实在中断处理过程中,没有使用ARM_ORIG_r0这个值,但是,为了防止system call restart,可以赋值为非系统调用号的值。

handle_arch_irq执行过程

请添加图片描述

注:此处借鉴A53的做法。
handle_arch_irq的注册:

//源码:drivers/irqchip/irq-gic.c
static int __init __gic_init_bases(struct gic_chip_data *gic,struct fwnode_handle *handle)
{char *name;int i, ret;......if (gic == &gic_data[0]) {/** Initialize the CPU interface map to all CPUs.* It will be refined as each CPU probes its ID.* This is only necessary for the primary GIC.*/for (i = 0; i < NR_GIC_CPU_IF; i++)gic_cpu_map[i] = 0xff;
#ifdef CONFIG_SMPset_smp_cross_call(gic_raise_softirq);//设置SMP核减交互的回调函数,用于IPI
#endifcpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,"irqchip/arm/gic:starting",gic_starting_cpu, NULL);set_handle_irq(gic_handle_irq);//设定相关的irq handler,异常处理的入口if (static_branch_likely(&supports_deactivate_key))pr_info("GIC: Using split EOI/Deactivate mode\n");}......return ret;
}

gic_handle_irq的实现:
请添加图片描述

  • generic_handle_irq函数最终会调用到desc->handle_irq(),这个也就是对应到上文中在建立映射关系的过程中,调用irq_domain_set_info函数,设置好了函数指针,也就是handle_fasteoi_irq和handle_percpu_devid_irq;
      
  • handle_fasteoi_irq:处理共享中断,并且遍历irqaction链表,逐个调用action->handler()函数,这个函数正是设备驱动程序调用request_irq/request_threaded_irq接口注册的中断处理函数,此外如果中断线程化处理的话,还会调用__irq_wake_thread()唤醒内核线程;
      
  • handle_percpu_devid_irq:处理per-CPU中断处理,在这个过程中会分别调用中断控制器的处理函数进行硬件操作,该函数调用action->handler()来进行中断处理;
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{u32 irqstat, irqnr;struct gic_chip_data *gic = &gic_data[0];void __iomem *cpu_base = gic_data_cpu_base(gic);do {// 获取中断号irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);irqnr = irqstat & GICC_IAR_INT_ID_MASK;if (likely(irqnr > 15 && irqnr < 1020)) {if (static_branch_likely(&supports_deactivate_key))writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);isb();handle_domain_irq(gic->domain, irqnr, regs);continue;}//SGI中断:0~15if (irqnr < 16) {writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);if (static_branch_likely(&supports_deactivate_key))writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
#ifdef CONFIG_SMP/** Ensure any shared data written by the CPU sending* the IPI is read after we've read the ACK register* on the GIC.** Pairs with the write barrier in gic_raise_softirq*/smp_rmb();handle_IPI(irqnr, regs);
#endifcontinue;}break;} while (1);
}

handle_domain_irq的实现:

//源码:include/linux/irqdesc.h
//参数含义:
//domain:gic_irq_domain_hierarchy_ops
//hwirq:硬件中断号
//regs:现场寄存器
static inline int handle_domain_irq(struct irq_domain *domain,unsigned int hwirq, struct pt_regs *regs)
{return __handle_domain_irq(domain, hwirq, true, regs);
}

/*** __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain* @domain:	The domain where to perform the lookup* @hwirq:	The HW irq number to convert to a logical one* @lookup:	Whether to perform the domain lookup or not* @regs:	Register file coming from the low-level handling code** Returns:	0 on success, or -EINVAL if conversion has failed*/
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,bool lookup, struct pt_regs *regs)
{struct pt_regs *old_regs = set_irq_regs(regs);unsigned int irq = hwirq;int ret = 0;irq_enter();#ifdef CONFIG_IRQ_DOMAINif (lookup)irq = irq_find_mapping(domain, hwirq);
#endif/** Some hardware gives randomly wrong interrupts.  Rather* than crashing, do something sensible.*/if (unlikely(!irq || irq >= nr_irqs)) {ack_bad_irq(irq);ret = -EINVAL;} else {generic_handle_irq(irq);}irq_exit();set_irq_regs(old_regs);return ret;
}

int generic_handle_irq(unsigned int irq)
{struct irq_desc *desc = irq_to_desc(irq);if (!desc)return -EINVAL;generic_handle_irq_desc(desc);return 0;
}
EXPORT_SYMBOL_GPL(generic_handle_irq);

/** Architectures call this to let the generic IRQ layer* handle an interrupt.*/
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{desc->handle_irq(desc);
}

中断退出过程

  无论是在内核态(包括系统调用和中断上下文)还是用户态,发生了中断后都会调用irq_handler进行处理,这里会调用对应的irq number的handler,处理softirq、tasklet、workqueue等(这些内容另开一个文档描述),但无论如何,最终都是要返回发生中断的现场。

  1、中断发生在user mode下的退出过程,代码如下:

//源码:arch/arm/kernel/entry-commin.S
ENTRY(ret_to_user_from_irq)ldr	r2, [tsk, #TI_ADDR_LIMIT]cmp	r2, #TASK_SIZEblne	addr_limit_check_failedldr	r1, [tsk, #TI_FLAGS]tst	r1, #_TIF_WORK_MASK ---------------Abne	slow_work_pending
no_work_pending:asm_trace_hardirqs_on save = 0 @ 和irq flag trace相关/* perform architecture specific actions before user return */arch_ret_to_user r1, lr   @ 有些硬件平台需要在中断返回用户空间做一些特别处理ct_user_enter save = 0  @ 和trace context相关restore_user_regs fast = 0, offset = 0 ------------B
ENDPROC(ret_to_user_from_irq)

  A:thread_info中的flags成员中有一些low level的标识,如果这些标识设定了就需要进行一些特别的处理,这里检测的flag主要包括:

#define _TIF_WORK_MASK   (_TIF_NEED_RESCHED | _TIF_SIGPENDING | _TIF_NOTIFY_RESUME)

  这三个flag分别表示是否需要调度、是否有信号处理、返回用户空间之前是否需要调用callback函数。只要有一个flag被设定了,程序就进入work_pending这个分支(work_pending函数需要传递三个参数,第三个是参数why是标识哪一个系统调用,当然,我们这里传递的是0)。

  B:从字面的意思也可以看成,这部分的代码就是将进入中断的时候保存的现场(寄存器值)恢复到实际的ARM的各个寄存器中,从而完全返回到了中断发生的那一点。具体的代码如下:

//源码:arch/arm/kernel/entry-header.S.macro	restore_user_regs, fast = 0, offset = 0uaccess_enable r1, isb=0
#ifndef CONFIG_THUMB2_KERNEL@ ARM mode restoremov	r2, spldr	r1, [r2, #\offset + S_PSR]	@ get calling cpsr @ r1保存了pt_regs中的spsr,也就是发生中断时的CPSRldr	lr, [r2, #\offset + S_PC]!	@ get pc           @ lr保存了PC值,同时sp移动到了pt_regs中PC的位置tst	r1, #PSR_I_BIT | 0x0fbne	1fmsr	spsr_cxsf, r1			@ save in spsr_svc     @ 赋值给spsr,进行返回用户空间的准备
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_32v6K)@ We must avoid clrex due to Cortex-A15 erratum #830321strex	r1, r2, [r2]			@ clear the exclusive monitor
#endif.if	\fastldmdb	r2, {r1 - lr}^			@ get calling r1 - lr.elseldmdb	r2, {r0 - lr}^			@ get calling r0 - lr @ 将保存在内核栈上的数据保存到用户态的r0~r14寄存器.endifmov	r0, r0				@ ARMv5T and earlier require a nop @ NOP操作,ARMv5T之前的需要这个操作@ after ldm {}^add	sp, sp, #\offset + PT_REGS_SIZE @ 现场已经恢复,移动svc mode的sp到原来的位置movs	pc, lr				@ return & move spsr_svc into cpsr @ 返回用户空间
1:	bug	"Returning to usermode but unexpected PSR bits set?", \@
#elif defined(CONFIG_CPU_V7M)@ V7M restore.@ Note that we don't need to do clrex here as clearing the local@ monitor is part of the exception entry and exit sequence..if	\offsetadd	sp, #\offset.endifv7m_exception_slow_exit ret_r0 = \fast
#else@ Thumb mode restoremov	r2, spload_user_sp_lr r2, r3, \offset + S_SP	@ calling sp, lrldr	r1, [sp, #\offset + S_PSR]	@ get calling cpsrldr	lr, [sp, #\offset + S_PC]	@ get pcadd	sp, sp, #\offset + S_SPtst	r1, #PSR_I_BIT | 0x0fbne	1fmsr	spsr_cxsf, r1			@ save in spsr_svc@ We must avoid clrex due to Cortex-A15 erratum #830321strex	r1, r2, [sp]			@ clear the exclusive monitor.if	\fastldmdb	sp, {r1 - r12}			@ get calling r1 - r12.elseldmdb	sp, {r0 - r12}			@ get calling r0 - r12.endifadd	sp, sp, #PT_REGS_SIZE - S_SPmovs	pc, lr				@ return & move spsr_svc into cpsr
1:	bug	"Returning to usermode but unexpected PSR bits set?", \@
#endif	/* !CONFIG_THUMB2_KERNEL */.endm

  2、中断发生在svc mode下的退出过程。具体代码如下:

//源码:arch/arm/kernel/entry-header.S.macro	svc_exit, rpsr, irq = 0.if	\irq != 0@ IRQs already off
#ifdef CONFIG_TRACE_IRQFLAGS@ The parent context IRQs must have been enabled to get here in@ the first place, so there's no point checking the PSR I bit.bl	trace_hardirqs_on
#endif.else@ IRQs off again before pulling preserved data off the stackdisable_irq_notrace
#ifdef CONFIG_TRACE_IRQFLAGStst	\rpsr, #PSR_I_BITbleq	trace_hardirqs_ontst	\rpsr, #PSR_I_BITblne	trace_hardirqs_off
#endif.endifuaccess_exit tsk, r0, r1#ifndef CONFIG_THUMB2_KERNEL@ ARM mode SVC restoremsr	spsr_cxsf, \rpsr @ 将中断现场的cpsr值保存到spsr中,准备返回中断发生的现场
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_32v6K)@ We must avoid clrex due to Cortex-A15 erratum #830321sub	r0, sp, #4			@ uninhabited addressstrex	r1, r2, [r0]			@ clear the exclusive monitor
#endifldmia	sp, {r0 - pc}^			@ load r0 - pc, cpsr @ 这条指令是ldm异常返回指令,这条指令除了字面上的操作, 还包括了将spsr copy到cpsr中。
#else@ Thumb mode SVC restoreldr	lr, [sp, #S_SP]			@ top of the stackldrd	r0, r1, [sp, #S_LR]		@ calling lr and pc@ We must avoid clrex due to Cortex-A15 erratum #830321strex	r2, r1, [sp, #S_LR]		@ clear the exclusive monitorstmdb	lr!, {r0, r1, \rpsr}		@ calling lr and rfe contextldmia	sp, {r0 - r12}mov	sp, lrldr	lr, [sp], #4rfeia	sp!
#endif.endm

http://www.ppmy.cn/news/334834.html

相关文章

Linux内核深入理解系统调用(1):初始化-入口-处理-退出

Linux内核深入理解系统调用&#xff08;1&#xff09;&#xff1a;初始化-入口-处理-退出 rtoax 2021年3月 1. Linux 内核系统调用简介 这次提交为 linux内核解密 添加一个新的章节&#xff0c;从标题就可以知道, 这一章节将介绍Linux 内核中 System Call 的概念。章节内容的选…

「内核知识」Linux下的系统调用write

本文以x86_64平台为例&#xff0c;分析linux下的系统调用是如何被执行的。 假设目标系统调用是&#xff0c;其对应的内核源码为&#xff1a; // fs/read_write.c SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,size_t, count) {return ksys_write(fd, …

u-boot-1.1.6源码分析

P { margin-bottom: 0.21cm; }A:link { }CODE.ctl { font-family: "Lohit Hindi",monospace; } u-boot-1.1.6源码分析 15年11月1日15:22:59 想要分析一个大的程序是从哪一个文件开始执行的&#xff0c;首先是分析它的Makefile&#xff0c;当然也可以采取一个取巧的…

Interventional Contrastive Learning with Meta Semantic Regularizer

1. 摘要 基于对比学习(CL)的自我监督学习模型以成对的方式学习视觉表征。虽然目前流行的CL模型已经取得了很大的进展&#xff0c;但在本文中&#xff0c;我们发现了一个一直被忽视的现象&#xff1a;当用完整图像训练CL模型时&#xff0c;在完整图像中测试的性能比在前景区域测…

opencv图像算法

图像的对比度增强 一&#xff1a; 绘制直方图 就是把各个像素值所含有的个数统计出来&#xff0c;然后画图表示。 可以看到在当前图像中&#xff0c;哪个像素值的个数最多。 同时&#xff0c;可以看当前图像总体的像素值大小在哪些范围。。靠近0的话&#xff0c;说明图像偏暗…

Linux Interrupt

在面试的时候我们常常问或者被问一个问题&#xff1a;几种中断下半部机制softirq、tasklet、workqueue有什么区别&#xff1f;linux为什么要设计这几种机制&#xff1f;真正能够回答清楚的人还是少数的。下面我们就详细分析一下这其中的区别。 本文的代码分析基于linux kernel …

gem5 arm架构 fullsystem spec2017 benchmark 仿真

gem5 system emulation 模式&#xff0c;内部实现了对system call的模拟&#xff0c;使用了一段时间后&#xff0c;有一些发现: 如果使用spec2017 X86编译&#xff0c;那么会存在对intel比较新的指令不支持的问题&#xff1b;后来使用gcc march K6 m32来解决&#xff0c;即使用…

内存分配存在性能瓶颈怎么办?

title: 性能优化-使用高效内存分配器 date: 2020-12-20 22:00:00 comments: true categories: 性能优化 tags: [性能优化] 性能优化是一个常有的事情&#xff0c;通常来说 不要过早优化-当你没有性能问题时&#xff0c;不需要过早考虑优化&#xff0c;当然对于一些代价很小&a…