当前位置: 首页 > news >正文

unix系统的终端、进程、进程组、会话、控制终端、作业控制之间的关系

一、前言

本章将介绍unix系统进程关系相关的内容。在unix系统中,进程与进程之间通常是协同工作的,本章就来讨论进程之间的关系。包括如下内容:
1.本文会先介绍终端的概念
2.介绍进程、进程组
3.介绍什么是会话
4.介绍何为控制终端
5.介绍什么是作业控制

二、什么是终端?

简单来说,终端就是用于与计算机进行交互的工具。它需要具备两个功能:输入和输出。(即能够接受计算机反馈的内容,又能够向计算机输入内容。)
这里简单的介绍一下终端的发展史。
最初的计算机是没有屏幕的,只有一些指示灯。后来,人们为了方便与计算机进行交互,于是发明了专门的硬件设备用于此功能,那就是最初的硬件终端,大小和那种老式的显示器差不多。如下图所示:
在这里插入图片描述

(2-1)
如上图所示的就是实际真实的硬件终端。
可能有人和笔者一样,看到终端的第一反应就是:这不就是一个显示器吗?
其实,终端和显示器的差别还是挺大的,显示器显示的是像素点数据,而终端接收的是字符数据,然后将字符数据按照ACSII码呈现出来。最初的物理终端实际上是参考打字机的原理设计的。
对于计算机来说,终端就是一个外设,它和鼠标键盘没有什么太大的区别。(用现代的眼光去看,物理终端的作用其实就相当于用串口板连接计算机,然后将接收的数据在另一台设备上显示,当然另一台设备也能通过串口板将命令下发到计算机中。)
后来,随着图形技术的发展,图形终端出现了,最典型的代表就是windows系统的窗口界面。图形终端是指使用图形用户界面(GUI)与用户进行交互的终端。用户可以通过鼠标、图形按钮、菜单等方式进行操作。
另外还有虚拟终端,虚拟终端是指在一个物理终端中模拟出多个终端会话的技术。像我们访问服务器,基本上就是使用虚拟终端去登录访问的。像windows就是伪终端。

三、什么是进程组?

进程组是一个或多个进程的集合。每个进程除了有一个进程ID外,还有一个组ID。
进程组的特征:

1.同一进程组中的各进程接收同一终端的各种信号。
2.每个进程组有唯一的进程组ID
3.每一个进程组有一个组长进程。
4.组成进程的进程ID等于组ID
5.组长进程可以创建一个进程组,创建该组中的进程,然后终止。
6.只要进程组中有一个进程存在,则进程组就存在,这与进程组组长是否终止无关。
7.进程组的生命周期为:从进程组创建开始到最后组中最后一个进程终止或离开。
8.一个进程组中的进程可以转移到另一个进程组中

3.1 获取组ID

unix中获取组ID的接口还是比较多的,本节将介绍三种。

3.1.1 getpgid()

头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t getpgid(pid_t pid);
传入参数:
pid:需要查询的进程
返回值:
返回pid进程的组ID,如果pid为0,则返回调用进程的组ID

参考代码:

/*************************************************************************> File Name: pgrd_test.c> Author: conbiao> Created Time: 2024年10月21日 星期一 10时31分06秒************************************************************************//************************************************************************                             HEADER**********************************************************************/
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>/************************************************************************                              MACRO**********************************************************************//************************************************************************                          GLOBAL VARIABLE**********************************************************************//************************************************************************                       FUNCTION DESCRIPTION**********************************************************************//***********************************************************************
* FUNCTION NAME:***********************************************************************
*
* Summary:
*
* Params:
*
* Return:
*
***********************************************************************//************************************************************************                                MAIN**********************************************************************/
int main(int argc, char *argv[])
{int ret = 0;pid_t gpid;gpid = getpgid(0);printf("%s: gpid is %d\n",__func__,gpid);return ret;
}

运行结果如下所示:
在这里插入图片描述

(3.1.1-1)

3.1.2 getpgrp

头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t getpgrp(void);
传入参数:
NULL
返回值:
返回调用进程的组ID

参考代码如下:

/*************************************************************************> File Name: pgrd_test.c> Author: conbiao> Created Time: 2024年10月21日 星期一 10时31分06秒************************************************************************//************************************************************************                             HEADER**********************************************************************/
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>/************************************************************************                              MACRO**********************************************************************//************************************************************************                          GLOBAL VARIABLE**********************************************************************//************************************************************************                       FUNCTION DESCRIPTION**********************************************************************//***********************************************************************
* FUNCTION NAME:***********************************************************************
*
* Summary:
*
* Params:
*
* Return:
*
***********************************************************************//************************************************************************                                MAIN**********************************************************************/
int main(int argc, char *argv[])
{int ret = 0;pid_t gpid;//    gpid = getpgid(0);gpid = getpgrp();printf("%s: gpid is %d\n",__func__,gpid);return ret;
}

运行结果如下图所示:
在这里插入图片描述

(3.1.2-1)

3.1.3 getpgrp

头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t getpgrp(pid_t pid);
传入参数:
pid:需要查询的进程
返回值:
返回pid进程的组ID,如果pid为0,则返回调用进程的组ID

这个接口的用法和getpgid基本是一样的,在此就不做过多的赘述了。

3.2 设置进程组ID

一个进程可以调用setpgid加入一个进程组或者创建一个进程组。

头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
int setpgid(pid_t pid, pid_t pgid);
传入参数:
pid:需要设置的进程
pgid:需要加入的进程组ID
返回值:
如果设置成功,返回0 如果失败,返回-1

如果pid为0,则使用调用给接口的进程的进程ID
如果pgid为0,则pid指定的进程ID将作为组ID

ps:一个进程只能为它自己或者它的子进程设置进程组ID,且在它的子进程调用exec后,它就不在更改子进程的进程组ID.

四、什么是会话?

会话是一个或多个进程组的集合。

4.1 创建会话

使用setsid创建一个新会话。

头文件:
#include<unistd.h>
函数原型:
pid_t setsid(void);
传入参数:
NULL
返回值:
若成功,返回进程组ID,若失败,返回-1

如果调用此函数的进程不是一个进程组组长,则会创建一个新的会话。具体会发生如下情况:

1.该进程会成为一个新会话的首进程(此时,该进程是会话中的唯一进程)
2.该进程成为新进程组的组长进程
3.该进程没有控制终端,如果之前有控制终端,则创建了新的会话后会切断和老会话终端之间的联系。 如果调用此函数的进程已经是一个进程组组长,则会出错。

4.2 获取会话首进程id

unix中并没有会话id的概念,但是会话首进程的进程id是唯一的,可以将会话首进程的进程id当成会话id。

头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t getsid(pid_t pid);
传入参数:
pid:需要查询的会话id的进程
返回值: 成功返回会话id 失败返回-1

参考代码如下:

/*************************************************************************> File Name: session-test.c> Author: conbiao> Created Time: 2024年10月21日 星期一 11时58分37秒************************************************************************//************************************************************************                             HEADER**********************************************************************/
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>/************************************************************************                              MACRO**********************************************************************//************************************************************************                          GLOBAL VARIABLE**********************************************************************//************************************************************************                       FUNCTION DESCRIPTION**********************************************************************//***********************************************************************
* FUNCTION NAME:***********************************************************************
*
* Summary:
*
* Params:
*
* Return:
*
***********************************************************************//************************************************************************                                MAIN**********************************************************************/
int main(int argc, char *argv[])
{int ret = 0;pid_t sid;pid_t pid;pid = fork();if(pid == 0){sid = getsid(0);if(sid == -1){printf("%s: sid is:%d,getsid failed\n",__func__,sid);return -1;}printf("%s: sid is %d\n",__func__,sid);sid = setsid();if(sid == -1){printf("%s: sid is:%d,setsid failed\n",__func__,sid);return -1;}else{printf("%s: setsid success!\n",__func__);sid = getsid(0);if(sid == -1){printf("%s: sid is:%d,getsid failed\n",__func__,sid);return -1;}printf("%s: sid is %d\n",__func__,sid);}}return ret;
}

运行结果如下图所示:
在这里插入图片描述

(4.2-1)
如上代码,我们在程序中使用fork创建子进程,这时候子进程的进程组id和父进程的一致,且保证了子进程一定不是进程组组长,然后再调用setsid去创建新的会话。
如果不创建子进程的话,默认进程可能已经是进程组组长了。

五、什么是控制终端?

我们从实际的使用场景来理解什么是控制终端。
当你在一台ubuntu电脑上打开一个终端窗口,然后希望在这个终端中同时跑三个程序:A、B、C.(假设这三个程序都是死循环)
这时候你会发现,当你在这个终端上运行了这三个程序中的其中一个后,就没法再运行另外两个程序了。除非让这三个程序中的两个在后台运行。
在这个场景中,同时运行的三个程序A、B、C以及shell组成了一个会话。
对于这个会话来说,我们打开的这个终端就是控制终端。
控制终端和会话的关系以及特征如下:

1.一个会话可以有一个控制终端(也可以没有)
2.一个会话中的进程组可以被分为一个前台进程组和多个后台进程组
3.如果一个会话有一个控制终端,则它有一个前台进程组,其他的都为后台进程组
4.无论何时键入终端的中断键(ctrl + c),都会将中断信号发给前台进程组的所有进程。
5.无论何时键入终端的退出键(ctrl + \),都会将退出信号发给前台进程组的所有进程
6.如果终端接口检测到调制解调器(或网络)已经断开,则将挂断信号发送至控制进程。(一个会话的首进程就是控制进程。)

ps:有时候不管标准输入和标准输出是否重定向,程序都要与控制终端交互。保证程序与控制终端交互的方法是open文件/dev/tty。如果程序没有控制终端,则打开失败。

5.1 获取终端的前台进程组

可以通过tcgetpgrd函数获取控制终端的前台进程组。

头文件:
#include <unistd.h>
函数原型:
pid_t tcgetpgrp(int fd);
传入参数:
fd:控制终端的文件描述符
返回值:
成功返回控制终端的前台进程组 失败返回-1

5.2 设置中断的前台进程组

如果进程有一个控制终端,则该进程可以通过tcsetpgrp来修改前台进程组。

头文件:
#include <unistd.h>
函数原型:
int tcsetpgrp(int fd, pid_t pgrp);
传入参数:
fd:控制终端的文件描述符
pgrp:需要设置为前台进程组的进程组ID
返回值:
成功返回0 失败返回-1

5.3 获取会话首进程的进程ID

可以通过tcgetsid获取会话首进程的进程ID.
头文件:

#include <termios.h>
函数原型:
pid_t tcgetsid(int fd);
传入参数:
fd:控制终端的文件描述符
返回值:
成功返回会话首进程的进程ID

六、什么是作业控制?

首先来了解什么是作业,可以简单的把作业理解成进程组。所谓的作业控制,指的就是在一个终端上,允许启动多个进程组(作业),并控制哪个进程组可以访问终端(作为前台进程组),哪些作业在后台运行的功能。
系统是否支持作业控制,需要有如下的前提条件:

1.支持作业控制的shell
2.内核中的终端驱动程序必须支持作业控制
3.内核必须提供对某些作业控制信号的支持

作业控制的特征如下:

1.只有前台作业的进程接收来自终端的信号
2.只有前台作业接收终端的输入。(如果后台作业试图获取终端的输入,终端驱动程序会向该后台作业发出一个特定信号SIGTTIN,该信号会停止此后台作业,而shell则将这种情况通知给用户,然后用户可以用命令将此后台作业转为前台作业,这时候它就能获取终端的输入了。)
3.可以允许或禁止后台作业向终端输出。(允许的话后台作业的输出会打印在中断。禁止的话,当后台作业向终端输出时,终端驱动程序检测到后,会向该后台进程发出SIGTTOUT的信号,该信号会阻塞该进程。这时候可以将该后台进程转为前台进程,那它就能向终端输出了。)

参考资料:

1.https://www.cnblogs.com/JohnABC/p/4079669.html
2.《UNIX环境高级编程(第3版) (史蒂文斯
(W.Richard Stevens) 拉戈 (Stephen A.Rago))(Z-Library)》


http://www.mrgr.cn/news/55511.html

相关文章:

  • 使用finalshell远程ssh连接ubuntu
  • 【机器学习】VQ-VAE(Vector Quantized Variational Autoencoder)
  • 用Spring AI 做智能客服,基于私有知识库和RAG技术
  • ​1553B总线的前景和应用
  • 全能型选手视频播放器VLC 3.0.21 for Windows 64 bits支持Windows、Mac OS等供大家学习参考
  • 如何通过博通官网下载VMware最新补丁
  • Python内置函数classmethod()详解
  • 有没有好用的待办事项清单软件? —— 一文带你了解
  • 企业成本与时间管理新策略 低代码自动化显身手
  • 《深度学习》模型的部署、web框架 服务端及客户端案例
  • 提升小学语文教学效果的思维导图方法
  • 完爆YOLOv10!Transformer+目标检测新算法性能无敌,狠狠拿捏CV顶会!
  • HTML 实例/测验之HTML 基础一口气讲完!(o-ωq)).oO 困
  • 《Frida Android SO逆向深入实践》书评——清华大学出版社
  • Electron兼容win7版本的打包流程
  • 周报 | 24.10.14-24.10.20文章汇总
  • AI 编译器学习笔记之八 -- Python基础学习
  • 从0到1构建Next.Js项目SSG和SSR应用
  • Effective C++ | 读书笔记 (一)
  • MySQL-31.索引-结构
  • 二叉树习题其二Java【力扣】【算法学习day.9】
  • web前端第一次作业
  • 多线程
  • JAVA Maven的简单介绍
  • go 包相关知识
  • 龙芯+FreeRTOS+LVGL实战笔记(新)——12按键输入初步