进程是指在系统中正在运行的一个应用程序的实例,是操作系统进行资源分配和调度的基本单位。它包含了程序执行的上下文环境,包括程序计数器、寄存器、堆栈以及程序代码和数据等。
程序:描述了进程要执行的任务,是一系列指令的集合。
数据:包括程序运行时需要处理的数据以及进程在运行过程中产生的中间结果和最终结果等。
进程控制块(PCB):是操作系统用于管理进程的核心数据结构,包含了进程的各种信息,如进程标识、状态、优先级、资源分配情况、程序计数器、寄存器值等。PCB 是进程存在的唯一标志,操作系统通过对 PCB 的操作来实现对进程的管理和控制。
并行:指多个任务在同一时刻同时进行,多个任务在不同的处理器或处理核心上同时执行,真正实现了多个任务的同时运行。例如,在具有多个 CPU 核心的计算机中,每个核心可以同时处理不同的任务,这些任务就是并行执行的。
并发:指多个任务在同一时间段内交替执行,通过快速切换上下文,使得多个任务看起来像是同时在运行,但实际上在某一时刻只有一个任务在执行。例如,在单处理器的计算机中,通过分时复用技术,让多个进程轮流使用处理器,实现多个任务的并发执行。
定义
PCB 是操作系统为了管理进程而专门设置的数据结构,它记录了进程的各种信息,是进程存在的唯一标志。当一个进程被创建时,操作系统会为其分配一个 PCB;当进程结束时,操作系统会回收该 PCB
作用
进程管理:操作系统通过 PCB 来识别和管理各个进程,比如查询进程的状态、优先级等信息,从而实现对进程的创建、调度、终止等操作。
资源分配:PCB 中记录了进程所占用的资源情况,操作系统依据这些信息为进程分配或回收资源,像内存、文件、I/O 设备等。
上下文切换:在多任务操作系统里,当需要从一个进程切换到另一个进程时,操作系统会借助 PCB 保存当前进程的上下文(如程序计数器、寄存器值等),并恢复下一个进程的上下文,保证进程的执行能够准确地继续
PCB内部成员有很多,以下是几个重要成员:
fork
函数原型
#include
#include
pid_t fork(void);
#include
#include
pid_t vfork(void);
与 fork
的比较
fork
会复制父进程的地址空间,子进程和父进程有各自独立的内存副本;而 vfork
子进程和父进程共享地址空间。fork
创建的子进程和父进程的执行顺序是不确定的,由操作系统调度决定;而 vfork
保证子进程先执行,在子进程调用 exec
或 _exit
之后父进程才会继续执行。fork
由于需要复制地址空间,开销相对较大;vfork
由于共享地址空间,开销较小。ps(Process Status)命令用于报告当前系统的进程状态,它可以显示系统中正在运行的进程的相关信息,帮助用户了解系统的运行状况
ps aux :这是最常用的组合选项,a
表示显示所有用户的进程,u
表示以详细的用户信息格式输出,x
表示显示没有控制终端的进程(即后台进程)
执行该命令后会输出以下信息:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.4 225372 8044 ? Ss 19:04 0:04 /sbin/init auto noprompt
root 2 0.0 0.0 0 0 ? S 19:04 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< 19:04 0:00 [rcu_gp]
USER
:进程的所有者。
PID
:进程的 ID。
%CPU
:进程占用 CPU 的百分比。
%MEM
:进程占用内存的百分比。
VSZ
:进程使用的虚拟内存大小(以 KB 为单位)。
RSS
:进程使用的物理内存大小(以 KB 为单位)。
TTY
:进程关联的终端设备。
STAT
:进程的状态,常见的状态有 R
(运行)、S
(睡眠)、D
(不可中断睡眠)、Z
(僵尸进程)等。
START
:进程启动的时间。
TIME
:进程累计使用 CPU 的时间。
COMMAND
:启动进程的命令。
ps -ef:-e
表示显示所有进程,-f
表示以完整格式输出
这种输出格式与 ps aux 类似,但列的顺序和名称可能略有不同
ps ajx:当你执行 ps ajx命令后,会输出类似下面的信息:
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 0:04 /sbin/init auto noprompt
0 2 0 0 ? -1 S 0 0:00 [kthreadd]
2 3 0 0 ? -1 I< 0 0:00 [rcu_gp]
2 4 0 0 ? -1 I< 0 0:00 [rcu_par_gp]
2 6 0 0 ? -1 I< 0 0:00 [kworker/0:0H-kb]
2 8 0 0 ? -1 I< 0 0:00 [mm_percpu_wq]
2 9 0 0 ? -1 S 0 0:00 [ksoftirqd/0]
PPID
:父进程 ID(Parent Process ID),表示创建该进程的父进程的唯一标识符。例如,init
进程(PID 为 1)通常是许多其他进程的父进程。PID
:进程 ID(Process ID),是系统为每个进程分配的唯一标识符,用于在系统中识别和管理进程。PGID
:进程组 ID(Process Group ID),每个进程都属于一个进程组,进程组可以包含一个或多个进程,常用于作业控制。SID
:会话 ID(Session ID),会话是一个或多个进程组的集合,通常与用户登录的终端会话相关。TTY
:与进程关联的终端设备。如果显示为 ?
,则表示该进程没有关联终端,通常是后台进程。TPGID
:控制终端的前台进程组 ID。如果显示为 -1
,表示该进程没有控制终端。STAT
:进程的状态码,用于表示进程当前的运行状态,常见的状态码有:
R
:运行状态(Running),表示进程正在 CPU 上执行。S
:睡眠状态(Sleeping),表示进程正在等待某个事件发生,如 I/O 操作完成。D
:不可中断睡眠状态(Uninterruptible Sleep),通常是进程在等待 I/O 操作完成,且不能被信号中断。Z
:僵尸状态(Zombie),表示进程已经终止,但父进程尚未回收其资源。T
:停止状态(Stopped),表示进程被暂停执行,通常是由于接收到 SIGSTOP
或 SIGTSTP
信号。UID
:用户 ID(User ID),表示运行该进程的用户的唯一标识符。TIME
:进程累计使用 CPU 的时间。COMMAND
:启动进程的命令行。筛选进程
可以结合 grep
命令来筛选出特定的进程,例如查找名为 nginx
的进程:
ps aux | grep nginx
kill
kill 命令用于向指定的进程发送信号,默认情况下发送的是 TERM(终止)信号,用于终止进程。但实际上,kill 可以发送多种不同的信号来实现不同的功能,如暂停进程、恢复进程等
常用信号
SIGTERM
(15):这是 kill
命令默认发送的信号,用于正常终止进程。进程接收到该信号后,可以进行一些清理工作,然后终止。SIGKILL
(9):强制终止进程,该信号不能被进程捕获或忽略,会立即终止进程,但可能会导致进程无法进行正常的清理工作。SIGSTOP
(19):暂停进程的执行。SIGCONT
(18):恢复被暂停的进程的执行。例如终止PID为1234 和 5678的进程:
kill -9 1234 5678
让父子进程来执行不相干的操作
能够替换进程地址空间的代码.text段
执行另外的程序,不需要创建额外的的地址空间
当前程序中调用另外一个应用程序
指定执行目录下的程序
#include
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
/* path:此为要执行的程序的完整路径名。例如,若要执行 ls 命令,其路径一般是 /bin/ls。
* arg:这是传递给新程序的第一个参数,通常为程序名本身。例如,在执行 ls 命令时,这个参数就是 "ls"。
* ...:这是可变参数列表,可传递多个参数给新程序。
* (char *) NULL:可变参数列表的结尾必须用 NULL 来标记,以此表明参数传递结束 */
/*若 execl 调用成功,它不会返回,因为当前进程的代码段、数据段、堆和栈等会被新程序的相应部分替换,进
程开始执行新程序。
*若调用失败,会返回 -1,并设置 errno 来指示具体的错误原因。*/
执行PATH环境变量能够搜索到的程序
#include
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
/*file:要执行的程序名。函数会在环境变量 PATH 所包含的目录中搜索该程序。例如,要执行 ls 命令,直接
传入 "ls" 即可,无需指定完整路径 /bin/ls。
*arg:传递给新程序的第一个参数,通常是程序名本身。比如执行 ls 命令时,这个参数为 "ls"。
*...:可变参数列表,可用来传递多个参数给新程序。
*(char *) NULL:可变参数列表的结尾必须用 NULL 标记,表明参数传递结束。*/
/*若 execlp 调用成功,它不会返回,因为当前进程的代码段、数据段、堆和栈等会被新程序的相应部分替换,
进程开始执行新程序。
*若调用失败,会返回 -1,并设置 errno 来指示具体的错误原因。*/
执行指定路径,指定环境变量下的程序
#include
int execle(const char *path, const char *arg, ..., (char *) NULL, char * const envp[]);
/*path:要执行的程序的完整路径。
*arg:传递给程序的第一个参数,一般是程序名本身。
*...:可变参数列表,用于传递更多的参数给程序。
*(char *) NULL:可变参数列表的结束标志。
*envp[]:一个字符串数组,用于指定程序运行时的环境变量。
如果函数运行成功不返回
如果执行失败,打印错误信息,退出子进程*/
#include
int execve(const char *filename, char *const argv[], char *const envp[]);
/*filename:要执行的程序的完整路径。
*argv[]:一个字符串数组,用于传递参数给程序,数组的第一个元素通常是程序名本身,最后一个元素必须是
NULL。
*envp[]:一个字符串数组,用于指定程序运行时的环境变量。
如果函数运行成功不返回
如果执行失败,打印错误信息,退出子进程*/
孤儿进程
定义
当一个父进程先于其子进程结束时,它的子进程就会变成孤儿进程。这些孤儿进程会被系统的 init
进程(在现代 Linux 系统中通常是 systemd
)收养,成为 init
进程的子进程。
产生原因
在多进程编程中,父进程可能因为正常结束、异常崩溃等原因先于子进程终止,此时子进程就会失去父进程的管理,从而成为孤儿进程。
例如:
#include
#include
#include
int main()
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
return 1;
}
else if (pid == 0)
{
// 子进程
sleep(5); // 让子进程休眠 5 秒,确保父进程先结束
printf("Child process (PID: %d), Parent PID: %d\n", getpid(), getppid());
}
else
{
// 父进程
printf("Parent process (PID: %d) is exiting.\n", getpid());
}
return 0;
}
父进程会先结束,而子进程会休眠 5 秒,此时子进程就会成为孤儿进程
僵尸进程
定义
当一个子进程结束时,它会向父进程发送 SIGCHLD
信号,通知父进程自己已经终止。但如果父进程没有及时调用 wait()
或 waitpid()
等函数来回收子进程的资源(主要是进程控制块 PCB),子进程就会变成僵尸进程。僵尸进程虽然已经终止,但它的进程控制块仍然保留在系统中,占用系统资源
产生原因
父进程没有正确处理子进程的终止信号,没有及时调用 wait()
或 waitpid()
函数来回收子进程的资源。
例如:
#include
#include
#include
int main()
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
return 1;
}
else if (pid == 0)
{
// 子进程
printf("Child process (PID: %d) is exiting.\n", getpid());
}
else
{
// 父进程
sleep(10); // 让父进程休眠 10 秒,不及时回收子进程资源
printf("Parent process (PID: %d) is still running.\n", getpid());
}
return 0;
}
进程会先结束,但父进程会休眠 10 秒,在这期间子进程就会成为僵尸进程
函数作用:
#include
#include
pid_t wait(int *status);
/*status:这是一个整型指针,用于存储子进程的退出状态信息。如果不关心子进程的退出状态,可以将其设置为 NULL。通过一些宏可以对 status 所指向的内容进行解析,常用的宏如下:
WIFEXITED(status):若子进程正常退出,该宏返回非零值。
WEXITSTATUS(status):当 WIFEXITED(status) 为真时,该宏返回子进程的退出状态码(通常是子进程中 return 语句或 exit 函数所返回的值)。
WIFSIGNALED(status):若子进程因收到信号而终止,该宏返回非零值。
WTERMSIG(status):当 WIFSIGNALED(status) 为真时,该宏返回导致子进程终止的信号编号*/
/*若调用成功,wait 函数返回终止子进程的进程 ID(PID)。
若调用出错,返回 -1,并设置 errno 来指示具体的错误原因。可能的错误情况包括:调用进程没有子进程(ECHILD)、调用被信号中断(EINTR)等。*/
#include
#include
pid_t waitpid(pid_t pid, int *status, int options);
参数说明
pid
:用于指定要等待的子进程,其取值有以下几种情况:
pid > 0
:等待进程 ID 等于 pid
的子进程结束。pid == -1
:等待任意一个子进程结束,此时 waitpid
函数的作用与 wait
函数类似。、pid == 0
:等待与调用进程处于同一进程组的任意子进程结束。pid < -1
:等待进程组 ID 等于 pid
绝对值的任意子进程结束。status
:这是一个整型指针,用于存储子进程的退出状态信息。如果不关心子进程的退出状态,可以将其设置为 NULL
。可以使用一些宏来解析 status
指向的内容,常见的宏如 WIFEXITED
、WEXITSTATUS
、WIFSIGNALED
、WTERMSIG
等,这些宏的作用与 wait
函数中相同。options
:该参数用于设置 waitpid
函数的行为选项,常用的选项有:
WNOHANG
:如果没有子进程结束,waitpid
函数不会阻塞,而是立即返回 0。WUNTRACED
:除了等待终止的子进程,还会等待因收到 SIGSTOP
或 SIGTSTP
信号而停止的子进程。WCONTINUED
:等待因收到 SIGCONT
信号而恢复执行的子进程。返回值
WNOHANG
选项且没有子进程结束,返回 0。