守护进程
守护进程一般是在后台执行的进程,长期生存,没有控制终端,没有父进程(即PPID为1)。
编写一个守护进程之前,要了解一些基本概念。
进程组
进程组是一个或者多个进程的集合,一般通过作业结合起来,同一个进程组的各个进程接收来自同一个终端的各种信号。拥有唯一的进程组ID,进程组有一个组长进程,进程组ID等于组长进程ID。进程组中只要还存在一个进程,则这个进程组就一直存在。
比如proc1和proc2就为一个进程组:
./proc1 | ./proc2
会话
会话是一个或者多个进程组的集合。会话首进程,是具有唯一进程ID的单个进程,可以将会话首进程的进程ID视为会话ID。
-
会话中几个进程组可以被分为一个一个前台进程组和多个后台进程组。
-
会话可以有一个控制终端。
-
如果会话有一个控制终端,则有一个前台进程组。
-
中断信号、退出信号会发给前台进程组的所有进程。
-
出现网络断开,发送挂断信号到会话首进程(控制进程)。
比如在终端中执行:
./proc1 | ./proc2 &
./a | ./b | ./c
登陆shell为一个进程组,proc1和proc2为一个进程组,a、b和c则组成一个进程组。
这几个进程组则组成一个会话。
孤儿进程组
定义:每个成员的父进程要么是该组的一个成员,要么不是该会话的成员。
对于孤儿进程组的处理:系统要求向新孤儿进程组中处于停止状态的每一个进程发送挂断信号,然后又发送继续信号。对于挂断信号的系统默认动作是终止进程。所以必须提供一个处理挂断信号的程序。
编写一个守护进程
编写一个守护进程的一般流程:
- 调用umask将文件模式创建屏蔽字设置为一个已知的值(通常为0)。
- 调用fork,父进程退出。
- 调用setsid,创建新会话。调用setsid()会发生(不能是进程组组长调用)
- 成为新会话首进程
- 成为一个新进程组的组长进程
- 没有控制终端
- 将当前目录更改为根目录。
- 关闭不需要的文件描述符。
- 某些守护进程打开/dev/null使其具有文件描述符0、1、2。
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> // umask()
#include <sys/resource.h> // getrlimit()
#include <signal.h> //sigaction
#include <syslog.h> //openlog()
void err_exit(const char *s)
{
std::cerr << "error: " << s << std::endl;
exit(-1);
}
void daemon(const char *name) {
// 1
umask(0);
// define some variables
int res_, fd_in_, fd_out_, fd_err_;
pid_t pid_;
rlimit rlimit_;
struct sigaction sig_act_;
// get max number of file descriptions.
res_ = getrlimit(RLIMIT_NOFILE, &rlimit_);
if (res_ < 0) {
err_exit("getrlimit()");
}
// 2
pid_ = fork();
if (pid_ < 0) {
err_exit("fork()");
}
// exit parent process and child process will be orphan process.
if (pid_ != 0) {
exit(0);
}
// 3
// now the process will be header process in the new session.
setsid();
// ignore SIGHUP signal
struct sigaction sa_;
sa_.sa_handler = SIG_IGN; //#define sa_handler __sigaction_u.__sa_handler
sigemptyset(&sa_.sa_mask);
sa_.sa_flags = 0;
res_ = sigaction(SIGHUP, &sa_, nullptr);
if (res_ < 0) {
err_exit("sigaction()");
}
// 4
res_ = chdir("/");
if (res_ < 0) {
err_exit("chdir()");
}
// 5
if (rlimit_.rlim_max == RLIM_INFINITY) {
rlimit_.rlim_max = 1024;
}
// close file descriptions.
for (int i = 0; i < rlimit_.rlim_max; ++i) {
close(i);
}
// 6
// redirect 0, 1, 2 to /dev/null
fd_in_ = open("/dev/null", O_RDWR);
fd_out_ = dup(0);
fd_err_ = dup(0);
// sent error information to syslogd if redirect error.
openlog(name, LOG_CONS, LOG_DAEMON);
if (fd_in_ != 0 || fd_out_ != 1 || fd_err_ != 2) {
syslog(LOG_ERR, "direct error");
exit(1);
}
}
int main(int argc, char *argv[]) {
daemon(argv[0]);
sleep(10);
return 0;
}
g++ daemon.cc -std=c++11 -o a.out && ./a.out # 编译运行
ps -efj | grep a.out | grep -v grep # 查看是否为守护进程
# UID PID PPID PGID SID TTY CMD
# TTY为?,PPID为1
守护进程的log处理
- 内核可以调用log函数。
- 用户进程调用syslog函数。
- 通过TCP/IP将日志消息发送到UDP端口514。
syslogd读取三种格式的日志消息。在配置文件/etc/syslog.conf中配置了这些消息应发送到哪里。
syslog函数产生日志消息,发送至unix域数据报套接字/dev/log。
#include <syslog.h>
void openlog(const char* ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog();
int setlogmask(int maskpri);
ident将被加到每一个日志消息中(一般是程序名)。
option参数
facility参数
priority参数