精华内容
下载资源
问答
  • 9.11 中断(中):进程栈与中断栈
    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 中的各种栈:进程栈 线程栈 内核栈 中断栈
  • 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
    
    
    展开全文
  • 本文从栈的角度详细解释函数调用栈和中断栈的实现机制,旨在理解操作系统中内核部分的中断机制

    目录

    1. 中断是什么?

    2. 中断现场状态具体指什么?

    3. 函数调用栈实现

    4. 中断栈实现

    4.1 执行main函数压栈

    4.2 X86硬件自动压栈

    4.3 中断向量压栈

     4.4 压入通用状态寄存器

    4.5 压入trapframe指针,以及Call函数调用

    4.6 C语言访问trapframe指针

    5. 中断形式的内核进入与退出


    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

    展开全文
  • 中断栈与内核栈的话题更多地属于内核的范畴,所以在《深入Linux设备驱动程序内核机制》第5章“中断处理”当中,基本上没怎么涉及到上述内容,只是在5.4节有些许的文字讨论中断栈在中断嵌套情形下可能的溢出问题。...
  • Linux内核中的中断栈与内核栈的补充说明 (2012-02-20 20:17) 标签: Linux内核栈 中断栈 Linux中断处理 设备驱动  分类: Linux系统内核 中断栈与内核栈的话题更多地属于内核的范畴,所以在《深入...
  • 问题1:不同线程/进程拥有着不同的,那系统所有的中断用的是同一个吗,那意味着不支持中断嵌套,如果中断可以被打断,那么不同中断必须使用不同,否则如何管控,怎么理解这个问题? M3的双堆栈(PSP/MSP), ...
  • 前几天在学习内核源码的时候,无意中看到了Linux内核中有中断栈的概念。这个是我以前没有注意到的,为了深入理解这个知识,我查找了一些资料:Linux内核中的中断栈与内核栈的补充说明 《深入Linux内核构架》第14章 ...
  • nuttx 中断栈配置

    2018-02-28 17:00:57
    1、某块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内核中的...中断栈与内核栈的话题更多地属于内核的范畴,所以在《深入Linux设备驱动程序内核机制》第5章“中断处理”当中,基本上没怎么涉及到上述内容,只是在5.4节有些许的文字讨论中断栈在中断...
  • 有一个宏,专门定义的 #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_...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 138,576
精华内容 55,430
关键字:

中断栈

友情链接: 算法.rar