起首 |STM32镶嵌式开拓
知圈 |进“底盘社群”请加微yanzhi-6,备注底盘
栈是什么?栈有什么作用?
领先,栈 (stack) 是一种串列表情的 数据结构。这种数据结构的特色是 后入先出 (LIFO, Last In First Out),数据只可在串列的一端 (称为:栈顶 top) 进行 推入 (push) 和 弹出 (pop) 操作。阐发栈的特色,很容易的思到不错应用数组,来收场这种数据结构。但是本文要商榷的并不是软件层面的栈,而是硬件层面的栈。
大巨额的处理器架构,都有收场硬件栈。有成心的栈指针寄存器,以及特定的硬件领导来完成 入栈/出栈 的操作。举例在 ARM 架构上,R13 (SP) 指针是堆栈指针寄存器,而 PUSH 是用于压栈的汇编领导,POP 则是出栈的汇编领导。
ARM 处理器领有 37 个寄存器。这些寄存器按部分叠加组形状加以陈列。每个处理器模式都有一个不同的寄存器组。编组的寄存器为处理处理器很是和特权操作提供了快速的崎岖文切换。
提供了下列寄存器:
- 三十个 32 位通用寄存器:
- 存在十五个通用寄存器,它们分别是 r0-r12、sp、lr
- sp (r13) 是堆栈指针。C/C++ 编译器永久将 sp 用作堆栈指针
- lr (r14) 用于存储调用子例程时的复返地址。要是复返地址存储在堆栈上,则可将 lr 用作通用寄存器
- 要领计数器 (pc):领导寄存器
- 应用要领景况寄存器 (APSR):存放算术逻辑单位 (ALU) 景况标志的副本
- 现时要领景况寄存器 (CPSR):存放 APSR 标志,现时处理器模式,中断禁用标志等
- 保存的要领景况寄存器 (SPSR):当发生很是时,使用 SPSR 来存储 CPSR
上头是栈的道理和收场,底下咱们来望望栈有什么作用。栈作用不错从两个方面体现:函数调用 和多任务复古 。
函数调用
咱们知谈一个函数调用有以下三个基本经由:
- 调用参数的传入
- 局部变量的空间管束
- 函数复返
函数的调用必须是高效的,而数据存放在 CPU通用寄存器 或者 RAM 内存 中无疑是最佳的遴荐。以传递调用参数为例,咱们不错遴荐使用 CPU通用寄存器 来存放参数。但是通用寄存器的数量都是有限的,当出现函数嵌套调用时,子函数再次使用原有的通用寄存器势必会导致打破。因此要是思用它来传递参数,那在调用子函数前,就必须先 保存原有寄存器的值,然后当子函数退出的时候再 恢收复有寄存器的值 。
函数的调用参数数量一般都相对少,因此通用寄存器是不错郁勃一定需求的。但是局部变量的数量和占用空间都是比拟大的,再依赖有限的通用寄存器难强迫东谈主所难,因此咱们不错继承某些 RAM 内存区域来存储局部变量。但是存储在那儿适宜?既不行让函数嵌套调用的时候有打破,又要防备效果。
这种情况下,栈无疑提供很好的处理方针。一、对于通用寄存器传参的打破,咱们不错再调用子函数前,将通用寄存器临时压入栈中;在子函数调用收场后,在将已保存的寄存器再弹出呈报回来。二、而局部变量的空间央求,也只需要向下移动下栈顶指针;将栈顶指针向回移动,即可就可完成局部变量的空间开释;三、对于函数的复返,也只需要在调用子函数前,将复返地址压入栈中,待子函数调用扫尾后,将函数复返地址弹出给 PC 指针,即完成了函数调用的复返;
于是上述函数调用的三个基本经由,就演变纪录一个栈指针的经由。每次函数调用的时候,都配套一个栈指针。即使轮回嵌套调用函数,只消对应函数栈指针是不同的,也不会出现打破。
函数调用经常是嵌套的,在兼并时刻,栈中会有多个函数的信息。每个未完成运行的函数占用一个零丁的一语气区域,称作栈帧(Stack Frame)。栈帧存放着函数参数,局部变量及呈报前一栈帧所需要的数据等,函数调用时入栈的礼貌为:
实参N~1 → 主调函数复返地址 → 主调函数帧基指针EBP → 被调函数局部变量1~N
栈帧的鸿沟由 栈帧基地址指针 EBP 和 栈指针 ESP 界定,EBP 指向现时栈帧底部(高地址),在现时栈帧内位置固定;ESP指向现时栈帧顶部(低地址),当要领实行时ESP会跟着数据的入栈和出栈而移动。因此函数中对大部分数据的看望都基于EBP进行。函数调用栈的典型内存布局如下图所示:
多任务复古
然则栈的真谛还不单是函数调用,有了它的存在,才气构建出操作系统的多任务模式。咱们以 main 函数调用为例,main 函数包含一个无尽轮回体,轮回体中先调用 A 函数,再调用 B 函数。
funcB: return;
funcA: B;
funcmain: while( 1) A;
试思在单处理器情况下,要领将永远停留在此 main 函数中。即使有另外一个任务在恭候景况,要领是没法从此 main 函数内部跳转到另一个任务。因为要是是函数调用关系,试验上照旧属于 main 函数的任务中,不行算多任务切换。此刻的 main 函数任务自身其实和它的栈绑定在了一皆,不管如何嵌套调用函数,栈指针都在本栈规模内移动。
由此不错看出一个任务不错应用以下信息来表征:
1. main 函数体代码
2. main 函数栈指针
3. 现时 CPU 寄存器信息
假如咱们不错保存以上信息,则透顶不错强制让出 CPU 去向理其他任务。只消异日思赓续实行此 main 任务的时候,把上头的信息呈报且归即可。有了这样的先决条目,多任务就有了存在的基础,也不错看出栈存在的另一个真谛。在多任务模式下,当诊治要领合计有必要进行任务切换的话,只需保存任务的信息(即上头说的三个内容)。呈报另一个任务的景况,然后跳转到前次运行的位置,就不错呈报运行了。
可见每个任务都有我方的栈空间,恰是有了零丁的栈空间,为了代码重用,不同的任务甚而不错混用任务的函数体自身,举例不错一个main函数有两个任求实例。至此之后的操作系统的框架也形成了,譬如任务在调用 sleep 恭候的时候,不错主动让出 CPU 给别的任务使用,或者分时操作系统任务在时代片用完是也会被动的让出 CPU。无论是哪种设施,只消思方针切换任务的崎岖文空间,切换栈即可。
任务是一个玄虚的认识,即指软件完成的一个步履;而线程则是完成任务所需的动作;进度则指的是完成此动作所需资源的统称;对于三者的关系,有一个形象的譬如:
- 任务 = 送货
- 线程 = 开送货车
- 系统诊治 = 决定适宜开哪部送货车
- 进度 = 谈路 + 加油站 + 送货车 + 修车厂
Linux 中有几种栈?各式栈的内存位置?先容完栈的职责道理和用途作用后,咱们追想到 Linux 内核上来。内核将栈分红四种:
进度栈
线程栈
内核栈
中断栈
进度栈
线程栈
内核栈
中断栈
进度栈是属于用户态栈,和进度 假造地址空间 (Virtual Address Space) 密切联系。那咱们先了解下什么是假造地址空间:在 32 位机器下,假造地址空间大小为 4G。这些假造地址通过页表 (Page Table) 映射到物理内存,页表由操作系统悭吝,并被处理器的内存管束单位 (MMU) 硬件援用。每个进度都领有一套属于它我方的页表,因此对于每个进度而言都大要独享了通盘假造地址空间。
Linux 内核将这 4G 字节的空间分为两部分,将最高的 1G 字节(0xC0000000-0xFFFFFFFF)供内核使用,称为 内核空间。而将较低的3G字节(0x00000000-0xBFFFFFFF)供各个进度使用,称为用户空间。每个进度不错通过系统调用堕入内核态,因此内核空间是由总计进度分享的。诚然说内核和用户态进度占用了这样地面址空间,但是并不料味它们使用了这样多物理内存,仅暗意它不错驾驭这样大的地址空间。它们是阐发需要,将物理内存映射到假造地址空间中使用。
Linux 对进度地址空间有个法度布局,地址空间中由各个不同的内存段构成 (Memory Segment),主要的内存段如下:
- 要领段 (Text Segment):可实行文献代码的内存映射
- 数据段 (Data Segment):可实行文献的已运行化全局变量的内存映射
- BSS段 (BSS Segment):未运行化的全局变量或者静态变量(用零页运行化)
- 堆区 (Heap) : 存储动态内存分拨,匿名的内存映射
- 栈区 (Stack) : 进度用户空间栈,由编译器自动分拨开释,存放函数的参数值、局部变量的值等
- 映射段(Memory Mapping Segment):任何内存映射文献
而上头进度假造地址空间中的栈区,正指的是咱们所说的进度栈。进度栈的运行化大小是由编译器和连络器筹谋出来的,但是栈的及时大小并不是固定的,Linux 内核会阐发入栈情况对栈区进行径态增长(其实也等于添加新的页表)。但是并不是说栈区不错无尽增长,它也有最大适度RLIMIT_STACK (一般为 8M),咱们不错通过 ulimit 来稽察或革新 RLIMIT_STACK 的值。
咱们要知谈栈的大小,那必须得知谈栈的肇端地址和扫尾地址。栈肇端地址 获取很通俗,只需要镶嵌汇编领导获取栈指针 esp 地址即可。栈扫尾地址 的获取有点远程,咱们需要先应用递归函数把栈搞溢出了,然后再 GDB 中把栈溢出的时候把栈指针 esp 打印出来即可。代码如下:
/* file name: stacksize.c */
void*orig_stack_pointer;
voidblow_stack( ) { blow_stack;}
intmain( ) { __asm__( "movl %esp, orig_stack_pointer");
blow_stack;return0; }$ g++ -g stacksize.c -o ./stacksize$ gdb ./stacksize(gdb) rStarting program: /home/home/misc-code/setrlimit
Program received signal SIGSEGV, Segmentation fault.blow_stack at setrlimit.c: 44blow_stack; (gdb) print ( void*)$esp $ 1= ( void*) 0xffffffffff7ff000(gdb) print ( void*)orig_stack_pointer $ 2= ( void*) 0xffffc800(gdb) print 0xffffc800-0xff7ff000$ 3= 8378368// Current Process Stack Size is 8M
上濒临进度的地址空间有个比拟全局的先容,那咱们看下 Linux 内核中是如何体现上头内存布局的。内核使用内存刻画符来暗意进度的地址空间,该刻画符暗意着进度总计地址空间的信息。内存刻画符由 mm_struct 结构体暗意,底下给出内存刻画符结构中各个域的刻画,请各人阿谀前边的 进度内存段布局 图一皆看:
structmm_struct{ structvm_area_struct* mmap; /* 内存区域链表 */structrb_rootmm_rb; /* VMA 形成的红黑树 */...structlist_headmmlist; /* 总计 mm_struct 形成的链表 */...unsignedlongtotal_vm; /* 全部页面数量 */unsignedlonglocked_vm; /* 上锁的页面数据 */unsignedlongpinned_vm; /* Refcount permanently increased */unsignedlongshared_vm; /* 分享页面数量 Shared pages (files) */unsignedlongexec_vm; /* 可实行页面数量 VM_EXEC & ~VM_WRITE */unsignedlongstack_vm; /* 栈区页面数量 VM_GROWSUP/DOWN */unsignedlongdef_flags; unsignedlongstart_code, end_code, start_data, end_data; /* 代码段、数据段 肇端地址和扫尾地址 */unsignedlongstart_brk, brk, start_stack; /* 栈区 的肇端地址,堆区 肇端地址和扫尾地址 */unsignedlongarg_start, arg_end, env_start, env_end; /* 敕令行参数 和 环境变量的 肇端地址和扫尾地址 */.../* Architecture-specific MM context */mm_context_tcontext; /* 体捆绑构独特数据 */
/* Must use atomic bitops to access the bits */unsignedlongflags; /* 景况标志位 */.../* Coredumping and NUMA and HugePage 联系结构体 */};
进度在运行的经由中,通过不停向栈区压入数据,当超出栈区容量时,就会消耗栈所对应的内存区域,这将触发一个 缺页很是 (page fault)。通过很是堕入内核态后,很是会被内核的 expand_stack 函数处理,进而调用 acct_stack_growth 来查验是否还有适宜的所在用于栈的增长。
要是栈的大小低于 RLIMIT_STACK(庸碌为8MB),那么一般情况下栈会被加长,要领赓续实行,嗅觉不到发生了什么事情,这是一种将栈扩张到所需大小的老例机制。然则,要是达到了最大栈空间的大小,就会发生 栈溢出(stack overflow),进度将会收到内核发出的段造作(segmentation fault) 信号。
动态栈增长是唯逐一种看望未映射内存区域而被允许的情形,其他任何对未映射内存区域的看望都会触发页造作,从而导致段造作。一些被映射的区域是只读的,因此企图写这些区域也会导致段造作。
二、线程栈
从 Linux 内核的角度来说,其实它并莫得线程的认识。Linux 把总计线程都行动念进度来收场,它将线程和进度不加隔离的协调到了 task_struct 中。线程只是被视为一个与其他进度分享某些资源的进度,而是否分享地址空间险些是进度和 Linux 中所谓线程的唯一区别。线程创建的时候,加上了 CLONE_VM 标志,这样 线程的内存刻画符 将径直指向 父进度的内存刻画符。
if(clone_flags & CLONE_VM) { /** current 是父进度而 tsk 在 fork 实行时代是分享子进度*/atomic_inc(¤t->mm->mm_users);tsk->mm = current->mm;}
诚然线程的地址空间和进度一样,但是对待其地址空间的 stack 照旧有些区别的。对于 Linux 进度或者说干线程,其 stack 是在 fork 的时候生成的,试验上等于复制了父亲的 stack 空间地址,然后写时拷贝 (cow) 以及动态增长。然则对于干线程生成的子线程而言,其 stack 将不再是这样的了,而是事前固定下来的,使用 mmap 系统调用,它不带有 VM_STACK_FLAGS 标志。这个不错从 glibc 的nptl/allocatestack.c 中的 allocate_stack 函数中看到:
《征求意见稿》还要求优化移动互联网应用服务体验。开通会员、收费等附加条件应显著提示。此外,《征求意见稿》还对App服务续期及时提醒,明示个人信息处理规则、合理申请使用权限和及时响应用户诉求提出了具体要求。
mem = mmap (NULL, size, prot,MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
由于线程的 mm->start_stack 栈地址和所属进度疏导,是以线程栈的肇端地址并莫得存放在task_struct 中,应该是使用 pthread_attr_t 中的 stackaddr 来运行化 task_struct->thread->sp(sp 指向 struct pt_regs 对象,该结构体用于保存用户进度或者线程的寄存器现场)。这些都不紧迫,紧迫的是,线程栈不行动态增长,一朝用尽就没了,这是和生成进度的 fork 不同的所在。由于线程栈是从进度的地址空间中 map 出来的一块内存区域,原则上是线程独到的。但是兼并个进度的总计线程生成的时候浅拷贝生成者的 task_struct 的许多字段,其中包括总计的 vma,要是欢乐,其它线程也照旧不错看望到的,于是一定要提神。
三、进度内核栈
在每一个进度的生命周期中,势必和会过到系统调用堕入内核。在实行系统调用堕入内核之后,这些内核代码所使用的栈并不是原先进度用户空间中的栈,而是一个单独内核空间的栈,这个称作进度内核栈。进度内核栈在进度创建的时候,通过 slab 分拨器从 thread_info_cache 缓存池均分拨出来,其大小为 THREAD_SIZE,一般来说是一个页大小 4K;
unionthread_union { structthread_infothread_info; unsignedlongstack[THREAD_SIZE/ sizeof( long)]; };
thread_union 进度内核栈 和 task_struct 进度刻画符有着考究的测度。由于内核经常要看望task_struct,高效获取现时进度的刻画符是一件很是紧迫的事情。因此内核将进度内核栈的头部一段空间,用于存放 thread_info 结构体,而此结构体中则纪录了对应进度的刻画符,两者关系如下图(对应内核函数为 dup_task_struct):
有了上述测度合构后,内核不错先获取到栈顶指针 esp,然后通过 esp 来获取 thread_info。这里有一个小时期,径直将 esp 的地址与上 ~(THREAD_SIZE - 1) 后即可径直取得 thread_info 的地址。由于 thread_union 结构体是从 thread_info_cache 的 Slab 缓存池中央求出来的,而thread_info_cache 在 kmem_cache_create 创建的时候,保证了地址是 THREAD_SIZE 对皆的。因此只需要对栈指针进行 THREAD_SIZE 对皆,即可取得 thread_union 的地址,也就取得了thread_union 的地址。告捷获取到 thread_info 后,径直取出它的 task 成员就告捷得到了task_struct。其实上头这段刻画,也等于 current 宏的收场设施:
registerunsignedlongcurrent_stack_pointer asm( "sp") ;
staticinlinestruct thread_info * current_thread_info( void) { return(struct thread_info *) (current_stack_pointer & ~(THREAD_SIZE - 1)); }
# defineget_current (current_thread_info->task)
# definecurrent get_current
四、中断栈
进度堕入内核态的时候,需要内核栈来复古内核函数调用。中断亦然如斯,当系统收到中断事件后,进行中断处理的时候,也需要中断栈来复古函数调用。由于系统中断的时候,系统天然是处于内核态的,是以中断栈是不错和内核栈分享的。但是具体是否分享,这和具体处理架构密切联系。
X86 上中断栈等于零丁于内核栈的;零丁的中断栈所在内存空间的分拨发生在arch/x86/kernel/irq_32.c 的 irq_ctx_init 函数中(要是是多处理器系统,那么每个处理器都会有一个零丁的中断栈),函数使用 __alloc_pages 在低端内存隔离拨 2个物理页面,也等于8KB大小的空间。道理的是,这个函数还会为 softirq 分拨一个相似大小的零丁堆栈。如斯说来,softirq 将不会在 hardirq 的中断栈上实行,而是在我方的崎岖文中实行。
而 ARM 上中断栈和内核栈则是分享的;中断栈和内核栈分享有一个负面身分,要是中断发生嵌套,可能会形成栈溢出,从而可能会浩大到内核栈的一些紧迫数据,是以栈空间或然候难免会百孔千疮。
Linux 为什么需要隔离这些栈?
为什么需要隔离这些栈,其实都是瞎想上的问题。这里就我看到过的一些不雅点进行汇总,供各人商榷:
为什么需要单独的进度内核栈?
总计进度运行的时候,都可能通过系统调用堕入内核态赓续实行。假定第一个进度 A 堕入内核态实行的时候,需要恭候读取网卡的数据,主动调用 schedule 让出 CPU;此时诊治器叫醒了另一个进度 B,恰巧进度 B 也需要系统调用投入内核态。那问题就来了,要是内核栈唯唯一个,那进度 B 投入内核态的时候产生的压栈操作,势必会浩大掉进度 A 已有的内核栈数据;一但进度 A 的内核栈数据被浩大,很可能导致进度 A 的内核态无法正确复返到对应的用户态了;
为什么需要单独的线程栈?
此时 A1 的栈指针 esp 要是为运行值 0x7ffc80000000,则线程 A1 一但出现函数调用,势必会浩大父进度 A 已入栈的数据。
要是此时线程 A1 的栈指针和父进度临了更新的值一致,esp 为 0x7ffc8000FF00,那线程 A1 进行一些函数调用后,栈指针 esp 增多到 0x7ffc8000FFFF,然后线程 A1 睡眠;诊治器再次换成父进度 A 实行,那这个时候父进度的栈指针是应该为 0x7ffc8000FF00 照旧 0x7ffc8000FFFF 呢?不管栈指针被缔造到哪个值,都会有问题不是吗?
Linux 诊治要领中并莫得隔离线程和进度,当诊治要领需要叫醒”进度”的时候,势必需要呈报进度的崎岖文环境,也等于进度栈;但是线程和父进度透顶分享一份地址空间,要是栈也用兼并个那就会遭逢以下问题。假如进度的栈指针运行值为 0x7ffc80000000;父进度 A 先实行,调用了一些函数后栈指针 esp 为 0x7ffc8000FF00,此时父进度主动睡眠了;接着诊治器叫醒子线程 A1:
进度和线程是否分享一个内核栈?
No,线程和进度创建的时候都调用 dup_task_struct 来创建 task 联系结构体,而内核栈亦然在此函数中 alloc_thread_info_node 出来的。因此诚然线程和进度分享一个地址空间 mm_struct,但是并不分享一个内核栈。
为什么需要单独中断栈?
这个问题其实不合诛仙手游阵灵,ARM 架构就莫得零丁的中断栈。
为什么需要单独的进度内核栈?
总计进度运行的时候,都可能通过系统调用堕入内核态赓续实行。假定第一个进度 A 堕入内核态实行的时候,需要恭候读取网卡的数据,主动调用 schedule 让出 CPU;此时诊治器叫醒了另一个进度 B,恰巧进度 B 也需要系统调用投入内核态。那问题就来了,要是内核栈唯唯一个,那进度 B 投入内核态的时候产生的压栈操作,势必会浩大掉进度 A 已有的内核栈数据;一但进度 A 的内核栈数据被浩大,很可能导致进度 A 的内核态无法正确复返到对应的用户态了;
总计进度运行的时候,都可能通过系统调用堕入内核态赓续实行。假定第一个进度 A 堕入内核态实行的时候,需要恭候读取网卡的数据,主动调用 schedule 让出 CPU;此时诊治器叫醒了另一个进度 B,恰巧进度 B 也需要系统调用投入内核态。那问题就来了,要是内核栈唯唯一个,那进度 B 投入内核态的时候产生的压栈操作,势必会浩大掉进度 A 已有的内核栈数据;一但进度 A 的内核栈数据被浩大,很可能导致进度 A 的内核态无法正确复返到对应的用户态了;
为什么需要单独的线程栈?
此时 A1 的栈指针 esp 要是为运行值 0x7ffc80000000,则线程 A1 一但出现函数调用,势必会浩大父进度 A 已入栈的数据。
要是此时线程 A1 的栈指针和父进度临了更新的值一致,esp 为 0x7ffc8000FF00,那线程 A1 进行一些函数调用后,栈指针 esp 增多到 0x7ffc8000FFFF,然后线程 A1 睡眠;诊治器再次换成父进度 A 实行,那这个时候父进度的栈指针是应该为 0x7ffc8000FF00 照旧 0x7ffc8000FFFF 呢?不管栈指针被缔造到哪个值,都会有问题不是吗?
此时 A1 的栈指针 esp 要是为运行值 0x7ffc80000000,则线程 A1 一但出现函数调用,势必会浩大父进度 A 已入栈的数据。
要是此时线程 A1 的栈指针和父进度临了更新的值一致,esp 为 0x7ffc8000FF00,那线程 A1 进行一些函数调用后,栈指针 esp 增多到 0x7ffc8000FFFF,然后线程 A1 睡眠;诊治器再次换成父进度 A 实行,那这个时候父进度的栈指针是应该为 0x7ffc8000FF00 照旧 0x7ffc8000FFFF 呢?不管栈指针被缔造到哪个值,都会有问题不是吗?
Linux 诊治要领中并莫得隔离线程和进度,当诊治要领需要叫醒”进度”的时候,势必需要呈报进度的崎岖文环境,也等于进度栈;但是线程和父进度透顶分享一份地址空间,要是栈也用兼并个那就会遭逢以下问题。假如进度的栈指针运行值为 0x7ffc80000000;父进度 A 先实行,调用了一些函数后栈指针 esp 为 0x7ffc8000FF00,此时父进度主动睡眠了;接着诊治器叫醒子线程 A1:
Linux 诊治要领中并莫得隔离线程和进度,当诊治要领需要叫醒”进度”的时候,势必需要呈报进度的崎岖文环境,也等于进度栈;但是线程和父进度透顶分享一份地址空间,要是栈也用兼并个那就会遭逢以下问题。假如进度的栈指针运行值为 0x7ffc80000000;父进度 A 先实行,调用了一些函数后栈指针 esp 为 0x7ffc8000FF00,此时父进度主动睡眠了;接着诊治器叫醒子线程 A1:
进度和线程是否分享一个内核栈?
No,线程和进度创建的时候都调用 dup_task_struct 来创建 task 联系结构体,而内核栈亦然在此函数中 alloc_thread_info_node 出来的。因此诚然线程和进度分享一个地址空间 mm_struct,但是并不分享一个内核栈。
No,线程和进度创建的时候都调用 dup_task_struct 来创建 task 联系结构体,而内核栈亦然在此函数中 alloc_thread_info_node 出来的。因此诚然线程和进度分享一个地址空间 mm_struct,但是并不分享一个内核栈。
为什么需要单独中断栈?
这个问题其实不合,ARM 架构就莫得零丁的中断栈。
这个问题其实不合,ARM 架构就莫得零丁的中断栈。
进度线程寄存器函数指针发布于:上海市声明:该文不雅点仅代表作家本东谈主,搜狐号系信息发布平台,搜狐仅提供信息存储空间办事。