1、进程组
每个进程除了有一个进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。通常,它们与同一个作业相关联,可以接受来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。每个进程组都可以有一个组长进程。组长进程的标识是,其进程组ID等于其进程ID。组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要在某个进程组中的一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。
2、作业
Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成。Shell可以运行一个前台作业和任意多个后台作业,这称为作业控制。
作业与进程组的区别:如果作业中的某个进程又创建了子进程,则子进程不属于作业。
一旦作业运行结束,Shell就把自己提到前台(子进程还在,可是子进程不属于作业),如果 原来的前台进程还存在(如果这个子进程还没终止),它自动变为后台进程组。
我们在重新理解一下,在前台新起作业,shell是无法运行,因为它被提到了后台。但是如果前台进程退出,shell就又被提到了前台,所以可以继续接受用户输入。
3、会话
会话是一个或多个进程组的集合。一个会话可以有一个控制终端。这通常是登陆到其上的终端设备(在终端登陆情况下)或伪终端设备(在网络登陆情况下)。建立与控制终端连接的会话首进程被称为控制进程。一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。
4、守护进程
守护进程也称精灵进程,是运行在后台的一种特殊进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如,ftp服务器,ssh服务器,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond等。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程(守护进程)不受用户登录注销的影响,它们一直运行着。这种进程有一个名称叫守护进程(Daemon)。
下面我们来看一看守护进程的代码。
#include <stdio.h>
#include <singnal.h>
#include <unisted.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
void mydaemon(void)
{
int i;
int fd0;
pid_t pid;
struct sigaction sa;
umask(0); //调用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 ) //将当前工作目录更改为根目录
{
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);
}
}