内核代码中和用户栈相关的几个片段
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信号,终止进程。
问题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信号,终止进程。
相关文章推荐
- 关于wordpress用户系统的几个代码小片段
- Android常用的几个系统参数相关代码片段
- 跟kernel相关的命令和几个内核参数
- 几个用于序列化的代码片段
- (转)分享几个有趣的 JavaScript 代码片段
- uClinux内核移植相关代码分析
- 几个简单代码片段-- Google C++ style guide
- 几个项目中用到的代码片段
- 嵌入式Linux内核移植相关代码分析
- 计算机视觉相关代码片段(Python)
- 嵌入式Linux内核移植相关代码分析
- linux内核网络链路层相关代码分析总结
- 嵌入式Linux内核移植相关代码分析…
- 代码片段----内核链表使用一例
- 与TIME_WAIT相关的几个内核参数修改测试讨论结论
- VScode中自定义用户代码片段(C user snippet)自动生成C语言头文件排除重复包含
- 内核态拦截用户模式代码注入
- Linux内核的framebuffer相关的内核代码注释