Linux进程实现原理:从创建到终止的全过程

在当今的计算机世界中,Linux 操作系统以其高度的稳定性、灵活性和开源特性而备受瞩目。而在 Linux 系统的运行背后,进程起着至关重要的作用。那么,究竟什么是 Linux 进程?它们是如何工作的?又有着怎样的关键原理支撑着整个 Linux 系统的高效运转呢?让我们一同踏上探索 Linux 进程原理的奇妙之旅。

一、概述

进程是执行中的程序,为提高 CPU 利用率和改善系统响应时间而出现。它是操作系统提供的一种抽象,通过并发执行来提高系统利用率。计算机的基本功能是计算,而进行计算的关键部件是 CPU,CPU 能够按照一定的顺序进行正确计算是在操作系统的控制之下完成的。操作系统对 CPU 进行管理的重要手段就是进程模型。进程是操作系统演化过程中的一个里程碑,由于进程的出现,人类希望的并发从理想变为了现实。从根本上说,进程出现的动机是人类渴望的并发。

进程就是进展中的程序,或者说进程是执行中的程序。一个程序加载到内存后就变为进程,即进程 = 程序 + 执行。单一操作员单一控制终端、批处理均存在效率低下的问题,即 CPU 使用率不高。为了提高 CPU 利用率,人们想起将多个程序同时加载到计算机里,并发执行。这些同时存在于计算机内存的程序就称为进程。进程让每个用户感觉到自己独占 CPU。

从物理内存的分配来看,每个进程占用一片内存空间。在物理层面上,所有进程共用一个程序计数器。而从逻辑层面上来看,每个进程可以执行,也可以暂时挂起让别的进程执行,之后又可以接着执行。这样,进程就需要某种办法记住每次挂起时自己所处的执行位置,所以每个进程有着自己的计数器,记录其下一条指令所在的位置。从时间上看,每个进程都必须往前推进。进程不一定必须终结,许多系统进程是不会终结的,除非强制终止或计算机关机。

进程模型的实现需要解决进程的存储和 CPU 在多个进程之间的交接或切换问题。操作系统通过内存管理解决进程存储问题,通过进程调度解决 CPU 交接或切换问题。进行多道编程的目的是提高计算机 CPU 的效率和改善系统响应时间。多道编程带来的好处与每个程序的性质、多道编程的度数、进程切换消耗等均有关系。但一般来说,只要度数适当,多道编程总是利大于弊。

进程的产生主要有系统初始化、执行进程创立程序、用户请求创立新进程等事件。进程的消亡可以分为寿终、自杀、他杀和因异常而被强行终结四种情况。

二、内核空间

⑴内核架构类型

宏内核简洁、性能好,把所有内核代码编译成一个二进制文件,运行在同一地址空间,Linux 和 Windows 是宏内核架构代表。

宏内核架构将所有核心模块集成在一起,各模块属于同一层次,功能较多,因此具有性能强大的优点。例如 Linux 内核走的是宏内核路线,主要包括进程调度、内存管理、设备驱动、文件系统、网络模块等。这种架构下,内核代码之间可以直接访问和调用,效率高、性能好。不过,宏内核的各模块之间耦合度较高,结构复杂,开发与维护困难。

微内核稳定性、实时性好,操作系统分为多个独立模块,通过消息完成交互,如 RT-Thread、FreeRTOS、UCOS 等嵌入式实时操作系统是微内核架构代表。

微内核架构中,内核仅保留少量模块(如 IPC 通信、内存管理等),其他功能进行模块化处理,转化成用户进程。用户进程利用消息机制与内核进行通信。这种高度模块化的设计使得微内核稳定性较好,但是由于用户进程需要频繁地与内核进行交互,性能较低。例如 iOS Darwin 使用微内核(Mach)和对应固件来支持不同的处理器平台,并提供操作系统原始的基础服务,内核态还有 BSD 系统为上层的功能性系统提供服务和工具,可见 Darwin 为混合内核架构。Mach 内核提供简单的进程、线程、IPC 通信、虚拟内存设备驱动相关的功能服务,BSD 则提供强大的安全特性,完善的网络服务,各种文件系统的支持,同时对 Mach 的进程、线程、IPC、虚拟内核组件进行细化、扩展延伸。

⑵Linux 进程原理

进程定义为计算机中已运行的程序,是程序的真正运行实例,进程由程序段、数据段和进程控制块组成。

操作系统作为硬件的使用层,提供使用硬件资源的能力,进程作为操作系统使用层,提供使用操作系统抽象出的资源层的能力。进程是指计算机中已运行的程序,进程本身不是基本的运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。进程由程序段、数据段和进程控制块组成。Linux 内核把进程称为任务(task),进程的虚拟地址空间分为用户虚拟地址空间和内核虚拟地址空间,所有进程共享内核虚拟地址空间,每个进程有独立的用户空间虚拟地址空间。

Linux 进程状态包括执行、就绪、阻塞等,多任务操作系统通过有效的进程调度策略实现多任务并行执行。

Linux 操作系统属于多任务操作系统,系统中的每个进程能够分时复用 CPU 时间片,通过有效的进程调度策略实现多任务并行执行。而进程在被 CPU 调度运行,等待 CPU 资源分配以及等待外部事件时会属于不同的状态。进程之间的状态关系:执行:该进程此刻正在执行。就绪:进程能够运行,但没有得到许可,因为 CPU 分配给另一个进程。调度器可以在下一次任务切换时选择该进程。阻塞:进程正在睡眠无法运行,因为它在等待一个外部事件。调度器无法在下一次任务切换时选择该进程。

Linux 上进程有多种状态,包括运行状态(TASK_RUNNING)、可中断睡眠状态(TASK_INTERRUPTIBLE)、不可中断睡眠状态(TASK_UNINTERRUPTBLE)、暂停状态(TASK_STOPPED)、僵尸状态(TASK_ZOMBIE)等。运行状态表示进程正在被 CPU 调度执行;可中断睡眠状态下,系统不会调度该进程执行,当系统产生一个中断或者释放了进程正在等待的资源或者进程收到一个信号,都可以唤醒进程转换到就绪状态;不可中断睡眠状态与可中断状态类似,但只能使用 wake_up 函数才能唤醒该进程;暂停状态当进程收到特定信号就会进入;僵尸状态是当进程停止时,其父进程还没有对其回收。

可以使用 ps 命令查看进程状态,ps 命令支持三种使用的语法格式:UNIX 风格,选项可以组合在一起,并且选项前必须有 “-” 连字符;BSD 风格,选项可以组合在一起,但是选项前不能有 “-” 连字符;GNU 风格的长选项,选项前有两个 “-” 连字符。ps 命令的常用参数众多,如 -A 显示所有进程、-a 显示一个终端的所有进程等。Linux 上进程有 5 种状态:运行(正在运行或在运行队列中等待)、中断(休眠中,受阻,在等待某个条件的形成或接受到信号)、不可中断(收到信号不唤醒和不可运行,进程必须等待直到有中断发生)、僵死(进程已终止,但进程描述符存在,直到父进程调用 wait4 () 系统调用后释放)、停止(进程收到 SIGSTOP,SIGSTP,SIGTIN,SIGTOU 信号后停止运行运行)。

Linux 进程有两种特殊形式:内核线程和用户线程,共享同一个用户虚拟地址空间的所有用户线程组成线程组。

Linux 进程有两种特殊的形式:没有用户虚拟地址空间的进程叫做内核线程;共享用户虚拟地址空间的进程叫做用户线程。共享同一个虚拟地址空间的所有用户线程叫线程组。Linux 内核中线程和进程区别不大,都是通过 task_struct 这个结构体定义的,调度机制也是相同的,统称为任务。

内核线程是由内核本身启动的进程,将内核函数委托给独立的进程与系统中其他进程并行执行,常常被称为守护进程。内核线程的作用有:周期性的将修改的内存页与页来源块设备同步(mmap 的文件映射);如果内存很少使用,则写入交换区;管理延时动作,deferred action;实现文件系统的事务日志。

用户线程是由用户程序创建和管理的线程,它在用户空间运行,受到进程的控制和限制。用户线程主要用于执行用户程序的业务逻辑,如计算、网络通信、图形界面等。用户线程不能直接访问操作系统的资源,需要通过系统调用来请求内核的帮助。

用户级线程和内核级线程有以下区别:

  • 内核支持线程是 OS 内核可感知的,而用户级线程是 OS 内核不可感知的。

  • 用户级线程的创建、撤销和调度不需要 OS 内核的支持,是在语言(如 Java)这一级处理的;而内核支持线程的创建、撤消和调度都需 OS 内核提供支持,而且与进程的创建、撤消和调度大体是相同的。

  • 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。

  • 在只有用户级线程的系统内,CPU 调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU 调度则以线程为单位,由 OS 的线程调度程序负责线程的调度。

  • 用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。

三、Linux内核进程

Linux 内核进程通过 task_struct 结构体定义,调度机制与进程类似,统称为任务。

Linux 内核中的进程和线程都是通过 task_struct 结构体定义的,其调度机制也类似,因此统称为任务。task_struct 结构体包含了丰富的信息,如进程的状态、优先级、程序计数器、内存指针等,这些信息对于内核进行进程调度和管理至关重要。内核线程由内核本身启动,作用包括同步内存页与块设备、写入交换区、管理延时动作和实现文件系统事务日志等。

内核线程是直接由内核启动的进程,通常也被称为守护进程。其作用主要有以下几个方面:

  • 周期性地将修改的内存页与页来源块设备同步。例如,当使用 mmap 的文件映射时,内核线程负责确保内存中的数据与块设备中的数据保持一致。

  • 如果内存页很少使用,则写入交换区。这有助于释放内存空间,提高系统的性能。

  • 管理延时动作。内核线程可以在特定的时间间隔执行某些任务,或者在满足特定条件时采取行动。

  • 实现文件系统的事务日志。确保文件系统的操作能够被可靠地记录和恢复,提高文件系统的稳定性和可靠性。

内核线程按照工作方式可以分为两种类型:一种是线程启动后一直在等待,直到内核请求线程执行某一特定操作;另一种是线程启动后按周期性间隔运行,检测特定资源的使用等操作,在必要时采取一些行动。

内核线程采用 kernel_thread 函数创建,接收的参数有线程执行的函数指针、传递给执行函数的参数以及用于创建进程的标志。这个函数最后的调用是创建一个进程,即调用 do_fork 函数。创建内核线程时,会设置一些标志,如 CLONE_VM 表示不用复制进程的页表,共享地址空间;CLONE_UNTRACED 表示内核不会被其它进程追踪。

在 Linux 上,地址空间被分成两个部分,内核线程不是用户进程,也不与用户进程相关联,所以在内核进程调度时,不需要切换其地址空间。因此内核线程的地址空间是随机的,为使得内核线程不访问用户空间部分,将内核线程的 mm 置为空指针,另设一个 active_mm 指针来保存前一个进程的 mm_struct 实例。但当内核调度完成时,需要将 active_mm 也断开与上一个进程的联系。

四、进程创建与父子关系

Linux 进程创建有进程树,系统启动时内核先创建 init 进程,init 进程负责后续用户空间管理工作,包括创建子进程。在 Linux 系统启动过程中,内核首先创建 init 进程。这个 init 进程具有特殊的地位,它承担着后续用户空间管理的重要任务。其中,创建子进程就是其关键职责之一。Init 进程如同大树的根基,为整个系统的进程架构奠定基础。

父子关系明确,init 进程由内核创建,其他进程都是由 init 进程或其子进程创建。Init 进程是由内核创建的,它是整个系统进程树的起点。从 init 进程开始,通过一系列的进程创建操作,如 fork () 系统调用等,产生了其他的进程。这些进程要么直接由 init 进程创建,要么由 init 进程的子进程进一步创建,从而形成了清晰的父子关系。就像家族树一样,每一个进程都有其明确的父进程,这种关系对于进程管理和资源分配至关重要。

父进程通过 fork () 系统调用创建子进程,并可通过 clone () 克隆自身数据给子进程,子进程创建后与父进程共享内存空间,采用写时复制机制。

在 Linux 中,父进程通常使用 fork () 系统调用创建子进程。同时,还可以使用 clone () 函数来更加灵活地控制子进程的创建。当子进程被创建后,它会与父进程共享一部分内存空间,这种共享采用了写时复制(Copy-on-Write)机制。这意味着在子进程没有对共享内存进行写操作之前,父子进程实际上是共享同一块物理内存页面。一旦子进程尝试修改共享内存中的数据,就会触发写时复制机制,为子进程分配独立的内存页面,并将需要修改的数据复制到新的页面中,从而保证父进程的数据不受影响。

例如,在使用 fork () 创建子进程的代码中,父进程和子进程在创建后会分别执行不同的代码路径,通过判断 fork () 的返回值来区分父子进程。如果返回值小于 0,则表示创建失败;如果返回值为 0,则是子进程;如果返回值大于 0,则是父进程,这个返回值就是子进程的 PID。除了 fork (),vfork 和 clone 也是常见的创建子进程的方式。vfork 创建的子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改都为父进程所见,并且父进程会被阻塞,直到子进程执行 exec () 或 exit ()。

clone 函数功能强大,带了众多参数,可以有选择性地继承父进程的资源,可以创建出像 vfork 一样和父进程共享虚存空间的线程,也可以不共享,甚至可以创建出和父进程不再是父子关系,而是兄弟关系的进程。clone、fork、vfork 实现方式大致相同,最终都是调用 do_fork 函数完成,区别在于 clone 会将 parent_tidptr、child_tidptr 传到 do_fork 的参数中,而 fork 和 vfork 的 clone_flags 不同。

五、Linux进程工作机制

启动过程涉及 BIOS 引导、启动扇区加载、内核初始化、用户空间初始化和系统运行级别设置。

  • BIOS 引导:Linux 的启动始于 BIOS,这是安装在 PC 主板上的固件,负责在计算机启动时进行硬件级别的初始化工作。BIOS 程序会通过特定的启动顺序尝试加载并执行位于特定位置的引导装载程序。

  • 启动扇区加载:一旦 BIOS 成功地将控制权交给引导装载程序,后者便会尝试从指定的启动扇区(通常是磁盘的第一扇区)加载 Linux 内核的引导程序。这一步通常涉及到将磁盘的第一扇区加载到内存中。

  • 内核初始化:引导程序接着会加载 Linux 内核,并进行初始化。这个过程包括内存映射、中断向量表设置、系统调用和进程控制等关键任务的设置。

  • 用户空间初始化:内核初始化完成后,系统会创建一个初始的进程(通常是进程号为 1 的 init 进程),该进程负责进一步初始化系统并启动其他必要的服务。

  • 系统运行级别设置:Linux 可以运行在不同的运行级别,这些级别定义了系统的运行状态和可访问性,如单用户模式、多用户模式等。

运行过程中内核和用户空间程序协同工作,通过进程管理、内存管理、文件系统、设备驱动程序和网络协议栈等提供系统服务。

  • 进程管理:Linux 内核负责进程的创建、执行、暂停和终止。它还负责进程间的通信和信号处理。

  • 内存管理:Linux 内核负责内存的分配和回收,以及虚拟内存的映射。它通过页表和段表来管理内存的访问权限和物理地址映射。

  • 文件系统:Linux 的文件系统为用户提供了一个层次化的文件访问接口。内核负责管理文件和目录的创建、删除、移动等操作,以及提供读写文件的能力。

  • 设备驱动程序:Linux 支持广泛的硬件设备,并通过设备驱动程序与硬件进行通信。驱动程序负责设备的初始化和数据的读写。

  • 网络协议栈:Linux 提供了完整的网络协议栈,支持多种网络协议和网络设备。网络协议栈负责将数据封装成数据包,并确保数据包能够准确地在网络上传输。

关闭过程包括系统挂起、文件系统卸载和内核卸载。

  • 系统挂起:当用户想要关闭系统时,会执行相应的命令(如 shutdown 或 halt),系统会进行清理工作并挂起当前运行的进程。

  • 文件系统卸载:系统会将所有挂载的文件系统卸载,释放占用的资源。

  • 内核卸载:最后,系统会卸载内核,并返回到启动扇区,等待下一次启动。

六、常见进程管理命令

⑴ps 命令用于确定进程运行状态等信息

ps 命令是 Linux 中强大的进程查看工具。它列出系统中当前运行的进程信息,包括进程号、命令、CPU 使用量、内存使用量等。ps 命令支持三种使用的语法格式:UNIX 风格,选项可以组合在一起,并且选项前必须有 “-” 连字符;BSD 风格,选项可以组合在一起,但是选项前不能有 “-” 连字符;GNU 风格的长选项,选项前有两个 “-” 连字符。常用参数众多,如 -A 显示所有进程、-a 显示一个终端的所有进程等。Linux 上进程有 5 种状态:运行(正在运行或在运行队列中等待)、中断(休眠中,受阻,在等待某个条件的形成或接受到信号)、不可中断(收到信号不唤醒和不可运行,进程必须等待直到有中断发生)、僵死(进程已终止,但进程描述符存在,直到父进程调用 wait4 () 系统调用后释放)、停止(进程收到 SIGSTOP,SIGSTP,SIGTIN,SIGTOU 信号后停止运行运行)。

⑵top 命令是性能分析工具,实时显示进程资源占用状况

top 命令以动态方式查看进程状态,执行后可以通过键盘输入特定命令查看特定信息,如 u 查询某个用户的进程、P 查询哪个进程占用 CPU 最高、M 查询哪个进程占用内存最高等。输出结果分为统计信息区和进程信息区,可以显示系统当前时间、机器运行时长、系统平均负载、cpu 的运行负载、内存的使用率、IO 的负载等信息。

⑶pgrep 命令查看指定进程

pgrep 命令以关键字查看进程(模糊匹配)。例如,pgrep bash 查看包含 bash 关键字的进程,仅列出进程 PID;pgrep -l bash 查看包含 bash 关键字的进程,列出进程 PID 和进程命令

七、系统调用

⑴系统调用的类别

系统调用大致可分为以下几类:

  • 进程控制:用于创建和管理进程,常见的有 fork、exit 等。例如,fork 创建新进程,exit 用于进程终止。

  • 文件管理:用于读写文件,如 open、read、write 等。通过这些系统调用,程序可以打开、读取、写入和关闭文件,实现对文件的操作。

  • 设备管理:用于管理和控制设备,如请求设备、释放设备、读写设备及获取 / 设置设备属性等。

  • 信息维护:获取和设置系统数据,例如 getpid () 可以获取进程的 ID,time () 可以获取当前的系统时间。

  • 通信:处理进程间的通信,如 IPC 机制中的信号、管道、消息队列、共享内存、信号量等可使用这类系统调用。

  • 内存管理:管理内存资源,像 brk、sbrk 等函数可以用于改变程序的数据段大小,即分配或释放内存。

  • 网络管理:进行网络通信,例如 socket ()、bind ()、connect ()、listen ()、accept () 等用于网络通信的系统调用。

⑵安全模型

除了一些嵌入式系统外,大多数现代处理器的架构都涉及到安全模型。例如,环形模型指定了软件可以执行的多个权限级别:一个程序通常被限制在自己的地址空间内,这样它就不能访问或修改其他运行中的程序或操作系统本身,并且通常被阻止直接操作硬件设备(如帧缓冲区或网络设备)。但是,很多时候许多应用程序需要访问这些组件来完成自己的任务,因此操作系统就提供了系统调用,为这类操作提供定义良好的、实现安全的通信方式。

操作系统以最高级别的权限执行,允许应用程序通过系统调用请求使用服务,而系统调用通常是通过中断发起的。中断会自动使 CPU 进入某种高权限级别,然后将控制权传递给内核,由内核决定调用程序是否应该被授予所请求的服务。如果请求服务被允许的话,内核会执行一组特定的指令来完成任务。而调用程序对内核没有直接控制权,任务完成后将控制权返回给调用程序。

⑶程序库 API

一般来说,系统环境会提供了一个程序库来暴露一些 API,这些 API 可以在应用程序和操作系统之间完成通信任务。在类似 Unix 的系统中,这些 API 通常是 C 程序库库 (libc) 实现的一部分,例如 glibc,它为系统调用提供了封装函数,通常与系统调用的名称相同。在 Windows NT 上,这个 API 是 Native API 的一部分,在 ntdll.dll 库中;这是一个未公开发行的 API,被常规 Windows API 的实现所使用,也被 Windows 上的一些系统程序直接使用。

该库的封装函数层提供了一个普通函数调用的方法(是汇编级的子程序调用),用于使用系统调用,这也使得系统调用更加模块化。在这里,封装层的主要功能是将所有要传递给系统调用的参数都放在相应的处理器寄存器中(有时也可以放在调用栈中),同时也为内核设置一个唯一的系统调用编号来调用。这样一来,存在于操作系统和应用程序之间的库就增加了可移植性。对库函数本身的调用不会导致切换到内核模式,通常是正常的子程序调用(例如,在某些指令集架构(ISA)中使用 “CALL” 汇编指令)。

实际的系统调用确实将控制权转移到了内核(而且这比抽象的库调用更依赖于具体的实现和平台环境)。例如,在类似 Unix 的系统中,fork 和 execve 是 C 库函数,这些函数反过来调用 fork 和 exec 这些系统调用的指令。直接在应用程序代码中进行系统调用比较复杂,可能需要使用嵌入式汇编代码 (在 C 和 C++ 中),并且需要了解系统调用操作的底层二进制接口,而这些二进制接口可能会随着时间的推移而变化,它们不是应用程序二进制接口的一部分,而程序库函数的作用就是为了抽象出这些逻辑而创建的。

在基于 exokernel 的系统中,库作为中介的作用尤为重要。在 exokernels 上,库将用户应用屏蔽在非常低级的内核 API 中,并提供抽象层和资源管理。由 OS/360 和 DOS/360 衍生出来的 IBM 操作系统,包括 z/OS 和 z/VSE,都是通过汇编语言宏程序库来实现系统调用的。它们的起源于汇编语言编程比高级语言更普遍的年代。因此,IBM 的系统调用不能被高级语言程序直接调用,而是需要一个封装的可调用的汇编语言子程序。

⑷系统中的示例和检测工具

在 Unix、Unix-like 和其他兼容 POSIX 的操作系统上,常用的系统调用有 open、read、write、close、wait、exec、exc、fork、exit 和 kill。许多现代操作系统都有数百个系统调用。例如,Linux 和 OpenBSD 各有 300 多个不同的调用,NetBSD 有近 500 个,FreeBSD 有 500 多个,Windows 7 有近 700 个,而 Plan9 有 51 个。

有些工具如 strace、ftrace 和 truss 等可以从进程一开始就跟踪报告该进程调用的所有系统调用,或者可以把这些工具绑定附加到一个已经运行的进程上来跟踪其进程调用情况。只要该追踪操作不违反用户的权限,就可以拦截该进程所做的任何系统调用。这些程序工具的这种特殊能力通常也是通过系统调用来实现的,例如 strace 是通过 ptrace 或 procfs 中的文件的系统调用来实现。

⑸进程系统调用

fork 和 exec 系列系统调用的实现:

  • 在传统 UNIX 中,有 fork、vfork 和 clone 等系统调用用于创建新进程。fork 创建一个与自己完全一样的新进程;vfork 主要用于创建子进程时优化资源使用;clone 则提供了更灵活的进程创建方式,可以指定共享的资源等。

  • 写时复制技术在进程创建中起到了重要作用。当使用 fork 创建新进程时,并不立即复制父进程的地址空间,而是父子进程共享地址空间。只有当子进程需要写入数据时,才会复制地址空间,从而提高了效率。

⑹内核线程

内核线程由内核启动,用于执行特定任务。内核线程与普通进程不同,它们运行在内核空间,直接由内核管理和调度。内核线程通常用于执行一些系统级的任务,如设备驱动程序、文件系统管理等。

⑺退出进程

进程用 exit 系统调用终止,内核释放资源。当一个进程调用 exit 系统调用时,内核会进行一系列的清理工作,包括释放进程占用的内存、关闭打开的文件、清理进程的内核数据结构等。然后,内核将资源归还给系统,以便其他进程可以使用。

八、进程原理与系统调用的关系

进程的创建、管理和调度等操作都依赖于系统调用,系统调用为进程提供了与操作系统交互的接口,实现了进程对各种资源的访问和控制。

进程的创建需要借助系统调用,如在 Linux 内核中,创建一个新进程主要依赖于 fork ()、clone () 等系统调用,最终由内核中的 do_fork () 函数完成。用户进程调用 fork ()、vfork () 或 clone () 等接口会触发进入内核态,通过系统调用号定位到相应的内核函数,如 sys_clone。do_fork () 函数接收用户态的参数并进行处理,包括检查参数、分配 task_struct、复制父进程资源、分配 PID、设置调度信息等步骤,最终完成新进程的创建并将其加入可运行队列。

进程的管理也离不开系统调用。操作系统需要为进程创建记录,维护进程记录的结构是进程表或进程控制块。进程控制块中通常包括寄存器、程序计数器、状态字、栈指针、优先级、进程 ID、信号、创建时间、所耗 CPU 时间、当前持有的各种句柄等信息。进程的创建过程包括分配进程控制块、初始化机器寄存器、初始化页表、将程序代码从磁盘读进内存、将处理器状态设置为用户态、跳转到程序的起始地址等步骤,这些步骤都涉及到系统调用。

进程的调度同样依赖于系统调用。进程调度定义在多进程并发环境下,决定在什么时候让什么进程使用 CPU。不同系统的调度目标略有不同,但都需要通过系统调用来获取进程的状态信息、调整进程的优先级、进行进程的切换等操作。例如,Linux 提供了一个系统调用族,用于管理与调度程序相关的参数,包括设置和获取进程的调度策略和实时优先级、设置和获取进程的实时优先级、返回给定调度策略的最大和最小优先级、调整进程的静态优先级等。

原文地址:https://mp.weixin.qq.com/s/4r9XxcOQFqBaoA-RenKv7Q