进程组/作业/会话
1.进程组
每个进程除了有一个进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。通常,他们与同一个作业相关联,可以接收来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。每个进程组都可以有一个组长进程。组长进程的标识是,其进程组ID等于进程ID。组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要某个进程中一个进程存在,则该进程组就存在,这与组长进程是否终止无关。
2.作业
Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成,Shell可以运行一个前台作业和任意多个后台作业,这称为作业控制。
作业与进程组的区别:如果作业中的某个进程又创建了子进程,则子进程不属于作业但属于进程组。
一旦作业运行结束,Shell就把自己提到前台(子进程还在,但是子进程不属于作业),如果原来的前台进程还存在(如果这个紫禁城还没终止),它自动变为后台进程组。
栗子:
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}else if(id == 0){
while(1){
printf("child(%d)# I am running!\n",getpid());
sleep(1);
}
}else{
int i = 5;
while(i){
printf("parent(%d)# I am going to dead ...%d\n",getpid(),i--);
sleep(1);
}
}
return 0;
}
运行之后发现,前台新起了一个作业,包含父子两进程,shell到了后台。
5s之类的shell是无法接受任何命令的,因为在后台。
但是5s后父进程退出了,子进程还在运行,但此时输入的命令Shell是可以处理的。
说明此时Shell是前台作业。也就是说子进程被提到了后台。Kill -9 杀掉即可。
3.会话
会话(Session)是一个或多个进程组的集合。一个会话可以有一个控制终端。这通常是登录到其上的终端设备(终端登录)或者伪终端(通过网络登录)设备。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的几个进程组可被分为一个前台进程以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(话首进程),一个前台进程组和任意多个后台进程组。
作业控制
Session与进程组"Shell可以同时运行一个前台进程和任意多个后台进程"其实是不全面的,现在我们来研究更复杂的情况。事实上,Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Procsee Group)。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以同时运行一个前台作业和任意多个后台作业,这称为作业控制(Job Control)。
作业控制有关的信号
(1)&
在运行程序是后面加上&,表示将当前进程放到后台运行。
(2)jobs
jobs命令可以查看当前有哪些作业。fg命令可以将某个作业提至前台运行,如果该作业的进程组正在后台运行,则提至前台运行,如果该作业处于停止状态,则给进程组的每个进程发SIGCONT信号使它继续运行。参数%1表示将第一个作业提至前台运行。
(3)bg
bg命令可以让某个停止的作业在后台继续运行,也需要给该作业的进程组的每个进程发SIGCONT信号。
守护进程
守护进程也称精灵进程(Deamon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如:ftp服务器,ssh服务器,Web服务器,http等。同时,守护进程完成许多系统任务。比如,作业归化进程crond等。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其他进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程(守护进程)不受用户登录和注销的影响,它们一直在运行着。这种进程就叫做守护进程(Deamon)。
ps axj 命令查看系统中的进程。参数a表示不仅列出当前用户的进程,也列出所有其他的用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。
凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。
在COMMAND一列用[]括起来的名字表示内核线程,这些线程在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用以k开头的名字,表示Kernel。
守护进程通常采用以d结尾的名字,表示Deamon。
创建守护进程
创建守护进程的最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。
#include <unistd.h>
pid_t setsid(void);
该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1.
注意,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1.要保证当前函数不是进程组的Leader也很容易,只要先fork之后再调用setsid就可以了,fork创建的子进程和父进程在同一个进程组里,进程组的Leader必然是该组的第一个进程,所以子进程不可能会是该组的第一个进程,在子进程中调用setsid就不会有问题了。
成功调用该函数的结果是:
创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
如果当前进程原本有一个控制终端,则他失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端任然是打开的,任然可以读写,但只是一个普通的打开文件而不是控制终端了。
栗子:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
void mydaemon(){
int i;
int fd0;
pid_t pid;
struct sigaction sa;
umask(0);//1.调用umask将文件模式创建屏蔽字设置为0.
//2.调用fork,福进程退出(exit)
//如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为命令已经执行完毕
//保证子进程不是一个会话的组长进程
if((pid = fork())<0){
perror("fork");
}else if(pid >0){
exit(0);
}
setsid();//3.调用setsid创建一个新会话
sa.sa_handler = SIG_IGN;//4.忽略SIGCHLD信号
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if(sigaction(SIGCHLD,&sa,NULL)<0){//注册子进程退出忽略信号
return;
}
//注意,再次fork,终止父进程,保持子
//进程不是话首进程,从而保证后续不会在和其他终端关联
//不是必须的步骤
if((pid = fork())<0){
printf("fork error!\n");
return;
}else if(pid != 0){
exit(0);
}
if(chdir("/")<0){//5.将当前工作目录更改为根目录
printf("child dir error\n");
return;
}
close(0);
fd0 = open("/dev/null",O_RDWR);
dup2(fd0,1);
dup2(fd0,2);
}
int main(){
mydaemon();
while(1){
sleep(1);
}
}