3. 进程管理

创建进程

在Linux中创建进程通常有两个目的:

  • 将同一个程序分成多个进程进行处理(例如,使用Web服务器接收多个请求)
  • 创建另一个程序(例如,从bash启动一个新的程序)

为了达成这两个目的,Linux提供了fork()函数与execve()函数(其底层分别请求名为clone()与execve()的系统调用)

fork函数

fork函数可以让同一个程序分成多个进程进行处理。在调用fork()函数后,就会基于发起调用的进程,创建一个新的进程。发出请求的进程称为父进程,新创建的进程称为子进程

创建新进程的流程如下:

  1. 为子进程申请内存空间,并复制父进程的内存到子进程的内存空间

  2. 父进程与子进程分裂成两个进程,以执行不同的代码。实现依赖于fork()函数分别返回不同的值给父进程和子进程。

  • fork()会返回0给子进程
  • fork()会返回新创建的子进程id给父进程

execve函数

启动另一个程序的流程:

  1. 读取可执行文件,并读取创建进程的内存映像所需的信息
  • 包括代码的代码段在文件中的偏移量,大小,以及内存映像的起始地址
  • 包含代码以外的变量等数据的数据段在文件中的偏移量,大小,以及内存映像的起始地址
  • 程序执行的第一条指令的内存地址(入口点)
  1. 用新进程的数据覆盖当前进程的内存
    基于读取的信息,将程序映射到内存上。

  2. 从最初的命令开始运行新的进程

在启动另一个程序时,并非新增一个进程,而是替换了当前进程

Linux的可执行文件的结构遵循的是ELF(Executable and Linkable Format)的格式,其相关信息可以通过readelf命令来获取。

  • -h选项:可以获取起始地址

  • -S选项:可以获取代码段与数据段在文件中的偏移量,大小和起始位置

    • 输出的数据每两行为一组
    • 全部数值皆为十六进制数
    • 在每组的第一行的第2个字段中,.text对应的是代码段的信息,.data对应的是数据段的信息
    • 在这些信息中,对于每组输出,只需要关注以下内容即可:
      • 每组的第1行的第4个字段 address:内存映像的起始地址
      • 每组的第1行的第5个字段 offset:在文件中的偏移量
      • 每种的第2行的第一个字段 size: 大小

# 获取/bin/sleep的elf信息
readelf -h /bin/sleep

在程序运行时创建的进程的内存映像信息,可以从/proc/{pid}/maps这个文件中找到。

在打算新建一个别的进程时,通常采用被称为fork and exec的方式,即由父进程调用fork()创建子进程,再由子进程调用exec()。

结束进程

可以使用_exit()函数(底层发起exit_group()系统调用)来结束进程。在进程运行结束后,所有分配给进程的内存将被回收。

不过,通常我们很少会直接调用_exit()函数,而是通过调用C标准库的exit()函数结束进程的运行。

posted @ 2024-07-10 14:17  Python习者  阅读(4)  评论(0)    收藏  举报