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系统设计思想——05中断框架剖析(三) -> 正文阅读

[系统运维]一起分析Linux系统设计思想——05中断框架剖析(三)

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。


中断从哪里来,到哪里去?中断号是如何分配的?带着这些疑问一起探索吧~

4 中断处理流程

前面的内容涉及的都是中断的准备工作,本节才真正打通从硬件中断产生到中断处理的整个流程。

4.1 汇编部分

汇编部分的代码主要负责和处理器架构相关的部分,例如中断向量的选择、中断现场保护和恢复等。

4.1.1 中断向量重映射

我们以 trap_init 函数作为突破口,来按图索骥地找到中断向量的汇编代码。

能够想到从trap_init函数作为突破口,基于下面两点:

  1. 中断可以看作陷阱的一种。
  2. trap_init函数被start_kernel函数调用了。

trap_init函数主要的功能就是将vectors、stubs等代码搬移到0xffff0000地址开始的所在页。这个动作我们可以给它起个大气的名字—— 重映射/重定位 。其中,vectors为中断向量表,stubs为中断处理函数总入口。

那我们为什么必须要进行中断向量重映射呢?

ARM架构的CPU的异常向量基址由 CONFIG_VECTORS_BASE 决定,不开启MMU时配置为 0x00000000 ,否则配置为 0xffff0000 。我们这里是开启了MMU的,因此CPU的异常向量基址是0xfff0000。如果异常向量基址只能是0x00000000,可以选择将中断向量代码直接链接在该地址。正是因为0xfff0000的存在才使得中断向量重映射的步骤必须存在。

/* arch/arm/kernel */
void __init 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[];
	int kuser_sz = __kuser_helper_end - __kuser_helper_start;

	/*
	 * 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.
	 */
	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);

	/*
	 * Copy signal return handlers into the vector page, and
	 * set sigreturn to be a pointer to these.
	 */
	memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
	       sizeof(sigreturn_codes));

	flush_icache_range(vectors, vectors + PAGE_SIZE); /*确保该页存储内容可被CPU执行*/
	modify_domain(DOMAIN_USER, DOMAIN_CLIENT); /*修改页面的访问权限*/
}


4.1.2 中断向量选择

好了,既然我们已经知道中断向量放在了__vectors_start开始的代码中,我们就一起来分析下 __vectors_start的代码。

代码如下所示,可以看到格式统一且简单,就是简单的跳转。对于外部中断发生时,硬件会自动控制PC指针指向 b vector_irq + stubs_offset 代码执行,也就是所谓的中断向量选择。

需要注意的一点是vector_irq代码在trap_init函数中进行过重定位,因此跳转时需要进行相对偏移的矫正,即加上偏移量stubs_offset。

/* arc/arm/kernel/entry-armv.S */
	.equ	stubs_offset, __vectors_start + 0x200 - __stubs_start

	.globl	__vectors_start
__vectors_start:
	swi	SYS_ERROR0
	b	vector_und + stubs_offset
	ldr	pc, .LCvswi + stubs_offset
	b	vector_pabt + stubs_offset
	b	vector_dabt + stubs_offset
	b	vector_addrexcptn + stubs_offset
	b	vector_irq + stubs_offset	/*外部中断跳转代码*/
	b	vector_fiq + stubs_offset

	.globl	__vectors_end
__vectors_end:

4.1.3 中断现场保护和恢复

4.1.3.1 vector_irq

继续顺藤摸瓜,下一步就是找到函数vector_irq。我们直接搜索是找不到vector_irq函数的,因为vector_irq是通过宏定义的,代码如下:

/* arch/arm/kernel/entry-armv.S */
	.globl	__stubs_start
__stubs_start:
/*
 * Interrupt dispatcher
 */
	vector_stub	irq, IRQ_MODE, 4 /* vector_stub为一个宏,需要展开 */

	.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

     ...
__vectors_end:   

vector_stub irq, IRQ_MODE, 4 宏定义展开如下:

/* 将宏定义展开 */
vector_irq: /*展开之后的真实标号名称*/
	sub	lr, lr, #4 /*计算返回地址(ARM9具有5级流水线)*/

	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr /*将r0和lr(返回地址)压栈*/
	mrs	lr, spsr
	str	lr, [sp, #8]		@ save spsr /*spsr=中断前的cpsr压栈*/

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled. /*为进入SVC模式做准备*/
	@
	mrs	r0, cpsr
	eor	r0, r0, #(IRQ_MODE ^ SVC_MODE)
	msr	spsr_cxsf, r0 

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f /*取得lr(此时lr=IRQ模式spsr=中断前的cpsr)的低四位(exception mode number)*/
	mov	r0, sp /*r0指向IRQ中断模式的堆栈指针*/
	ldr	lr, [pc, lr, lsl #2] /*根据之前的模式换算成偏移量,取得将要跳转的地址*/
	movs	pc, lr			@ branch to handler in SVC mode
	.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

     ...
__vectors_end:        

vector_irq根据进入中断前的模式又做了一次跳转。

4.1.3.2 __irq_usr

这里,我们选择__irq_usr进行分析。该函数包含了中断处理的大体框架:现场保护,中断处理和中断返回等。由于内容较多,不一一展开了,后面重点分析irq_handler。

/* arc/arm/kernel/entry-armv.S */
	.align	5
__irq_usr:
	usr_entry /*这是一个宏,主要用来保存现场*/

...
	get_thread_info tsk /*获取当前进程的进程描述符中的成员thread_info的地址,并将该地址保存到tsk*/
...

	irq_handler /*中断处理*/
...

	mov	why, #0
	b	ret_to_user /*中断返回*/

	.ltorg
4.1.3.3 irq_handler

irq_handler主要的工作就是获取两个参数:irq number和struct pt_regs *。然后调用asm_do_IRQ并将参数传递给它。

之后中断的处理工作就正式交接给C语言进行。

/* arch/arm/kernel/entry_armv.S */
/*
 * Interrupt handling.  Preserves r7, r8, r9
 */
	.macro	irq_handler
	get_irqnr_preamble r5, lr
1:	get_irqnr_and_base r0, r6, r5, lr /*从芯片的中断寄存器中获取中断号并存放在r0中,并依据IRQ_EINT0做了偏移校正,保证第一个中断号和IRQ_EINT0相等*/
	movne	r1, sp
	@
	@ routine called with r0 = irq number, r1 = struct pt_regs *
	@
	adrne	lr, 1b
	bne	asm_do_IRQ

4.2 C语言部分

4.2.1 asm_do_IRQ

/* arch/arm/kernel/irq.c */
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);
	struct irq_desc *desc = irq_desc + irq; /*根据中断号定位到对应的中断描述符*/

	/*
	 * Some hardware gives randomly wrong interrupts.  Rather
	 * than crashing, do something sensible.
	 */
	if (irq >= NR_IRQS)
		desc = &bad_irq_desc;

	irq_enter();

	desc_handle_irq(irq, desc); /*该行是中断处理的核心代码*/

	/* AT91 specific workaround */
	irq_finish(irq);

	irq_exit();
	set_irq_regs(old_regs);
}

desc_handle_irq源码如下:

static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
	desc->handle_irq(irq, desc);
}

其中desc->handle_irq是一个函数指针(中断框架前面的文章中分析过),它的初始化在setup_arch中完成。

4.2.2 handle_IRQ_event

desc->handle_irq指向的中断处理函数又调用了handle_IRQ_event函数来调用用户注册的中断服务例程。

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
	irqreturn_t ret, retval = IRQ_NONE;
	unsigned int status = 0;

	handle_dynamic_tick(action);

	if (!(action->flags & IRQF_DISABLED))
		local_irq_enable_in_hardirq();

	do {/*遍历用户注册的中断服务例程*/
		ret = action->handler(irq, action->dev_id);
		if (ret == IRQ_HANDLED)
			status |= action->flags;
		retval |= ret;
		action = action->next;
	} while (action);

	if (status & IRQF_SAMPLE_RANDOM)
		add_interrupt_randomness(irq);
	local_irq_disable();

	return retval;
}

至此,对中断处理在C语言部分的处理流程再做一个梳理:

  1. 根据中断号irq找到对应的中断描述符desc。
  2. 调用desc->handle_irq函数指针,该指针初始化在setup_arch函数中完成。
  3. 调用handle_IRQ_event函数,并在该函数中对用户注册的中断例程进行遍历执行。

恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-09-06 11:30:56  更:2021-09-06 11:32:27 
 
开发: 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/15 13:31:24-

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