在 Linux 中创建守护进程

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

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

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

吃完叉子后怎么办?

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;
}
269084 次浏览

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

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

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

也许这个会有帮助: Http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html

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

守护进程在后台工作,并且(通常...)不属于 TTY,这就是为什么不能以您可能希望的方式使用 stdout/stderr。 通常使用 syslog 守护进程(西格勒德)将消息记录到文件(调试、错误、 ...)。

除此之外,还有一些 所需步骤可以将进程妖魔化。


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

  • Fork 关闭父进程,如果 fork 成功,则让它终止。- > 因为父进程已经终止,所以子进程现在在后台运行。
  • Setsid -创建一个新会话。调用进程成为新会话的领导者和新进程组的进程组领导者。该进程现在与其控制终端(CTTY)分离。
  • 捕捉信号 -忽略和/或处理信号。
  • Fork again & 让父进程终止,以确保您摆脱了会话引导进程。(只有会议领导人才能再次获得 TTY。)
  • Chdir -更改守护进程的工作目录。
  • Umask -根据守护进程的需要更改文件模式掩码。
  • Close -关闭从父进程继承的所有打开的文件描述符。

为了给你一个起点: 看看这个框架代码,它显示了基本的步骤。这段代码现在也可以在 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意味着我们的进程不是会议的领导者
    (因为第二个叉子())
  • 因为 PID! = SID 我们的进程 不能再控制 TTY 了

阅读 syslog:

  • 找到你的 syslog 文件。我的在这里: /var/log/syslog
  • 做一个: grep firstdaemon /var/log/syslog

  • 产出应与此类似:

firstdaemon[3387]: First daemon started.
firstdaemon[3387]: First daemon terminated.


注意: 实际上,您还希望实现一个信号处理程序并正确设置日志(文件、日志级别...)。

进一步阅读:

我可以在第一个需求“ A daemon 这是无法阻止的...”

朋友,这是不可能的; 但是,您可以使用一个更好的工具——内核模块来实现同样的功能。

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

可以停止所有守护进程。有些人比其他人更容易被阻止。即使一对守护进程与合作伙伴在按住,复活的合作伙伴,如果失去,可以停止。你只需要再努力一点。

您不能在 linux 中创建不能删除的进程。Root 用户(uid = 0)可以向进程发送信号,有两个信号无法捕获,SIGKILL = 9,SIGSTOP = 19。其他信号(未捕获时)也可能导致进程终止。

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

以下是必要的措施,包括:

#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 %s\n",name,path,outfile,infile);
pid_t child;
//fork, detach from process group leader
if( (child=fork())<0 ) { //failed fork
fprintf(stderr,"error: failed fork\n");
exit(EXIT_FAILURE);
}
if (child>0) { //parent
exit(EXIT_SUCCESS);
}
if( setsid()<0 ) { //failed to become session leader
fprintf(stderr,"error: failed setsid\n");
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 fork\n");
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 failed\n");
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 ()重定向标准输入 standard 输出和标准错误到/dev/null; 否则不进行任何更改 制作成这些文件描述符。

man 7 daemon 详细描述了如何创建守护进程。

至少有两种类型的守护进程:

  1. 传统的 SysV守护进程(老式的) ,
  2. System 守护进程(新风格)。

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()发生 之后初始化完成,并且所有外部通信通道都已建立并可访问。

注意这个警告:

使用 BSDdaemon()函数 不应该,因为它只实现这些步骤的 子集

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

请注意,daemon()POSIX不兼容。


新型守护进程

对于新型的守护进程,推荐使用 以下步骤:

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

为了学习更多的阅读整个 man 7 daemon

如果你的应用程序是:

{
".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

(还可以方便地监视应用程序目录中的代码更改,并在发生代码更改时自动重新启动应用程序进程)

守护模板

我按照新样式的守护进程 链接编写了一个守护进程模板

您可以在 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