您的位置:首页 > 编程语言

内核代码中和用户栈相关的几个片段

2008-02-21 07:07 218 查看
今天突然脑子里面几处知识点大串联,貌似是迸发出了些许思维的火花,以前一直感觉模模糊糊的有关用户栈的大小限制、栈是怎么自动扩展等问题,通过对照以前看过的内核代码的一些关键片段,似乎清晰了起来。机不可失,趁还没迷糊之前拿笔记下来。

问题1    Linux下用户程序的默认栈大小是多少?如何查看,又如何更改默认值(上层指令和底层实现)?
   
    在bash中输入“ulimit -s ",就可以查看用户程序的默认栈大小,在我的机器上的结果是(单位为KB)


whodare@whodare:~$ ulimit -s


8192



    修改默认栈大小也很容易


whodare@whodare:~$ ulimit -s 16384


whodare@whodare:~$ ulimit -s


16384



    注意,以上的修改只对该bash之后产生成的子进程具有影响,不具备持久性。

    知道了方法,那就来看看有时如何在内核中体现的吧。首先要清楚系统中的每个进程都有一组相关的资源限制,指定了能获取和使用的系统资源的上限,以避免用户过度使用系统资源。

     这样的一组限制在kernel中是通过元素为rlimit结构的数组来实现的,数组的每个元素对应一种资源限制。


    struct rlimit


   {


          unsigned long rlim_curr;


          unsigned long rlim_max;


     }



     其中rlim_curr表示的是资源限制的当前值(注意不要误解为是资源当前使用量),而rlim_max表示的是资源限制的最大值。前者是软限制,可以由用户自行修改,只要不超过rlim_max;后者是硬限制,只有管理员才有权限对rlim_max进行更改。

    每个进程的资源限制数组存放在current->signal->rlim字段中,并且资源限制会被子进程自动继承。

    这样就容易理解前面的bash命令了,实质就是读和更新current->signal->rlim[RLIMIT_STACK].rlim_curr的值。

2。栈空间溢出

     如果在函数内部声明了尺寸巨大的数组,很容易会出现栈溢出的错误,即程序运行时提示:"segment error.core dumped"。内核是如何检测到栈溢出的错误呢?

     概括的说,这是通过对缺页异常的处理来完成的。假设我们定义如下一个函数
 


#define SIZE 0x800000




int foo ( )




...{




     char array[SIZE];
        printf("foo");


     return 0;


}



   
    其中的数组生命通常会被编译器翻译为“ sub 0x800000 %exp",即通过更改ESP来为局部变量分配空间。而对于这里的函数foo,由于这里局部数组的大小和默认栈大小相同(8192KB),因此ESP的值会变为非法,确切的说,在执行完“ sub 0x800000 %exp"这条语句后,ESP所指向的内存地址不在进程的任何一个合法内存线性区的范围之内;当程序运行到printf()时,会用到ESP进行寻址,这时候会出现缺页异常,而异常处理程序会根据导致异常发生的不同场景采取不同的处理;对于这个foo函数,缺页异常处理函数会判定属于访问非法地址,因此向进程发送SIGSEGV信号,导致进程终止执行。

    3.栈的自动扩展

    事实上,当一个程序开始运行时(fork+exec),为其用户栈分配的VMA并不是对应着全部栈空间的,而只是一小部分;随着函数调用的一层层叠加,栈会自动扩展,自动增大栈的当前大小。这也是在缺页异常处理中完成的。
    与前一种情况的区别在于,只有在导致缺页异常的那个线性地址并不比当前esp低很多的情况下,才会调用expand_stack()这个函数来试图扩展栈空间,而该函数内部调用acct_stack_growth() 来检测是否栈空间已经达到currnent->signal->rlim[RLIMIT_STACK].rlim_curr规定的上限,如果没有,那么就扩展栈空间,否则同样发送SIGSEGV信号,终止进程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息