嵌入式linux-进程状态与进程关系

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1. 进程状态

1.1什么是进程状态

Linux 系统下进程通常存在 6 种不同的状态分为:就绪态、运行态、僵尸态、可中断睡眠状态浅度
睡眠、不可中断睡眠状态深度睡眠以及暂停态。
下面我们来一一总结一下:

进程状态含义
就绪态Ready指该进程满足被 CPU 调度的所有条件但此时并没有被调度执行只要得到 CPU就能够直接运行;意味着该进程已经准备好被 CPU 执行当一个进程的时间片到达操作系统调度程序会从就绪态链表中调度一个进程;
运行态指该进程当前正在被 CPU 调度运行处于就绪态的进程得到 CPU 调度就会进入运行态;
僵尸态僵尸态进程其实指的就是僵尸进程指该进程已经结束、但其父进程还未给它“收尸”
可中断睡眠状态可中断睡眠也称为浅度睡眠表示睡的不够“死”还可以被唤醒一般来说可以通过信号来唤醒;
不可中断睡眠状态不可中断睡眠称为深度睡眠深度睡眠无法被信号唤醒只能等待相应的条件成立才能结束睡眠状态。把浅度睡眠和深度睡眠统称为等待态或者叫阻塞态表示进程处于一种等待状态等待某种条件成立之后便会进入到就绪态;所以处于等待态的进程是无法参与进程系统调度的。
暂停态暂停并不是进程的终止表示进程暂停运行一般可通过信号将进程暂停譬如 SIGSTOP信号;处于暂停态的进程是可以恢复进入到就绪态的譬如收到 SIGCONT 信号

一个新创建的进程会处于就绪态只要得到 CPU 就能被执行。以下列出了进程各个状态之间的转换关系如下所示:
在这里插入图片描述

1.2 进程关系

进程号进程 ID、PID也有自己的生命周期进程都有自己的父进程、而父进程也有父进程这就形成了一个以 init 进程为根的进程家族树;当子进程终止时父进程会得到通知并能取得子进程的退出状态。

除此之外进程间还存在着其它一些层次关系譬如进程组和会话;所以由此可知进程间存在着多种不同的关系主要包括:无关系相互独立、父子进程关系、进程组以及会话。

  1. 无关系
    两个进程间没有任何关系相互独立。

  2. 父子进程关系
    两个进程间构成父子进程关系譬如一个进程 fork()创建出了另一个进程那么这两个进程间就构成了父子进程关系调用 fork()的进程称为父进程、而被 fork()创建出来的进程称为子进程;当然如果“生父”先与子进程结束那么 init 进程“养父”就会成为子进程的父进程它们之间同样也是父子进程关系。

  3. 进程组
    每个进程除了有一个进程 ID、父进程 ID 之外还有一个进程组 ID用于标识该进程属于哪一个进程组进程组是一个或多个进程的集合这些进程并不是孤立的它们彼此之间或者存在父子、兄弟关系或者在功能上有联系。

tips:Linux 系统设计进程组实质上是为了方便对进程进行管理。假设为了完成一个任务需要并发运行 100个进程但当处于某种场景时需要终止这 100 个进程若没有进程组就需要一个一个去终止这样非常麻烦且容易出现一些问题;有了进程组的概念之后就可以将这 100 个进程设置为一个进程组这些进程共享一个进程组 ID这样一来终止这 100 个进程只需要终止该进程组即可。

注意:关于进程组需要注意以下以下内容:

  1. 每个进程必定属于某一个进程组、且只能属于一个进程组;
  2. 每一个进程组有一个组长进程组长进程的 ID 就等于进程组 ID;
  3. 在组长进程的 ID 前面加上一个负号即是操作进程组;
  4. 组长进程不能再创建新的进程组;
  5. 只要进程组中还存在一个进程则该进程组就存在这与其组长进程是否终止无关;
  6. 一个进程组可以包含一个或多个进程进程组的生命周期从被创建开始到其内所有进程终止或离开该进程组;
  7. 默认情况下新创建的进程会继承父进程的进程组 ID。

1.2.1 getpgid()函数与getpgrp()函数的使用

#include <unistd.h>

pid_t getpgid(pid_t pid);
pid_t getpgrp(void);

下面是进程代码编写:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
 pid_t pid = getpid();
 printf("进程组 ID<%d>---getpgrp()\n", getpgrp());
 printf("进程组 ID<%d>---getpgid(0)\n", getpgid(0));
 printf("进程组 ID<%d>---getpgid(%d)\n", getpgid(pid), pid);
 exit(0);
}

在这里插入图片描述
从上面的结果可以发现其新创建的进程对应的进程组 ID 等于该进程的 ID。

1.2.2 setpgid()函数 或 setpgrp()函数的使用

调用系统调用 setpgid()或 setpgrp()可以加入一个现有的进程组或创建一个新的进程组其函数原型如下所示:

#include <unistd.h>

int setpgid(pid_t pid, pid_t pgid);
int setpgrp(void);

setpgid()函数将参数 pid 指定的进程的进程组 ID 设置为参数 gpid。如果这两个参数相等pid==gpid则由 pid 指定的进程变成为进程组的组长进程创建了一个新的进程;如果参数 pid 等于 0则使用调用者的进程 ID;另外如果参数 gpid 等于 0则创建一个新的进程组由参数 pid 指定的进程作为进程组组长进程。setpgrp()函数等价于 setpgid(0, 0)。

此外一个进程只能为它自己或它的子进程设置进程组 ID在它的子进程调用 exec 函数后它就不能更改该
子进程的进程组 ID 了。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
 printf("更改前进程组 ID<%d>\n", getpgrp());
 setpgrp();
 printf("更改后进程组 ID<%d>\n", getpgrp());
 exit(0);
}

这个结果就不运行了

1.3 会话

介绍完进程组之后再来看下会话会话是一个或多个进程组的集合其与进程组、进程之间的关系如下图所示:
在这里插入图片描述
一个会话可包含一个或多个进程组但只能有一个前台进程组其它的是后台进程组;每个会话都有一个会话首领leader即创建会话的进程。一个会话可以有控制终端、也可没有控制终端在有控制终端的情况下也只能连接一个控制终端这通常是登录到其上的终端设备在终端登录情况下或伪终端设备譬如通过 SSH 协议网络登录一个会话中的进程组可被分为一个前台进程组以及一个或多个后台进程组。

会话的首领进程连接一个终端之后该终端就成为会话的控制终端与控制终端建立连接的会话首领进程被称为控制进程;产生在终端上的输入和信号将发送给会话的前台进程组中的所有进程譬如 Ctrl + C产生 SIGINT 信号、Ctrl + Z产生 SIGTSTP 信号、Ctrl + \产生 SIGQUIT 信号等等这些由控制终端产生的信号。

当用户在某个终端登录时一个新的会话就开始了;当我们在 Linux 系统下打开了多个终端窗口时实际上就是创建了多个终端会话。

一个进程组由组长进程的 ID 标识而对于会话来说会话的首领进程的进程组 ID 将作为该会话的标识也就是会话 IDsid在默认情况下新创建的进程会继承父进程的会话 ID。通过系统调用 getsid()可以获取进程的会话 ID其函数原型如下所示:

#include <unistd.h>

pid_t getsid(pid_t pid);

写个最简单的查看会话代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
 printf("会话 ID<%d>\n", getsid(0));
 exit(0);
}

在这里插入图片描述
同理可得:
使用setsid()可以创建一个会话如下方所示:

#include <unistd.h>

pid_t setsid(void);

如果调用者进程不是进程组的组长进程调用 setsid()将创建一个新的会话调用者进程是新会话的首领进程同样也是一个新的进程组的组长进程调用 setsid()创建的会话将没有控制终端。setsid()调用成功将返回新会话的会话 ID;失败将返回-1并设置 errno。

本文参考正点原子的嵌入式LinuxC应用编程。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux