Android C++系列:Linux进程间关系

Posted 轻口味丿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android C++系列:Linux进程间关系相关的知识,希望对你有一定的参考价值。

1. 终端

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

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

文件与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;

硬件驱动程序负责读写实际的硬件设备,比如从键盘读入字符和把字符输出到显示器, 线路规程像一个过滤器,对于某些特殊字符并不是让它直接通过,而是做特殊处理,比如在 键盘上按下Ctrl-Z,对应的字符并不会被用户程序的read读到,而是被线路规程截获,解释成SIGTSTP信号发给前台进程,通常会使该进程停止。线路规程应该过滤哪些字符和做哪些特殊处理是可以配置的。
![image-20211202195521501](/Users/shenjunwei/Documents/Doc/vegetable/source/_note/编程语言/写给android开发的C++教程/_v_images/Android C++系列:Linux进程间关系/image-20211202195521501.png)

1.1 网络终端

虚拟终端或串口终端的数目是有限的,虚拟终端(字符控制终端)一般就是/dev/tty1∼/ dev/tty6六个,串口终端的数目也不超过串口的数目。然而网络终端或图形终端窗口的数目 却是不受限制的,这是通过伪终端(Pseudo TTY)实现的。一套伪终端由一个主设备(PTY Master)和一个从设备(PTY Slave)组成。主设备在概念上相当于键盘和显示器,只不过 它不是真正的硬件而是一个内核模块,操作它的也不是用户而是另外一个进程。从设备和上 面介绍的/dev/tty1这样的终端设备模块类似,只不过它的底层驱动程序不是访问硬件而是 访问主设备。网络终端或图形终端窗口的Shell进程以及它启动的其它进程都会认为自己的 控制终端是伪终端从设备,例如/dev/pts/0、/dev/pts/1等。下面以telnet为例说明网络登 录和使用伪终端的过程。
![image-20211202195639422](/Users/shenjunwei/Documents/Doc/vegetable/source/_note/编程语言/写给Android开发的C++教程/_v_images/Android C++系列:Linux进程间关系/image-20211202195639422.png)
如果telnet客户端和服务器之间的网络延迟较大,我们会观察到按下一个键之后要过几秒钟才能回显到屏幕上。这说明我们每按一个键telnet客户端都会立刻把该字符发送给服务 器,然后这个字符经过伪终端主设备和从设备之后被Shell进程读取,同时回显到伪终端从 设备,回显的字符再经过伪终端主设备、telnetd服务器和网络发回给telnet客户端,显示 给用户看。也许你会觉得吃惊,但真的是这样:每按一个键都要在网络上走个来回!

2. 进程组

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

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;
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
组长进程可以创建一个进程组,创建该进程组中的进程,然后终止,只要进程组中有一 个进程存在,进程组就存在,与组长进程是否终止无关
进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组) 一个进程可以为自己或子进程设置进程组ID

int setpgid(pid_t pid, pid_t pgid)
  • 如改变子进程为新的组,应在fork后,exec前使用
  • 非root进程只能改变自己创建的子进程,或有权限操作的进程

setpgid()加入一个现有的进程组或创建一个新进程组,如改变父子进程为新的组

#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)); // 返回组id sleep(5);
printf("Group ID of child is changed to %d\\n",getpgid(0));
exit(0);

sleep(1);
setpgid(pid,pid); // 父进程改变子进程的组id为子进程本身
sleep(5);
printf("parent process PID is %d\\n",getpid());
printf("parent of parent process PID is %d\\n",getppid());
printf("Group ID of parent is %d\\n",getpgid(0));
setpgid(getpid(),getppid()); // 改变父进程的组id为父进程的父进程
printf("Group ID of parent is changed to %d\\n",getpgid(0));
return 0;

3. 会话

pid_t setsid(void)
  1. 调用进程不能是进程组组长,该进程变成新会话首进程(session header)
  2. 该进程成 为一个新进程组的组长进程。
  3. 需有root权限(ubuntu不需要)
  4. 新会话丢弃原有的控制终端,该会话没有控制终端
  5. 该调用进程是组长进程,则出错返回
  6. 建立新会话时,先调用 fork, 父进程终止,子进程调用
pid_t getsid(pid_t pid)

pid为0表示察看当前进程session ID
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;

4.总结

本文介绍了linux终端的进程知识,本地终端、网络终端的虚拟终端原理;进程组概念getpgid、getpgrp;进程会话概念setsid等。

以上是关于Android C++系列:Linux进程间关系的主要内容,如果未能解决你的问题,请参考以下文章

Android C++系列:Linux守护进程

方法教程 | C++爬虫:进程 · 全家桶

进程间关系

Linux环境进程间通信:管道及有名管道

一篇文章了解相见恨晚的 Android Binder 进程间通讯机制

Linux 进程间通信(IPC)总结