linux进程的地址空间,核心栈,用户栈,内核线程
地址空间:
32位linux系统上,进程的地址空间为4G,包括1G的内核地址空间,和3G的用户地址空间。
内核栈:
进程控制块task_struct中保存了2个page大小的信息。
为什么每一个进程都是用各自的内核栈呢?
引用(http://hi.baidu.com/iruler/blog/item/0c3363f377ccc5c90a46e023.html)“
假设某个进程通过系统调用运行在内核态(使用这个全局内核堆栈),此时如果被抢占,发生一次切换,另一个进程开始运行,如果这个当前进程又通过系统调用陷入内核,那么这个进程也将使用这个全局内核堆栈,这样的话就把以前那个进程的内核空间堆栈给破坏了。
而如果进程使用独立的内核栈,就避免了这种情况的发生
内核线程:
拥有自己独立内核栈的调度单元,可以参与调度,在内核空间执行。
用户栈:
每一个线程有一个用户栈,由ss和esp指向。
===================================================
进程1 进程2
内核代码区 kcode (0xc0001000)
kcode (0xc0001000)
内核栈区 kstack(0xc000F000) kstack(0xc001F000)
内核栈区 kstack(0xc000D000) kstack(0xc001D000)
...
内核数据区 kdata (0xc0003000) kdata (0xc0003000)
---------------------------------------------------------------------------------------------
用户代码区 ucode (0x70001000) ucode (0x70001000)
用户栈区 ustack (0x7000F000) ustack (0x7000F000)
用户栈区 ustack (0x7000D000) ustack (0x7000D000)
...
用户数据区 udata (0x70003000) udata (0x70003000)
===================================================
合理的解释:
内核1G空间的映射页表(256个entries*4M)只有一份,n个进程共享(都复制了一份在自己的进程页表内, 256个内核的entries+768个用户空间的entries, 总共1024个entries,假定使用4M页面,并且一开始就全都分配好)。
每个进程用户空间的这些entries各不相同,比如说,同样的0x70001000虚拟地址, 进程1指向物理内存0x2000, 而进程2指向0x1000。
每个线程对应的内核栈的虚拟地址不重叠。
thread1's kernel stack = 0f000,
thread2's
kernel stack = 0d000,
thread3's
kernel stack = 1f000,
thread4's
kernel stack = 1d000
...
思考1:
如果内核栈不是预先分配好的(分
配的意思是指"在内核空间中分出一段一段不重叠空间作为各个线程的栈", e.g. kmalloc调用),那么步入内核态的时候,
压栈,发生缺页异常,必须对内核栈占用的这个页进行换页,而换页历程的调用必然涉及参数的压栈出栈,而这个时候内核栈没有就绪,异常发生嵌套,系统出错!
思考2:
在内核里面做kmalloc是可以的,添加一个entries,关联一块物理内存,ok可以用了。
思考3:
如果希望进程共享某一个虚拟内存地址0x80001000的数据, 那么在需要共享的进程p1,p2的页表中添加1个entries(0x80001000->0x3000)。
另外内核是天然的共享对象,所以才在每一个进程中页表中设置内核空间页表的一份拷贝。
如果有个家伙特立独行,创建n个关于内核空间的页表,指向n个物理内存块,那么他就需要再在这n个物理内存中"铺设"n分内核代码和数据的副本(真是自找麻烦)。
思考4:
内核栈确实不适合共享(一个特殊的内存区域)。怎么办?像用户空间栈一样固定在某一个虚拟地址,安插页表项entries指向不同的物理内存?显然不行! 只有一个办法,在内核空间内分配n个不重叠的空间出来。
思考5:
内核步入的时候最初的“内核栈”并不是真正的内核栈,这个栈是全局的,每个cpu一个,是过渡到真正的内核栈使用的。(http://bbs.pediy.com/archive/index.php?t-87518.html)
思考6:
独立内核栈的场景, at first 我们分析一下如果共用一个内核栈会出现什么情况,假设有A、B三个进程,A调用系统调用read(1,...)读按键,此时正好又没有按键,
所以A被阻塞在内核,此时内核调度B执行,此时B也调用一个系统调用被阻塞了,而此时按键事件到来,进程A被唤醒,A继续执行。我们想想B进入内核
已经破坏了A进入内核的内核栈,那此时A能正常返回吗?所以从上面分析A、B肯定是拥有各自的内核栈。此内核栈好像是和task_struct以前分配的
一共分配了3个页面,除了task_struct占得内存外,其余的就是内核栈。而在x86上这个栈等指针保存在TSS断的SS0和Esp0中。(http://bbs.chinaunix.net/thread-1930753-1-1.html)
参考资料:
1, 内核栈的使用(http://tech.ddvip.com/2008-09/122095404362368.html)