一、进程组
每个进程除了有一个进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。通常它们与同一个作业相关联,可以接收来自同一终端的各种信号。每一个进程组有一个唯一的进程组ID。每一个进程组可以有一个组长进程。组长进程的标识符,其进程组ID等于其进程ID。组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要是某个进程组中一个进程存在,则该进程组存在,这与其组长是否终止无关。
检测代码:
注释:
‘&’:表示将进程组放在后台执行
进程:3756 3757 3758
组长:3756,进程组当中的第一个进程
ps选项:
a:不仅列当前用户的进程,也列出所有其他用户的进程
x:表示不仅列出有控制终端的进程,也列出无控制终端的进程
j:表示列出与作业控制相关的信息
二、作业
shell分前后台来控制的不是进程而是作业(job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,shell可以有一个前台作业和任意多个后台作业,这称为作业控制。
作业与进程组的区别:果作业中的某个进程创建了子进程,则该子进程不属于作业。 一旦作业运行结束 ,shell,就把自己提到前台(子进程还在,可是子进程不属于作业),如果原来的前台进程还存在(如果那个子进程还没终止)它自动变为后台组。
我们应该重新认识一下,在前台新起作业,shell是无法运行的,因为它被提到了后台。但是如果前台进程退出,shell就又被提到前台,所以可以继续接收用户输入。
三、会话
会话(session)是一个后多个进程组的集合。一个会话可以有一个控制终端。这通常是登陆在其上的终端设备(在终端登陆情况下)或伪终端设备(在网络登陆情况下)。建立和控制终端连接的会话首进程被称为控制进程。一个会话中的几个进程组可被分为一个前台进程组或多个后台进程组。所以,一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。
检测代码:
注释:
SID:会话id,2552,三个进程都属于同一个进程组,同一个会话
查看2552:
bash!也就是我们的解释器,会话首进程,而且三个进程的父进程都是bash。
多打开几个终端,可以发现,每打一个终端,就好比打开一个会话。
四、守护进程的创建
守护进程也称为精灵进程(Daemon),是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程来实现的。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其他进程都是在用户登陆或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受运行结束和注销的影响,它们一直在运行着,这种进程有一个名称叫守护进程(Daemon)。
创建守护进程最关键的一步是调用setsid函数创建一个新的session,并成为session Leader。
代码:
#include<unistd.h>
pid_t setsid(void);
该函数调用成功时返回新创建的session的id(其实就是当前进程的id),出错返回-1.
注意:调用这个函数之前,当前进程不允许是进程的Leader,否则该函数的返回值为-1。要保证当前进程不是进程的Leader也很容易,只要先fork在调用setsid就行了。fork创建的子进程和父进程在同一个进程中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid就不会有问题了。
程序代码:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void mydaemon(void){
int fd0;
pid_t pid;
struct sigaction sa;
//调用umask将文件创建模式屏蔽字设置为0;
umask(0);
//调用fork,副进程退出(exit)
//如果该命令作为简单的shell命令启动的,呢父进程终止使得shell认为命令已经执行完毕
//保证子进程不是一个进程组的组长进程
if(pid = fork()<0){
perror("fork");
}else if(pid>0){
//父进程
exit(0);
}
//调用setsid创建一个新的会话
setsid();
//忽略SIGCHLD信号
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
//注册子进程的退出信号
if(sigaction(SIGCHLD,&sa,NULL)<0){
return;
}
//将目前目录更改成根目录
if(chdir("/")<0){
printf("child dir error\n");
return;
}
//关闭不再需要的文件描述符,,或者重定向至/dev/null
close(0);
fd0 = open("/dev/null",O_RDWR);
dup2(fd0,1);
dup2(fd0,2);
}
//主函数
int main(){
mydaemon();
while(1){
sleep(1);
}
}