g__*_*__k 2 stack operating-system process
在典型的 C 程序中,Linux 内核提供 84K - ~100K 的内存。当进程使用给定的内存时,内核如何为堆栈分配更多的内存。
IMO,当进程占用堆栈的所有内存并且现在使用下一个连续内存时,理想情况下它应该发生页面错误,然后内核处理页面错误。内核是否在这里为给定进程的堆栈提供了更多内存,Linux内核中的哪个数据结构标识了进程堆栈的大小?
根据操作系统(Linux 实时与普通)和下面的语言运行时系统,可以使用多种不同的方法:
1) 动态的,通过页面错误
通常会预先分配一些实际页面到更高的地址,并将初始 sp 分配给该地址。栈向下增长,堆向上增长。如果页面错误发生在堆栈底部下方,则会分配并映射丢失的中间页面。有效地自动从顶部到底部增加堆栈。通常存在执行此类自动分配的最大值,该最大值可以或不能在环境 (ulimit)、exe 标头中指定,或由程序通过系统调用 (rlimit) 动态调整。尤其是这种可调整性在不同操作系统之间差异很大。通常还存在一个限制,即距离堆栈底部“多远”,页面错误被认为是正常的并且会发生自动增长。请注意,并非所有系统的堆栈都向下增长:在 HPUX 下,它(使用?)向上增长,因此我不确定 PA-Risc 上的 linux 会做什么(有人可以对此发表评论)。
2)固定尺寸
其他操作系统(尤其是在嵌入式和移动环境中)要么根据定义具有固定大小,要么在 exe 标头中指定,或者在创建程序/线程时指定。特别是在嵌入式实时控制器中,这通常是一个配置参数,并且各个控制任务会获得修复堆栈(以避免失控线程占用更高优先级控制任务的内存)。当然,在这种情况下,内存也可能仅被虚拟分配,直到真正需要为止。
3) 按页、意大利面条和类似的
这种机制往往会被遗忘,但仍在某些运行时系统中使用(我知道 Lisp/Scheme 和 Smalltalk 系统)。它们根据需要动态分配和增加堆栈。但是,不是作为单个连续段,而是作为多页块的链接链。它需要编译器生成不同的函数入口/出口代码,以便处理段边界。因此,此类方案通常由语言支持系统而不是操作系统本身来实现(过去很早 - 叹息)。原因是,当您在交互式环境中拥有许多(例如数千个)线程时,预分配例如 1Mb 只会填满您的虚拟地址空间,并且您无法支持以前未知单个线程的线程需求的系统(这是通常是在动态环境中的情况,其中用户可能会将评估代码输入到单独的工作区中)。因此,上面方案 1 中的动态分配是不可能的,因为会有其他线程拥有自己的堆栈。堆栈由较小的段(例如 8-64k)组成,这些段从池中分配和释放,并链接到堆栈段链中。对于延续、协程等的高性能支持也可能需要这样的方案。
现代 unixes/linuxes 和(我猜,但不是 100% 确定)windows 使用方案 1) 作为 exe 的主线程,2) 用于额外的 (p-) 线程,这些线程需要线程创建者给出的固定堆栈大小最初。大多数嵌入式系统和控制器使用固定(但可配置)的预分配(在许多情况下甚至是物理预分配)。
编辑:错别字