对进程组、作业和会话进行说明。
1、进程组
每个进程除了有一个进程ID外,还属于一个进程组,有一个唯一的进程组ID。进程组是一个或多个进程的集合,每个进程组都可以有一个组长进程。通常,它们与同一作业相关联,可以接收来自同一终端的各种信号。组长进程可以创建一个进程组,创建该组中的进程,然后终止。一般第一个进程就是该进程的组长进程。组长进程的标识是其进程组的ID等于其进程的ID。只有在某个进程组中一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。
函数getpgrp()和getgpid()可以得到进程的进程组ID。getpgrp()=getpgid(0);
原型如下:
一个进程可以为自己或子进程设置进程组ID。setpgid()加入一个现有的进程组或创建一个新进程组
原型:#include<unistd.h> int setpgid(pid_t pid, pid_t pgid); //返回值:成功则返回0,出错则返回-1
eg:父进程改变自身和子进程的组id
2、作业
shell分前后台来控制的不是进程而是作业(Job)或进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成,Shell可以运行一个前台作业和多个后台作业,这成为作业控制。
作业与进程组的区别:如果作业中的某个进程又创建了子进程,则子进程不属于作业。一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程还存在(如果这个子进程还没终止),它自动变为后台进程组。如下例子,父进程先退出,子进程后退出:
运行结果如下:
在父进程退出后,子进程自动变为后台进程组,通过“ctrl+C”等指令无法终止程序,需要用kill -9 进程号,强制杀死进程。
3、会话
会话(Session)是一个或多个进程组的集合。一个会话可以有一个控制终端。这通常是登陆到其上的终端设备(在终端登陆情况下)或伪终端设备(在网络登陆情况下)。会话开始于用户登录,终止于用户退出,在此期间所有进程都属于这个会话期。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的几个进程组可被分为一个前台进程组以及一个后台进程组。
1 $ proc1 | proc2 &
2 $ proc3 | proc4 | proc5
其中proc1与proc2属于同一个后台进程组,proc3,proc4和proc5属于同一个前台进程组,
Shell本身属于一个单独的进程组。这些进程组的控制终端相同,它们同属于一个会话,当用户在控制终端输⼊入特殊的控制键(如Ctrl+C,产生SIGINT,Ctrk+\,产生SIGQUIT,Ctrl+Z,产生SIGTSTP),内核发送相应的信号给前台进程组中的所有进程。
建立新会话:setsid()函数
原型:#include<unistd.h> pid_t setsid(void); //返回值:若成功则返回进程组ID,若出错则返回-1
1、该调用进程是组长进程,则出错返回。先调用fork, 父进程终止,子进程调用
2、该调用进程不是组长进程,则创建一个新会话
•该进程变成新会话首进程(session header)
•该进程成为一个新进程组的组长进程。
•该进程没有控制终端,如果之前有,则会被中断。具体也就是说:如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
会话ID:会话首进程的进程组ID,获取会话ID用getsid()函数,原型如下:
#include<unistd.h> pid_t getsid(pid_t pid); //返回值:若成功则返回会话进程的进程组ID,若出错则返回-1
4、精灵进程(或守护进程)
守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond等。下面我们用ps axj命令查看系统中的进程。
凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。
PPID:bash。PGID:进程组id。SID:会话id。STAT:状态,其中s+表示前台sleep;s不带+表示后台sleep。
在COMMAND一列用[]括起来的名字表示内核线程,这些线程在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用以k开头的名字,表Kernel。init进程, 内核调用,负责内核启动后启动Linux系统。udevd负责维护/dev目录下的设备文件。acpid负责电源管理。syslogd负责维护/var/log下的日志文件。可以看出,守护进程通常采用以d结尾的名字,表示Daemon。
Linux系统启动时会启动很多系统服务进程,例如“网络登录过程”的inetd,这些系统服务进程没有控制终端,不能直接和用户交互。其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受用户登录注销的影响,它们一直在运行着。这种进程有一个名称叫守护进程(Daemon)。
守护进程没有终端限制。让某个进程不因为用户、终端或者其他的变化而受到影响,那么就必须把这个进程变成一个守护进程。
创建守护进程
创建守护进程最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader(新会话首进程)。
注意:调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进程组的Leader也很容易,只要先fork再调用setsid就行了。fork创建的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid就不会有问题了。
创建守护进程的步骤
1、在后台运行。调用fork,父进程退出(exit)。所有工作在子进程中进行,形式上脱离了控制终端。
原因:1)如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕。2)保证子进程不是一个进程组的组长进程。
2、脱离控制终端,登录会话和进程组。调用setsid在子进程中创建一个新会话。
setsid会导致:
1)调用进程成为新会话的首进程。 2)调用进程成为一个进程组的组长进程 。3)调用进程没有控制终端。(再次fork一次,保证daemon进程,之后不会打开tty设备
3、将当前工作目录更改为根目录。用chdir()函数进行,更改目录防止占用可卸载的文件系统,也可以换成其他路径。
4、调用umask将文件模式创建屏蔽字设置为0。目的:防止继承的文件创建屏蔽字拒绝某权限,增加守护进程灵活性。
5、关闭不在需要的文件描述符。继承的打开文件不会用到,浪费系统资源,无法卸载;getdtablesize();返回所有文件的文件描述符表的项数,即该进程打开的文件数目。
6、忽略SIGCHLD信号。
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。由于会话期组长进程终止时会向会话期中的其他进程发送一个SIGHUP信号而照成其他进程终止,所以要忽略该信号。
7、禁止进程重新打开控制终端
现在,fork后进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
if(pid=fork())
exit(0);//结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
具体实现如下: