fork,execve,_exit从第一个程序到所有程序

操作系统启动后到底做了什么

  • CPU Reset → Firmware → Loader → Kernel _start() → 第一个程序 /bin/init → 程序 (状态机) 执行 + 系统调用

  • 操作系统会加载 “第一个程序”

    • 寻找启动程序代码

    • if (!try_to_run_init_process("/sbin/init") ||
      	    !try_to_run_init_process("/etc/init") ||
      	    !try_to_run_init_process("/bin/init") ||
      	    !try_to_run_init_process("/bin/sh"))
      		return 0;
      
      	panic("No working init found.  Try passing init= option to kernel. "
      	      "See Linux Documentation/admin-guide/init.rst for guidance.");
      
    • linux中的pstree的systemd的来历

    • 在这里插入图片描述

fork()

  • 操作系统:状态机的管理者
    • C 程序 = 状态机
      • 初始状态:main(argc, argv)
      • 程序可以直接在处理器上执行
    • 虚拟化:操作系统在物理内存中保存多个状态机
      • 通过虚拟内存实现每次 “拿出来一个执行”
      • 中断后进入操作系统代码,“换一个执行”
  • int fork();
    • 立即复制状态机 (完整的内存)
    • 新创建进程返回 0
    • 执行 fork 的进程返回子进程的进程号
  • 因为状态机是复制的,因此总能找到 “父子关系”
    • 因此有了进程树 (pstree)
    • 在这里插入图片描述

execve()

  • 状态机管理:替换状态机
    • int execve(const char *filename, char * const argv, char * const envp);
      • 执行名为 filename 的程序
      • 允许对新状态机设置参数 argv (v) 和环境变量 envp (e)
      • 刚好对应了 main() 的参数!
  • 环境变量:“应用程序执行的环境”
    • 使用env命令查看
      • PATH: 可执行文件搜索路径
      • PWD: 当前路径
      • HOME: home 目录
      • DISPLAY: 图形输出
      • PS1: shell 的提示符
    • export: 告诉 shell 在创建子进程时设置环境变量
  • _exit()
    • 状态机管理:终止状态机
      • void _exit(int status)
        • 销毁当前状态机,并允许有一个返回值
        • 子进程终止会通知父进程
    • 结束程序执行的三种方法
      • exit(0) - stdlib.h 中声明的 libc 函数
        • 会调用 atexit(清理空间,安全退出)
      • _exit(0) - glibc 的 syscall wrapper
        • 执行 “exit_group” 系统调用终止整个进程 (所有线程)
        • 不会调用 atexit
      • syscall(SYS_exit, 0)
        • 执行 “exit” 系统调用终止当前线程
        • 不会调用 atexit
  • 程序的创建执行和销毁过程
    系统初始化的程序,通常是 init(在一些现代系统如 Fedora、Ubuntu 上通常是 systemd),负责进一步初始化操作系统并启动其他服务或进程。下面,我们将详细探讨在操作系统启动第一个程序后,如何使用 fork, execve, _exit 来创建、执行和销毁程序的过程。

程序的创建、执行和销毁

  1. 初始化和启动首个进程

    • 操作系统通过加载并执行 init 程序(或者在一些系统中是 systemd)开始。这个程序成为系统中的第一个进程(通常是进程号为1)。
  2. 进程的创建 (使用 fork)

    • 当系统需要创建一个新的进程时,init(或任何正在运行的进程)会调用 fork() 系统调用。fork() 创建一个与父进程几乎完全相同的子进程,拥有相同的内存映像和运行状态,但有一个新的唯一进程标识符。
    • 父进程中 fork() 返回新创建的子进程的进程ID,而在子进程中 fork() 返回0。
  3. 进程的执行 (使用 execve)

    • 通常在 fork() 之后,子进程需要运行与父进程不同的代码。为此,子进程会调用 execve() 系统调用,这个调用加载一个新的程序到当前进程的地址空间,并开始执行这个程序,从其 main 函数开始。
    • execve() 需要指定程序的路径、传递给程序的参数列表(argv),以及环境变量列表(envp)。这意味着执行后,子进程的原有代码和数据将被新程序替换。
  4. 进程的终止 (使用 _exit)

    • 当程序执行完成后,它可以通过调用 _exit() 系统调用来终止。这个调用立即结束进程的执行,并将一个状态码返回给操作系统,这个状态码可以被父进程通过 wait() 系列的调用来检索。
    • 使用 _exit() 而不是标准的 exit() 函数,因为后者还会执行标准库注册的各种清理函数(如由 atexit() 注册的函数),这在某些情况下可能不是必需或期望的。

实例:简单的 shell

假设 init 系统已经启动,并且我们需要从一个简化的 shell 启动一个程序,如 ls 命令。以下是这个过程的概述:

  1. Shell 进程调用 fork(),创建一个子进程。
  2. 子进程使用 execve() 调用来加载并运行 ls 命令。
  3. ls 命令执行完成后,子进程调用 _exit() 来结束执行,返回执行结果状态。
  4. Shell 进程使用 wait() 等待子进程结束,并获取其终止状态。

这样,我们就看到了一个完整的程序生命周期:创建、执行和终止,都是通过操作系统提供的系统调用来管理的。

  • 37
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

poison_Program

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值