进程管理

本文最后更新于:2024年8月28日 中午

进程管理

ELF

ELF加载

重定位

上下文切换

进程与线程

fork

内核线程

kernel_thread

session和进程组

在 Unix 操作系统中,进程组标识符(PGID,Process Group Identifier)和会话标识符(SID,Session Identifier)是用于组织和管理进程的两个不同的标识符。它们的主要区别在于它们所关联的层次结构和范围。

进程组标识符(PGID):

  • 定义: 进程组是一个或多个进程的集合,每个进程组都有一个唯一的进程组标识符(PGID)。
  • 关系: 进程组用于将一组相关的进程组织在一起。这些进程可以共享一些控制终端,以便它们可以在终端上进行协同工作。
  • 创建: 使用系统调用 setpgid 可以创建新的进程组。

会话标识符(SID):

  • 定义: 一个会话是一个或多个进程组的集合,每个会话都有一个唯一的会话标识符(SID)。
  • 关系: 会话是更高层次的组织单位,它包含一个或多个进程组。一个会话通常对应于一个用户登录到系统的会话,但也可以通过 setsid 系统调用创建一个新的会话。
  • 创建: 使用系统调用 setsid 可以创建新的会话。

总结来说,PGID 主要用于将进程组内的进程进行组织,使它们可以在同一终端上进行协同工作。而 SID 更高层次,它是一个会话的标识符,一个会话可以包含多个进程组,通常对应于用户登录会话。在创建新会话时,一个进程会成为新会话的首进程,并且新的会话会包含一个新的进程组。

内核栈

进程调度

CFS调度器时间片

1
2
3
4
5
6
7
8
9
10
11
12
// kernel/sched/core.c

const int sched_prio_to_weight[40] = {
/* -20 */ 88761, 71755, 56483, 46273, 36291,
/* -15 */ 29154, 23254, 18705, 14949, 11916,
/* -10 */ 9548, 7620, 6100, 4904, 3906,
/* -5 */ 3121, 2501, 1991, 1586, 1277,
/* 0 */ 1024, 820, 655, 526, 423,
/* 5 */ 335, 272, 215, 172, 137,
/* 10 */ 110, 87, 70, 56, 45,
/* 15 */ 36, 29, 23, 18, 15,
};

kernel/sched/core.c 文件中的 sched_prio_to_weight 数组定义了不同优先级(nice 值)对应的权重。权重决定了进程在调度中的相对重要性。nice 值越低,进程的优先级越高,对应的权重越大;nice 值越高,优先级越低,对应的权重越小。

数组中包含 40 个元素,分别对应 nice 值从 -20 到 19。数组中的值是这些优先级对应的权重,权重值越大,表示进程在调度中获得的 CPU 时间越多。

例如,sched_prio_to_weight[0] 对应 nice 值 -20,权重为 88761;sched_prio_to_weight[20] 对应 nice 值 0,权重为 1024;sched_prio_to_weight[39] 对应 nice 值 19,权重为 15。

这是一个常用的 CFS 调度权重表,用来计算进程的虚拟运行时间(vruntime)。具体来说,虚拟运行时间的计算公式是:

\[ \text{虚拟运行时间} = \text{实际运行时间} \times \frac{\text{基准权重}}{\text{当前进程的权重}} \]

其中基准权重一般取 sched_prio_to_weight[20](即 nice 值为 0 的权重 1024)。通过这种方式,不同优先级的进程会按照权重比例公平地分配 CPU 时间。

举例说明

假设有两个进程 A 和 B,分别具有以下 nice 值和权重:

  • 进程 A:nice 值为 -10,对应的权重为 9548
  • 进程 B:nice 值为 10,对应的权重为 110

假设这两个进程分别运行了 10 毫秒,虚拟运行时间的计算如下:

  • 进程 A 的虚拟运行时间: \[\text{vruntime}_A = 10 \text{ms} \times \frac{1024}{9548} \approx 1.07 \text{ms}\]

  • 进程 B 的虚拟运行时间: \[\text{vruntime}_B = 10 \text{ms} \times \frac{1024}{110} \approx 93.09 \text{ms}\]

可以看到,虽然进程 A 和 B 实际运行时间相同,但由于进程 B 的权重较小,它的虚拟运行时间增加得更快。因此,在 CFS 的调度策略下,进程 B 在下次调度时将被认为已经消耗了更多的 CPU 时间,从而更倾向于调度进程 A 以确保整体的公平性。

进程优先级

什么是进程优先级?

Linux进程的优先级是用来确定在多个进程同时运行时,哪个进程会获得更多的CPU时间片。

Linux进程的优先级分为实时优先级和普通优先级。

实时优先级用于实时应用程序,如硬实时任务和实时控制系统,而普通优先级用于非实时应用程序。

进程优先级原理
动态优先级:0-139

实时进程:动态优先级为0-99的进程,采用实时调度算法调度。

普通进程:动态优先级为100-139的进程,采用完全公平调度算法调度。

nice值:是用于调整普通进程优先级的参数。范围:-20-19。

prio(动态优先级)

  • 动态优先级,有效优先级,调度器最终使用的优先级数值,范围0-139,值越小,优先级越高。

static_prio(静态优先级)

  • 静态优先级,采用SCHED_NORMAL和SCHED_BATCH调度策略的进程(即普通进程)用于计算动态优先级的,范围100-139。

  • prio = static_prio = nice + DEFAULT_PRIO = nice + 120。

normal_prio(归一化优先级)

  • 用于计算prio的中间变量,不需要太关心。

rt_priority(实时优先级)

  • 实时优先级,采用SCHED_FIFO和SCHED_RR调度策略进程(即实时进程)用于计算动态优先级,范围0-99。

  • prio = MAX_RT_PRIO - 1 - rt_prio = 100 - 1 - rt_priority;

  • 实时优先级数值越大,得到的动态优先级数值越小,优先级越高。

20240802161019
查看进程优先级
top中的PR和NI

top中的PR表示优先级,但是跟上述的值不是直接对等的。在top中,实时优先级的[0,99]没有具体的表示,统一用RT来表示。

而静态优先级和top中的优先级关系为:

  • top_PR = static_prio - 100

也就是说,top中的PR取值为[0,39],对应图1的优先级[100,139]

top中的NI表示nice等级,nice的取值为[-20,19],对应图1中的优先级为[100,139],也就是说nice等级可以改变用户进程(非实时进程的优先级)。

在top界面中,输入r即可启动nice系统,先输入进程id,回车后再输入nice等级即可修改。

top中的PR和NI
ps -elf 命令

执行ps -elf 命令查看进程优先级

PRI:进程优先级,数值越小,优先级越高。(并非动态优先级)

NI:nice值。

这里的PRI与图1 中的优先级关系为:

  • ps_PRI = static_prio - 40

这边PRI的取值范围为[-40,99],也就是说,ps中PRI值为80等价于nice值为0,等价于静态优先级的120。

执行ps -elf 命令查看进程优先级

进程状态

僵尸进程
孤儿进程

文件描述符

信号

进程间通信

System V IPC与POSIX IPC的区别

System V IPC(Inter-Process Communication)和 POSIX IPC 都是用于进程间通信的机制,但它们在实现方式和接口上有一些区别。

实现方式:

  • System V IPC 是早期 UNIX 系统引入的进程间通信机制,包括共享内存(Shared Memory)、消息队列(Message Queues)和信号量(Semaphores)。它们是通过系统调用(如 shmget、msgget、semget 等)来实现的。

  • POSIX IPC 是在 IEEE POSIX 标准中定义的进程间通信机制,包括命名管道(Named Pipes)、消息队列(Message Queues)、信号量(Semaphores)和共享内存(Shared Memory)。它们是通过标准的 POSIX 函数(如 mq_open、sem_open、shm_open 等)来实现的。

接口和使用:

  • System V IPC 的接口更底层,使用起来相对复杂,需要使用一系列的系统调用来创建和操作 IPC 对象。

  • POSIX IPC 提供了更高级的接口,使用起来更加简单和直观,通过标准的函数调用就可以完成大部分的操作。

跨平台性:

  • POSIX IPC 在设计上更加面向跨平台,因此具有更好的可移植性,在不同的 UNIX 系统上都有一致的接口和行为。

  • System V IPC 的接口和行为在不同的 UNIX 系统上可能会有一些差异,因此可移植性相对较差。

特性支持:

  • System V IPC 提供了一些额外的特性,如消息队列中可以指定消息的优先级,而 POSIX IPC 则没有这个特性。
  • POSIX IPC 提供了一些额外的安全特性,如命名对象的权限控制和更加严格的同步机制。

综上所述,System V IPC 和 POSIX IPC 在实现方式、接口和使用、跨平台性和特性支持等方面有一些区别,开发者可以根据具体需求和平台选择合适的 IPC 机制。

内核消息队列的实现

在内核中,消息队列是通过数据结构来实现的。一般情况下,消息队列的实现涉及到以下几个关键组件:

  • 消息队列结构(Message Queue Structure):

    • 内核中会维护一个或多个消息队列的结构,用于存储消息和管理消息队列的状态。这个结构通常包含队列的头指针、尾指针、消息数量、消息大小限制等信息。
  • 消息结构(Message Structure):

    • 每个消息都有自己的结构,在内核中会定义一个用于表示消息的结构体。这个结构体通常包含消息的内容、大小、发送者标识、接收者标识等信息。
  • 内核函数(Kernel Functions):

    • 内核会提供一系列函数,用于对消息队列进行操作,例如创建消息队列、删除消息队列、发送消息、接收消息等。这些函数会在用户态程序调用相应的系统调用时被调用,从而实现对消息队列的操作。
  • 同步机制(Synchronization Mechanism):

    • 为了保证多个进程对消息队列的并发访问不会出现冲突,内核中通常会使用一些同步机制来实现消息队列的互斥访问。例如,使用互斥锁、信号量或者其他的同步原语来保护对消息队列的操作,避免出现竞争条件。
  • 系统调用接口(System Call Interface):

    • 用户空间程序通过调用系统调用接口来访问内核提供的消息队列功能。例如,在 Linux 中,用户程序可以通过 msgget、msgsnd、msgrcv 等系统调用来创建、发送和接收消息。

综上所述,消息队列在内核底层是通过数据结构、内核函数、同步机制和系统调用接口等组件来实现的。内核会维护消息队列的状态并提供相应的接口供用户空间程序使用,从而实现进程间的通信。

互斥技术

percpu变量

信号量

mutex

RCU

死锁检测


进程管理
https://realwujing.github.io/linux/kernel/进程管理/
作者
Wu Jing
发布于
2023年4月19日
许可协议