有本事就出来,没本事就当鳖!

假设让我回答关于进程栈,线程栈的问题,仅仅要问题不笼统,仅仅要问题明白。我会一五一十地回答,正确率上九成,然而,可悲的是,问题往往他妈的都不是非常明白,因此,游戏到此结束。!

艹。可是假设给我一个问的机会。我会问以下一个问题,记住。使出你拉屎的劲来回答(该问题足够糙。不必太当回事,重要的东西在以下-):

UNIX/Linux的stack在大多数平台是向下扩展的(注意,我已经告诉事实了。我并没有问...是怎样扩展的,这是能够背诵下来并朗读出来的),在一个运行流调用了一个函数A,而该函数A在stack上分配了一个大数组导致了stack扩展(注意,这又是一段陈述。我还没有给出问题),然后A返回了,UNIX/Linux理应回收调A里面大数组分配stack空间-由于它再也没实用了。可是它并没有这么做(这里可能是一个陷阱,真的是UNIX/Linux理应这么做却没有做,还是说我仅仅是在逗你玩...不确定,但陈述就是如此)。(注意,我的问题来了),请问,UNIX/Linux为什么这么做???!!!

       凡是回答操作系统这么规定的之类的答案,一律零分!况且。你能证明我说的一定对吗?万一我是逗你玩呢?操作系统能规定一个错的东西吗?或者我能够继续问为什么这么规定,直到像我初中的历史老师被我问到朝我眼角猛打一拳那样。假设我能找回点贱人所谓的快感,那么来打吧!问题就是这样,无论这个问题是一个伪命题还是你有自己的想法,能说5分钟的,我认为也够能够了。该题目的答题要求例如以下:
时间限制:5分钟。

作答方式:全口述。不能绘图,不能打手势...语言含糊不清的。表达能力不好的,算错误。
答题建议:假设你对OS虚拟内存管理以及Linux的VMA实现细节没有相当深入的理解,请不要推測答案。请直接回答“不知道”,然后看完此文。

.................................
5分钟过去。我要发布一点我的想法了。
       首先,这个问题在本身看来。有问题。由于尽管Linux理应这么做,但它:
第一,它不一定能做到;
第二,它根本没有必要做。
那么论据是什么?凭什么这样说?

积极论点

不是必需这样做。运行流还会调用别的函数或者再次调用A。频繁回收栈损耗性能。

消极论点

非常难或者不能做到。

stack操作是处理器控制的,和OS内核地址空间管理机制之间没有同步机制。一个函数调用结束后。CPU自己主动处理stack寄存器的收缩,弹出栈帧。然而它无法通知OS内存管理系统去更新进程地址空间的映射关系。

怎样处理stack所在地址空间区域的争议

stack会一直扩展到碰到异常的地址B,B可能是一个readonly的地址或者是一个保护空洞。在向下扩展stack情况下。假设地址B偏上,会导致stack空间变小,假设偏下,一旦函数局部变量差点儿占满了stack底到B的空间,mmap尽管也能unmap掉这段区域然后remap,然而这会使数据混乱,造成严重问题。

拍脑袋的结论

mmap或者brk期间,比較stack顶部与esp寄存器,若小于则回收(等于是正常的。大于是不可能的)。

Linux真实的做法

Linux没有推断什么esp寄存器,Linux的原则非常easy,仅仅要一个地址处在一个vma范围内或者处在stack可扩展的范围内,且拥有权限的。它就是能够訪问,内核是无论这个VMA是属于stack还是heap或者别的什么。详细由应用程序自己控制。也就是说,你全然能够写一段代码。把地址空间中所有能够写的区域所有清零,这全然有可能。缓冲区溢出可能是一种蓄意的破坏,然而程序猿偶然的错误也会造成破坏,尽管他们大多数都不知道错误是怎样发生的。我不想用文字长篇大论Linux是怎样管理VMA的,你知道这个应该是一个前提,你必须知道这个。

我用一段代码以及两个图示来展示Linux系统内核是怎样管理stack附近的地址空间映射的,而且在第二张图中给出,假设你非要蓄意破坏。会造成什么问题。

也就是说。一旦发生莫名奇异的错误,你必须能从细节上理解这个错误是怎样发生的。

演示代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h> #define LARGE 70000000
#define PAGESIZE 4096 // 该函数什么都不做。仅仅为了把stack向下扩展
// 请注意用ulimit将stack限制大小去掉。这样会更easy说明问题
void call()
{
int i;
char a[LARGE]; // 请相信,一定是在中间的赋值中触发segfault,由于两边的元素要么处在stack/fixmap vma,
// 要么处在游离的,及其孤独的,被fixmap给截断了的vma中。因此以下的赋值不会引发段错误:
// a[0] = 1;
// a[LARGE-1] = 1;
for (i = 0; i < LARGE; i++) {
a[i] = 1;
}
} int main(int argc, char **argv)
{
int i;
char *p_map, *p_base, *p_base2; printf("%d\ninit state\n", getpid()); // 获取stack的大致地址,而且PAGESIZE对齐。 p_base = (char *)&i;
p_base2 = (char *)(((unsigned long)p_base) & ~4095); // 获取pagesize对齐的用来fixmap的地址,该地址起点在当前stack的以下。
p_base2 = (char *)((unsigned long)p_base2 - (unsigned long)36*PAGESIZE);
getchar(); // 调用fixmap,显然。假设你细致在getchar期间分析了/proc/xx/maps文件而且
// 得到了上述的那些magic number,以下的mmap无论怎样是会成功的!
p_map = (char *)mmap((void*)p_base2, PAGESIZE*3, PROT_READ | PROT_WRITE,
MAP_FIXED |MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p_map == MAP_FAILED) {
printf("failed 1\n");
} else {
printf("before unmap fixmap around stack\n");
getchar();
// 成功了就释放掉它,此时的地址空间恢复成mmap之前的状况
munmap(p_map, PAGESIZE*3);
} printf("after unmap fixmap around stack\n");
getchar(); call(); printf("after extend stack[first]\n");
getchar(); // 依旧调用之前的那个一模一样的mmap进行fixmap,由于调用了call,stack
// 空间已经扩展到了这个fixmap的fixaddress,非常遗憾,成功了。然而它将stack vma
// 一刀切成了两段。无论怎样。訪问还是能够进行的。
p_map = (char *)mmap((void*)p_base2, PAGESIZE*3, PROT_READ | PROT_WRITE,
MAP_FIXED |MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p_map == MAP_FAILED) {
printf("failed 2\n");
} printf("after second fixmap around stack at the same address\n");
getchar(); // 这里更狠。部分unmap了上面那个fixmap vma,留下一个空洞。
munmap(p_map, PAGESIZE); printf("after unmap fixmap around stack incompletely\n");
getchar(); // 当空洞被touch的时候,不会引发stack extend!而是直接segfault! 爆炸! call(); // 永远不会到达这里!
printf("after extend stack[second]\n");
getchar(); return 0;
}

针对上述代码的图解

以下一幅图展示一直到出事之前,该进程的stack附近的地址空间映射区域是怎么演化的:

以下一幅图展示出事的过程以及这个事故的原因:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZG9nMjUw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

測试方式

假设你认为图是我自己画出来的,那么肯定有一个疑问。我是基于什么画出来的,其实,我并非通过看代码画出来的。我是通过不断查看procfs的maps文件实时了解该进程地址空间的细节,将其转换成了上面的图示,为了让我有机会到还有一个终端去查maps文件,我在代码中添加了getchar调用。每次查看完maps文件。我会拍一下键盘的回车键。我的測试例如以下:

代码编译后的进程输出

root@abcd:~# ./a.out
7846
init state

before unmap fixmap around stack

after unmap fixmap around stack

after extend stack[first]

after second fixmap around stack at the same address

after unmap fixmap around stack incompletely

段错误 (core dumped)

以下是查看每一步进程maps文件的输出:

root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01483000-2b2c01487000 r--p 00157000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01487000-2b2c01488000 rw-p 0015b000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff6236a000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01487000-2b2c01488000 rw-p 0015b000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff62359000-7fff6235c000 rw-p 00000000 00:00 0
7fff6236a000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01483000-2b2c01487000 r--p 00157000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01487000-2b2c01488000 rw-p 0015b000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff6236a000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01483000-2b2c01487000 r--p 00157000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01487000-2b2c01488000 rw-p 0015b000 fe:00 387296                     /lib/libc-2.11.2.so
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff5e0bb000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff5e0bb000-7fff62359000 rw-p 00000000 00:00 0
7fff62359000-7fff6235c000 rw-p 00000000 00:00 0
7fff6235d000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
root@abcd:~# cat /proc/`ps -e|grep a.out|awk '{print $1}'`/maps |tail -n 6|head -n 4
2b2c01488000-2b2c0148f000 rw-p 00000000 00:00 0
7fff5e0bb000-7fff62359000 rw-p 00000000 00:00 0
7fff6235a000-7fff6235c000 rw-p 00000000 00:00 0
7fff6235d000-7fff6237f000 rw-p 00000000 00:00 0                          [stack]
我怕上面的文字信息太乱,格式在不同浏览器会有问题。我还特意截了一张图:

有什么用

你能够用这样的方式彻底限制一个进程的stack的大小。越界了不是报错,而是segfault。然后你能够signal捕获这个segfault,在里面把那个未全然unmap的fixmap vma以及那个可怜且孤独的残缺的stack vma给彻底unmap掉。只是这确实没什么好玩的。

有什么用呢?它的作用就是让你更加深入理解Linux对虚拟地址空间的管理方式。

小Tips

本文不涉及线程栈,可是倒也不难,线程栈一般在heap区或者中间的大块mmap区动态分配,mmap的时候给它一个MAP_GROWSDOWN标志就能够了。关于它的管理方式。没啥区别。核心问题在于。缺页异常处理程序是怎么识别到一个缺页是一个vma内部的缺页(结果就是调页),还是vma外部的缺页。在后一种情况下。缺页处理逻辑还要进一步识别是stack的缺页(结果就是extend stack然后调页),还是非stack缺页(结果就是segfault...)。


       Linux的stack除非遇到本文所述的这样的方式的挤兑收缩,它是永远扩展的。假设你想阅读Linux的内核代码。那么也须要理解以下的事实:
1.find_vma函数能找到vma仅仅有一个限制,即输入地址仅仅要小于查找vma的end就可以。并非非常多人想象的那样输入地址必须处在查找vma的start和end之间;
2.find_vma函数之所以实现得如此incompletely。是由于为了简化缺页中断的处理,同一时候也是为了提供一种更加统一的方式同一时候处理upgrows和downgrows的vma。

后话

尽管这个问题问得有点乱,可是假设能找上述回答连续扯5分钟的,应该是真行!只是我不知道怎样的语言表达能力能够不用图解和代码把上面的每个细节说清楚...总之,我认为我的这个题目是一个好题目。

能够建议给看到此文的人,把它做面试题吧。凡是发现不了题目问题的以及说不出所以然的。一律不要。这真是一道好測试题啊。它是如此之好,以至于我还想再出几道比它更好的。

请使劲回答一个关于UNIX/Linux自己主动扩展stack的问题的更多相关文章

  1. 来自Unix/Linux的编程启发录

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家公布重点内容 2017年第一篇文章,祝各位好友新年快乐. 年前因为不小心坐到了自己左手大拇指导致轻微的骨裂,没有按时更新,实在是羞愧.今年 ...

  2. 《UNIX/Linux网络日志分析与流量监控》新书发布

    本书从UNIX/Linux系统的原始日志(Raw Log)采集与分析讲起,逐步深入到日志审计与计算机取证环节.书中提供了多个案例,每个案例都以一种生动的记事手法讲述了网络遭到入侵之后,管理人员开展系统 ...

  3. 28个Unix/Linux的命令行神器_转

    28个Unix/Linux的命令行神器 下面是Kristóf Kovács收集的28个Unix/Linux下的28个命令行下的工具,有一些是大家熟悉的,有一些是非常有用的,有一些是不为人知的.这些工具 ...

  4. UNIX/Linux下C语言的学习路线

    一.工具篇 “公欲善其事,必先利其器”.编程是一门实践性很强的工作,在你以后的学习或工作中,你将常常会与以下工具打交道, 下面列出学习C语言编程常常用到的软件和工具. 1.操作系统    在UNIX或 ...

  5. 如何使用Unix/Linux grep命令——磨刀不误砍柴工系列

     http://man.linuxde.net/grep ---------------------------------------------------- 如何使用Unix/Linux gre ...

  6. 28个Unix/Linux的命令行神器

    下面是Kristóf Kovács收集的28个Unix/Linux下的28个命令行下的工具(原文链接),有一些是大家熟悉的,有一些是非常有用的,有一些是不为人知的.这些工具都非常不错,希望每个人都知道 ...

  7. Unix/Linux运维首选工具Xmanager Enterprise 3.0的使用教程

    管理Uinx和Linux服务器的兄弟们应该很熟悉Xmanager,一个窗口可以同时控制上百台Linux和Unix服务器,功能非常强大!^_^请看: manager是一个简单易用的高性能的运行在Wind ...

  8. 28 个 Unix/Linux 的命令行神器

    28 个 Unix/Linux 的命令行神器   下面是Kristóf Kovács收集的28个Unix/Linux下的28个命令行下的工具(原文链接),有一些是大家熟悉的,有一些是非常有用的,有一些 ...

  9. Unix/Linux环境C编程入门教程(32) 环境变量那些事儿

    1. getenv() putenv()setenv()函数介绍 getenv(取得环境变量内容) 相关函数 putenv,setenv,unsetenv 表头文件 #include<stdli ...

随机推荐

  1. UltraEdit Companion Utility

    UltraEdit Companion Utility 配色组件 http://www.danielwmoore.com/extras/index.php?action=downloads;sa=vi ...

  2. 分享关于浏览器对象 history对象

    window.history.forward() == window.history.go(-1) //返回下一页 window.history.back() == window.history.go ...

  3. xfce4 + docky ,docky 上面那透明的一条黑色横线去掉方法

    在安装完Debian 9 + xfce4桌面后 ,添加docky启动后,会在docky 上面有一条黑色横线看起来非常不舒服. 去掉方法:设置管理器->窗口管理器微调->合成器->取消 ...

  4. JS之字符串和数组

    字符串: 属性: length:可以获取字符串的长度: <script type="text/javascript"> var str = "haha&quo ...

  5. SQL 自增列清零方法

    SQL Identity自增列清零方法1.使用DBCC控制台命令: dbcc checkident(表名,RESEED,0) 2.truncate table 也可将当前标识值清零 但当有外键等约束时 ...

  6. python编程练习

    python练习之冒泡排序: python代码: #coding=utf-8 if __name__=="__main__": arr=[3,2,1,7,11,4,5,8] pri ...

  7. Android 採用HTML设计界面

    由于Android软件开发分工眼下还没有细化,程序猿往往须要负责软件界面的开发,尽管软件的界面图片已经由美工设计好了.可是假设使用layout技术把软件做成美丽的界面确实非常困难,而是也比較耗时.An ...

  8. js---12数据类型,数据类型转换,NaN,

    <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...

  9. Spark RPC

    在Spark中,对于网络调用的底层封装(粘包拆包,编解码,链路管理等)都是在common/network-common包中实现的(详见[common/network-common]).在common/ ...

  10. C/C++(数据结构栈的实现)

    栈的实现 特点FILO(先进后出) 假设栈的空间为8 top == 0 不能出栈,已到栈底 top == 8 不能入栈,已到栈顶 top始终指向一个待插入的位置 push操作,1.写入数据,2.top ...