引言
终端登陆
首先看看终端登录过程,这个过程是BSD的,但是Linux基本相同:
- 管理员创建/etc/ttys文件,每个终端设备有一行表明设备名和getty启动参数。
- 系统自举创建init进程,init进程读取/etc/ttys文件,对每个终端fork并且exec gettty.
- getty打开终端设备,这样就映射到了文件描述符0,1,2.然后初始化环境,exec login.
- login基本功能就是读取用户密码,然后验证。如果失败的话,那么直接退出。
- 失败的话,那么init会接收到失败的信号,然后重新fork一个getty出来。
如果login成功的话,那么会执行下面这些动作:
- 更改目录为当前用户home目录
- chown终端权限所有权,使登录用户为所有者
- 将终端设备访问权限修改称为用户读写
- 调用setgid和initgroups设置进程的组id
- 设置环境变量,然后exec shell
bash启动之后会读取.bash_profile.这样用户最终的话,通过终端连接到终端设备驱动程序, 而终端设备的读写被映射成为0,1,2文件描述符被shell使用。用户操作终端的话,会被终端设备驱动程序接收到, 而对于shell来说,这些操作就是直接从0,1,2读取和写入数据。对于Linux来说,唯一不同的就是,对于 gettty启动过程参数不是在文件/etc/ttys而是在/etc/inittab里面描述的。
网路登录
网络登录基本上和终端登录相同。不过init进程并不一开始就开辟多个getty进程,因为通过网络进程没有办法 估计有多少个用户登录,同时需要处理网络传输。init进程启动的是inted这个进程,inted监听某些登录端口, 假设用户通过telnet登录,inetd监听23端口。如果用户请求到达的话,那么会启动一个telnetd这个服务,好比getty, 只不过telnetd连接的是一个伪终端设备驱动程序,但是文件描述符依然是0,1,2.但是telnetd并不会直接exec login. 因为如果login执行失败的话,那么没有办法重新启动telnetd(注意现在login失败的话,那么父进程是init而不是telnetd). 所以telnetd通过fork一次,子进程exec login.如果子进程失败的话,那么父进程可以感知到。如果成功的话,那么和终端登录一样。
进程组
进程组是一个或者是多个进程的集合,通常和一个作业相关联,可以接受来自同一终端的各种信号。每个进程组有一个唯一的进程组ID, 也有一个组长进程,组长进程标识是组长进程id==进程组id.或者进程组id可以通过
pid_t getpgrp();
pid_t getpgid(pid_t pid); //如果pid==0,那么就是调用进程进程组id
进程组的存在和进程组长是否终止没有关系,进程组的生命周期是最后一个进程消亡或者是离开了进程组。
也可以使用:
int setpgid(pid_t pid,pid_t pgid);
将pid的进程组id设置为pgid.pid==0的话,那么使用调用进程的pid,如果pgid==0的话,那么将pid设置为pgid.
会话
会话是一个或者是多个进程组集合。进程可以通过调用
pid_t setsid();
来建立一个新会话。如果调用此函数的进程不是进程组长的话,那么就会创建一个新的会话。那么此时会:
- 该进程称为会话首进程(session leader).
- 该进程称为进程组组长.
- 该进程没有控制终端,即使之前有控制终端那么这种联系也会断掉。
终端控制
会话和进程组有一些其他特性,包括下面这些:
- 一个会话持有一个控制终端(controlling terminal),可以是终端设备也可以是伪终端
- 建立与控制终端连接的会话首进程被称为控制进程(controlling process).
- 一个会话有多个进程组,允许存在多个后台进程组(backgroup process group)和一个前台进程组(foregroup process group).
- 键入终端的中断键(Ctrl+C)会发送中断信号给前台进程组所有进程。
- 键入终端的退出键(Ctrl+)会发送退出信号给前台进程组所有进程。
- 终端或者是网络断开的话,那么会将挂断信号发送给会话首进程。
通常来说我们不必关心控制终端,因为在登录shell时候已经自动建立控制终端了。
查看当前shell使用的控制终端可以:
ubuntu@VM-188-113-ubuntu:~/Code/apue$ ps
PID TTY TIME CMD
2433 pts/0 00:00:00 ps
27286 pts/0 00:00:00 bash
通过控制终端可以设置前台进程组和获取前台进程组信息,以及获取会话首进程。设置了前台进程组的话, 这样终端设备驱动程序就可以知道终端输入和输出信号送到何处了。
作业控制
一个作业是几个进程的集合,通常是一个进程管道,
作业控制,指控制当前正在运行的进程的行为,也称为进程控制。是Shell的一个特性,使用户能在多个独立进程间进行切换。
命令或快捷键 | 功能说明 |
---|---|
cmd& | 该命令在后台运行 |
Ctrl+d | 终止一个正在前台运行的进程(含有正常含义) |
Ctrl+c | 终止一个正在前台运行的进程(含有强行含义) |
Ctrl+z | 挂起一个正在前台运行的进程 |
jobs | 显示后台作业和被挂起的进程 |
bg | 重新启动一个挂起的作业,并在后台运行 |
fg | 把一个在后台运行的作业放到前台运行 |
常用的作业标识符
标识符 | 说明 |
---|---|
%N | 第N号作业 |
%S | 以字符串S开头的被命令行调用的作业 |
%?S | 包含字符串S的被命令行调用的作业 |
%+ | 默认作业(前台最后结束的作业,或后台最后启动的作业),等同于%% |
%- | 第二默认作业 |
孤儿进程组
僵尸进程:先于父进程终止,但是父进程没有对其进行善后处理(获取终止子进程有关信息,释放它仍占有的资源)。消灭僵尸进程的唯一方法是终止其父进程。
孤儿进程:该进程的父进程先于自身终止。其特点是PPID=1(init进程的ID)。一个孤儿进程可以自成孤儿进程组。
进程属于一个进程组,进程组属于一个会话,会话可能有也可能没有控制终端
一个其父进程已终止的进程称为孤儿进程(orphan process),这种进程由 init 进程”收养”
孤儿进程组(orphaned process group)
- POSIX.1 将孤儿进程组定义为:该组中每个成员的父进程要么是该组中的一个成员,要么不是该组所属会话(session)的成员。
- 进程组成为孤儿进程组后,POSIX.1 要求内核向新的孤儿进程组中处于停止状态的每一个进程发送挂断信号(SIGHUP),接着又向其发送继续运行信号(SIGCONT)。
参考
[1] https://dirtysalt.github.io/apue.html#orgheadline98
[2] http://www.cnblogs.com/custa/archive/2010/11/04/1868666.html
[3] http://blog.chinaunix.net/uid-15084954-id-190350.html
[4] http://lesca.me/archives/process-relationship.html (个人推荐)