系统运输
有本事就出来,没本事就成鳗鱼了!
如果让他们回答进程堆栈、线程堆栈,如果问题不笼统,如果问题明确,他们会从头到尾回答。 正确率是9成。 但是,可悲的是,问题往往不是该死的
因为很清楚,所以游戏到此结束! 艹。 但是,如果给了我听的机会,我会问以下问题。 请记住。 用你拉屎的力气回答(没有必要太当回事,重要的东东
西边在下面- ) :
UNIX/Linux
请注意,的堆栈在大多数平台上是向下扩展的,并且是在运行流中调用的。 我告诉了他事实。 我…我没问是怎么扩张的
请注意,有一个函数a,该函数a为stack分配了一个大数组,从而扩展了stack,a返回(请参阅)
是的,UNIX/Linux应该回收调谐a中的大数组并分配堆栈空间。 因为那已经没用了,但我没有那样做。 (这里可能是陷阱。 真的是UNIX。 )
(/Linux是应该这样做却没有做,还是我只是逗你玩……虽然不确定,但陈述就是这样)。 (注意,我的问题来了),对不起,UNIX/Linux为什么
要做吗?
操作系统如此规定等的回答,一律是零分! 再说,你能证明我说的一定是正确的吗? 如果我在开玩笑呢? 操作系统能规定错误的东西吗? 或者我可以继续问
这样的规定,直到我的中学历史老师要我朝眼角打一拳,如果我能找回卑鄙所谓的快感,就试试吧。 问题就是这样,即使这个问题是假的生命
问题还是你有自己的想法,能说五分钟,我觉得也足够了。 这个主题的解答要求如下
时间限制: 5分钟。
回答方式:口述、不能画画、不能做手势…语言模糊、表达能力差的,属于计算错误。
解答建议:如果您对操作系统虚拟内存管理和Linux VMA实现的详细信息不是很了解,请不要猜测答案。 请直接回答“我不知道”,然后读这篇文章。
..
五分钟就去。 发表一点我的想法。
首先,这个问题从其本身来看,是有问题的。 Linux应该这么做,但是:
第一,它不一定能做到;
第二,那个没有必要做。
论据是什么? 你为什么这么说?
积极的论点
你没必要那样做。 执行流程时,调用其他函数,或再次调用a,频繁回收堆栈丢失性能;
消极论点
很难或者做不到。 堆栈操作由处理器控制,与OS内核地址空间管理机构之间没有同步机构。 函数调用结束后,CPU会自动处理堆栈寄存器的保存并弹出堆栈帧,但无法通知操作系统内存管理系统更新进程地址空间的映射关系。
如何应对stack所在地址空间领域的争论
堆栈
扩展到遇到异常的地址b,b有可能是readonly的地址或保护空洞,将stack向下方扩展时,如果地址b偏移,则变为stack
如果空间变小,偏向下方,函数的局部变量几乎填满了从stack底到b的空间,则mmap也可以对该区域进行unmap并remap,但是这会使数据混乱,制作
会成为严重的问题。
击中头部的结论
在map或brk期间,比较stack的开头和esp寄存器,小时回收(等于正常,不可能大于)。
Linux的真正做法
Linux
esp寄存器没有做出任何判断。 Linux的原则很简单。 如果地址位于vma范围内,或者位于堆栈的可扩展范围内,并且您拥有权限,则可以访问它
问:无论此VMA属于堆栈、头还是其他什么,内核都由APP应用程序自己控制。 这意味着您可以完全写地址空间中的所有可写代码
的区域全部被清除。 这完全有可能。 缓冲区溢出可能是有意的破坏,但程序员的偶然错误也会引起破坏。 但是他们大多不知道错误是怎么发生的。 我不想用
前提应该是知道Linux是如何管理VMA的。 我必须知道这个。 使用代码和两个图标显示Linux系统内核是如何管理的
堆栈附近的地址空间已映射,如第二张图所示。 如果必须故意破坏,会发生什么问题? 也就是说,一旦发生奇怪的错误,就要从细节上理解。
这个错误是怎么发生的?
演示代码
#includestdio.h
#includestdlib.h
#includesys/types.h
#includeunistd.h
#includesys/mman.h
#defineLARGE70000000
#definePAGESIZE4096
//此函数不执行任何操作,只是为了向下扩展堆栈
请注意,如果在ulimit中取消堆栈的大小限制,将更容易说明问题
voidcall ( ) )
{
inti;
chara[LARGE];
//请相信一定是通过中央赋值触发了segfault。 两侧的元素位于stack/fixmapvma中,或者、
//要么游离,要么孤独,处于被fixmap抛弃的vma中。 因此,以下赋值不会导致段错误。
//a[0]=1;
//a[LARGE-1]=1;
for(I=0; iLARGE; I ) {
a[i]=1;
}
}
intmain(intargc,char**argv ) ) ) ) ) ) ) )。
{
inti;
char*p_map,*p_base,*p_base2;
printf(%d\\\ninitstate\\\n,getpid ) );
获取堆栈的大致地址,对齐PAGESIZE。
p_base=(char* ) I;
p_base2=(char* ) ) ) ) unsignedlong ) p_base(~4095 );
//获取用起点位于当前堆栈下的pagesize对齐的fixmap的地址。
p_base2=(char* ) ( unsignedlong ) p _ base2- ( unsigned long ) 36*PAGESIZE );
getchar (;
调用fixmap显然可以在getchar期间仔细分析/proc/xx/maps文件
//得到上述的magicnumber后,下一个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 (故障1\\ & amp; quot; n;
}else{
打印( beforeunmapfixmaparoundstack\\ n );
getchar (;
//成功后释放,此时的地址空间恢复到mmap以前的状况
munmap(p_map,PAGESIZE*3);
}
打印任务( afterunmapfixmaparoundstack\\ n );
getchar (;
call (;
打印( afterextendstack [ first ]\\\n );
getchar (;
//因为调用了以前的一模一样的mmap,在保持fixmap的状态下调用了call,所以stack
//这个传真地图的传真地址有很大的空间,很遗憾成功了,但是把stackvma
()一号尺寸变成了两段。 不管怎样,访问是可以的。
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 (故障2\\ n );
}
printf ( aftersecondfixmaparoundstackatthesameaddress\\ n;
getchar (;
//这里更狠! unmap中有上面的fixmapvma,留下了空洞。
munmap(p_map,PAGESIZE );
打印( afterunmapfixmaparoundstackincompletely\\ n );
getchar (;
//空洞被touch时,不引起stackextend! 直接安全! 爆炸!
call (;
//永远到不了这里!
打印( afterextendstack [ second ]\\ n;
getchar (;
返回0;
}
上述代码的图解
下图显示了该进程堆栈附近的地址空间映射区域如何演变,直到发生事故。
下图显示了事故的过程及其原因。
测试方式
例如
如果你觉得是你自己画的图,那我一定有疑问是根据什么画的。 其实,我并不是看着代码画的,而是继续看着procfs的maps
文件实时了解了该进程的地址空间细节,并将其转换为上图。 在代码中添加了getchar调用,以便有机会在其他终端上检查maps文件,每次都进行了检查
看完maps文件后,拍键盘上的回车键。 我的测试如下。
代码编译后的进程输出
root@abcd:~# ./a.out
7846
init state
before unmap fixmap around stack
after unmap fixmap around stack
after extend stack[first]
aftersecondfixmaparoundstackatthesameaddress
afterunmapfixmaparoundstackincompletely
段错误( core dumped )
以下是每个进程的maps文件的输出。
root @ ABCD:~ # cat/proc/` PS-e|grepa.out|awk\& amp; quot; {print$1}\\&; quot; /maps|tail-n6|head-1
2b2c 01483000-2b2c 01487000 rp 00157000 Fe:00387296/lib/libc-2.11.2.so
2b2c 01487000-2b2c 01488000 rw-p 0015 b 000 Fe:00387296/lib/libc-2.11.2.so
2b2c 01488000-2b2c 0148 f 000r w-p 000000000:00
7 fff 6236 a 000-7 fff 6237 f 000r w-p 000000000:00 [ stack ]
root @ ABCD:~ # cat/proc/` PS-e|grepa.out|awk\& amp; quot; {print$1}\\&; quot; /maps|tail-n6|head-1
2b2c 01487000-2b2c 01488000 rw-p 0015 b 000 Fe:00387296/lib/libc-2.11.2.so
2b2c 01488000-2b2c 0148 f 000r w-p 000000000:00
7 fff 62359000-7 fff 6235 c 000r w-p 000000000:00
7 fff 6236 a 000-7 fff 6237 f 000r w-p 000000000:00 [ stack ]
root @ ABCD:~ # cat/proc/` PS-e|grepa.out|awk\& amp; quot; {print$1}\\&; quot; /maps|tail-n6|head-1
2b2c 01483000-2b2c 01487000 rp 00157000 Fe:00387296/lib/libc-2.11.2.so
2b2c 01487000-2b2c 01488000 rw-p 0015 b 000 Fe:00387296/lib/libc-2.11.2.so
2b2c 01488000-2b2c 0148 f 000r w-p 000000000:00
7 fff 6236 a 000-7 fff 6237 f 000r w-p 000000000:00 [ stack ]
root @ ABCD:~ # cat/proc/` PS-e|grepa.out|awk\& amp; quot; {print$1}\\&; quot; /maps|tail-n6|head-1
2b2c 01483000-2b2c 01487000 rp 00157000 Fe:00387296/lib/libc-2.11.2.so
2b2c 01487000-2b2c 01488000 rw-p 0015 b 000 Fe:00387296/lib/libc-2.11.2.so
2b2c 01488000-2b2c 0148 f 000r w-p 000000000:00
7ff F5 E0 bb 000-7 fff 6237 f 000r w-p 000000000:00 [ stack ]
root @ ABCD:~ # cat/proc/` PS-e|grepa.out|awk\& amp; quot; {print$1}\\&; quot; /maps|tail-n6|head-1
2b2c 01488000-2b2c 0148 f 000r w-p 000000000:00
7ff F5 E0 bb 000-7 fff 62359000 rw-p 000000000:00
7 fff 62359000-7 fff 6235 c 000r w-p 000000000:00
7 fff 6235 d 000-7 fff 6237 f 000r w-p 000000000:00 [ stack ]
root @ ABCD:~ # cat/proc/` PS-e|grepa.out|awk\& amp; quot; {print$1}\\&; quot; /maps|tail-n6|head-1
2b2c 01488000-2b2c 0148 f 000r w-p 000000000:00
7ff F5 E0 bb 000-7 fff 62359000 rw-p 000000000:00
7 fff 6235 a 000-7 fff 6235 c 000r w-p 000000000:00
7 fff 6235 d 000-7 fff 6237 f 000r w-p 000000000:00 [ stack ]
因为害怕上面的文字信息太乱,格式会因为浏览器而成为问题,所以特意切了图:
有什么用
你
这样就可以彻底限制一个程序的stack的大小,越境的不是误报,而是segfault。 然后signal捕获了这个segfault,在里面
那个不完全unmap的fixmap vma和那个可怜孤独的残缺的stack
vma完全掉在了unmap上。 但这确实没意思。 有什么用? 它的作用是帮助您更好地理解Linux管理虚拟地址空间的方式。
小Tips
本文虽然不涉及线程堆栈,但倒也不难。 线程堆栈通常动态分配给heap区域或中间的较大mmap区域。 对于mmap,请标记MAP_GROWSDOWN标志。 关于它的管理方法,差别不大。 核心问题是缺失页面异常处理程序如何识别缺失页面是vma内部的缺失页面还是vma外部的缺失页面。 在后一种情况下,缺页处理逻辑还识别堆栈缺页、扩展堆栈调整页还是非堆栈缺页。 结果是segfault…)。
只要不遇到本论文所述方法的兑换收缩,Linux的堆栈就会永远扩展。 如果要阅读Linux内核代码,还必须了解以下事实:
1.find_vma函数能找到vma只有一个限制。 即,输入地址比寻找vma的end小即可,并非像很多人想象的那样输入地址必须在寻找vma的开始和结束之间;
2.find_vma函数以这种方式在incompletely中实现,是为了简化对页面错误的处理,并提供同时处理upgrows和down grows VMA的更统一的方法。
后话
但是
但是,这个问题有点混乱。 但是,如果能找上述回答并持续拉动5分钟的话,应该真的很好。 但是,我不知道什么样的语言表达能力可以在不使用图解和代码的情况下清楚地说出上面的所有细节
楚…总之,我觉得我这个主题是个好主题。 我建议看到这篇文章的人把它当作面试问题。 找不到主题问题或不能说的,一律放弃! 这真的是
很好的测试题啊。 那很好。 所以,我想再拿出几个比那个更好的东西。
详情请访问云服务器、域名注册、虚拟主机的问题,请访问西部数码代理商官方网站: www.chenqinet.cn