每天3分钟操作系统修炼秘籍(9):栈空间之用户栈和内核栈
载请务必在文章最开头标明原文地址
用户栈和内核栈
用户栈
每当进程调用一次函数,都会在用户栈中为该函数分配一个栈帧(stack frame),也称为调用栈(call stack),当该函数返回时又会释放该栈帧。释放的栈帧不会从虚拟内存中移除,它可以被之后调用的函数重新使用,所以栈空间的大小是不会减小的。
根据这个特性并结合上图所描述的程序执行过程,可以推断出一个重要的结论。由于函数内部调用函数时,外部函数的栈帧不会释放,只有内部函数全部退出了才会继续执行外部函数并在执行完成的时候释放外部函数的栈帧,所以,递归函数(即函数内部调用函数自身)如果递归调用的层次太多(比如无限递归),会分配大量的栈帧,并且不会释放,直到栈空间不足,无法再分配新的栈帧,这时会报栈溢出(stack overflows)错误。所以,必须要合理编写递归函数,使得递归函数能够在达到某些条件时返回,从而释放栈帧,避免无限递归。
栈帧中保存了传递给该函数的参数、该函数中定义的局部变量、函数的返回值、调用该函数的程序计数器副本,以及一些其它重要信息。这里有必要解释下栈帧中的程序计数器副本。
什么是程序计数器(Program Counter,PC)?这是CPU中的一个寄存器,在这个寄存器中保存了下一个要执行指令的指针。所以,CPU每执行一个指令的时候,就会设置这个寄存器使它指向下一个指令。
前面描述程序执行流程的时候说过,当main()函数调用func1()函数的时候,需要保存main()函数中调用func1()的位置,以便func1()返回时可以跳转回main()函数继续向下执行。其实,main()函数在开始调用func1()函数的时候,PC寄存器就已经指向了这个指令,CPU可以将这个指令的指针的值(也就是PC的副本)保存在func1()函数的栈帧中,这样func1()执行完成后就能将这个指针重新放回到CPU的PC寄存器中,使得CPU重新回到main()函数调用func1()的位置处,从而调用者main可以取得函数func1()栈帧中的返回值(这时候func1()的栈帧被释放),并继续执行下面的代码。
内核栈
操作系统还为每个进程维护另一个栈:内核栈。这个栈的位置在内核的内存区域中,只有内核能够访问,用户进程无法访问。
内核栈的作用是存放上下文切换时的进程信息。
当进程A要切换到进程B时,首先要陷入内核,然后内核将CPU中关于进程A的进程信息(即某些寄存器中的值)保存在进程A的内核栈中,然后从进程B的内核栈中恢复进程B的信息到CPU的某些寄存器中,再退出内核模式回到进程B,这样CPU就开始执行进程B了。
Linux&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
网站架构系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.html
MySQL/MariaDB系列文章:https://www.cnblogs.com/f-ck-need-u/p/7586194.html
Perl系列:https://www.cnblogs.com/f-ck-need-u/p/9512185.html
Go系列:https://www.cnblogs.com/f-ck-need-u/p/9832538.html
Python系列:https://www.cnblogs.com/f-ck-need-u/p/9832640.html
Ruby系列:https://www.cnblogs.com/f-ck-need-u/p/10805545.html
操作系统系列:https://www.cnblogs.com/f-ck-need-u/p/10481466.html https://www.cnblogs.com/f-ck-need-u/p/11779299.html