Linux守护进程的实现

会话、进程组与控制终端

会话是一个或多个进程组的集合。一个典型的会话过程是从我们的登陆shell开始的,这个登陆shell就是会话首进程,这个进程验证了我们的用户名和密码。会话首进程也可以是调用setsid新建会话的进程。setsid可以新建一个会话。

除了会话首进程,会话还可以包括一个前台进组和多个后台进程组,如图1所示。进程组是一个或多个进程的集合,进程组ID由组长进程的PID标识。系统调用fork可以给当前进程组创建一个新的进程。

图1 会话与进程组

控制终端所在的进程组内的进程都属于前台进程组,这些进程可以从终端读取输入。而后台进程组只能向终端设备写入,无法从终端获得输入。

会话对于终端具有独占性,即一个控制终端只属于一个会话。如果新建的会话要使用终端,该会话只能自己申请打开终端。一次会话中,只有会话首进程(会话组长)具有申请打开控制终端的权限。所以如果想要进程独立于控制终端,就需要结束会话组长进程,fork子进程继续执行任务。

守护进程

守护进程是一种后台运行并且独立于终端的进程。理解了会话、进程组与控制终端的关系之后,实现守护进程就很容易了。下面是一个经典的守护进程实现代码。

守护进程的实现

一个经典的守护进程实现例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// 守护进程初始化函数
void init_daemon()
{
pid_t pid;
int i = 0;

if ((pid = fork()) == -1) {
printf("Fork error !\n");
exit(1);
}
if (pid != 0) {
exit(0); // (1)父进程退出
}

setsid(); // (2)子进程开启新会话,并成为会话首进程和组长进程
chdir("/tmp"); // (3)改变工作目录
umask(0); // (4)重设文件掩码
for (; i < getdtablesize(); ++i) {
close(i); // (6)关闭打开的文件描述符
}

return;
}

int main(int argc, char *argv[])
{
int fp;
time_t t;
char buf[] = {"This is a daemon:\n"};
char *datetime;
int len = 0;

// 初始化 Daemon 进程
init_daemon();

// 每隔一分钟记录运行状态
while (1) {
if (-1 == (fp = open("/tmp/daemon.log", O_CREAT|O_WRONLY|O_APPEND, 0600))) {
printf("Open file error !\n");
exit(1);
}
len = strlen(buf);
write(fp, buf, len);
t = time(0);
datetime = asctime(localtime(&t));
len = strlen(datetime);
write(fp, datetime, len);
close(fp);
sleep(60);
}
return 0;
}

执行效果:在文件/tmp/daemon.log中会记录每分钟的打印值。

0%