主流程:
-
调用fork()函数,创建一个子进程,然后使父进程退出,这样就能保证子进程不再有控制终端。
-
调用setsid()函数,创建一个新的会话期(session),并使当前进程成为该会话期的首进程和组长进程,这样就能摆脱之前的控制终端、会话期和进程组。
-
修改工作目录和文件权限掩码,确保守护进程不占用任何挂载的文件系统,并且对文件的访问权限受到适当的限制。
-
关闭标准输入、标准输出和标准错误文件描述符,以避免在后台运行时受到这些文件描述符的影响。
在Linux系统中,确实有一个名为daemon
的函数。daemon
函数是一个标准的Unix/Linux系统调用,用于将当前进程转变为守护进程。
daemon
函数的原型如下:
#include <unistd.h>
int daemon(int nochdir, int noclose);
其中,nochdir
和noclose
是两个参数,用于控制守护进程的行为:
- 如果
nochdir
设置为0,daemon
函数会将当前工作目录切换到根目录(/); - 如果
nochdir
设置为非0,daemon
函数会保持当前工作目录不变。 - 如果
noclose
设置为0,daemon
函数会关闭所有的已打开文件描述符(除了标准输入、标准输出和标准错误); - 如果
noclose
设置为非0,daemon
函数不会关闭任何文件描述符。
daemon
函数的返回值为0表示成功,返回-1表示失败。
使用daemon
函数可以简化将进程转变为守护进程的过程。例如,下面的代码演示了如何使用daemon
函数创建一个守护进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
// 创建守护进程
if (daemon(0, 0) == -1) {
perror("daemon");
return -1;
}
// 在守护进程中执行任务
printf("This is a daemon process.\n");
sleep(10);
printf("Daemon process finished.\n");
return 0;
}
上述代码中,调用daemon(0, 0)
函数将当前进程转变为守护进程。在守护进程中,可以执行需要在后台运行的任务。在示例中,守护进程会打印一条消息,然后睡眠10秒,最后打印另一条消息。
需要注意的是,daemon
函数仅适用于Unix/Linux系统,不适用于所有操作系统。在其他操作系统上实现守护进程可能需要使用不同的方法和函数。
源码是https://github.com/lattera/glibc/blob/master/misc/daemon.c
int
daemon (int nochdir, int noclose)
{
int fd;
switch (__fork()) {
case -1:
return (-1);
case 0:
break;
default:
_exit(0);
}
if (__setsid() == -1)
return (-1);
if (!nochdir)
(void)__chdir("/");
if (!noclose) {
struct stat64 st;
if ((fd = __open_nocancel(_PATH_DEVNULL, O_RDWR, 0)) != -1
&& (__builtin_expect (__fxstat64 (_STAT_VER, fd, &st), 0)
== 0)) {
if (__builtin_expect (S_ISCHR (st.st_mode), 1) != 0
#if defined DEV_NULL_MAJOR && defined DEV_NULL_MINOR
&& (st.st_rdev
== makedev (DEV_NULL_MAJOR, DEV_NULL_MINOR))
#endif
) {
(void)__dup2(fd, STDIN_FILENO);
(void)__dup2(fd, STDOUT_FILENO);
(void)__dup2(fd, STDERR_FILENO);
if (fd > 2)
(void)__close (fd);
} else {
/* We must set an errno value since no
function call actually failed. */
__close_nocancel_nostatus (fd);
__set_errno (ENODEV);
return -1;
}
} else {
__close_nocancel_nostatus (fd);
return -1;
}
}
return (0);
}
nginx 的实现方式
ngx_int_t
ngx_daemon(ngx_log_t *log)
{
int fd;
switch (fork()) {
case -1:
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
return NGX_ERROR;
case 0:
break;
default:
exit(0);
}
ngx_parent = ngx_pid;
ngx_pid = ngx_getpid();
if (setsid() == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
return NGX_ERROR;
}
umask(0);
fd = open("/dev/null", O_RDWR);
if (fd == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"open(\"/dev/null\") failed");
return NGX_ERROR;
}
if (dup2(fd, STDIN_FILENO) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
return NGX_ERROR;
}
if (dup2(fd, STDOUT_FILENO) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDOUT) failed");
return NGX_ERROR;
}
#if 0
if (dup2(fd, STDERR_FILENO) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDERR) failed");
return NGX_ERROR;
}
#endif
if (fd > STDERR_FILENO) {
if (close(fd) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "close() failed");
return NGX_ERROR;
}
}
return NGX_OK;
}
fork 后锁的情况
互斥锁在父子进程中也是独立的,锁了的状态也会被复制,,需要在各自的进程解锁
#include <iostream>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t mutex; // 定义互斥锁
int main() {
pid_t pid;
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 父进程锁定互斥锁
pthread_mutex_lock(&mutex);
printf("Parent process: Mutex locked.\n");
// 使用 fork 创建子进程
pid = fork();
if (pid == -1) {
// 错误处理
perror("fork failed");
exit(1);
}
if (pid == 0) { // 子进程
// 子进程在这里会等到父进程锁定互斥锁
printf("Child process: Waiting to unlock the mutex...\n");
// 子进程解锁互斥锁
pthread_mutex_unlock(&mutex);
printf("Child process: Mutex unlocked.\n");
exit(0);
} else { // 父进程
// 父进程在这里等待子进程执行完毕
wait(NULL); // 等待子进程结束
printf("Parent process: Mutex still locked after child exits.\n");
// 父进程完成任务后解锁互斥锁
pthread_mutex_unlock(&mutex);
printf("Parent process: Mutex unlocked.\n");
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
}
return 0;
}