APUE 第 10 章信号讲完,回过头来看一下第 9 章的进程关系。终端登录和网络登录部分,我们只讲 Linux 系统的。

一、终端登录

我记得我们讲 root 登录设置时有提到,参看:C语言再学习 -- Ubuntu 12.04 root用户登录设置
其中,使用Ctrl+Alt+F1 进入纯命令模式,重新依据上面的设置,重启 OK!

Ctrl+Alt切换到Windows

Ctrl+Alt+F7 退出纯命令模式。

我们来看一下,进入纯命令模式界面:

第二种方法也可以通过 Ctrl+Alt+T 打开终端。
那什么是 Linux 的终端登录?
参看:Linux 终端及终端登录过程简介
1、当系统自举时,内核创建进程 ID 为 1 的进程,也就是 init 进程。init 进程使系统进入多用户模式。init 进程根据配置文件 /etc/inittab 确定需要打开哪些终端,对每一个允许登录的终端设备,init 调用一次 fork,它所生成的子进程则执行 getty(exec)程序。(不同操作系统配置文件可能不同)。
2.getty 为终端设备调用 open 函数,以读写方式将终端打开。然后 getty 输出“longin:”之类的信息,并等待用户键入用户名。
3.当用户键入用户名后,getty 工作完成。然后调用 login 程序:
execle(“/bin/login”,”login”,”-p”,username,(char *)0,envp)
4.密码验证无误后,login 将切换目录到用户的 home 目录,改变该终端设备的权限,login 进程改变为登录用户 ID 并调用改用户的登录 shell:execl(“/bin/sh”,”-sh”,(char *)0)
5.登录shell读取其启动文件 (Bourne shell) 和 Korn shell。
从 getty 开始 exec 到 login,再 exec 到 bash,其实都是同一个进程,因此控制终端没变,文件描述符 0、1、2 也仍然指向控制终端。由于 fork 会复制 PCB 信息,所以由 Shell 启动的其它进程也都是如此。
扩展:Linux进程间关系之终端与终端登录
人家讲的比我详细多了。。

二、网络登录

linux 网络登录使用扩展的因特网服务守护进程 xinetd,它等待大多数网路连接。
作为系统启动的一部分,init 调用一个 shell,使其执行 shell 脚本 /etc/rc。由此 shell 脚本启动一个守护进程
 xinetd。一旦此 shell 脚本终止,xinetd 的父进程就变成 init。xinetd 等待 TCP/IP 连接请求到达主机,而当一个连接请求到达时,它执行一次 fork,然后生成的子进程 exec 适当的程序。
就比如我们之前有讲过的 telnet 远程控制,,参看:Hi3516A开发--环境搭建工具
重点是,当通过终端或网络登录时,我们得到了一个登录 shell,其标准输入、标准输出和标准错误要么连接一个终端设备,要么连接一个伪终端设备上。

三、进程组 ID

我们之前讲过进程 ID、父进程 ID。参看:UNIX再学习 -- 函数 fork 和 vfork
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回:调用进程的进程 ID
pid_t getppid(void); 返回:调用进程的父进程 ID
uid_t getuid(void); 返回:调用进程的实际用户 ID
uid_t geteuid(void); 返回:调用进程的有效用户 ID
gid_t getgid(void); 返回:调用进程的实际组 ID
gid_t getegid(void); 返回:调用进程的有效组 ID
注意,这些函数都没有出错返回  

(1)示例说明

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>  int main (void)
{  printf ("pid = %d\n", getpid ());  printf ("ppid = %d\n", getppid ());  printf ("uid = %d\n", getuid ());  printf ("euid = %d\n", geteuid ());  printf ("gid = %d\n", getgid ());  printf ("egid = %d\n", getegid ());  return 0;
}
输出结果:
pid = 3028
ppid = 2808
uid = 0
euid = 0
gid = 0
egid = 0
//每次执行结果都不一定相同  

每个进程除了有一个进程 ID 之外,还属于一个进程组。我们讲信号的时候,有提到过。那时不知道啥意思。

参看:UNIX再学习 -- 信号
进程组是一个或多个进程的集合。通常,它们是在同一作业中结合起来的,同一进程组中的各进程接收来自同一终端的各种信号。每个进程组有一个唯一的进程组 ID。进程组 ID 类似于进程 ID — 它是一个正整数,并存放在 pid_t 数据类型中。函数 getpgrp 返回调用进程的进程组 ID。
#include <unistd.h>int setpgid(pid_t pid, pid_t pgid);pid_t getpgid(pid_t pid);pid_t getpgrp(void);                 /* POSIX.1 version */pid_t getpgrp(pid_t pid);            /* BSD version */int setpgrp(void);                   /* System V version */int setpgrp(pid_t pid, pid_t pgid);  /* BSD version */

可以看出系统不同用的函数也不同,建议使用 POSIX.1 规定中的无参数 getprgp() 函数

示例说明:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>  int main (void)
{  printf ("pid = %d\n", getpid ());  printf ("ppid = %d\n", getppid ());  printf ("uid = %d\n", getuid ());  printf ("euid = %d\n", geteuid ());  printf ("gid = %d\n", getgid ());  printf ("egid = %d\n", getegid ());  printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());return 0;
}
输出结果:
pid = 3497
ppid = 3401
uid = 0
euid = 0
gid = 0
egid = 0
-----------------
pgrp = 3497
每个进程组有一个组长进程。组长进程的进程组 ID 等于其进程 ID
进程组组长可以创建一个进程组,常见该组中的进程,然后终止。只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。从进程组创建开始到其中最后一个进程离开为止的时间区间称为进程组的生命期。某个进程组中的最后一个进程可以终止,也可以转移到另一个进程组。
进程调用 setpgid 可以加入一个现有的进程组或者创建一个新进程组。
setpgid 函数将 pid 进程的进程组 ID 设置为 pgid。如果这两个参数相等,则由 pid 指定的进程编程进程组组长。如果 pid 是 0,则使用调用者的进程 ID。另外,如果 pgid 是 0,则由 pid 指定的进程 ID 用作进程组 ID。
示例说明:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>int main (void)
{  printf ("pid = %d\n", getpid ());  printf ("ppid = %d\n", getppid ());  printf ("uid = %d\n", getuid ());  printf ("euid = %d\n", geteuid ());  printf ("gid = %d\n", getgid ());  printf ("egid = %d\n", getegid ());  printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());if (-1 == (setpgid (0, getppid ())))perror ("setpgid"), exit (1);printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());return 0;
}
输出结果:
pid = 3593
ppid = 3401
uid = 0
euid = 0
gid = 0
egid = 0
-----------------
pgrp = 3593
-----------------
pgrp = 3401

四、 会话

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

举个例子:
如上例中,在一个会话中有 3 个进程组。通常是由 shell 的管道将几个进程编程一组的。

1、进程调用 setsid 函数建立一个新会话。

#include <unistd.h>
pid_t setsid(void);
返回值:成功返回进程组 ID;失败返回 -1

函数解析:

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

(1)该进程变成新会话的会话首进程(会话首进程是创建该会话的进程)。此时,该进程是新会话中的唯一进程。
(2)该进程成为一个新进程组的组长进程。新进程组 ID 是该调用进程的进程 ID。
(3)该进程没有控制终端。如果在调用 setsid 之前进程有一个控制终端,那么这种联系也被切断。

示例说明:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>int main (void)
{  printf ("pid = %d\n", getpid ());  printf ("ppid = %d\n", getppid ());  printf ("uid = %d\n", getuid ());  printf ("euid = %d\n", geteuid ());  printf ("gid = %d\n", getgid ());  printf ("egid = %d\n", getegid ());  printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());
/*if (-1 == (setpgid (0, getppid ())))perror ("setpgid"), exit (1);printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());*/if(setsid() == -1)perror ("setsig"), exit (1);return 0;
}
输出结果:
pid = 3738
ppid = 3401
uid = 0
euid = 0
gid = 0
egid = 0
-----------------
pgrp = 3738
setsig: Operation not permitted

示例解析:

该示例中进程组长 ID 就是进程 ID。看一下条件,如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新会话。那 setsig 肯定出错了。
如果将注释去掉,改变它的进程组 ID,则 setsig 会建立新会话。且返回进程组 ID。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>int main (void)
{  printf ("pid = %d\n", getpid ());  printf ("ppid = %d\n", getppid ());  printf ("uid = %d\n", getuid ());  printf ("euid = %d\n", geteuid ());  printf ("gid = %d\n", getgid ());  printf ("egid = %d\n", getegid ());  printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());if (-1 == (setpgid (0, getppid ())))perror ("setpgid"), exit (1);printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());pid_t sig = setsid ();if(sig == -1)perror ("setsig"), exit (1);printf ("sig = %d\n", sig);return 0;
}
输出结果:
pid = 3771
ppid = 3401
uid = 0
euid = 0
gid = 0
egid = 0
-----------------
pgrp = 3771
-----------------
pgrp = 3401
sig = 3771

2、getsid 函数返回会话首进程的进程 ID

#include <unistd.h>
pid_t getsid(pid_t pid);
返回值:成功返回会话首进程的进程组 ID;失败返回 -1

(1)函数解析

如若 pid 是 0,getsid 返回调用进程的会话首进程的进程组 ID。
出于安全方面的的考虑,一些实现有如下限制:如若 pid 并不属于调用者所在的会话,那么调用进程就不能得到该会话首进程的进程组 ID。

(2)示例说明

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>int main (void)
{  printf ("pid = %d\n", getpid ());  printf ("ppid = %d\n", getppid ());  printf ("uid = %d\n", getuid ());  printf ("euid = %d\n", geteuid ());  printf ("gid = %d\n", getgid ());  printf ("egid = %d\n", getegid ());  printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());//pid 为 0printf ("sid = %d\n", getsid (0));return 0;
}
输出结果:
pid = 3809
ppid = 3401
uid = 0
euid = 0
gid = 0
egid = 0
-----------------
pgrp = 3809
sid = 3401

五、控制终端

一个会话可以有一个控制终端。这通常是终端设备(在终端登录情况下)或伪终端设备(在网络登录情况下)。
建立与控制终端连接的会话首进程被称为控制进程。
一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。
如果一个会话有一个控制终端,则它有一个前台进程组,其他进程组为后台进程组。
无论何时键入终端的中断键(常常是 Delete 或 Ctrl+C),都会将中断信号发送至前台进程组的所有进程。
无论何时键入终端的退出键(常常是 Ctrl+\),都会将退出信号发送至前台进程组的所有进程。
如果终端接口检测调制解调器(或网线)已经断开连接,则将挂断信号发送至控制进程(会话首进程)。
通常我们不必担心控制终端,登录时,将自动建立控制终端。

六、作业控制

作业控制是 BSD 在 1980 年左右增加的一个特性。它允许在一个终端上启动多个作业(进程组),它控制哪一个作业可以访问该终端以及哪些作业在后台运行。作业控制要求以下 3 种形式的支持。
(1)支持作业控制的 shell
(2)内核中的终端驱动程序必须支持作业控制
(3)内核必须提供对某些作业控制信号的支持
后台运行是用 ‘&’比如:   #./a.out &   
比如 shell 脚本里使用,./thttpd.sh &  
实际上有 3 个特殊字符可使终端驱动程序产生信号,并将它们发送至前台进程组,它们是:
中断字符(一般采用Delet 或 Ctrl+C)产生 SIGINT
退出字符(一般采用Ctrl+\)产生 SIGQUIT
挂起字符(一般采用 Ctrl+Z)产生 SIGTSTP
可以用 bg 使其后台继续运行,fg 使其转入前台运行。

七、shell 执行程序

让我们检验一下 shell 是如何执行程序的,以及这与进程组、控制终端和会话等概念的关系。
shell 编程有讲,参看:UNIX再学习 -- shell编程

使用 ps 命令,首先使用不支持作业控制的、在 Solaris 上运行的经典 Bourne shell。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>    int main (void)
{    pid_t pid, pr;    pid = fork ();    if  (pid == -1)    perror ("fail to fork"), exit (1);    else if (pid == 0)    {    printf ("这是子进程 pid = %d", getpid ());    printf ("父进程的 ppid = %d\n",  getppid ());    }    else    {    pr = wait (NULL);  //while (1);    sleep (10); //可以保证子进程先被调度    printf ("这是父进程 ppid = %d\n", getpid ());    }    return 0;
}    实验:
在一个终端执行 ./a.out
# ./a.out
这是子进程 pid = 3951父进程的 ppid = 3950
(十秒后)
这是父进程 ppid = 3950  在另一个终端,查看进程信息
# ps -C a.out -o pid,ppid,pgid,sid,comm PID  PPID  PGID   SID COMMAND3950  3868  3950  3868 a.out

UNIX再学习 -- 进程关系相关推荐

  1. UNIX再学习 -- 守护进程(转)

    参看:守护进程 一.什么是守护进程 守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程.它是一个生存期较长的进程,通常独立于控制 ...

  2. UNIX再学习 -- 内存管理

    C 语言部分,就一再的讲内存管理,参看:C语言再学习 -- 再论内存管理  UNIX.Linux 部分还是要讲,足见其重要. 一.存储空间布局 1.我们先了解一个命令 size,继而引出我们今天要讲的 ...

  3. UNIX再学习 -- 线程

    终于要讲到线程部分,线程和进程让人够头痛的内容. 一.线程概念 老样子,我们还是按我们讲进程时的方式说起,参看:UNIX再学习 -- 进程环境 首先需要了解下,什么是线程. Linux 下的线程,可能 ...

  4. UNIX再学习 -- 信号

    终于讲到信号部分,很多比较重要的应用程序都需处理信号.第 9 章需要先了解信号机制再看,所以先跳过不讲.现在开始详解信号. 一.信号概念 信号是提供异步事件处理机制的软件中断. 这些异步事件可能来自硬 ...

  5. UNIX再学习 -- 用户 ID 和组 ID

    用户 ID和组 ID 的内容已经在好几章中出现过了.之前都没有讲到,现在放到一起总结. 一.用户 ID 和 组 ID 回顾 1.我们在APUE 第 4.6.8 章,都有涉及到. 其中我们用到的地方: ...

  6. UNIX再学习 -- exec 函数族

    我们在讲,文件I/O的时候,简单提到过 exec 函数,讲到 vfork 的时候,也有用到.下面我们来详细介绍下它. 参看:UNIX再学习 -- 文件I/O  参看:UNIX再学习 -- 函数 for ...

  7. UNIX再学习 -- exit 和 wait 系列函数

    我们一开始讲进程环境时,就有提到了.进程有 8 种方式使进程终止. 其中 5 种为正常终止,它们是: (1)在 main 函数中执行 return (2)调用 exit 函数,并不处理文件描述符,多进 ...

  8. UNIX再学习 -- 死磕内存管理

    malloc/free简化实现:malloc 和 sbrk 关系:虚拟内存机制. 一个内存管理 C 语言部分讲,UNIX部分讲,Linux部分还讲,死磕到底!! 一.mallc/free简化实现 上篇 ...

  9. UNIX再学习 -- shell编程

    UNIX环境高级编程看了三章,遇到不少重定向等shell命令.本想到Linux时再讲,看来有必要提前了.之前有看过一本<嵌入式Linux软硬件开发详解>这本书里有简单介绍了一部分shell ...

最新文章

  1. 从HelloWorld看Knative Serving代码实现
  2. 【sox】使用sox增加混响效果
  3. 页面导航的基础与深入
  4. 1.2开发文档简读,了解全貌.mp4
  5. ecs服务器数据迁移_如何非常方便地从Windows文件服务器把数据完整地迁移到ONTAP Select...
  6. 基于JAVA+SpringMVC+Mybatis+MYSQL的高校后勤管理系统
  7. CCFA中国国际零售创新大会,观远数据用智能分析驱动零售决策
  8. 机器 – 程序 – 人 (2)
  9. Foxconn Core Concept
  10. HashMap遍历有序性问题——map.entrySet()的无序性
  11. 有道云 语法高亮_antlr语法增强使用
  12. 北师范《计算机导论》在线作业,福建师范大学17年8月《计算机导论》作业考核试题答案材料...
  13. 计算机设置密码打印机无法共享,win7设置开机密码后无法连接共享打印机
  14. “绿水青山就是金山银山”
  15. 制作Linux的优盘(usb)启动盘
  16. 一个好的学习方法——MAS 学习法
  17. 洛谷 P2530 [SHOI2001]化工厂装箱员
  18. java.nio.file.NoSuchFileException异常解决
  19. 关闭Delphi2010出现以下bpl错误,解决方案.
  20. 仿网易云音乐新版详情页(沉浸式状态栏,上滑隐藏)

热门文章

  1. C# 程序执行时间差
  2. Android开发 - 设置DialogFragment全屏显示
  3. c++虚函数的作用是什么?
  4. 清北学堂模拟赛d3t2 b
  5. POJ 2420 A Star not a Tree?【爬山法】
  6. 新个人项目-- 拼图游戏
  7. Surviving the Release Version
  8. windows 解决 Go下载包失败 设置代理
  9. CentOS x64上Matlab R2015b的镜像安装方法与卸载
  10. 【转】linux su和sudo命令的区别——百度知道