C语言 在Linux中创建守护进程



在Linux中,我想添加一个不能停止并监视文件系统更改的守护进程。如果检测到任何更改,它应该将路径写入启动它的控制台,并加上一个换行符。

我已经准备好了文件系统更改代码,但是我不知道如何创建一个守护进程。

我的代码来自这里:http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html

fork后该怎么做?

int main (int argc, char **argv) {
pid_t pID = fork();
if (pID == 0)  {              // child
// Code only executed by child process    
sIdentifier = "Child Process: ";
}
else if (pID < 0) {
cerr << "Failed to fork" << endl;
exit(1);
// Throw exception
}
else                                   // parent
{
// Code only executed by parent process
sIdentifier = "Parent Process:";
}       
return 0;
}

在Linux中,我想添加一个不能停止的守护进程,它监视文件系统的更改。如果检测到任何更改,它应该将路径写入启动它的控制台+换行符。

守护进程在后台工作,(通常…)不属于TTY,这就是为什么你不能以你可能想要的方式使用stdout/stderr。通常使用syslog守护进程(syslogd)将消息记录到文件(debug, error,…)。

除此之外,还有一些必需的步骤来守护进程。


如果我没记错的话,这些步骤是:

  • 关闭父进程&如果分叉成功,让它终止。->由于父进程已经终止,子进程现在在后台运行。
  • setsid—新建会话。调用进程成为新会话的领导者和新流程组的流程组领导者。该进程现在从其控制终端(CTTY)分离。
  • <
  • 捕获信号/strong>—忽略和/或处理信号。
  • 叉再次,让父进程终止,以确保摆脱会话引导进程。(只有会话领导才能再次获得TTY)
  • —修改守护进程工作目录
  • umask—根据守护进程的需要,修改文件模式掩码。
  • -关闭所有可能从父进程继承的打开的文件描述符。

为您提供一个起点:看一下显示基本步骤的框架代码。这段代码现在也可以在GitHub上分叉:linux守护进程的基本框架

/*
* daemonize.c
* This example daemonizes a process, writes a few log messages,
* sleeps 20 seconds and terminates afterwards.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
static void skeleton_daemon()
{
pid_t pid;
/* Fork off the parent process */
pid = fork();
/* An error occurred */
if (pid < 0)
exit(EXIT_FAILURE);
/* Success: Let the parent terminate */
if (pid > 0)
exit(EXIT_SUCCESS);
/* On success: The child process becomes session leader */
if (setsid() < 0)
exit(EXIT_FAILURE);
/* Catch, ignore and handle signals */
//TODO: Implement a working signal handler */
signal(SIGCHLD, SIG_IGN);
signal(SIGHUP, SIG_IGN);
/* Fork off for the second time*/
pid = fork();
/* An error occurred */
if (pid < 0)
exit(EXIT_FAILURE);
/* Success: Let the parent terminate */
if (pid > 0)
exit(EXIT_SUCCESS);
/* Set new file permissions */
umask(0);
/* Change the working directory to the root directory */
/* or another appropriated directory */
chdir("/");
/* Close all open file descriptors */
int x;
for (x = sysconf(_SC_OPEN_MAX); x>=0; x--)
{
close (x);
}
/* Open the log file */
openlog ("firstdaemon", LOG_PID, LOG_DAEMON);
}
int main()
{
skeleton_daemon();
while (1)
{
//TODO: Insert daemon code here.
syslog (LOG_NOTICE, "First daemon started.");
sleep (20);
break;
}
syslog (LOG_NOTICE, "First daemon terminated.");
closelog();
return EXIT_SUCCESS;
}


  • 编译代码:gcc -o firstdaemon daemonize.c
  • 启动进程:./firstdaemon
  • 检查是否一切正常:ps -xj | grep firstdaemon

  • 输出应该类似如下:

<前>+------+------+------+------+-----+-------+------+------+------+-----+| ppid | pid | pgid | sid | tty | tpgid | stat | uid | time | CMD |+------+------+------+------+-----+-------+------+------+------+-----+| 1 | 3387 | 3386 | 3386 | ?| -1 | s | 1000 | 0:00 | ./|+------+------+------+------+-----+-------+------+------+------+-----+

你应该看到的是:

  • 守护进程没有控制终端(TTY = ?)
  • 父进程ID (PPID))1(init进程)
  • PID != SID这意味着我们的进程不是会话领导者
    (因为第二个fork())
  • 因为PID != SID我们的进程不能再控制TTY

读取syslog日志:

  • 找到syslog文件。我的在这里:/var/log/syslog
  • Do a:grep firstdaemon /var/log/syslog

  • 输出应该类似如下:

firstdaemon[3387]:第一个守护进程已启动。firstdaemon[3387]:第一个守护进程被终止。


注意:实际上,您还需要实现一个信号处理程序并正确设置日志记录(文件,日志级别…)。

进一步阅读:

  • Linux-UNIX-Programmierung - German
  • Unix Daemon Server Programming

man 7 daemon详细描述了如何创建守护进程。我的回答只是节选自这本手册。

至少有两种守护进程:

  1. 传统SysV守护进程(老式),
  2. systemd daemons (new-style).

SysV守护进程如果您对传统的SysV守护进程感兴趣,您应该实现以下步骤:

  1. 关闭所有打开的文件描述符,除了标准输入输出错误(即前三个文件描述符0,1,2)。这确保没有意外传递的文件描述符留在守护进程中。在Linux上,这最好通过迭代/proc/self/fd来实现,并从文件描述符3迭代到getrlimit()RLIMIT_NOFILE返回的值。
  2. 将所有信号处理程序重置为默认值。这最好通过迭代可用信号直到_NSIG的限制并将其重置为SIG_DFL来完成。
  3. sigprocmask()复位信号掩码
  4. 清理环境块,删除或重置可能对守护进程运行时产生负面影响的环境变量。
  5. 调用fork(),创建后台进程
  6. 子进程中,调用setsid()脱离任意终端,创建独立会话。
  7. 在子进程中,再次调用fork(),以确保守护进程不会再重新获取终端。
  8. 在第一个子进程中调用exit(),这样只有第二个子进程(实际的守护进程)留在周围。这确保守护进程被重新父化到init/PID 1,所有守护进程都应该这样。
  9. 在守护进程中,将/dev/null连接标准输入输出错误
  10. 在守护进程中将umask设置为0,使传递给open()mkdir()等的文件模式直接控制创建的文件和目录的访问方式。
  11. 在守护进程中,修改当前目录为根目录(/),以避免守护进程不自觉地阻塞挂载点卸载。
  12. 在守护进程中,将守护进程PID(由getpid()返回)写入PID文件,例如/run/foobar.pid(用于假想的守护进程"foobar"),以确保守护进程不能多次启动。这必须以无竞争的方式实现,以便PID文件只有在验证之前存储在PID文件中的PID不再存在或属于外部进程时才会更新。
  13. 在守护进程中,如果可能且适用,删除特权。
  14. 从守护进程通知启动的原始进程初始化完成。这可以通过在第一个fork()之前创建的未命名管道或类似的通信通道实现,因此在原始进程和守护进程中都可用。
  15. 在原进程中调用exit()。调用守护进程的进程必须能够依赖于此exit()发生在之后。初始化完成,所有外部通信通道建立并可访问。

注意这个警告:

daemon()函数不应该因为它只实现了这些步骤的子集

需要提供与SysV系统的兼容性的守护进程应该实现上面指出的方案。但是,建议通过命令行参数将此行为设置为可选和可配置的,以简化调试,并简化使用systemd与系统的集成。

注意daemon()不兼容POSIX。


新型守护进程对于new-style守护进程,建议执行以下步骤:

  1. 如果收到SIGTERM,关闭守护进程并干净退出。
  2. 如果SIGHUP被接收,重新加载配置文件,如果这适用。
  3. 提供主守护进程的正确退出代码,因为init系统使用此代码来检测服务错误和问题。建议遵循LSB建议中为SysV初始化脚本定义的退出码方案。
  4. 如果可能和适用,通过D-Bus IPC系统公开守护进程的控制接口,并获取总线名称作为初始化的最后一步。对于systemd中的集成,提供一个.service单元文件,该文件包含有关启动、停止和维护守护进程的信息。详见systemd.service(5)
  5. 尽可能依赖init系统的功能来限制守护进程对文件、服务和其他资源的访问,例如,在systemd的情况下,依赖systemd的资源限制控制而不是实现自己的控制,依赖systemd的特权删除代码而不是在守护进程中实现,等等。可用的控件见systemd.exec(5)
  6. 如果使用D-Bus,通过提供D-Bus服务激活配置文件使您的守护进程总线可激活。这有很多优点:您的守护进程可以按需惰性启动;它可以与其他需要它的守护进程并行启动——这样可以最大限度地提高并行性和启动速度;您的守护进程可以在出现故障时重新启动,而不会丢失任何总线请求,因为总线为可激活服务的请求排队。详情见下文。
  7. 如果您的守护进程通过套接字向其他本地进程或远程客户端提供服务,则应该按照下面指出的方案将其设置为套接字可激活。与D-Bus激活一样,这支持按需启动服务,并允许改进服务启动的并行化。此外,对于无状态协议(如syslog、DNS),可以重新启动实现基于套接字激活的守护进程,而不会丢失单个请求。详情见下文。
  8. 如果可以的话,守护进程应该通过sd_notify(3)接口通知init系统启动完成或状态更新。与使用syslog()调用直接记录到系统syslog服务不同,一个新型的守护进程可以选择简单地通过fprintf()记录到标准错误,然后由init系统转发到syslog。如果需要日志级别,可以用字符串"><4>"作为单个日志行的前缀(对于日志级别4的syslog优先级方案中的"WARNING"),采用与Linux内核的printk()级别系统类似的风格对日志级别进行编码。详情请参见sd-daemon(3)systemd.exec(5)

要了解更多信息,请阅读完整的man 7 daemon.

不能在linux中创建不能被杀死的进程。根用户(uid=0)可以向进程发送信号,并且有两个信号不能被捕获,SIGKILL=9, SIGSTOP=19。其他信号(如果未被捕获)也会导致进程终止。

您可能需要一个更通用的daemonize函数,您可以在其中指定程序/守护进程的名称,以及运行程序的路径(可能是"/"或"/tmp")。您可能还需要为stderr和stdout提供文件(可能还需要使用stdin提供控制路径)。

下面是必要的include:

#include <stdio.h>    //printf(3)
#include <stdlib.h>   //exit(3)
#include <unistd.h>   //fork(3), chdir(3), sysconf(3)
#include <signal.h>   //signal(3)
#include <sys/stat.h> //umask(3)
#include <syslog.h>   //syslog(3), openlog(3), closelog(3)

这是一个更一般的函数,

int
daemonize(char* name, char* path, char* outfile, char* errfile, char* infile )
{
if(!path) { path="/"; }
if(!name) { name="medaemon"; }
if(!infile) { infile="/dev/null"; }
if(!outfile) { outfile="/dev/null"; }
if(!errfile) { errfile="/dev/null"; }
//printf("%s %s %s %sn",name,path,outfile,infile);
pid_t child;
//fork, detach from process group leader
if( (child=fork())<0 ) { //failed fork
fprintf(stderr,"error: failed forkn");
exit(EXIT_FAILURE);
}
if (child>0) { //parent
exit(EXIT_SUCCESS);
}
if( setsid()<0 ) { //failed to become session leader
fprintf(stderr,"error: failed setsidn");
exit(EXIT_FAILURE);
}
//catch/ignore signals
signal(SIGCHLD,SIG_IGN);
signal(SIGHUP,SIG_IGN);
//fork second time
if ( (child=fork())<0) { //failed fork
fprintf(stderr,"error: failed forkn");
exit(EXIT_FAILURE);
}
if( child>0 ) { //parent
exit(EXIT_SUCCESS);
}
//new file permissions
umask(0);
//change to path directory
chdir(path);
//Close all open file descriptors
int fd;
for( fd=sysconf(_SC_OPEN_MAX); fd>0; --fd )
{
close(fd);
}
//reopen stdin, stdout, stderr
stdin=fopen(infile,"r");   //fd=0
stdout=fopen(outfile,"w+");  //fd=1
stderr=fopen(errfile,"w+");  //fd=2
//open syslog
openlog(name,LOG_PID,LOG_DAEMON);
return(0);
}

下面是一个示例程序,它成为一个守护进程,挂起,然后离开。

int
main()
{
int res;
int ttl=120;
int delay=5;
if( (res=daemonize("mydaemon","/tmp",NULL,NULL,NULL)) != 0 ) {
fprintf(stderr,"error: daemonize failedn");
exit(EXIT_FAILURE);
}
while( ttl>0 ) {
//daemon code here
syslog(LOG_NOTICE,"daemon ttl %d",ttl);
sleep(delay);
ttl-=delay;
}
syslog(LOG_NOTICE,"daemon ttl expired");
closelog();
return(EXIT_SUCCESS);
}

注意,SIG_IGN表示捕获并忽略该信号。您可以构建一个可以记录信号接收的信号处理程序,并设置标志(例如表示正常关机的标志)。

尝试使用daemon函数:

#include <unistd.h>
int daemon(int nochdir, int noclose);

从手册页:

daemon()函数用于希望分离自己的程序从控制终端和后台运行作为系统守护进程。

如果nochdir为零,daemon()改变调用进程的当前值工作目录到根目录("/");否则,电流工作目录保持不变

如果noclose为零,daemon()重定向标准输入,标准输出和标准错误到/dev/null;否则,不更改

我可以停在第一个要求"守护进程不能被停止…"

不可能,我的朋友;但是,您可以使用一个更好的工具来实现相同的目标,即内核模块。

http://www.infoq.com/articles/inotify-linux-file-system-event-monitoring

所有守护进程都可以停止。有些人比其他人更容易被阻止。即使一个守护配对的伙伴在按下,重生的伙伴,如果失去了,可以停止。你只要再努力一点就行了。

如果你的应用是:

{
".sh": "bash",
".py": "python",
".rb": "ruby",
".coffee" : "coffee",
".php": "php",
".pl" : "perl",
".js" : "node"
}

如果你不介意NodeJS依赖,那么安装NodeJS,然后:

npm install -g pm2
pm2 start yourapp.yourext --name "fred" # where .yourext is one of the above
pm2 start yourapp.yourext -i 0 --name "fred" # run your app on all cores
pm2 list

保持所有应用程序在重启时运行(和守护进程pm2):

pm2 startup
pm2 save

现在你可以:

service pm2 stop|restart|start|status

(也可以让你很容易地观察你的应用目录中的代码变化,并在代码发生变化时自动重启应用进程)

守护进程模板

我写了一个新的守护进程模板:link

你可以在GitHub上找到完整的模板代码:这里

Main.cpp

// This function will be called when the daemon receive a SIGHUP signal.
void reload() {
LOG_INFO("Reload function called.");
}
int main(int argc, char **argv) {
// The Daemon class is a singleton to avoid be instantiate more than once
Daemon& daemon = Daemon::instance();
// Set the reload function to be called in case of receiving a SIGHUP signal
daemon.setReloadFunction(reload);
// Daemon main loop
int count = 0;
while(daemon.IsRunning()) {
LOG_DEBUG("Count: ", count++);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
LOG_INFO("The daemon process ended gracefully.");
}

Daemon.hpp

class Daemon {
public:
static Daemon& instance() {
static Daemon instance;
return instance;
}
void setReloadFunction(std::function<void()> func);
bool IsRunning();
private:
std::function<void()> m_reloadFunc;
bool m_isRunning;
bool m_reload;
Daemon();
Daemon(Daemon const&) = delete;
void operator=(Daemon const&) = delete;
void Reload();
static void signalHandler(int signal);
};

Daemon.cpp

Daemon::Daemon() {
m_isRunning = true;
m_reload = false;
signal(SIGINT, Daemon::signalHandler);
signal(SIGTERM, Daemon::signalHandler);
signal(SIGHUP, Daemon::signalHandler);
}
void Daemon::setReloadFunction(std::function<void()> func) {
m_reloadFunc = func;
}
bool Daemon::IsRunning() {
if (m_reload) {
m_reload = false;
m_reloadFunc();
}
return m_isRunning;
}
void Daemon::signalHandler(int signal) {
LOG_INFO("Interrup signal number [", signal,"] recived.");
switch(signal) {
case SIGINT:
case SIGTERM: {
Daemon::instance().m_isRunning = false;
break;
}
case SIGHUP: {
Daemon::instance().m_reload = true;
break;
}
}
}

daemon-template.service

[Unit]
Description=Simple daemon template
After=network.taget
[Service]
Type=simple
ExecStart=/usr/bin/daemon-template --conf_file /etc/daemon-template/daemon-tenplate.conf
ExecReload=/bin/kill -HUP $MAINPID
User=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=daemon-template
[Install]
WantedBy=multi-user.target

通过调用fork(),您创建了一个子进程。如果fork成功(fork返回一个非零的PID),执行将从子进程的这个点开始继续。在这种情况下,我们希望优雅地退出父进程,然后在子进程中继续我们的工作。

也许这将帮助:http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html

守护进程就是后台的一个进程。如果您想在操作系统启动时启动您的程序,在linux上,您可以将启动命令添加到/etc/rc.d/rc本地(在所有其他脚本之后运行)或/etc/startup.sh

在windows上,您创建一个服务,注册该服务,然后在管理->服务面板中将其设置为启动时自动启动。

最新更新