LINUX系统进程管理

本文深入探讨了Linux系统中的进程管理,包括进程概述、进程描述、进程相关操作以及进程管理工具。进程作为操作系统分配和管理系统资源的基本单位,由进程控制块(PCB)、程序段和操作数据集构成。在Linux中,通过tast_struct结构来管理进程,包含进程标识符、调度信息、通信信息等多个方面。此外,文章介绍了进程创建(如fork和exec函数族)、等待和结束,以及各种进程管理工具如top、htop、ps等的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、进程概述

进程是计算机系统中最重要的概念之一,在计算机系统中并行运行着大量的程序,这些程序不可能独占系统的全部资源,而是需要这些程序共享系统资源,所以这些程序在运行时会产生一定的竞争关系。那在这种情况下,系统资源怎么分配?运行程序该如何管理呢?操作系统为了解决这一系列的问题引入了进程的概念,用来作为操作系统分配和管理系统资源的基本单位。所以进程的出现是为了让系统资源的利用更加合理,从而提高系统的运行效率。
定义进程,就避免不了提到另一个概念“程序”。二者有什么关联和区别呢?
进程是在系统中的一次执行过程,从创建到分配资源到销毁,是一个动态的执行过程。而程序更像是一种静态的执行过程, 程序是有限的指令集合,这些指令集合就是进程要去执行的内容。换而言之,程序是进程在执行过程中的所执行的活动,是一种行为规则,所以二者为相辅相成的关系。

二、进程描述

进程从构造的元素看,由三部分组成,进程控制块(process control block,简称PCB)、有关程序段、操作数据集。其中,程序控制块中保存了进程的相关信息,它标识和描述进程存在及相关特性的数据块,是进程全部属性的集合。系统为每个进程都设置了一个PCB,当创建一个进程时,系统首先创建其PCB,然后根据PCB中的信息,对进程实施有效的管理和控制。当进程完成其功能后,系统则释放PCB,进程也随之销毁。
PCB是进程中广义的称呼,根据系统的不同,PCB的定义和含义也随之不同。在Linux系统中,由一个名为tast_struct的数据结构来等同PCB的作用。Linux系统正是通过task_struct结构来对进程进行有效管理和控制的。同广义PCB大致相同,tast_struct大致包含以下内容:

1. 进程标识符信息

每个进程都有系统唯一的进程名称或标识符号。其中包括进程标识符(pid)、用户标识符(uid)、有效用户标识符(euid)、组标识符(gid)等一些信息。
Linux系统中,每个进程都有唯一的进程标识符(Process ID,PID),PID可作为进程唯一的身份认证信息,内核通过这个标识符来识别不同的进程,同时,进程标识符也是内核提供给用户程序的接口。PID存放在进程描述符的PID域中,被顺序编号,每创建一个新进程,其PID通常是前一个进程PID加1。
在32位系统上,PID是32位无符号整数,允许最大的PID号为32767(2^15 - 1)。其中一个PID(0)通常保留在"swapper"或"init"进程,当内核在系统中创建第32768个进程时,就必须重启闲置的PID号。而在64位系统中,PID的最大值远大于32位系统,具体的最大值取决于内核的配置和硬件支持,通常在百万级别。

2. 进程的调度信息

进程调度程序利用这些调度信息,控制进行的优先级,并运用合理的策略保证系统高效公平的运行。这些信息主要包括调度标志、调度策略、进程类型、进程优先级、进程状态。
Linux采用的是基于优先级的可抢占式的调度系统,所以为了选择一个进程运行,调度程序必须要考虑每个进程的优先级,也就是我们平时所说的nice值。
Linux采取了两种优先级,静态和动态优先级。静态优先级只针对实时进程,它由用户赋给实时进程,范围是1~99,调度程序不会改变它。动态优先级只应用于普通进程,实质上它是基本时间片与当前时期内剩余时间片之和,翻译过来就是他的优先级要根据系统调度程序来动态分配。
实时进程的静态优先级总是高于普通进程的动态优先级,所以对于普通进程只有处于运行状态中,且没有实时进程后,调度程序才开始运行普通进程。哪些是普通进程呢,一般指前台进程和可交互进程。

进程有如下几种状态:

状态 描述
R (TASK_RUNNING) 进程正在CPU上运行或正在运行队列中等待。
S (TASK_INTERRUPTIBLE) 进程处于休眠状态,等待某个条件的形成,一旦等待的条件成立,进程便会从该状态转化成就绪状态。
D (TASK_UNINTERRUPTIBLE) 类似于TASK_INTERRUPTIBLE,但不可以通过发信号的方式将其唤醒,只有它所等待的资源可用的时候他才会被唤醒。
T (TASK_STOPPED or TASK_TRACED) 进程已停止,通常是接收到信号SIGSTOP或者调试原因,这个暂停的进程也可以通过发送SIGCONT 信号让进程继续运行。
Z (EXIT_ZOMBIE) 僵尸状态,当子进程已经终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信息,此时子进程成为僵尸进程。
X (EXIT_DEAD) 死亡状态,进程被杀死,即进程的最终状态。 该状态为一个返回值,不会出现在状态列表中。

Linux对实时进程和普通进程是两种调度策略。
实时进程采用 SCHED_FIFO 和 SCHED_RR 两种调度算法。普通进程采用SCHED_OTHER策略。这里不深入讲解,感兴趣可以自行查阅。
经过调度算法处理后,进程状态的流程图如下。
进程状态流程图
在了解了基本的调度原理后,科普一下如何通过命令来手动更改进程优先级(nice值)。
在 Linux 中,可以使用 nice 值来设置进程的优先级。nice 值越低,进程的优先级越高。可以使用 renice 命令来调整正在运行的进程的 nice 值。
例如,创建一个优先级较高的进程:

nice -n -5 /path/to/your/program
# 这将以 -5 的 nice 值运行程序,使得进程的优先级较高。

对于正在运行的进程,可以使用 renice 命令来调整其优先级:

renice -5 PID
# 这里 PID 是进程的 ID,-5 是要设置的 nice 值。

3. 进程间通信信息

在多任务编程环境中,进程间必然需要进行通信来完成系统功能。Linux支持多种不同形式的进程间通信方式,如信号、管道,也支持SystemV进行进程间的通信,如信号量、消息队列和共享内存等方式。
PCB中主要有这些域与进程通信有关:

函数 描述
sig 信号处理函数,包括自定义的和系统默认的处理函数
blocked 进程所能接收信号的位掩码
sigmask_lock 信号掩码的自旋锁
semundo 进程信号量的取消操作队列,进程每操作一次信号量,都会生成一个对此操作的取消操作,这些属于统一进程的取消操作组成一个链表,当进程异常终止时,内核就会执行取消操作。
semsleeping 与信号量相关的等待队列,每一个信号量集合对应一个等待队列

4. 进程链接信息

Linux系统中,除了初始化进程init外,所有进程都有一个父进程,通过fork或者clone方式来创建子进程,创建的子进程中除了PID以及必要信息外,task_struct结构中的大部分信息都是从父进程拷贝过来的。所以每个进程task_struct中都保存了指向父进程和兄弟进程以及子进程的指针。所以该信息更像是记录进程血脉传承的信息。

5. 时间和定时器信息

内核需要记录进程创建的时间以及在其生命周期内消耗的CPU时间,CPU的耗时又分为用户态耗时和内核态耗时。每个时钟中断,内核都要更新当前进程的耗时时间。

6. 文件系统信息

进程会访问文件系统资源,例如打开或关闭文件。所以内核需要对进程使用的文件情况进行记录。task_struct中有两个数据结构用来描述文件信息:

/* 文件节点信息 */
struct fs_struct		*fs;

/* 文件描述符信息 */
struct files_struct		*files;

7. 虚拟内存信息

8.处理器特定信息

寄存器以及堆栈信息,用于异常情况的恢复。

三、进程的相关操作

这里讲的操作都是指代码层面的方法调用与使用,至于通过命令控制统一归类于“进程管理工具”,会在第四部分描述。

(一)进程标识符相关操作

1. 代码中获取PID

#include <iostream>
#include <unistd.h>
using namespace std;
int main(int argc, char const *argv[]) {
   
    pid_t pid = getpid();
    cout << "get id number = " << pid << endl;
    return 0;
}

运行结果:

nio@LT5CG7272399:~/Demo/linux_study/process_coding$ g++ pid.cpp -o pid
nio@LT5CG7272399:~/Demo/linux_study/process_coding$ ./pid 
get id number = 1250233

2. 读取PID文件
在Linux系统的"/var/run"路径下存放着许多名为*.pid的文件,这些文件是在新安装程序时产生的,内容只有一行,及该进程的进程号。
在这里插入图片描述
这些PID文件的作用是什么呢?PID文件是为了防止进程启动多个副本,只有获得了响应PID的写入权限的进程,才可以正常启动,并把自身的PID写入PID文件之中。PID文件位于固定路径"/var/run",并且文件名也是固定的(进程名字.pid)。
写了个简单的例子,通过PID文件来判断进程是否运行:

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
using namespace std;

static char* test_pid_file = "/var/run/acpid.pid";
static bool check_id(char* file);

int main(int argc, char const *argv[]) {
   
    // pid_t pid = getpid();
    // cout << "get id number = " << pid << endl;

    FILE* fp = fopen(test_pid_file, "w");
    if (fp) {
   
        fprintf(fp, "%d", getpid
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值