Ⅰ. 终端
用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(Controlling Terminal
),fork
会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。
默认情况下(没有重定向),每个进程的标准输入、标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。
以输入队列为例,从键盘输入的字符经线路规程(line discipline)过滤后进入输入队列,用户程序以先进先出的顺序从队列中读取字符,一般情况下,当输入队列满的时候再输入字符会丢失,同时系统会响铃警报。终端可以配置成回显(Echo)模式,在这种模式下,输入队列中的每个字符既送给用户程序也送给输出队列,因此我们在命令行键入字符时该字符不仅可以被程序读取,我们也可以同时在屏幕上看到该字符的回显
网络终端或图形终端窗口的数目却是不受限制的,这是通过伪终端(Pseudo TTY)实现的。一套伪终端由一个主设备(PTY Master)和一个从设备(PTY Slave)组成。主设备在概念上相当于键盘和显示器,只不过它不是真正的硬件而是一个内核模块,操作它的也不是用户而是另外一个进程。从设备的底层驱动程序不是访问硬件而是访问主设备。
查看终端对应的设备文件名
#include <unistd.h>
char *ttyname(int fd);
#include <stdio.h>
#include <unistd.h>
/**
* @brief 查看标准输入、标准输出、标准错误输出的设备文件名
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[]) {
printf("fd %d:%s\n", 0, ttyname(0)); //标准输入
printf("fd %d:%s\n", 1, ttyname(1)); //标准输出
printf("fd %d:%s\n", 2, ttyname(2)); //标准错误输出
return 0;
}
Ⅱ. 作业
Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以同时运行一个前台作业和任意多个后台作业,这称为作业控制(Job Control)
比较命令:
%ps -o pid,ppid,pgrp,session,tpgid,comm | cat
PID PPID PGRP SESS TPGID COMMAND
7690 7689 7690 7690 14886 zsh
14886 7690 14886 7690 14886 ps
14887 7690 14886 7690 14886 cat
- ps和cat是zsh的子进程,因此它们的ppid就是zsh的pid
- ps和cat同属于一个process group,因此它们的组pgrp一样且为ps的pid(因为ps先执行)
- bash自己就是一个process group,因此pgrp 就是它自己的pid
- 这三个进程属于同一个session,Leader是zsh,因此是它的pid
- 当前前台在运行的是ps和cat,它们又是一个组的,因此前台组id(tpgid)就是它们的pgrp
%ps -o pid,ppid,pgrp,session,tpgid,comm | cat &
PID PPID PGRP SESS TPGID COMMAND
7690 7689 7690 7690 7690 zsh
15468 7690 15468 7690 7690 ps
15469 7690 15468 7690 7690 cat
- &就是放到后台运行
- 当前前台在运行的是bash,因此前台组id(tpgid)就是bash的pgrp
Ⅲ. 守护进程
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受用户登录注销的影响,它们一直在运行着。这种进程有一个名称叫守护进程(Daemon)
创建守护进程最关键的一步是调用setid
函数创建一个新的Session,并成为 Session Leader。
#include <sys/types.h>
#include <unistd.h>
pid_t setsid(void);
该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。注意,调用这个函数之前,当前进程不允许是进程组(process group)的Leader,否则该函数返回-1。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[]) {
pid_t pid = fork(); //子进程肯定不是leader
if (pid < 0) {
perror("fork");
exit(-1);
}
if (pid) {
//父进程不需要,可以结束了
exit(0);
}
pid_t sid = setsid();
printf("new session id is %d\n", sid);
//修改守护进程的工作目录
if (chdir("/") < 0) {
perror("chdir");
exit(-2);
}
//关闭打开着的文件描述符(原session的)
close(0);
open("dev/null", O_RDWR); //返回值是0,因为刚把文件描述符0关闭了,而open会返回最小的可用的文件描述符
dup2(0, 1); //将标准输出重定向到dev/null
dup2(0, 2);
//让守护进程一直运行着
while (1) {
sleep(1);
}
return 0;
}
评论 (0)