-
2020-11-01 23:45:26
9.11 中断(中):进程栈与中断栈
进程上下文
进程上下文
pc 指针指向 task函数体
sp 指针指向 任务栈栈是C语言运行的基础
函数调用:局部变量、函数参数、返回地址、寄存器
现场保护:状态寄存器 、寄存器、返回地址SP指到哪,我就可以在哪里执行
函数栈帧符号访问:SP/FP + 相对偏移
SP指针的切换
饭店与小摊儿进程栈
task3执行完之后
状态寄存器、堆栈指针、中断地址重新弹到cpu
![[Pasted image 20201003182707.png]]Linux进程的内核栈
![[Pasted image 20201003183300.png]]
什么是中断栈
中断函数调用
中断嵌套现场保护
![[Pasted image 20201003183334.png]]Linux中断栈
跟内核栈共享内存空间
独立中断栈:硬中断栈、软中断栈栈空间大小
任务函数体局部变量、参数
函数调用层次
中断嵌套层次更多相关内容 -
Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈
2018-05-06 12:03:22---------------------------------------------------Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈 -
Linux内核栈和中断栈
2019-08-09 11:47:21内核栈 #define MIN_THREAD_SHIFT (14 + KASAN_THREAD_SHIFT) #define THREAD_SIZE (UL(1) << THREAD_SHIFT) union thread_union { #ifndef CONFIG_THREAD_INFO_IN_TASK struct thread_info ...内核栈
#define MIN_THREAD_SHIFT (14 + KASAN_THREAD_SHIFT) #define THREAD_SIZE (UL(1) << THREAD_SHIFT) union thread_union { #ifndef CONFIG_THREAD_INFO_IN_TASK struct thread_info thread_info; #endif unsigned long stack[THREAD_SIZE/sizeof(long)]; };
注意这里有一个宏来区分stack的实现方式
CONFIG_THREAD_INFO_IN_TASK
,如果定义了该宏,实际上会通过动态申请物理页来作为stack,并且把stack放到task_struct
结构体中,用task_struct->stack
来表示。中断栈
- 对于x86平台:
x86平台上,中断栈是独立于内核栈的存在,两者并不共享,如果是多处理器架构,那么每个CPU都对应有一个中断栈,本文不对x86做介绍。
- 对于 ARM平台:
中断栈和内核栈则是共享的,中断栈和内核栈共享有一个负面因素,如果中断发生嵌套,可能会造成栈溢出,从而可能会破坏到内核栈的一些重要数据。
- 对于ARM64平台:
中断栈的实现是独立的,并且区分两种情况,分别是vmap申请内存,还是直接静态定义,并且根据CPU个数,每个CPU对应单独的一个stack。
arch/arm64/kernel/irq.c: #ifdef CONFIG_VMAP_STACK static void init_irq_stacks(void) { int cpu; unsigned long *p; for_each_possible_cpu(cpu) { /* * To ensure that VMAP'd stack overflow detection works * correctly, the IRQ stacks need to have the same * alignment as other stacks. */ p = __vmalloc_node_range(IRQ_STACK_SIZE, THREAD_ALIGN, VMALLOC_START, VMALLOC_END, THREADINFO_GFP, PAGE_KERNEL, 0, cpu_to_node(cpu), __builtin_return_address(0)); per_cpu(irq_stack_ptr, cpu) = p; } } #else /* irq stack only needs to be 16 byte aligned - not IRQ_STACK_SIZE aligned. */ DEFINE_PER_CPU_ALIGNED(unsigned long [IRQ_STACK_SIZE/sizeof(long)], irq_stack); static void init_irq_stacks(void) { int cpu; for_each_possible_cpu(cpu) per_cpu(irq_stack_ptr, cpu) = per_cpu(irq_stack, cpu); } #endif
-
X86中断栈执行过程分析
2022-04-10 14:37:29本文从栈的角度详细解释函数调用栈和中断栈的实现机制,旨在理解操作系统中内核部分的中断机制目录
1. 中断是什么?
一个程序正在执行自己的过程中,突然被一个事件给打扰了,并且去处理这个事件,处理完后又执行自己。用比较形象的例子描述,就是我正在认真看书学习,突然我妈喊我吃饭,然后我去吃饭了,吃完饭我又回来学习。如图是中断的执行过程。
那么对于顺序执行的程序来说,CPU是如何处理中断的呢?CPU虽然是一直取指令,执行指令;但是CPU并不是不管外部事件的,它会每执行一条指令后查询有没有外部事件发生,如果有就到该事件的处理函数中执行,执行完就回到顺序执行的程序中。就拿如下的程序片段举例子。
int main() { L1: int a=1; L2: int b=2; L3: int c=a+b; return c; } void ISR() { cout<<"I am a interrupt"; }
正常的执行顺序是L1,L2,L3;假设突然来了中断,中断发生的时刻是在L1和L2之间,那么程序“隐式”的执行结果类似下面的样子。
int main() { L1: int a=1; ISR(); L2: int b=2; L3: int c=a+b; return c; } void ISR() { cout<<"I am a interrupt"; }
2. 中断现场状态具体指什么?
在L1和L2之间被CPU自动的加入了一段ISR()函数的调用,这种过程很类似函数调用,但是并不是函数调用。在用户程序执行中是感受不到中断的存在,因为中断作为一个事件随时可能发生,那么谁统一管理用户程序的执行和中断的执行呢?这个责任自然放在了CPU上。这里需要解决的核心问题就是,中断现场的保护和恢复。用户程序执行的过程中有它的状态,状态主要表现在三个方面:1)进行逻辑或者算术计算的通用状态,表现在通用寄存器EAX,EBX,ECX等。2)下一条需要执行的指令位置CS\IP等。3)栈位置栈顶ESP和栈底EBP。
3. 函数调用栈实现
前两个状态很好理解,这里解释下栈状态。从C语言提出函数过程的概念后,就引入了栈的技术,栈底EBP指向某个函数第一行代码内存位置,栈顶ESP指向这个函数内部某行局部变量内存地址,拿以下函数调用举例子。在下面的例子执行过程中,函数传参和局部变量均使用栈内存作为变量的存储空间。因此栈的状态代表了函数的传参和局部变量的存储状态,这就是函数调用栈的目的。
int main() { L1: int a=1; L2: int b=2; L3: fun(a); L4: int c=a+b; return c; } void ISR() { cout<<"I am a interrupt"; } void int fun(int aa) { int p = 1; int q = 2; aa = p+q; }
对应的汇编代码如下
//%ebp=0,%esp=0 label main: push %ebp;//main’s EBP mov %esp,%ebp; pushal;//main’s saved register push $1;//Argument#a push $2;//Argument#b mov $1,%eax; push %eax;//Argument#aa push L4;//Return Address L4 jump fun; push %ebp;//fun's EBP mov %esp,%ebp; pushal;//fun’s saved register push $1;//Argument#p, push $2;//Argument#q, mov 0x0c(%ebp),%ebx; mov 0x10(%ebp),%edx; add %ebx,%edx; mov %ebx,-0x08(%ebp);//aa=p+q
main函数和fun函数调用栈对应如下
上面代码显示了main()函数中调用fun()函数过程,其中局部变量a\b\p\q均压入栈内存中,形式参数aa压入栈中。函数调用是通过栈顶指针ESP和栈底指针EBP来实现的。函数传参是通过访问栈变量内存位置实现的,如汇编代码在mov %ebx,-0x08(%ebp)。对于函数中的局部变量和形参寻址,在高级语言C中并不能看到这样的过程,然而在汇编语言中就能看到,这是因为从高级语言到汇编语言经过了重要的编译步骤,这其中需要解决局部变量存储和形参传递,这些都是编译器帮助我们完成的。如果不使用编译器,想要实现一个函数过程调用,就需要程序设计者自己实现函数局部参数入栈出栈、形参的内存寻址等。
4. 中断栈实现
除了函数调用栈,用栈来描述传参的形参寻址和局部变量寻址,那么从用户程序到中断程序是否也有类似的地方呢?答案是肯定的,就是中断的实现也要在栈上实现。
由于中断发生的时刻不确定,中断打断的代码位置也是未知的,所以栈的位置不确定。栈的位置只有中断发生时的CPU知道,因此CPU会自动进行入栈出栈如下信息:1)栈位置栈顶ESP和栈底EBP。2)下一条需要执行的指令位置CS\IP等。
当栈的位置确定后,当前时刻的逻辑或者算术计算的通用状态(通用寄存器EAX,EBX,ECX等),用户可以通过汇编指令进行入栈出栈操作。
假设不考虑内核状态和用户状态,看看如下代码突然来了中断,中断发生的时刻是在L1和L2之间,那么程序“隐式”的执行结果类似下面的样子。
int main() { L1: int a=1; ISR_timer(); L2: int b=2; L3: int c=a+b; return c; } void ISR_timer() { cout<<"I am a interrupt"; }
这段程序的中断栈执行流程是什么?他又是如何与代码相关联起来的呢?
4.1 执行main函数压栈
4.2 X86硬件自动压栈
4.3 中断向量压栈
中断执行代码
.globl vector32 vector32: pushl $0 pushl $32 jmp __alltraps
压栈结果如下
4.4 压入通用状态寄存器
汇编代码如下
.text .globl __alltraps __alltraps: # push registers to build a trap frame # therefore make the stack look like a struct trapframe pushl %ds pushl %es pushl %fs pushl %gs pushal # load GD_KDATA into %ds and %es to set up data segments for kernel movl $GD_KDATA, %eax movw %ax, %ds movw %ax, %es # push %esp to pass a pointer to the trapframe as an argument to trap() pushl %esp # call trap(tf), where tf=%esp call trap # pop the pushed stack pointer L27:popl %esp
压栈结果
4.5 压入trapframe指针,以及Call函数调用
汇编代码如下
.text .globl __alltraps __alltraps: …… # push %esp to pass a pointer to the trapframe as an argument to trap() pushl %esp # call trap(tf), where tf=%esp call trap # pop the pushed stack pointer L27:popl %esp 备注: call指令 call 0x12345 调用0x12345这个地址,可分解为: pushl %eip ——> 将cpu下一条要执行的指令压入栈中 movl $0x12345, %eip ——> eip = 0x12345 注意:CPU下一条指令将会从地址0x12345中取。
压栈结果
4.6 C语言访问trapframe指针
根据函数调用栈中的分析结果,每个形式参数位置是固定的,因此trapframe的指针tf位置也是固定的。这个固定位置在汇编指令中是-8(EBP),那么tf指针代表了整个trapframe结构体的内容,也就不难理解tf=%esp了。
5. 中断形式的内核进入与退出
在第4节中讨论了一般的内核态程序中断栈的执行过程,但是对于X86来说,具有用户态和内核态程序的区别。如果用户态的程序来了中断大致是怎样的流程呢?大致的流程如下:
5.1 CPU正在执行用户程序,突然来了中断
此时用户态的栈要切换到内核状态,与第4节不同的是在内核栈中压入用户态栈的位置信息,其他的过程基本上是相同的。
5.2 CPU正在执行用户程序,用户调用系统调用
触发中断处理,此时用户态的栈要切换到内核状态,与第4节不同的是在内核栈中压入用户态栈的位置信息,其他的过程基本上是相同的。
-
中断栈溢出后的结果
2021-05-11 04:58:16说一下上文中最开始提到的“某个问题”:如果一台主机网卡比较多,然后每个网卡分队列又比较多,总之结果...一旦中断栈溢出,那么将会导致怎样的结果,这曾在之前的文章里隐含的提到过,这里再重新整理一遍。在继...说一下上文中最开始提到的“某个问题”:如果一台主机网卡比较多,然后每个网卡分队列又比较多,总之结果就是系统里的网卡设备的中断号比较多(关于超过256个中断数的情况,请见参考1,2,3),一旦所有这些中断都绑定到同一个CPU,那么如果网卡收到数据包进而触发中断,而众多硬中断一嵌套就非常容易出现中断栈溢出。一旦中断栈溢出,那么将会导致怎样的结果,这曾在之前的文章里隐含的提到过,这里再重新整理一遍。
在继续下面的描述之前,先看两个知识点:
1,Linux 2.4.x的中断栈:
a),由硬中断/软中断共同使用同一个中断栈
b),中断栈与内核栈共享一个栈
c),中断执行的时候使用的栈就是当前进程的内核栈
2,Linux 2.6.x的中断栈:
a),硬中断与软中断分离使用不同的中断栈
b),中断栈与内核栈分离
c),X86_64 double fault、NMI还可以有额外的栈(64bit特性:IST(Interrupt Stack Table))
可以看到,对于Linux 2.4.x内核而言,因为中断处理函数使用内核栈作为中断栈,所以导致更加容易发生内核栈溢出(因内核函数本身用栈过多导致溢出,或内核函数本身还未导致内核栈溢出,但此时来了一个中断,因中断函数使用栈而导致溢出,即中断函数成了压死骆驼的最后一根稻草),而内核栈溢出的直接结果就是踩坏task结构体,从而无法正常执行对应的task进程而出现oops宕机。
由于“中断执行的时候使用的栈就是当前进程的内核栈”,所以如果是执行到中断函数后才溢出,那么导致oops里提示的进程信息可能每次都不一样,因此如果出现这种情况,需要考虑是中断函数导致内核栈溢出,否则需怀疑普通的内核函数导致栈溢出即可。
对于Linux 2.6.x内核而言,因为其中断/内核栈分离、软/硬中断栈分离,即每个CPU私有两个栈(见下面注释)分别处理软中断和硬中断,因此出现内核栈溢出,特别是中断栈溢出的概率大大降低。
注释:这个说法来之书本《Understanding.the.Linux.Kernel.3rd.Edition》4.6.1.4. Multiple Kernel Mode stacks,而这本书针对的内核版本是2.6.11,且主要是指32位架构,所以与现在的新版内核源码有些许出入(比如现在情况的栈大小可能是占用2页),但这些细微改变与本文的具体问题相关不大(无非是溢出的难易程度问题),这里不再深入研究,具体情况请参考源代码自行斟酌。
The hard IRQ stack is used when handling interrupts. There is one hard IRQ stack for each CPU in the system, and each stack is contained in a single page frame.The soft IRQ stack is used when handling deferrable functions (softirqs or tasklets; see the later section “Softirqs and Tasklets”). There is one soft IRQ stack for each CPU in the system, and each stack is contained in a single page frame.
回到本文的主题,在之前的文章里提到过,即如果中断/异常处理函数本身在处理的过程中出现异常,那么就有可能发生double fault,比如中断栈溢出。中断栈溢出导致的最终结果有两种情况,这由所使用的具体Linux内核版本来决定,更具体点说是由double fault异常的栈是否单独来决定(见参考1)。
1,double fault的栈被单独出来
这意味着double fault的处理函数还能正常执行,因此打印oops,宕机。
2,double fault的栈没有被单独出来
这意味着double fault的处理函数也无法正常执行,进而触发triple fault,机器直接重启。
对于86-64架构下的Linux 2.6.x内核,因为IST(Interrupt Stack Table)的帮助,所以中断栈溢出导致的最终结果就是打印oops,宕机。
下面来看内核源码文档kernel-stacks,
1,每一个活动线程都有一个内核栈,大小为2页。
2,每一个cpu有一些专门的栈,只有当cpu执行在内核态时,这些栈才有用;一旦cpu回退到用户态,这些特定栈就不再包含任何有用数据。
3,主要的特定栈有:
a,中断栈:外部硬件中断的处理函数使用,单独的栈可以提供给中断处理函数更多的栈空间。
这里还提到,在2.6.x-i386下,如果设置内核栈只有4K,即CONFIG_4KSTACKS,那么中断栈也是单独开的。备注:这个已有修改,2010-06-29 x86: Always use irq stacks,即不管设置的内核栈是否只有4K,中断栈都是独立的了。
另外,这里有个说法与前面的引用有点出入:
The interrupt stack is also used when processing a softirq.
即软中断和硬中断一样,也是使用这个中断栈。
b,x86_64所特有的(也就是i386没有,即同时2.6.30.8内核,32位的Linux就不具备下面所说的这个特性),为double fault或NMI单独准备的栈,这个特性被称为Interrupt Stack Table(IST)。每个cpu最多支持7个IST。关于IST的具体原理与实现暂且不说,直接来看当前已经分配的IST独立栈:
STACKFAULT_STACK. EXCEPTION_STKSZ (PAGE_SIZE)
12号中断Stack Fault Exception (#SS)使用
DOUBLEFAULT_STACK. EXCEPTION_STKSZ (PAGE_SIZE)
8号中断Double Fault Exception (#DF)使用
NMI_STACK. EXCEPTION_STKSZ (PAGE_SIZE)
2号中断non-maskable interrupts (NMI)使用
DEBUG_STACK. DEBUG_STKSZ
1号中断硬件调试和3号中断软件调试使用
MCE_STACK. EXCEPTION_STKSZ (PAGE_SIZE)
18号中断Machine Check Exception (#MC)使用
正因为double fault异常处理函数所使用的栈被单独了出来,所以在出现中断栈溢出时,double fault异常的处理函数还能正常执行,顺利打印出oops信息。
最后的最后,有补丁移除IST功能(貌似是因为如果没有IST功能,那么kvm可以得到更好的优化,具体请见参考5),但通过对比补丁修改与实际源码(2.6.30.8以及3.6.11)来看,这个补丁并没有合入mainline主线。
参考资料:
6,Interrupt Descriptor Table
http://wiki.osdev.org/IDT
-
关于软中断原理,以及中断栈的概念等
2014-09-15 18:57:06中断栈与内核栈的话题更多地属于内核的范畴,所以在《深入Linux设备驱动程序内核机制》第5章“中断处理”当中,基本上没怎么涉及到上述内容,只是在5.4节有些许的文字讨论中断栈在中断嵌套情形下可能的溢出问题。... -
Linux内核中的中断栈与内核栈的补充说明
2016-07-22 17:01:02Linux内核中的中断栈与内核栈的补充说明 (2012-02-20 20:17) 标签: Linux内核栈 中断栈 Linux中断处理 设备驱动 分类: Linux系统内核 中断栈与内核栈的话题更多地属于内核的范畴,所以在《深入... -
[堆栈]Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈
2019-05-26 22:18:00问题1:不同线程/进程拥有着不同的栈,那系统所有的中断用的是同一个栈吗,那意味着不支持中断嵌套,如果中断可以被打断,那么不同中断必须使用不同栈,否则如何管控,怎么理解这个问题? M3的双堆栈(PSP/MSP), ... -
Linux独立中断栈学习笔记及验证实验(ARM、x86)
2016-01-21 09:27:06前几天在学习内核源码的时候,无意中看到了Linux内核中有中断栈的概念。这个是我以前没有注意到的,为了深入理解这个知识,我查找了一些资料:Linux内核中的中断栈与内核栈的补充说明 《深入Linux内核构架》第14章 ... -
nuttx 中断栈配置
2018-02-28 17:00:571、某块stm32单板跑nuttx系统,出现异常挂起,分析发现中断栈已经用完,需要调整中断栈大小up_hardfault: PANIC!!! Hard fault: 40000000up_assert: Assertion failed at file:armv7-m/up_hardfault.c line: 171 ... -
为什么中断要使用栈
2021-11-03 15:49:31为什么中断要使用栈? 这是根据中断和栈的特性来选择出来的。 中断,是从A函数执行中去调用另一个B函数,执行完B函数时,再返回到A函数。所以需要先记下A函数的具体数据,换上B的数据去执行。执行完B函数时,删除B的... -
Linux内核栈与中断栈补充说明
2016-04-25 12:53:59中断栈与内核栈的话题更多地属于内核的范畴,所以在《深入Linux设备驱动程序内核机制》第5章“中断处理”当中,基本上没怎么涉及到上述内容,只是在5.4节有些许的文字讨论中断栈在中断嵌套情形下可能的溢出问题。... -
Linux内核中的中断栈与内核栈的补充说明【转】
2017-06-24 21:11:00原文地址:Linux内核中的...中断栈与内核栈的话题更多地属于内核的范畴,所以在《深入Linux设备驱动程序内核机制》第5章“中断处理”当中,基本上没怎么涉及到上述内容,只是在5.4节有些许的文字讨论中断栈在中断... -
[optee_os]-optee中的内核栈、中断栈、abort栈的定义
2020-07-22 20:08:02有一个宏,专门定义栈的 #define DECLARE_STACK(name, num_stacks, stack_size, linkage) \ linkage uint32_t name[num_stacks] \ [ROUNDUP(stack_size + STACK_CANARY_SIZE, STACK_ALIGNMENT) / \ sizeof(uint... -
每个cpu都有一个16K的中断栈
2017-09-18 14:30:21从DECLARE_PER_CPU 可以知道,每个cpu都有一个16k的中断栈 可以通过#define IRQ_STACK_PTR(cpu) ((unsigned long)per_cpu(irq_stack, cpu) + IRQ_STACK_START_SP) 来得到每个cpu的中断栈 static inline bool on_irq_...