进程(Process)定义:程序执行时的一个实例。(在自身的虚拟地址空间运行的一个单独的程序)
A process is an instance of a computer program that is currently being executed
源代码和生成的可执行程序(elf\exe)存放在硬盘(flash)中,进程位于内存中。
- 一个可执行程序可以对应多个进程实例,比如多个用户登录同一台Linux,都打开vim,此时就存在多个对应同一个可执行程序的独立进程。
- 一个可执行程序也可以启动多个进程(前面是多次启动,这里是一次启动),注意区分多线程,启动的多个进程都有独立的内存。
进程执行时,操作系统为它在内存中分配一块存储空间,包括该进程对应的代码正文、数据、堆栈(即指令+数据)。如果该进程需要处理(磁盘上的)文件,相应的数据也会在需要的时候加载到内存中(文件缓存)。
分类方式不唯一。我这里分为三种(守护进程和内核进程也可以统一称为系统进程):
用户进程(User Processes
):系统中的大多数进程都是用户进程。 用户进程是由常规用户帐户启动并在用户空间中运行的进程。 除非以赋予进程特殊权限的方式运行,否则普通用户进程对处理器或系统上不属于启动该进程的用户的文件没有特殊访问权限。
守护进程(Daemon Process
):守护进程是一种设计为在后台运行的应用程序,通常管理某种正在进行的服务。 守护进程可能会监听传入的访问服务的请求。 例如,httpd 守护进程侦听查看网页的请求。 或者,守护进程可能旨在随着时间的推移自行启动活动。 例如,crond 守护进程旨在按预设启动 cron 作业。
次。
内核进程(Kernel processes
):内核进程仅在内核空间中执行。 它们类似于守护进程。 主要区别在于内核进程可以完全访问内核数据结构,这使得它们更容易比在用户空间中运行的守护进程更强大。内核进程也不像守护进程那样灵活。 你可以通过更改配置文件和重新加载服务来更改守护进程的行为。 然而,更改内核进程可能需要重新编译内核。
常见守护进程(通常名称以d结尾:deamon
):
名称 | 作用 | 名称 | 作用 |
---|---|---|---|
sshd | SSH守护进程,用于远程登录和身份验证 | httpd (Apache) | 用于运行Web服务器的守护进程 |
mysqld (MySQL) | MySQL数据库服务器守护进程 | postgresql | PostgreSQL数据库服务器守护进程 |
cupsd | 通用Unix打印系统守护进程 | named (bind) | DNS服务器守护进程 |
ntpd | 网络时间协议守护进程 | crond (cron) | 定时任务守护进程 |
syslogd | 系统日志守护进程 | sendmail/postfix | 电子邮件传输代理守护进程 |
sssd | 系统安全服务守护进程 | nfsd | 网络文件系统(NFS)守护进程 |
dockerd (Docker) | Docker容器守护进程 | … | … |
常见内核进程:
名称 | 作用 | 名称 | 作用 |
---|---|---|---|
systemd(或init) | 初始化系统的第一个用户级进程,负责系统初始化 | kthreadd | 创建其他内核线程和执行初始化内核任务 |
kswapd | 交换内存管理,管理页面的交换和回收 | ksoftirqd | 处理软中断任务 |
kworker | 内核工作线程,执行各种内核任务,如I/O、调度、中断处理 | rcu_sched | 实现数据结构的并发访问 |
migration | 处理CPU任务迁移和负载均衡 | kdevtmpfs | 管理/dev目录中的设备节点 |
watchdogd | 监视系统的健康状态,并在出现问题时执行操作 | kerneloops | 记录和报告内核崩溃信息 |
内核进程通常在系统启动后的早期就运行了:
除此之外,为了便于理解,还有这些分类或叫法:
前台进程(Foreground Process):前台进程是用户当前正在与之交互的进程。通常,图形用户界面(GUI)应用程序的主窗口和用户正在使用的终端会运行在前台进程中。前台进程通常具有用户输入焦点,并响应用户的操作。
后台进程(Background Process):后台进程是在不干扰用户的前台工作的情况下运行的进程。它们通常用于执行不需要用户交互的任务,如文件传输、数据备份等。
父进程(Parent Process)和子进程(Child Process):父进程可以创建一个或多个子进程。子进程通常继承了父进程的一些属性和资源,并执行不同的任务。父子关系在进程树中以树状结构表示。
前端进程(Foreground Process):前端进程是一种与用户界面或用户终端交互的进程。它们通常接受用户输入,并将输出发送到用户终端,例如终端会话或图形用户界面。
实时进程(Real-time Process):实时进程需要在特定的时间要求内完成任务,通常用于实时控制和嵌入式系统中,例如飞行控制系统、医疗设备等。
交互式进程(Interactive Process):交互式进程需要快速响应用户的输入,并通常用于用户界面应用程序,如操作系统的图形用户界面、办公软件等。
批处理进程(Batch Process):批处理进程是一组任务或作业的集合,它们通常在没有用户交互的情况下自动运行。批处理通常用于定期执行一组重复的任务,如数据处理、报告生成等。
并发进程(Concurrent Process):并发进程是同时运行的多个进程,它们可以共享计算机资源,但通常是独立运行的。并发通常用于多任务操作系统中,以提高资源利用率。
多线程进程(Multi-threaded Process):多线程进程包含多个线程,这些线程共享相同的内存空间,但独立执行不同的任务。多线程通常用于提高并发性和性能。
多个进程之间的关系,而不是一个进程的不同状态。
不同进程之间的关系:
进程关系 | 描述 |
---|---|
父子关系(Parent-Child) | 父进程创建并管理子进程,子进程继承父进程的环境和资源。 |
同级关系(Siblings) | 同一父进程创建的多个子进程之间,它们是同级关系,共享同一父进程。 |
竞争关系(Competition) | 进程竞争有限的系统资源,如CPU时间、内存等,可能需要系统调度来协调。 |
合作关系(Cooperation) | 进程通过进程间通信(IPC)合作,以交换数据和信息,实现协同工作。 |
依赖关系(Dependency) | 某些进程依赖其他进程或服务来执行任务,如网络服务依赖数据库服务。 |
父子依赖关系(Parent-Child Dependency) | 子进程可能依赖父进程的存在和资源,如果父进程终止,子进程可能成为孤儿进程。 |
协调关系(Coordination) | 多个进程之间可能需要协调来执行任务,通常使用同步和互斥机制来实现。 |
此外,根据进程创建的先后,进程之间的关系也可以描述为:
- 祖先进程:在一个进程树中的顶层进程,也就是没有父进程的进程。在 Unix/Linux 系统中,通常由 systemd充当祖先进程。
- 父进程 、子进程、兄进程、弟进程。看名字就明白了。
PID
是进程标识符(Process ID
)的缩写。每当一个进程被创建时,操作系统都会为其分配一个唯一的数字(整数),即PID,用于在操作系统中识别该进程。
一般来说,PID 取值范围如下:
32,767
(2^15-1)。这意味着最多可以有32,767个并发运行的进程,每个进程都有一个唯一的PID。2^22
(4,194,304
) 或更大。这意味着64位系统可以支持更多的并发进程,这对于处理大规模工作负载和服务器应用程序非常有用。对于PID小于300的情况,只允许分配一次,一般对应了系统线程,每个PID分配成功后,会将当前的PID设置为last_pid,下次便从last_pid + 1开始往下查找。
我的服务器:
root@CQUPTLEI:~# cat /proc/sys/kernel/pid_max
4194304
PID 的一些主要作用:
进程唯一标识:PID用于唯一标识每个正在运行的进程。没有两个进程可以拥有相同的PID,这确保了每个进程都具有唯一的身份标识。
进程管理:PID用于操作系统管理进程的创建、调度、终止和资源分配。操作系统可以根据PID来确定要执行的进程,并控制它们的运行。
进程间通信:PID允许进程之间进行通信。进程可以使用其他进程的PID来发送消息、请求资源或进行其他形式的协作。这对于实现进程间通信(IPC)非常重要。
父子关系:在许多操作系统中,父进程创建子进程,并子进程通常继承父进程的PID。这种关系使得父进程可以管理和监视其子进程。
资源控制:操作系统可以使用PID来跟踪进程使用的资源,例如内存、CPU时间和文件句柄。这有助于限制进程的资源消耗和确保公平分配资源。
进程状态跟踪:PID用于标识和管理进程的状态。操作系统可以知道哪些进程正在运行、挂起、终止或等待状态,以便合理分配系统资源。
迒程监控和维护:系统管理员可以使用PID来监视和维护系统中运行的进程。他们可以根据PID查找特定的进程,检查其资源使用情况,或在需要时终止进程。
安全性和权限控制:PID也用于权限控制。一些操作系统要求进程具有合适的权限(例如,具有相同用户权限)才能与其他进程交互。PID有助于确保权限和安全性。
总结来说,PID唯一标识一个进程,对进程的各自操作、管理,都借助PID实现。
相关命令:
ps
命令是一个用于查看和列出正在运行的进程的工具,在不同的Unix、Linux和macOS系统中可以有不同的参数。以下是一些常见的 ps
命令参数,它们可以用于查看不同方面的进程信息:
常用参数:
aux
:显示所有用户的所有进程信息,包括细节信息。a
:显示所有用户的进程,包括其他终端上的进程。u
:以用户为基础显示更多详细信息。x
:显示没有控制终端的进程。其他参数:
e
:显示环境变量。f
:显示进程之间的父子关系(进程树)。g
:显示进程组信息。t
:显示终端信息。r
:只显示运行中的进程。l
:显示更多的详细信息,包括进程的状态、CPU利用率等。o
:自定义输出格式,可以选择显示特定的列。-C <命令>
:显示与特定命令相关的进程。--sort
:按指定的列对进程进行排序。输出列的定制:
pid
:进程ID。ppid
:父进程ID。%cpu
:CPU利用率。%mem
:内存利用率。cmd
:命令行命令。例如,要列出所有用户的所有进程,包括详细信息,您可以使用以下命令:
ps aux
要以树状结构显示进程及其父子关系,可以使用 -f
参数:
ps auxf
要自定义输出格式,可以使用 -o
参数,例如,仅显示PID和命令:
ps -o pid,cmd
查看某个用户的进程:
ps -u root
命令结合使用就行了,忘了就man
一下。
Windows下可以用
tasklist
和ps(参数不同了)。
pstree
是一个用于在Unix和类Unix操作系统中以树状结构显示进程之间父子关系的命令。它以可读性更好的方式展示了进程之间的关系,帮助用户更清晰地了解进程之间的层次结构:
命令格式:
pstree [选项] [进程ID]
常见选项:
-p
:显示进程的PID。-n
:显示进程的PID,并按数字顺序排序。-u
:显示用户的用户名。-a
:显示命令行参数。示例:
显示当前系统中所有进程的进程树,包括PID、命令、以及父子关系:
pstree
显示特定进程(例如,PID 1234)的进程树,包括PID和命令:
pstree -p 1234
显示特定用户(例如,用户名为"john")的进程树,包括PID、用户名和命令行参数:
pstree -u -a john
Windows没有专门的命令。可以去微软官网下载
pslist
:https://learn.microsoft.com/zh-cn/sysinternals/downloads/pslist,下载后解压,然后在环境变量中添加路径,之后就可以在命令行使用了。
top、htop等命令也有相关功能。
用一个PID可以唯一标识一个进程,但是还不够,还需要有一个数据结构来存储进程的相关信息,比如状态、计数器等等。
进程控制块(Process Control Block,PCB
):PCB 是一种包含了关于进程状态、寄存器值、程序计数器、打开文件描述符、资源分配等信息的数据结构。每个进程都有一个对应的 PCB。
PCB还有一些别的叫法:进程描述符、执行体进程块(EPROCESS,Windows下)。
PCB 的主要用途:
进程状态管理:PCB 包含了描述进程当前状态的信息,如运行、就绪、等待、停止等。这使操作系统能够追踪每个进程的状态,以便适时进行调度和资源分配。
程序计数器(Program Counter,PC):PCB 存储了进程的程序计数器,即指示下一个要执行的指令地址。这使操作系统能够恢复进程的执行状态。
寄存器集合:PCB 中包含了进程的寄存器状态,包括通用寄存器、程序状态字等。这些信息用于保存进程的执行环境,以便在进程切换时能够恢复到正确的状态。
进程标识:PCB 包含了唯一的进程标识符(Process ID,PID),以便操作系统能够识别和管理各个进程。
进程优先级:PCB 可以包含进程的优先级信息,以帮助操作系统进行进程调度。
进程拥有的资源:PCB 通常包含了进程所拥有的资源列表,包括已打开的文件、分配的内存、打开的文件描述符等。这有助于操作系统在进程终止时释放资源。
进程状态信息:PCB 中存储了进程的状态信息,如父进程、子进程等。这有助于操作系统维护进程之间的关系。
计时器和优先级信息:PCB 可以包含与进程相关的计时器信息,如时间片大小等,以及与调度相关的信息,如进程的优先级。
调度信息:PCB 可以包含进程的调度信息,以帮助操作系统选择合适的进程进行执行。
其他控制和状态信息:PCB 还可以包含其他与进程控制和状态有关的信息,根据操作系统的需要而有所不同。
PCB 是操作系统内部数据结构,通过维护和管理 PCB,操作系统能够有效地调度进程、分配资源、处理进程间通信等,以确保系统的正常运行。普通用户可能只会用到里面的PID,而程序员在程序中会有一些进程相关的操作,但也是间接使用到PCB(如过你是进行操作系统开发、内核级编程,则可能会直接操作和优化PCB)。
PCB只是一个概念,实际的数据结构名为:task_struct
。位于/usr/src/linux-headers-5.4.0-121/include/linux/sched.h
中(名称不一定相同)。
sched.h
是 Linux 内核中的一个头文件,主要用于定义与调度和进程调度相关的数据结构、宏、函数和常量。它包含了许多与进程调度和时间片轮转调度器相关的信息。
task_struct
定义在600~1300行左右(我的是624-1286)。部分图示:
给出简化后的部分内容,完整的几百行自己看源码:
struct task_struct {
volatile long state; // 进程状态
struct thread_info *thread_info; // 线程信息
struct list_head tasks; // 任务链表
struct mm_struct *mm; // 内存管理信息
pid_t pid; // 进程标识符
pid_t tgid; // 进程组标识符
int prio, static_prio; // 进程优先级
struct list_head run_list; // 就绪队列
unsigned int time_slice; // 时间片大小
struct sched_entity se; // 调度实体
// 更多字段和信息...
// 例如,文件描述符表、信号处理、调度策略等等
};
在Windows操作系统中,进程控制块(PCB)的功能是由EPROCESS结构体来实现的。EPROCESS结构体位于内核层之上,它侧重于提供各种管理策略,同时为上层应用程序提供基本的功能接口。
EPROCESS结构体包含了进程的资源相关信息,如句柄表、虚拟内存、安全、调试、异常、创建信息、I/O转移统计以及进程计时等。以下是EPROCESS的部分数据结构(Windows和linux不一样,源码没公开哟):
typedef struct _EPROCESS {
KPROCESS Pcb; // KPROCESS被内核用来进行线程调度使用
EX_PUSH_LOCK ProcessLock; // ProcessLock域是一个推锁 (push lock)对象,用于保护EPROCESS中的数据成员
LARGE_INTEGER CreateTime; // 创建时间
LARGE_INTEGER ExitTime; // 退出时间
EX_RUNDOWN_REF RundownProtect; // RundownProtect域是进程的停止保护锁
HANDLE UniqueProcessId; // 进程的唯一编号
LIST_ENTRY ActiveProcessLinks; // 所有的活动进程都连接在一起,构成了一个链表
SIZE_T QuotaUsage [PsQuotaTypes]; // 进程的内存使用量和尖峰使用量
SIZE_T QuotaPeak [PsQuotaTypes];
SIZE_T CommitCharge; // 虚拟内存已提交的"页面数量"
SIZE_T PeakVirtualSize; // 虚拟内存大小的尖峰值
SIZE_T VirtualSize; // 一个进程的虚拟内存大小
LIST_ENTRY SessionProcessLinks; // 当进程加入到一个系统会话中时,这个进程的SessionProcessLinks域将作为一个节点加入到该会话的进程链表中
PVOID DebugPort; // 当前进程对应的调试端口和异常端口
PVOID ExceptionPort;
PHANDLE_TABLE ObjectTable; // 当前进程的句柄表
EX_FAST_REF Token; // 一个快速引用,指向该进程的访问令牌,用于该进程的安全访问检查
PFN_NUMBER WorkingSetPage; // 包含进程工作集的页面
KGUARDED_MUTEX AddressCreationLock; // 一个守护互斥体锁 (guard mutex),用于保护对地址空间的操作
};
进程调度包括:不同进程之间的切换;同一进程的不同状态之间的切换。目的是高效利用系统资源。
如过你去阅读了前面的sched.h
文件(其它文件中也有相关定义),就会看到这些宏定义,这些状态宏允许内核更精确地跟踪任务的状态变化以进行适当的处理。
以表格形式呈现的这些Linux内核进程状态常量及其解释:
常量 | 解释 |
---|---|
TASK_RUNNING | 进程正在运行,占用CPU时间。 |
TASK_INTERRUPTIBLE | 进程处于可中断等待状态,等待事件发生,可以被唤醒。 |
TASK_UNINTERRUPTIBLE | 进程处于不可中断等待状态,类似可中断等待,不响应信号。 |
__TASK_STOPPED | 进程已经停止执行。 |
__TASK_TRACED | 进程正在被跟踪。 |
EXIT_DEAD | 进程已终止并被回收,不再存在。 |
EXIT_ZOMBIE | 进程已终止,但父进程尚未回收资源和状态信息,是僵尸进程。 |
EXIT_TRACE | 同时拥有EXIT_ZOMBIE 和EXIT_DEAD 状态,表示进程已终止和成为僵尸。 |
TASK_PARKED | 进程处于停滞状态,通常与CPU休眠或省电模式相关。 |
TASK_DEAD | 进程已终止并被回收,类似EXIT_DEAD 。 |
TASK_WAKEKILL | 进程被要求终止,可能在等待期间被杀死。 |
TASK_WAKING | 进程正在被唤醒。 |
TASK_NOLOAD | 进程被标记为无需负载平衡。 |
TASK_NEW | 进程是新创建的,尚未运行。 |
TASK_STATE_MAX | 状态常量的最大值。 |
但这太细致了,不便于理解,而且普通的开发不会用到,细致的进程控制是由内核完成的,我们不用管。
简化来讲,进程有3种状态:
运行态(Running State):这是指进程正在执行,占用 CPU 时间片。在任何给定时刻,通常只有一个进程处于运行状态。其他进程可能在就绪队列中等待运行,等待被操作系统的调度算法选中。
阻塞态(Blocked State):进程处于阻塞状态时,它正在等待某种事件的发生,例如等待输入/输出操作完成、等待资源的释放等。在阻塞状态下,进程不会占用 CPU 时间,因为它无法执行任何指令。
就绪态(Ready State):就绪态表示进程已经准备好执行,但尚未被分配到 CPU 时间片。就绪态的进程通常排队在就绪队列中,等待操作系统的调度来选择下一个要运行的进程。