进程组
每个进程除了有⼀个进程ID之外,还属于⼀个进程组。进程组是⼀个或多个进程的集合。通常,它们与同⼀作业相关联,可以接收来⾃同⼀终端的各种信号。每个进程组有⼀个唯⼀的进程组ID。每个进程组都可以有⼀个组⻓进程,组长的pid与pgid相同。组⻓进程可以创建⼀个进程组,创建该组中的进程,然后终⽌。只要在某个进程组中⼀个进程存在,则该进程组就存在,这与其组⻓进程是否终⽌⽆关。
删除13295进程后,发现该进程组依然存在:
作业
shell分前后台来控制的不是进程,而是作业或进程组。一个作业可以由多个进程组组成,shell可以运行一个前台作业和任意多个后台作业,这就称为作业控制。
如果作业中的某个进程又创建了子进程,则子进程不属于作业,但是属于进程组。一但前台作业运行完之后,shell就把自己提到前台,如果前台的子进程还没终止,他就自动变成后台进程。
shell进程本身属于一个单独的进程组,同时他也是会话首进程(控制进程)。
会话
会话是一个或多个进程组的集合。一个会话可以有一个控制终端。建立与控制终端连接的会话首进程被称为控制进程。这个控制进程通常是bash。
一个会话中的进程组可以被分为一个前台进程组和一个或多个后台进程组。所以一个会话中应该包含控制进程(会话首进程),一个前台进程和任意后台进程。内核发送的信号,会被前台进程组中的所有进程接收。
只要会话中还有任意一个进程组,则会话就存在。
进程间关系
多个进程可以构成进程组,多个进程组可以构成会话。一次只能有一个前台作业或进程组,和任意多个后台作业或进程组。
守护进程(daemon)
守护进程也叫作精灵进程,它是在后台运行的一种特殊进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。
linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。这些系统服务进程不受用户登录注销的影响,他们一直运行着,这些系统服务进程就是守护进程。
守护进程的特点:
- 独立于控制终端
- 父进程是1号进程(init),所以守护进程是孤儿进程
- 守护进程是独自成组进程的
- 守护进程是会话的会话首进程,或者是一个会话中的某个进程组
- 守护进程的运行不受用户登录和注销的影响
为什么需要守护进程?
控制终端因为某些原因会发送一些信号,接受到信号的进程去执行这些信号的默认处理动作会导致进程退出。这就使得进程不能正常的处理某些业务,所以就需要像守护进程这样接受不到信号的进程,让进程独立于控制终端,执行某些任务或处理某些事件。
创建守护进程:
创建守护进程最关键的一步是调用setsid函数创建一个新的session,并成为会话首进程。
#include<unistd.h> pid_t setsid();
//返回值:这个函数成功时返回新创建的会话的id,也就是当前进程的id,出错返回-1。
注意:调用这个函数之前,当前进程不允许是进程组的组长,否则就返回-1。所以我们一般fork出一个子进程,然后杀死父进程,则这个子进程一定不是组长,然后让这个子进程调用setsid成为守护进程。
成功的结果:
- 创建一个新的会话,当前进程自成会话首进程。
- 创建一个新的进程组,当前进程成为进程组的组长。
- 如果当前进程原本有一个控制终端,则它会失去这个控制终端,成为一个没有控制终端的进程。所谓的失去控制终端是指,原来的控制终端仍然是打开的,仍然可读可写,但只是一个普通文件了。守护进程是独立于控制终端存在的。
步骤:
- 创建子进程,子进程继续执行,父进程终止,保证子进程不是当前进程组的组长
- 子进程调用 setsid 创建一个新的会话。(还可以再fork一次)
- 忽略 SIGHUP 信号
- 忽略 SIGCHLD 信号
- 修改工作目录为 /(根目录)
- 重定向文件描述符(守护进程独立于控制终端)
- 修改 umask,将权限掩码设置为0
具体实现:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
void MyDaemon(){
//1.创建子进程,子进程继续执行,父进程终止
int ret = fork();
if(ret < 0){
perror("fork");
return;
}
if(ret > 0){
exit(0);
}
//2.子进程调用 setsid
setsid();
//3.忽略 SIGHUP 信号
signal(SIGHUP, SIG_IGN);
//4.忽略 SIGCHLD 信号
signal(SIGCHLD, SIG_IGN);
//5.修改工作目录为 /
chdir("/");
//6.重定向文件描述符
int fd = open("/dev/null", O_RDWR);
if(fd < 0){
perror("open");
exit(1);
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
//7.修改 umask
umask(0);
}
int main(){
MyDaemon();
//daemon(0, 0);
while(1){
sleep(1);
}
return 0;
}
使用nohup + 需要变为守护进程的进程&
也能创建守护进程,只不过回车之后并没有直接变成守护进程,而是关闭终端或会话后才成为守护进程。
fork一次和fork两次的区别:
实际上创建守护进程的过程中fork一次可以,fork两次也可以。最主要的区别在于fork一次的守护进程的进程组id(pgid) 和会话id(sid) 都与pid相同,表示当前守护进程自成会话,自成进程组。而fork两次的话,进程组id(pgid) 和会话id(sid) 都与pid不同,这表明守护进程是属于某个会话,某个进程组。
由此可以看出,fork两次出来的守护进程更加安全。