虚拟地址空间
虚拟地址空间将程序和物理内存隔离开,程序中访问的内存地址不是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。
- 内核区
- 内核空间不允许应用程序读写
- 内核驻留在内存中
- 系统中所有进程对应的虚拟地址空间的内核区都会映射到同一块物理内存上
- 用户区
- (1)保留区: 未赋予物理地址,任何对它的引用都是非法的,程序中的空指针指向的内存地址。
- (2).text段: 代码段,存放程序的执行代码 (CPU执行的机器指令),代码段是只读的。
- (3).data段: 数据段,存放程序中已初始化且初值不为0的全局变量和静态变量。
- (4).bss段: 存放程序中未初始化以及初始为0的全局变量和静态变量,操作系统把这些未初始化的变量初始化为0。
- (5)堆区:用于存放进程运行时动态分配的内存。由程序员控制它的分配和释放,如果程序执行结束还没有释放,操作系统会自动回收。堆向高地址扩展,由于系统用链表来存储空闲内存地址,是不连续的内存区域,堆中内容只能通过指针间接访问。
- (6)内存映射区:磁盘文件映射或程序运行过程中需要调用的动态库。
- (7)栈区: 存储函数内部的非静态局部变量、函数参数等信息,栈内存由编译器自动分配释放。栈向低地址扩展,分配的内存是连续的。
- (8)命令行参数:存储进程执行时传递给main函数的参数。
- (9)环境变量: 存储和进程相关的环境变量,比如:工作路径,进程所有者等信息。
exec族系统调用
exec族系统调用用于执行一个可执行程序,执行成功后不会返回,因为执行该系统调用的进程,虚拟内存空间的用户区(包括代码段、数据段、堆、栈等)都被新内容替代。我们一般在子进程中调用 exec 族函数,子进程的用户区被替换掉开始执行新程序中的代码逻辑,而父进程不受任何影响仍然可以继续正常工作。
// path:新的可执行程序的路径,推荐使用绝对路径
// arg:使用命令ps aux查看进程信息时,启动的进程的名字。可以随意指定,一般和可执行程序名相同
// ...:要执行的命令需要的参数,可以写多个,最后以NULL结尾,表示参数指定完毕。
int execl(const char* path, const char* arg, ...);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
int main()
{
// 创建子进程
pid_t pid = fork();
// 子进程
if (pid == 0)
{
// 屏蔽代码块的方式
#if 0
// 磁盘上的可执行程序
execl("/bin/ps", "title", "aux", NULL); // execl("/bin/ps", "title", "a", "u", "x", NULL);
#else
// 已经设置了环境变量的可执行程序
execlp("ps", "title", "aux", NULL); // execl("ps", "title", "a", "u", "x", NULL);
#endif
// 如果以上函数调用失败,才会执行下面的代码
perror("execlp");
}
// 父进程
else if (pid > 0)
{
printf