• (转)linux进程的地址空间,核心栈,用户栈,内核线程


    转自:http://blog.csdn.net/wuzh1230/article/details/7183763

    地址空间:

    32位linux系统上,进程的地址空间为4G,包括1G的内核地址空间,和3G的用户地址空间。

    内核栈:

    系统栈(也叫核心栈、内核栈)是内存中属于操作系统空间的一块区域,其主要用途为:
                  (1)保存中断现场,对于嵌套中断,被中断程序的现场信息依次压入系统栈,中断返回时逆序弹出;
                  (2)保存操作系统子程序间相互调用的参数、返回值、返回点以及子程序(函数)的局部变量。
        用户栈是用户进程空间中的一块区域,用于保存用户进程的子程序间相互调用的参数、返回值、返回点以及子程序(函数)的局部变量。

        那么为什么不直接用一个栈,何必浪费那么多的空间呢??原因有二:

                (1)如果只用系统栈。系统栈一般大小有限,如果中断有16个优先级,那么系统栈一般大小为15(只需保存15个低优先级的中断,另一个高优先级中断处理 程序处于运行),但用户程序子程序调用次数可能很多,那样15次子程序调用以后的子程序调用的参数、返回值、返回点以及子程序(函数)的局部变量就不能被 保存,用户程序也就无法正常运行了。。

             (2)如果只用用户栈。我们知道系统程序需要在某种保护下运行,而用户栈在用户空间(即cpu处于用户态,而cpu处于核心态时是受保护的),不能提供相应的保护措施(或相当困难)。

     

    进程控制块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)

  • 相关阅读:
    第3次实践作业
    第2次实践作业
    第09组 团队Git现场编程实战
    第二次结对编程作业
    团队项目-需求分析报告
    团队项目-选题报告
    第一次结对编程作业
    第一次个人编程作业
    第一次博客作业
    课程设计第十四天
  • 原文地址:https://www.cnblogs.com/yysblog/p/2755226.html
Copyright © 2020-2023  润新知