文章目录
- 1. 守护进程及实现步骤
- 2. 使用fork 方式创建守护进程
- 3. 使用daemon 函数创建
1. 守护进程及实现步骤
特点:
- 长期运行:守护进程是一种生存期很长的一种进程,它们一般在系统启动时开始运行,除非强行终止,否则直到系统关机都会保持运行, 不受用户登录注销的影响。
- 与控制终端脱离:Linux 中,系统与用户交互的界面称为终端,每一个从终端开始运行的进程都会依附于这个终端这个终端也就是会话的控制终端。当控制终端关闭的时候,该会话就会退出,由此终端运行的所有进程都会被终止,所以普通进程都是和运行该进程的终端相绑定的;但守护进程能突破这种限制,它脱离终端并且在后台运行,脱离终端的目的是为了避免进程在运行的过程中的所产生的信息在终端显示,并且进程也不会被任何终端所产生的信息所打断。
实现: - 1、 创建子进程、终止父进程
父进程调用 fork()创建子进程,然后使用 exit()退出父进程,这样做实现了下面几点。
(1)如果该守护进程是作为一条简单地 shell 命令启动,那么父进程终止会让 shell 认为这条命令已经执行完毕。
(2)虽然子进程继承了父进程的进程组ID,但它有自己独立的进程ID,这保证了子进程不是一个进程组的组长进程,这是下面将要调用 setsid 函数的先决条件! - 2、子进程调用 setsid 创建会话
这步是关键,由于之前子进程并不是进程组的组长进程,所以调用 setsid()会使得子进程创建一个新的会话,子进程成为新会话的首领进程,同时也创建了新的进程组、子进程成为组长进程,此时创建的会话将没有控制终端。所以这里调用 setsid 有三个作用:
(1)让子进程摆脱原会话的控制
(2)让子进程摆脱原进程组的控制
(3)让子进程摆脱原控制终端的控制。
在调用 fork 函数时,子进程继承了父进程的会话、进程组、控制终端等,虽然父进程退出了,但原先的会话期、进程组、控制终端等并没有改变,因此,那还不是真正意义上使两者独立开来。setsid 函数能够使子进程完全独立出来,从而脱离所有其他进程的控制。 - 3、 将工作目录更改为根目录
子进程是继承了父进程的当前工作目录,由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后使用会造成很多的麻烦。因此通常的做法是让“/”作为守护进程的当前目录,当然也可以指定其它目录来作为守护进程的工作目录。 - 4、重设文件权限掩码 umask
文件权限掩码 umask 用于对新建文件的权限位进行屏蔽,由于使用 fork 函数新
建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为 0,确保子进程有最大操作权限、这样可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是 umask,通常的使用方法为 umask(0)。 - 5、关闭不再需要的文件描述符
子进程继承了父进程的所有文件描述符,这些被打开的文件可能永远不会被守护进程(此时守护进程指的就是子进程,父进程退出、子进程成为守护进程)读或写,但它们一样消耗系统资源,可能导致所在的文件系统无法卸载,所以必须关闭这些文件,这使得守护进程不再持有从其父进程继承过来的任何文件描述符。 - 6、 将文件描述符号为 0、1、2 定位到/dev/null
将守护进程的标准输入、标准输出以及标准错误重定向到/dev/null,这使得守护进程的输出无处显示、也无处从交互式用户那里接收输入。 - 7、 其它:忽略 SIGCHLD 信号
处理 SIGCHLD 信号不是必须的,但对于某些进程,特别是并发服务器进程往往是特别重要的,服务器进程在接收到客户端请求时会创建子进程去处理该请求,如果子进程结束之后,父进程没有去 wait 回收子进程,则子进程将成为僵尸进程;如果父进程 wait 等待子进程退出,将又会增加父进程的负担、也就是增加服务器的负担,影响服务器进程的并发性能,在 Linux 下,可以将 SIGCHLD 信号的处理方式设置为SIG_IGN,也就是忽略该信号,可让内核将僵尸进程转交给 init 进程去处理,这样既不会产生僵尸进程、又省去了服务器进程回收子进程所占用的时间。
2. 使用fork 方式创建守护进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <string.h>
/**
* 守护进程,使用子进程实现, 进行日志记录测试
*/
int main(void) {
pid_t pid;
int fd;
int fdLog;
time_t t;
int i;
// 创建子进程
pid = fork();
if (pid < 0) {
perror("fork error");
exit(-1);
} else if (0 < pid) {// 父进程
exit(0); // 直接退出父进程
}
//子进程
// 1. 创建新的会话,脱离终端控制
if (0 > setsid()) {
perror("setid error");
exit(-1);
}
// 2. 设置当前工作目录为根目录
if (0 > chdir("/")) {
perror("chdir error");
exit(-1);
}
// 3. 重新设置文件权限掩码
umask(0);
// 4. 关闭所有文件描述符
for (i = 0; i < sysconf(_SC_OPEN_MAX); i++) {
close(i);
}
// 5. 将文件描述符0,1,2(标准输入,输出,错误)重定向到/dev/null
pid = open("/dev/null", O_RDWR);
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
/* 6.忽略 SIGCHLD 信号 */
signal(SIGCHLD, SIG_IGN);
// 进入到守护进程
for (;;) {
fdLog = open("/home/orangepi/workespace-linux-code/daemon/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fdLog == -1) {
printf("open log file error");
}
t = time(NULL);
char *buf = asctime(localtime(&t));
write(fdLog, buf, strlen(buf)); // 在日志中记录时间
close(fdLog);
sleep(10);
}
exit(0);
}
测试结果:
3. 使用daemon 函数创建
#include <unistd.h>
int daemon(int nochdir, int noclose);
参数:
- nochdir:为0时表示将当前目录更改至“/”
- noclose:为0时表示将标准输入、标准输出、标准错误重定向至“/dev/null”
返回值:
- 成功: 返回0
- 失败: 返回-1
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
/**
* 守护进程,使用daemon创建, 定时写文件测试
*/
static int flag = 1;
// 使用SIGQUIT 信号控制守护进程的退出,由于守护进程在后台运行,打印信息是看不到的
void handler(int sig) {
printf("quit signal %d\n", sig);
flag = 0;
}
int main(void) {
time_t t;
int fd;
// 创建守护进程
if (-1 == daemon(0, 0)) {
printf("daemon error\n");
exit(-1);
}
// 设置信号处理函数
struct sigaction act;
act.sa_handler = handler;
sigemptyset(&act.sa_mask); // 初始化信号掩码为空
act.sa_flags = 0;
if (sigaction(SIGQUIT, &act, NULL)) {
printf("sigaction error\n");
exit(-1);
}
// 进入守护进程
while (flag) {
fd = open("/home/orangepi/workespace-linux-code/daemon/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd == -1) {
printf("open log file error");
}
t = time(NULL);
char *buf = asctime(localtime(&t));
write(fd, buf, strlen(buf)); // 在日志中记录时间
close(fd);
sleep(10);
}
return 0;
}
测试:
注: 部分参考自正点原子的linux C应用开发指南