文章目录

  • 六、进程间关系
    • 终端
      • **tty**
      • 网络终端
    • 进程组
    • 会话
      • setsid()创建新会话函数
      • getsid()获取会话sid函数
    • 守护进程
      • 守护进程创建步骤

六、进程间关系

终端

​ 在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(Controlling Terminal),在讲进程时讲过,控制终端是保存在PCB中的信息,而我们知道fork会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。默认情况下(没有重定向),每个进程的标准输入、标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。信号中还讲过,在控制终端输入一些特殊的控制键可以给前台进程发信号,例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。

​ 终端,在前面的理解终端就是shell,这并不准确,在以前,终端表示的是设备,如打字机,所以终端的英文简写为tty(teletype,电传打字机),在设备文件夹/dev下有一个文件为tty,即代表的是终端设备文件。那么shell是什么呢?shell本身是命令解释器的统称,一般Linux默认的shell是bash,以前将shell称为终端,真正的终端并非是shell,shell只是终端和内核之间翻译命令的进程。

​ 在Unix系统开发时,为了实现多用户系统,就必须要有多个终端(用户与计算机沟通的硬件设备),然而当时电脑非常昂贵,Unix创作者就选择电传打字机这种即有键盘(输入设备),又有打字设备(好比显示器,输出设备),关键是便宜的硬件设备作为终端,通过串口实现多终端多用户访问Unix操作系统下的计算机,所以终端就是早期的tty(电传打字机)。到现在,电传打字机早已经躺在博物馆里面了,现代操作系统指的终端是终端模拟器

对于 Shell,终端模拟器会「假装」成一个传统终端设备;而对于现代的图形接口,终端模拟器会「假装」成一个 GUI 程序。一个终端模拟器的标准工作流程是这样的:

1.捕获你的键盘输入;
2.将输入发送给 Shell(Shell 会认为这是从一个真正的终端设备输入的);
3.拿到命 Shell 的输出结果;
4.调用图形接口(比如 X11),将输出结果渲染至显示器。

终端模拟器有很多,这里就举几个经典的例子:

  • GNU/Linux:gnome-terminal、Konsole;
  • macOS:Terminal.app、iTerm2;
  • Windows:Win32 控制台、ConEmu 等。

​ 部分终端模拟器都是在图形用户界面 (GUI) 中运行的,但是也有例外。

​ 比如在 GNU/Linux 操作系统中,按下 Ctrl + Alt + F1,F2…F6 等组合键可以切换出一个黑不溜秋的全屏终端界面,不过不要被它们唬着了,虽然它们并不运行在图形界面中,但其实它们也是终端模拟器的一种。
​ 这些全屏的终端界面与那些运行在 GUI 下的终端模拟器的唯一区别就是它们是 由操作系统内核直接提供的。这些由内核直接提供的终端界面被叫做 虚拟控制台 (Virtual Console),而上面提到的那些运行在图形界面上的终端模拟器则被叫做 终端窗口 (Terminal Window)。除此之外并没有什么差别。

​ 当然了,因为终端窗口是跑在图形界面上的,所以如果图形界面宕掉了那它们也就跟着完蛋了。这时候你至少还可以切换到 Virtual Console 去救火,因为它们由内核直接提供,只要系统本身不出问题一般都可用。

shell 是一个程序,它接受从键盘输入的命令,然后把命令传递给操作系统去执行。

Linux登录终端和shell过程:

init-->fork-->exec-->getty-->用户输入帐号-->login-->输入密码-->exec-->shell

​ getty到输入密码这个过程是登录终端的过程,getty是向终端模拟器申请开启一个新的tty(这里是第一次开启该终端需要登录,如果是打开前面已经开启过的终端,就不需要登录过程,好比第二次按Ctrl + Alt + F1,第二次打开F1终端就直接进入shell),再登录,登录成功后该新终端创建成功,自动加载shell进程。

  • 在图形界面系统下,首先进入系统,锁屏界面登录用户,在图形桌面下点开terminal(窗口终端),这时会弹出一个窗口,里面自动打开的就是当前用户登录的shell,因为前面登录了系统,所以省略了登录终端的过程。图形界面下打开终端,会省略登录过程,直接将本操作系统的用户登录进去了。
  • 如果使用Ctrl + Alt + F1-F6打开新终端,这打开的并不是窗口终端,而是操作系统直接提供的终端(字符控制终端),需要登录在开启shell。

who命令查看当前系统用户及启动的终端

jiaojian@KyLin:~$ who
jiaojian tty7         2022-03-16 15:39 (:0)
jiaojian tty3         2022-03-16 15:40
jiaojian pts/1        2022-03-16 16:21 (172.20.117.1)

如上,终端有tty7和tty3和pts/1,其中tty7为该系统默认的图像界面终端,tty3为自己开启的终端,pts/s为远程登录开启的虚拟终端。

tty

​ 文件与I/O中讲过,每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端。事实上每个终端设备都对应一个不同的设备文件,/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端既可以通过/dev/tty也可以通过该终端设备所对应的设备文件来访问。ttyname函数可以由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备而不能是任意文件。下面我们通过实验看一下各种不同的终端所对应的设备文件名。

打印当前终端名
#include <unistd.h>
#include <stdio.h>
int main()
{printf("fd 0: %s\n", ttyname(0));printf("fd 1: %s\n", ttyname(1));printf("fd 2: %s\n", ttyname(2));return 0;
}
0,1,2为终端文件描述符,不能用普通文件描述符。

​ 在程序中出现的文件“/dev/tty”,其含义就是当前终端,文件描述符,0,1,2指向的文件就是“/dev/tty”,而“/dev/tty”是一个类似于泛型指针的文件,其指向的是当前用户使用的终端。所以在不同终端内使用该文件,其指向的就是本终端,如在终端1上运行进程A,A内的“/dev/tty”指向的就是终端1(进程运行的终端),进程的PCB内记录了自己的控制终端。

是否每一个进程都需要一个终端呢?

​ 不是,在系统内有大量的进程的tty为?,即很多进程是没有控制终端的,命令ps -ajx可以查看进程以及其控制终端。

​ 硬件驱动程序负责读写实际的硬件设备,比如从键盘读入字符和把字符输出到显示器,线路规程像一个过滤器,对于某些特殊字符并不是让它直接通过,而是做特殊处理,比如在键盘上按下Ctrl-Z,对应的字符并不会被用户程序的read读到,而是被线路规程截获,解释成SIGTSTP信号发给前台进程,通常会使该进程停止。线路规程应该过滤哪些字符和做哪些特殊处理是可以配置的。

网络终端

​ 虚拟终端或串口终端的数目是有限的,虚拟终端(字符控制终端)一般就是/dev/tty1/dev/tty6六个,串口终端的数目也不超过串口的数目。然而网络终端或图形终端窗口的数目却是不受限制的,这是通过伪终端(Pseudo TTY)实现的。一套伪终端由一个主设备(PTYMaster)和一个从设备(PTY Slave)组成。主设备在概念上相当于键盘和显示器,只不过它不是真正的硬件而是一个内核模块,操作它的也不是用户而是另外一个进程。从设备和上面介绍的/dev/tty1这样的终端设备模块类似,只不过它的底层驱动程序不是访问硬件而是访问主设备。网络终端或图形终端窗口的Shell进程以及它启动的其它进程都会认为自己的控制终端是伪终端从设备,例如/dev/pts/0、/dev/pts/1等。下面以telnet为例说明网络登录和使用伪终端的过程。

如上图为远程登录终端开启以及输入命令操作bash的过程。

​ 如果telnet客户端和服务器之间的网络延迟较大,我们会观察到按下一个键之后要过几秒钟才能回显到屏幕上。这说明我们每按一个键telnet客户端都会立刻把该字符发送给服务器,然后这个字符经过伪终端主设备和从设备之后被Shell进程读取,同时回显到伪终端从设备,回显的字符再经过伪终端主设备、telnetd服务器和网络发回给telnet客户端,显示给用户看。也许你会觉得吃惊,但真的是这样:每按一个键都要在网络上走个来回!

进程组

一个或多个进程的集合,进程组ID是一个正整数。 用来获得当前进程程组ID的函数:F

pid_t getpgid(pid_t pid)[参数]
pid为指定进程pid,查询指定进程的组pgid,如果pid为0,查询当前进程的组pgidpid_t getpgrp(void)
查询当前进程组的pgid[返回值]
成功返回获取的组pgid,失败返回-1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{pid_t pid;if ((pid = fork()) < 0) {perror("fork");exit(1);}else if (pid == 0) {printf("child process PID is %d\n",getpid());printf("Group ID is %d\n",getpgrp());printf("Group ID is %d\n",getpgid(0));printf("Group ID is %d\n",getpgid(getpid()));exit(0);}sleep(3);printf("parent process PID is %d\n",getpid());printf("Group ID is %d\n",getpgrp());return 0;
}

组长进程标识:其进程组ID==其进程ID,即pid与pgid相同的进程为组长进程

组长进程可以创建一个进程组,创建该进程组中的进程,然后终止,只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关,并且组长进程终止,原来的gid也不会改变,与组长进程消亡没有关系。

进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组),该进程组就消亡了。

一个进程可以为自己或子进程设置进程组ID

int setpgid(pid_t pid, pid_t pgid)
[参数]pid为需要操作的进程pid。pgid为要加入的目标进程组pgid。如果pid=1000,pgid=1000,相当于给进程1000创建了一个进程组,并且自己是组长如果pid=1000,pgid=2000,那么相当于将进程1000加到pgid为2000的进程组内
[返回值]成功返回0;失败返回-1。
如改变子进程为新的组,应在fork后,exec前使用
非root进程只能改变自己创建的子进程,或有权限操作的进程

会话

会话是一个或多个进程组的集合,以用户登录系统为例,可能存在如图所示的情况。

setsid()创建新会话函数
pid_t setsid(void)
[返回值]成功创建新会话返回新会话sid;失败返回-1。

setsid()函数使用条件与特点:

1.调用进程不能是进程组组长,该进程变成新会话首进程(session header) ,体现了进程组与会话的严格层次关系,进程组是包含在会话内的,是会话的组成单位。

2.该进程成为一个新进程组的组长进程进程组pgid为调用进程pid,且该进程变为新会话首进程,新会话sid也为该进程pid。

3.需有root权限才能执行setsid()创建新会话(ubuntu不需要)

4.新会话丢弃原有的控制终端,新会话没有控制终端

5.如果setsid()的调用进程是组长进程,则出错返回-1

6.建立新会话时,先调用fork, 父进程终止,子进程可调用setsid()创建新会话

如果允许进程组组长迁移到新的会话,而进程组的其他成员仍然在老的会话中,那么,就会出现同一个进程组的进程分属不同的会话之中的情况,这就破坏了进程组和会话的严格的层次关系了。

创建新的会话最大的作用是脱离控制终端

组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。

getsid()获取会话sid函数
pid_t getsid(pid_t pid)
[参数]pid为需要查询其所属会话的进程pid如果pid = 0表示获取当前进程的sid
[返回值]成功返回获取的会话sid失败返回-1

ps ajx命令查看系统中的进程。参数 a 表示不仅列当前用户的进程,也列出所有其他用户的进程,参数 x 表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数 j 表示列出与作业控制相关的信息。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{pid_t pid;if ((pid = fork())<0) {perror("fork");exit(1);} else if (pid == 0) {printf("child process PID is %d\n", getpid());printf("Group ID of child is %d\n", getpgid(0));printf("Session ID of child is %d\n", getsid(0));sleep(10);setsid(); // 子进程非组长进程,故其成为新会话首进程,且成为组长进程。该进程组id即为会话进程printf("Changed:\n");printf("child process PID is %d\n", getpid());printf("Group ID of child is %d\n", getpgid(0));printf("Session ID of child is %d\n", getsid(0));sleep(20);exit(0);}return 0;
}

守护进程

Daemon(精灵)进程,是Linux中的后台服务进程,生存期较长的进程,通常独立于控制终端(即没有控制终端)并且周期性地执行某种任务或等待处理某些事件发生。

守护进程创建步骤

1. 创建子进程,父进程退出所有工作在子进程中进行形式上脱离了控制终端
2. 在子进程中创建新会话setsid()函数使子进程完全独立出来,脱离控制
3. 改变当前目录为根(/)目录chdir()函数防止占用可卸载的文件系统(U盘)也可以换成其它路径
4. 重设文件权限掩码umask()函数防止继承的文件创建屏蔽字拒绝某些权限增加守护进程灵活性
5. 关闭文件描述符(0,1,2文件描述符原本是指向终端的,现在脱离终端了,就回收0,1,2)继承的打开文件不会用到,浪费系统资源,无法卸载
6. 开始执行守护进程核心工作
7. 守护进程退出处理

实例:

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>void daemonize(void)
{pid_t pid;/** 成为一个新会话的首进程,失去控制终端*/if ((pid = fork()) < 0) {perror("fork");exit(1);} else if (pid != 0) /* parent */exit(0);setsid();/** 改变当前工作目录到/目录下.*/if (chdir("/") < 0) {perror("chdir");exit(1);}/* 设置umask为0 */umask(0);/** 重定向0,1,2文件描述符到 /dev/null,因为已经失去控制终端,再操作0,1,2没有意义.*/close(0);open("/dev/null", O_RDWR);//上一步关闭了0描述符,那么这一步打开null文件,则将描述符0分配给null(先打开文件的文件描述符为该进程文件描述符表中未使用的最小的那个,这里是0)dup2(0, 1);dup2(0, 2);
}int main(void)
{daemonize();//创建守护进程的函数(自定义)while(1); /* 在此循环中可以实现守护进程的核心工作,while里面就是守护进程具体要干是事情了。*/
}

运行这个程序,它变成一个守护进程,不再和当前终端关联。用ps命令看不到,必须运行带x参数的ps命令才能看到。另外还可以看到,用户关闭终端窗口或注销也不会影响守护进程的运行。

如下为创建一个守护进程,该进程每隔5s在/tmp/dameon.log中写入当前时间

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
void mydaemon(void)
{pid_t pid_f, i;if((pid_f = fork()) < 0){perror("foek():");exit(1);}if(pid_f > 0){printf("father_pid = %d,and child_pid = %d\n", getpid(), pid_f);exit(1);}pid_t sid_d;printf("daemon_pid = %d\n", getpid());sid_d = setsid();if(chdir("/") < 0){perror("chdir:");exit(1);}printf("new sid = %d\n",sid_d);umask(0);close(0);open("/dev/null",O_RDWR);dup2(0,1);dup2(0,2);
}int do_daemon(int fd)
{time_t t;char *time_1;char arr[30] = {0};time(&t);time_1 = strcat(ctime(&t),"\n");strcpy(arr, time_1);if(write(fd, arr, 25) < 0){perror("write:");}return 0;}int main()
{mydaemon();int fd;fd = open("/tmp/daemon.log", O_RDWR | O_CREAT | O_APPEND, 0777);while(1){do_daemon(fd);sleep(5);}return 0;
}

Linux系统编程06 --进程间关系相关推荐

  1. Linux系统编程(四)--进程间关系

    文章目录 1 进程扇与进程链 2 进程组 2.1 概念 2.1 进程组的创建与设置 3 会话 3.1 概念 3.2 创建会话 4 控制终端.前台进程组与后台进程组 5 后台进程组与控制终端 6 孤儿进 ...

  2. 【Linux系统编程】进程概述和进程号

    00. 目录 文章目录 00. 目录 01. 进程概述 02. 进程状态 03. 进程控制块 04. 进程号 05. 进程号相关函数 06. 案例实战 07. 附录 01. 进程概述 我们平时写的 C ...

  3. linux系统编程之进程(八):守护进程详解及创建,daemon()使用

    linux系统编程之进程(八):守护进程详解及创建,daemon()使用 一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等 ...

  4. vbs结束进程代码_物联网学习教程—Linux系统编程之进程控制

    Linux系统编程之进程控制 一.结束进程 首先,我们回顾一下 C 语言中 continue, break, return 的作用: continue: 结束本次循环 break: 跳出整个循环,或跳 ...

  5. Linux系统编程之进程与线程控制原语对比

    Linux系统编程之进程与线程控制原语对比 进程 线程 fork pthread_create exit pthread_exit wait pthread_join kill pthread_can ...

  6. 【Linux系统编程】进程替换:exec 函数族

    00. 目录 文章目录 00. 目录 01. exec函数族 02. 参考示例 2.1 execl函数示例 2.2 execv函数示例 2.3 execlp() 或 execvp()函数示例 2.4 ...

  7. 【Linux系统编程】进程退出和回收进程资源

    00. 目录 文章目录 00. 目录 01. 进程退出函数 02. 进程退出讨论 03. 回收进程资源 04. 附录 01. 进程退出函数 #include <stdlib.h>void ...

  8. linux系统编程之进程概念(操作系统---管理,进程创建,进程状态,进程优先级, 环境变量,程序地址空间,进程O(1)调度方法)

    系统编程: 进程概念->进程控制->基础IO->进程间通信->进程信号->多线程 进程概念 冯诺依曼体系结构----现代计算机硬件体系结构 冯诺依曼体系结构----现代计 ...

  9. 【Linux系统编程】进程内存模型

    00. 目录 文章目录 00. 目录 01. Linux可执行程序结构 02. Linux进程结构 03. 存储类型总结 04. 附录 01. Linux可执行程序结构 在 Linux 下,程序是一个 ...

最新文章

  1. CVPR 2020 | CentripetalNet:目标检测新网络,COCO 48 % AP超现所有Anchor-free网络
  2. 大巧不工-WEB前端设计修炼之道pdf
  3. Sqlserver 游标的写法记录
  4. 白话生成对抗网络 GAN,50 行代码玩转 GAN 模型!【附源码】
  5. 金蝶云星空操作手册_金蝶国际CFO林波谈云业务:金蝶云·星空预计今年可以实现盈利...
  6. Python的继承多态
  7. Boost::Bind 基础
  8. datatable删除行、列
  9. matlab 找到数组中第一个不连续点_MATLAB新手入门篇1(基础)
  10. Rog14 Win10系统迁移新的三星固态硬盘
  11. 算法笔记(一)(教你系统学习基础算法)
  12. html 特殊符号怎么打出来,特殊符号怎么打出来_特殊符号图案大全-太平洋电脑网...
  13. element-UI中分页组件显示英文的解决方案
  14. 解决Chrome账户无法同步
  15. 计算机磁盘管理看不到盘符,Win10系统本地磁盘盘符不见了的解决方法
  16. wps如何在html中在线浏览器,wps如何设置表格内链接用电脑默认浏览器打开
  17. R语言作图——violin plot(小提琴图)
  18. 简析交换机三种端口模式
  19. 关于立象OS-214TT条码打印的一些问题
  20. Jackson 转换中 关于 浮点数处理的问题

热门文章

  1. 项目:基于Flask的任务清单管理页面
  2. 对于XL改进方案的初步分析
  3. 实时数据流分析的“极速前进”
  4. AMD全新ZEN CPU真身首曝 八核心双模块设计
  5. Android OkHttp + Glide + RecyclerView + ButterKnife流行框架的综合实现
  6. [洛谷P2482][SDOI2010]猪国杀
  7. 《java语言程序设计 基础篇》原书第10版 PDF版 梁勇著 戴开宇译
  8. 一个事物两个方面的对比举例_《介绍一种事物》课堂实录
  9. 掌门1对1微服务体系Solar第1弹:全链路灰度蓝绿发布智能化实践
  10. C#调用windows api 实现打印机控制