扩展Benoit 对问题 3 的回答......
将程序划分为代码、常量数据、可修改数据和堆栈等逻辑部分是由不同的代理在不同的时间点完成的。
首先,您的编译器(和链接器)创建指定此划分的可执行文件。如果您查看许多可执行文件格式(PE、ELF 等),您会发现它们支持某种节或段或任何您想要调用的格式。除了文件中的地址、大小和位置之外,这些部分还具有告诉操作系统这些部分的用途的属性,例如,此部分包含代码(这里是入口点),this - 已初始化的常量数据,that - 未初始化的数据(通常不占用文件中的空间),这里有一些关于堆栈的内容,还有依赖项列表(例如 DLL)等。
接下来,当操作系统开始执行程序时,它会解析文件以查看程序需要多少内存,每个部分需要在哪里以及需要什么内存保护。后者通常通过页表完成。代码页被标记为可执行和只读,常量数据页被标记为不可执行和只读,其他数据页(包括堆栈的数据页)被标记为不可执行和读写。正常情况下应该是这样。
通常,程序需要读写,同时需要动态生成代码的可执行区域,或者只是为了能够修改现有代码。组合的 RWX 访问可以在可执行文件中指定,也可以在运行时请求。
可以有其他特殊页面,例如用于动态堆栈扩展的保护页面,它们被放置在堆栈页面旁边。例如,您的程序从为 64KB 堆栈分配的足够页面开始,然后当程序尝试访问超出该点时,操作系统会拦截对这些保护页面的访问,为堆栈分配更多页面(直到支持的最大大小)和进一步移动保护页。这些页面不需要在可执行文件中指定,操作系统可以自己处理它们。该文件应仅指定堆栈大小以及可能的位置。
如果操作系统中没有硬件或代码来区分代码内存和数据内存或强制执行内存访问权限,则划分非常正式。16 位实模式 DOS 程序(COM 和 EXE)没有以某种特殊方式标记代码、数据和堆栈段。COM 程序将所有内容放在一个共同的 64KB 段中,它们以 IP=0x100 和 SP=0xFFxx 开头,内部代码和数据的顺序可以是任意的,它们几乎可以自由地交织在一起。DOS EXE 文件只指定了起始 CS:IP 和 SS:SP 位置,除此之外,代码、数据和堆栈段与 DOS 无法区分。它所需要做的就是加载文件,执行重定位(仅适用于 EXE),设置 PSP(程序段前缀,包含命令行参数和一些其他控制信息),加载 SS:SP 和 CS:IP。