原文:http://www.cnblogs.com/Seiyagoo/archive/2012/05/26/2518849.html
点击打开链接
以下简称用户栈、内核栈
1、用户栈和内核栈的区别
内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,
存在于用户空间,一个内核栈,存在于内核空间。
记住,进程对应的用户栈和内核栈都是进程私有的。当进程在用户空间
运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面
的
内容是内核栈空间地址,使用内核栈。
注:有些系统中专门为全局中断处理提供了中断栈,但是x86中并没有中断栈,中断在当前进程的内核栈中处理。
2、linux中有多少个内核栈
在/include/linux/sched.h中定义了如下一个联合结构:
union task_union {
struct task_struct task;
unsigned long stack[2048];
};
每个进程在创建的时候会在内核空间连续分配两个page即8K的数据用来保存进程结构(task_struct),这个进程结
构大概有1K左右,剩下的7K用作该进程的内核栈(写中断程序的时候不要用什么递归,大的局部变量)。 也就是说,
除
了每个进程都有一个用户栈之外,同时都有一个系统空间栈。
实际上,进程的task_struct结构所占的内存是由内核动态分配的,更确切地说,内核根本不给task_struct分配内
存,
而仅仅给内核栈分配8K的内存,并把其中的一部分给task_struct使用。
2.1、首先要搞清楚linux的调度机制,在内核态时是不会发生调度的;
2.2、进入内核态与返回用户态对堆栈的使用是平衡的:
在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户
栈,当进程陷入到内核态时,内核栈保存进程在内核态运行的相关信息,但是一旦进程返回到用户态后,内核栈中保存
的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。
2.3、linux把堆栈与task_struct放在一起,并用简单操作得到current指针(esp & 8191UL),这在共享堆栈情况下
是不允许的,也无法区分是哪一个进程;
2.4、进程的独立性:如果一个进程(中断,调用)的时候由于某种原因(中断处理程序写的不对)使堆栈不平衡了,那就会影
响整个系统,而如果堆栈是独立的,那只会影响此进程,大不了把它kill掉。这就像linux的设计:用户与内核分的非常清楚,好
理解也更强壮,你死你的,不关别人的事。
3、进程用户栈和内核栈的切换
当进程因为中断或者系统调用而陷入内核态时,进程所使用的堆栈也要从用户栈转到内核栈。进程陷入内核态后,首先把
用户态的堆栈地址保存在内核堆栈中,然后设置堆栈指针寄存器的地址为内核栈地址(
CPU
从任务状态段
TSS
中装入内核栈指
针
esp
),这样就完成了用户栈向内核栈的转换; 当进程从内核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈
里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。
那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们
是如何知道内核栈的地址的呢?
关键在进程从用户态转到内核态的时候,进程的内核栈总是空的(理由见上面的2.2)。所以在进程陷入内核的时候,直接
把
内核栈的栈顶地址给堆栈指针寄存器就可以了。
4、用户态、内核态之间的共享
4.1、我们知道linux的虚拟地址空间是内核态使用3G以上的高地址空间,那么所有的用户进程是如何共享这一个内核空间的呢?
Linux系统中的init进程(pid=1)是除了idle进程(pid=0,也就是init_task)之外另一个比较特殊的进程,它是Linux内核开始
建立起进程概念时第一个通过kernel_thread产生的进程,其开始在内核态执行,然后通过一个系统调用,开始执行用户空间的
/sbin/init程序,期间Linux内核也经历了从内核态到用户态的特权级转变,/sbin/init极有可能产生出了shell,然后所有的用户
进
程都有该进程派生出来。而linux采用2级页表(1K x 1K x 4K),页目录的1/4(3G/4G)即256B是属于内核的;所以创建用
户
进程时会复制init进程的这256B的页目录以及后面的一级、二级页表,也即实现了内核空间的共享。
4.2、一个进程在内核态 可以直接通过虚拟地址访问其他进程内核态的数据,因为他们是一个页表。
一个进程在内核态 不可以直接通过虚拟地址访问其他进程的用户态的数据,因为他们不使用同一个页表。
4.3、由于系统中只有一个内核实例在运行,因此所有进程都映射到单一内核地址空间。内核中维护全局数据结构和每个进程的
一些对象信息,后者包括的信息使得内核可以访问任何进程的地址空间。通过地址转换机制进程可以直接访问当前进程的地址空
间(通过MMU),而通过一些特殊的方法也可以访问到其它进程的地址空间。
4.4、内核态与用户态的交互
举个特例:当系统调用的参数超过6个时,将借助寄存器将所要传递给内核的参数包装成一个结构体,并将结构体指针放到
指定寄存器。
参考:进程的用户栈和内核栈