Linux进程

进程的概念

定义

进程是指在系统中正在运行的一个应用程序的实例,是操作系统进行资源分配和调度的基本单位。它包含了程序执行的上下文环境,包括程序计数器、寄存器、堆栈以及程序代码和数据等。

组成

      程序:描述了进程要执行的任务,是一系列指令的集合。

      数据:包括程序运行时需要处理的数据以及进程在运行过程中产生的中间结果和最终结果等。

      进程控制块(PCB):是操作系统用于管理进程的核心数据结构,包含了进程的各种信息,如进程标识、状态、优先级、资源分配情况、程序计数器、寄存器值等。PCB 是进程存在的唯一标志,操作系统通过对 PCB 的操作来实现对进程的管理和控制。

并行和并发

      并行:指多个任务在同一时刻同时进行,多个任务在不同的处理器或处理核心上同时执行,真正实现了多个任务的同时运行。例如,在具有多个 CPU 核心的计算机中,每个核心可以同时处理不同的任务,这些任务就是并行执行的。

      并发:指多个任务在同一时间段内交替执行,通过快速切换上下文,使得多个任务看起来像是同时在运行,但实际上在某一时刻只有一个任务在执行。例如,在单处理器的计算机中,通过分时复用技术,让多个进程轮流使用处理器,实现多个任务的并发执行。

并行和并发的区别:
并发,指的是多个事情,在同一时间段内同时发生了
并行,指的是多个事情,在同一时间点上同时发生了
并发的多个任务之间是互相抢占资源的
并行的多个任务之间是不互相抢占资源的,只有在多CPU 或者一个 CPU 多核的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的

PCB(进程控制块)

定义

每个进程在内核中都有一个进程控制块( PCB )来维护进程相关的信息, linux 内核的进程控制块是 task_struct 结构体

PCB 是操作系统为了管理进程而专门设置的数据结构,它记录了进程的各种信息,是进程存在的唯一标志。当一个进程被创建时,操作系统会为其分配一个 PCB;当进程结束时,操作系统会回收该 PCB

作用

       进程管理:操作系统通过 PCB 来识别和管理各个进程,比如查询进程的状态、优先级等信息,从而实现对进程的创建、调度、终止等操作。

       资源分配:PCB 中记录了进程所占用的资源情况,操作系统依据这些信息为进程分配或回收资源,像内存、文件、I/O 设备等。

       上下文切换:在多任务操作系统里,当需要从一个进程切换到另一个进程时,操作系统会借助 PCB 保存当前进程的上下文(如程序计数器、寄存器值等),并恢复下一个进程的上下文,保证进程的执行能够准确地继续

PCB内部成员有很多,以下是几个重要成员:

   进程 id: 系统中每个进程有唯一的 id, C 语言中用 pit_t 类型表示,其实就是一个非负整数
   进程的状态:有就绪,运行,挂起,停止等状态 
   进程切换时需要保存和恢复一些 CPU 寄存器
   描述虚拟地址空间的信息
   描述控制终端的信息
   当前工作目录( Cueernt Working Directory)
   umask 掩码
   文件描述符,包含很多指向 file 结构体的指针
   和信号相关的信息
   用户 id 和组 id
   会话( Session )和进程组
   进程可以适用的资源上线( Resource Limit)(ulimit -a查看

进程状态

进程基本的状态有五种,分别为初始态,就绪态,运行态,挂起态和终止态
其中初始态为进程准备阶段,常常与就绪态结合来看
Linux进程_第1张图片

进程控制

1.fork和vfork

fork

函数原型

#include 
#include 

pid_t fork(void);

返回值:
      =0 :当前进程为子进程
      >0 :当前进程为父进程
      ‐1 : 出错
fork() 函数的主要作用是创建一个与调用进程几乎完全相同的新进程。这个新进程被称为子进程,而调用 fork()  的进程则被称为父进程。子进程会复制父进程的大部分状态,包括代码段、数据段、堆、栈等,但它们是相互独立的进程, 拥有各自独立的内存空间和进程控制块(PCB)
vfork
#include 
#include 

pid_t vfork(void);

与 fork 的比较

  • 地址空间fork 会复制父进程的地址空间,子进程和父进程有各自独立的内存副本;而 vfork 子进程和父进程共享地址空间。
  • 执行顺序fork 创建的子进程和父进程的执行顺序是不确定的,由操作系统调度决定;而 vfork 保证子进程先执行,在子进程调用 exec 或 _exit 之后父进程才会继续执行。
  • 资源开销fork 由于需要复制地址空间,开销相对较大;vfork 由于共享地址空间,开销较小。
Linux进程_第2张图片

Linux进程_第3张图片

2.getpid()和getppid()

getpid():得到当前进程的PID
getppid():得到当前进程的父进程的PID

ps和kill命令

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

exec函数族

让父子进程来执行不相干的操作
能够替换进程地址空间的代码.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 秒,在这期间子进程就会成为僵尸进程

进程回收

函数作用:

1. 阻塞并等待子进程退出
2. 回收子进程残留资源
3. 获取子进程结束状态(退出原因)

wait函数

#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)等。*/

waitpid函数

#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 指向的内容,常见的宏如 WIFEXITEDWEXITSTATUSWIFSIGNALEDWTERMSIG 等,这些宏的作用与 wait 函数中相同。
  • options:该参数用于设置 waitpid 函数的行为选项,常用的选项有:
    • WNOHANG:如果没有子进程结束,waitpid 函数不会阻塞,而是立即返回 0。
    • WUNTRACED:除了等待终止的子进程,还会等待因收到 SIGSTOP 或 SIGTSTP 信号而停止的子进程。
    • WCONTINUED:等待因收到 SIGCONT 信号而恢复执行的子进程。

返回值

  • 若成功,返回终止子进程的进程 ID(PID)。
  • 若指定了 WNOHANG 选项且没有子进程结束,返回 0。
  • 若调用出错,返回 -1,无子进程

进程退出

1. 正常退出
1.main 函数调用 return
2. 进程调用 exit(), 标准 C
3. 进程调用 _exit() 或者 _Exit(), 属于系统调用
补充:
4. 进程最后一个线程返回
5. 最后一个线程调用 pthread_exit
2. 异常退出
1. 调用 abort 函数
2. 当进程收到某些信号时,比如 ctrl +C
3. 最后一个线程对取消( cancellation )请求做出响应
不管进程如何终止,最后都会执行内核中的同一段代码,这段代码和相应进程关闭所有打开描述符,释放它所使用 的存储器等。
对上述任一一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数 (exit, _exit 和 _ Exit) ,实现这一点的方法是,将其退出状态 (exit status) 作为参数传送给函数,在异常终止情况下,内核 ( 不是 进程本身) 产生一个指示其异常终止原因的终止状态 (termination status) 。在任意一种情况下,该终止进程的父进程都能用wait waitpid 函数取得终止状态。

你可能感兴趣的:(Linux,linux)