IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> linux arm64 中断处理流程(从盘古讲起)(未完待续) -> 正文阅读

[系统运维]linux arm64 中断处理流程(从盘古讲起)(未完待续)

中断流程老生常谈,但我一直以来也只是知道中断过来之后,会保护现场,跳到中断向量表,执行中断,恢复现场,然后返回。至于更多细节,就不得而知了。这篇文章旨在把更完整的linux 中断处理流程梳理一遍。主要带着以下几个问题:

  1. 中断向量表是在代码哪里配置的
  2. 响应中断时,必然有一些读写gic 寄存器的操作,这些操作在什么位置
  3. /proc/interrupts 这个proc 文件是如何实现的
  4. linux 进入中断前,会屏蔽其他所有中断,这个是如何实现的
  5. 系统调用的系统中断0x80,是如何从用户态调用到内核态file_operations接口的?和普通的中断有什么区别

本文代码均来自linux4.19

配置中断向量表

从 arch/arm64/kernel/vmlinux.lds.S 中可以看到,内核ENTRY 为_text,

/*arch/arm64/kernel/vmlinux.lds.S*/
ENTRY(_text)
...
	.head.text : {
		_text = .;
		HEAD_TEXT
	}

HEAD_TEXT定义在如下位置

/*include/asm-generic/vmlinux.lds.h*/
/* Section used for early init (in .S files) */
#define HEAD_TEXT  KEEP(*(.head.text))

也就是说linux的入口为指定为.head.text senction 的代码,grep 可以发现

/*include/linux/init.h*/
#define __HEAD      .section    ".head.text","ax"

__HEAD宏仅在在如下位置使用

/*arch/arm64/kernel/head.S*/
	__HEAD
_head:
	...
	b	stext
	...
ENTRY(stext)
	...
	b	__primary_switch
ENDPROC(stext)

__primary_switched:
	...
	adr_l	x8, vectors			// load VBAR_EL1 with virtual
	msr	vbar_el1, x8			// vector table address
	isb
	...
		b	start_kernel
ENDPROC(__primary_switched)

从上边的代码中可以看到从linux 入口到设置向量表的完整流程。最终设置向量表的指令,就是将vector 的地址,写入vbar_el1 寄存器。
对于vbar_el1 寄存器的作用,arm 官方文档的描述如下
在这里插入图片描述
对于其他vbar_elX 的写入类似,不再赘述。

中断向量表详解

/*arch/arm64/kernel/entry.S*/
ENTRY(vectors)
	...
	kernel_ventry	1, sync				// Synchronous EL1h
	kernel_ventry	1, irq				// IRQ EL1h
	kernel_ventry	1, fiq_invalid			// FIQ EL1h
	kernel_ventry	1, error			// Error EL1h

	kernel_ventry	0, sync				// Synchronous 64-bit EL0
	kernel_ventry	0, irq				// IRQ 64-bit EL0
	kernel_ventry	0, fiq_invalid			// FIQ 64-bit EL0
	kernel_ventry	0, error			// Error 64-bit EL0
	...

kernel_ventry 宏定义(忽略所有#ifdef)如下:

	.macro kernel_ventry, el, label, regsize = 64
	.align 7
	sub sp, sp, #S_FRAME_SIZE
	b   el\()\el\()_\label
	.endm

可以看到,“kernel_ventry 1, irq” 最终跳转到 el1_irq去执行

现场保护与中断栈切换

el1_irq:
	kernel_entry 1	/*保存现场*/
	enable_da_f (msr daifclr, #(8 | 4 | 1)) /*屏蔽了D(Debug mask bit)A(SError mask bit)I( IRQ mask bit) F(FIQ mask bit.)中的DAF*/

	irq_handler	/*进入中断处理*/

	kernel_exit 1	/*恢复现场*/
ENDPROC(el1_irq)

保存现场比较冗长,放到了文末。
接着屏蔽了DAIF中的DAF。这里看起来是不希望处理irq 的时候,被DAF 打断。具体为什么暂不探究了。。。
然后就开始进入中断处理irq_handler 了。处理完成后使用kernel_exit 恢复现场。
irq_handler 代码如下,

/*
 * Interrupt handling.
 */
	.macro	irq_handler
	ldr_l	x1, handle_arch_irq
	mov	x0, sp
	irq_stack_entry
	blr	x1
	irq_stack_exit
	.endm

注意,在跳转handle_arch_irq 之前与之后分使用irq_stack_entry 和irq_stack_exit 进行了栈指针切换。irq_stack_entry 代码如下

	.macro	irq_stack_entry
	mov	x19, sp	 // 将当前的栈指针sp保存到x19,在irq_stack_exit恢复栈指针时,再从x19中取出。疑问:如何在中断代码中保证x19的值不被覆盖?

	/*
	 * 将 sp 与任务堆栈的基数进行比较。如果顶部 ~(THREAD_SIZE - 1) 位匹配,我们在任务堆栈上,
	 * 并且应该切换到 irq 堆栈。
	 */
	ldr	x25, [tsk, TSK_STACK]
	eor	x25, x25, x19
	and	x25, x25, #~(THREAD_SIZE - 1)
	cbnz	x25, 9998f

	ldr_this_cpu x25, irq_stack_ptr, x26 /*读取当前cpu irq 栈指针*/
	mov	x26, #IRQ_STACK_SIZE
	add	x26, x25, x26	/*预留irq 栈空间,即,irq的栈使用不能超过IRQ_STACK_SIZE*/

	/* switch to the irq stack */
	mov	sp, x26
9998:
	.endm

handle_arch_irq 详解

从这里就完全进入c 代码了。先看下handle_arch_irq 是怎么来的。

/*kernel/irq/handle.c*/
void (*handle_arch_irq)(struct pt_regs *) __ro_after_init; /*这里定义了一个函数指针*/

IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
	gic_of_init
		gic_init_bases
			set_handle_irq(handle_irq=gic_handle_irq)
				handle_arch_irq = handle_irq
gic_handle_irq
	

附:kernel_entry 保护现场详解

	.macro	kernel_entry, el, regsize = 64
	.if	\regsize == 32
	mov	w0, w0				// zero upper 32 bits of x0
	.endif
	stp	x0, x1, [sp, #16 * 0]
	stp	x2, x3, [sp, #16 * 1]
	stp	x4, x5, [sp, #16 * 2]
	stp	x6, x7, [sp, #16 * 3]
	stp	x8, x9, [sp, #16 * 4]
	stp	x10, x11, [sp, #16 * 5]
	stp	x12, x13, [sp, #16 * 6]
	stp	x14, x15, [sp, #16 * 7]
	stp	x16, x17, [sp, #16 * 8]
	stp	x18, x19, [sp, #16 * 9]
	stp	x20, x21, [sp, #16 * 10]
	stp	x22, x23, [sp, #16 * 11]
	stp	x24, x25, [sp, #16 * 12]
	stp	x26, x27, [sp, #16 * 13]
	stp	x28, x29, [sp, #16 * 14]

	.if	\el == 0
	clear_gp_regs
	mrs	x21, sp_el0
	ldr_this_cpu	tsk, __entry_task, x20	// Ensure MDSCR_EL1.SS is clear,
	ldr	x19, [tsk, #TSK_TI_FLAGS]	// since we can unmask debug
	disable_step_tsk x19, x20		// exceptions when scheduling.

	apply_ssbd 1, x22, x23

	.else
	add	x21, sp, #S_FRAME_SIZE
	get_thread_info tsk
	/* Save the task's original addr_limit and set USER_DS */
	ldr	x20, [tsk, #TSK_TI_ADDR_LIMIT]
	str	x20, [sp, #S_ORIG_ADDR_LIMIT]
	mov	x20, #USER_DS
	str	x20, [tsk, #TSK_TI_ADDR_LIMIT]
	/* No need to reset PSTATE.UAO, hardware's already set it to 0 for us */
	.endif /* \el == 0 */
	mrs	x22, elr_el1
	mrs	x23, spsr_el1
	stp	lr, x21, [sp, #S_LR]

	/*
	 * In order to be able to dump the contents of struct pt_regs at the
	 * time the exception was taken (in case we attempt to walk the call
	 * stack later), chain it together with the stack frames.
	 */
	.if \el == 0
	stp	xzr, xzr, [sp, #S_STACKFRAME]
	.else
	stp	x29, x22, [sp, #S_STACKFRAME]
	.endif
	add	x29, sp, #S_STACKFRAME

#ifdef CONFIG_ARM64_SW_TTBR0_PAN
	/*
	 * Set the TTBR0 PAN bit in SPSR. When the exception is taken from
	 * EL0, there is no need to check the state of TTBR0_EL1 since
	 * accesses are always enabled.
	 * Note that the meaning of this bit differs from the ARMv8.1 PAN
	 * feature as all TTBR0_EL1 accesses are disabled, not just those to
	 * user mappings.
	 */
alternative_if ARM64_HAS_PAN
	b	1f				// skip TTBR0 PAN
alternative_else_nop_endif

	.if	\el != 0
	mrs	x21, ttbr0_el1
	tst	x21, #TTBR_ASID_MASK		// Check for the reserved ASID
	orr	x23, x23, #PSR_PAN_BIT		// Set the emulated PAN in the saved SPSR
	b.eq	1f				// TTBR0 access already disabled
	and	x23, x23, #~PSR_PAN_BIT		// Clear the emulated PAN in the saved SPSR
	.endif

	__uaccess_ttbr0_disable x21
1:
#endif

	stp	x22, x23, [sp, #S_PC]

	/* Not in a syscall by default (el0_svc overwrites for real syscall) */
	.if	\el == 0
	mov	w21, #NO_SYSCALL
	str	w21, [sp, #S_SYSCALLNO]
	.endif

	/*
	 * Set sp_el0 to current thread_info.
	 */
	.if	\el == 0
	msr	sp_el0, tsk
	.endif

	/*
	 * Registers that may be useful after this macro is invoked:
	 *
	 * x21 - aborted SP
	 * x22 - aborted PC
	 * x23 - aborted PSTATE
	*/
	.endm

参考资料

1.DDI0487D_b_armv8_arm.pdf
2.DEN0024A_v8_architecture_PG.pdf
3. https://www.jianshu.com/p/a9b5bc27ec83
4. Generic Interrupt Controller Architecture Specification

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-03-04 16:00:29  更:2022-03-04 16:03:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 4:21:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码