一.
1.进程组
每个进程除了有一个ID,还属于一个进程组。进程组是一个或多个进程的集合。通常,它们与同一作业相关联,可以接收来自同一终端的各种信号。每个进程组有唯一的一个进程组ID,每个进程组都有一个组长进程。组长进程的标识是,其进程组ID等于其进程ID。
2.作业
Shell 分前后台来控制的不是进程而是作业或进程组。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成, Shell可以运行一个前台作业和任意多个后台作业,这称为作业控制。
作业与进程组的区别:如果作业中的某个进程又创建了子进程,则子进程不属于作业。一
旦作业运行结束, Shell就把其提到前台,如果原来的前台进程还存在(如果这个子进
程还没终止),它自动变为后台进程组。
3.会话
会话(Session)是一个或多个进程组的集合。一个会话可以有一个控制终端。建立与控制终端连接的会话被称为控制进程。一个会话中的一个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会话进程),一个前台进程组和任意后台进程组。
1 $ proc1 | proc2 &
2 $ proc3 | proc4 | proc5
其中proc1与proc2属于同一个后台进程组, proc3, proc4和proc5属于同一个前台进程组,Shell本身属于一个单独的进程组。这些进程组的控制终端相同,它们同属于一个会话,当用户在控制终端输入特殊的控制键(如Ctrl+C,产生SIGINT,Ctrk+\,产生SIGQUIT,Ctrl+Z,
产生SIGTSTP), 内核发送相应的信号给前台进程组中的所有进程。
二.终端概念
在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端 (Controlling Terminal),控制终端是保存在PCB中的信息,我们知道fork会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。默认情况下(没有重定向),每个进程的标准输入、标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。此外在控制
终端输入特殊的控制键可以给前台进程发信号,例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。
每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终 端。事实上每个终端设备
都对应多个不同的设备文件,/dev/tty提供了一个通用的接口 ,一个进程要访问它的控制终端
既可以通过/dev/tty也可以通过该终端设备所对应的设备文件来访问。 ttyname函数可以由
文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备而不能是任意文件。
下面我们通过实验看一下各种不同的终端所对应的设备文件名。
查看终端对应的设备
重开一个终端:
终端登录过程:
init创建子进程,进行程序替换到一个getty进程,输入用户名之后,执行login,在输入密码进行验证,验证成功之后再替换到bash。
大致情况:从getty开始exec到login在exec到shell。
终端设备的输入输出缓冲区:
三.作业控制
Session与进程组
$ proc1 | proc2 &
$ proc3 | proc4 | proc5
其中proc1和proc2属于同一个后台进程组,proc3、 proc4、 proc5属于同一个前台进程组,Shell进
程本身属于一个单独的进程组。这些进程组的控制终端相同,它们属于同一个Session。当用户在控制终端输入特殊的控制键(例如Ctrl-C)时,内核会发送相应的信号(例如SIGINT)给前台进程组的所有进程。
各进程、进程组、 Session的关系如下图所示:
从Session和进程组的度重新来看登录和执命令的过程。例子:
这个作业由ps和cat两个进程组成,在前台运行。从PPID列可以看出这两个进程的父进程是
bash。 从PGRP列可以看出,bash在id为6994的进程组中,这个id等于bash的进程id,所以它是进程组的Leader,而两个子进程在id为8762的进程组中,ps是这个进程组的Leader。从SESS可以看出三个进程都在同一Session中,bash是Session Leader。从TPGID可以看出,前台进程组的id是8762,也就是两个子进程所在的进程组。
四.守护进程
守护进程也被称为精灵进程,是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它与终端无关,自成一个会话,不与终端进行关联。所以不会在标准输出上打印东西,也不能从后台到前台。
创建守护进程:
创建守护进程最关键的一步是调用setsid函数创建立一个新的Session,并成为Session Leader。
该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。注意,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进程组的Leader也很容易,只要先fork再调setsid就可以了。 fork创建的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调setsid就不会有问题了。
1. 调umask将文件模式创建屏蔽字设置为0.
2. 调fork,父进程退出(exit) 。原因: 1)如果该守护进程是作为一条简单的shell命令
启动的,那么父进程终止使得shell认为该命令已经执行完毕。 2)保证子进程不是这个
进程组的组长进程。
3. 调setsid创建一个新会话。 setsid会导致: 1)调进程成为新会话的首进程。 2)调进程成为一个进程组的组长进程 。 3)调进程没有控制终端。(再次fork一次,保证
daemon进程,之后不会打开tty设备)
4. 将当前工作目录更改为根目录。
5. 关闭不在需要的文件描述符。
6. 其他:忽略SIGCHLD信号。
自己的守护进程:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<signal.h>
5 void my_daemon()
6 { umask(0);
7 if(fork()>0)
8 {
9 exit(0);
10 }
11
12 setsid();
13 chdir("/");
14 close(0);
15 close(1);
16 close(2);
17 signal(SIGCHLD,SIG_IGN);
18 }
19 int main()
20 {
21 my_daemon();
22 while(1)
23 {;}
return 0;
}