在Linux中创建守护程序


110

在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;
}


1
可能的重复:stackoverflow.com/questions/5384168/…用作守护程序部分,stackoverflow.com/ questions/931093/… 用作文件系统监视
Ciro Santilli郝海东冠状病六四事件法轮功

如果不需要POSIX合规性,您可能会对inotifyAPI 感兴趣。请参阅:inotify_initinotify_add_watchinotify_rm_watch
patryk.beza

Answers:


216

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

守护程序在后台工作,并且(通常...)不属于TTY,这就是为什么您不能以可能想要的方式使用stdout / stderr的原因。通常是syslog守护程序(syslogd)将消息记录到文件中(调试,错误等)。

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


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

  • 叉子父进程,并在分叉成功时让其终止。->由于父进程已终止,因此子进程现在在后台运行。
  • setid创建一个新会话。调用过程将成为新会话的负责人和新过程组的过程组负责人。现在,该过程已从其控制终端(CTTY)分离。
  • 捕获信号 -忽略和/或处理信号。
  • 再次分叉并让父进程终止,以确保您摆脱了会话引导进程。(只有会议负责人才能再次获得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 | 时间| CMD |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +
| 1 | 3387 | 3386 | 3386 | ?| -1 | S | 1000 | 0:00 | ./ |
+ ------ + ------ + ------ + ------ + ----- + ------- + ------ + ------ + ------ + ----- +

您应该在这里看到的是:

  • 守护程序没有控制终端(TTY =?
  • 父进程ID(PPID)为1(初始化进程)
  • PID!= SID,这意味着我们的进程不是会话组长
    (因为第二叉())
  • 因为PID!= SID,所以我们的过程无法再控制TTY

阅读系统日志:

  • 找到您的系统日志文件。我的在这里:/var/log/syslog
  • 做一个: grep firstdaemon /var/log/syslog

  • 输出应与此类似:

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


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

进一步阅读:


哇谢谢!那很棒。所以我必须将我的代码放入while循环中,就是这样?
chrisMe

基本上是。但是这段代码只是一个例子。这完全取决于您要使用守护进程实现的目标。一定也要阅读以下答案:@Edwin
Pascal Werkl 2013年

1
fork()为何不使用第二个而不是第二个setsid()呢?
Chimera

1
注意sigaction()功能为控制信号提供了更全面,更可靠的机制。新的应用程序应该使用sigaction()而不是signal()
patryk.beza

4
应当向观看者注意,此方法是“旧的”方法。推荐的创建守护程序的新方法是在此处找到“新样式守护程序”:0pointer.de/public/systemd-man/daemon.html#New-Style%20Daemons
Starlord

30

man 7 daemon详细描述了如何创建守护程序。我的回答只是本手册的摘录。

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

  1. 传统的SysV守护程序(旧式),
  2. systemd守护程序(新样式)。

SysV守护程序

如果您对传统的SysV守护程序感兴趣,则应执行以下步骤

  1. 关闭所有打开的文件描述符,但标准输入输出错误除外(即前三个文件描述符0、1、2)。这样可以确保在守护进程中不会意外传递文件描述符。在Linux上,最好通过迭代来实现,最好是/proc/self/fd从文件描述符3迭代到getrlimit()for所返回的值RLIMIT_NOFILE
  2. 所有信号处理程序重置为其默认值。最好通过迭代可用信号直至的最大限制_NSIG并将它们重置为来完成此操作SIG_DFL
  3. 使用重置信号屏蔽sigprocmask()
  4. 清理环境块,删除或重置可能对守护程序运行时间产生负面影响的环境变量。
  5. 呼叫 fork(),以创建后台进程。
  6. 在子setsid()进程中,呼叫从任何终端分离并创建一个独立的会话
  7. 在孩子里,打电话 fork()再次以确保守护程序永远不会再重新获取终端。
  8. 呼叫 exit()第一个孩子,以便只有第二个孩子(实际的守护进程)停留在周围。这样可以确保守护程序进程与所有守护程序一样都重新绑定到init / PID 1。
  9. 在守护进程中,连接/dev/null到标准inputoutputerror
  10. 在守护进程中,将重置umask为0,以便将文件模式传递给open()mkdir()以及诸如此类的直接控制创建文件和目录的访问模式。
  11. 在守护进程中,更改当前目录为根目录(/,按顺序),以避免该守护进程不由自主块从被卸载安装点。
  12. 在守护进程中,例如,将守护进程PID(由返回getpid())写入PID文件/run/foobar.pid(对于假设的守护程序“ foobar”),以确保不能多次启动该守护程序。这必须以无竞争的方式实现,以便仅在同时验证先前存储在PID文件中的PID不再存在或属于外部进程的同时验证PID文件时,才更新PID文件。
  13. 在守护进程中,如果可能且适用的话,请放下特权。
  14. 从守护程序进程中,通知启动的原始进程初始化已完成。这可以通过在第一个管道之前创建的未命名管道或类似的通信通道来实现fork(),因此可以在原始进程和守护进程中使用。
  15. 调用exit()原始过程。调用守护程序的进程必须能够依赖于此exit()情况初始化完成并且所有外部通信通道都已建立且可访问之后发生。

请注意以下警告:

BSD daemon()功能不应该被使用,因为它仅实现一个子集的这些步骤。

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

注意daemon()不是符合POSIX


新型守护程序

对于新型守护程序,建议执行以下步骤

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

要了解更多内容,请阅读全文man 7 daemon


11

您无法在Linux中创建无法杀死的进程。根用户(uid = 0)可以将信号发送到进程,并且有两个无法捕获的信号:SIGKILL = 9,SIGSTOP = 19。其他信号(未捕获时)也可能导致进程终止。

您可能需要一个更通用的守护程序功能,您可以在其中指定程序/守护程序的名称以及运行程序的路径(也许是“ /”或“ / 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指示捕获并忽略该信号。您可以构建一个可以记录信号接收情况的信号处理程序,并设置标志(例如表示正常关闭的标志)。


8

尝试使用该daemon功能:

#include <unistd.h>

int daemon(int nochdir, int noclose);

手册页

daemon()函数用于希望与控制终端分离并在后台作为系统守护程序运行的程序。

如果nochdir为零,则daemon()将调用进程的当前工作目录更改为根目录(“ /”);否则,当前工作目录将保持不变。

如果noclose为零,daemon()会将标准输入,标准输出和标准错误重定向到/ dev / null;否则,将不对这些文件描述符进行任何更改。


2
请注意,daemon(7)手册中提到了创建守护程序的步骤,并警告:不要使用BSD daemon()函数,因为它仅实现这些步骤的一部分。 daemon该功能首先出现在4.4BSD中,并且 兼容POSIX
patryk.beza 16/09/18

2
还要注意,有关使用daemon()的警告位于daemon(7)手册页的旧式SysV部分中。不建议在systemd中使用daemon()。
格雷格·麦克弗兰

7

我可以在第一个条件下停止“ 无法停止的守护程序 “ ...”

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

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

所有守护程序都可以停止。有些比其他更容易停止。甚至在伙伴处于按下状态的守护程序对中,如果丢失则重新生成伙伴也可以停止。您只需要加倍努力。


7
我认为,通过说“无法停止的守护程序”,作者实际上意味着该守护程序在会话终止时始终在后台运行。
FaceBro 2014年

6

如果您的应用是以下之一:

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

(还可以轻松地让您监视应用目录中的代码更改,并在发生代码更改时自动重启应用进程)


2
这与C.无关
。– melpomene

4
我感谢有一个C标签。但是,OP在问题中未提及有关C的要求。标题是在Linux中创建一个恶魔。这个答案就满足了。
danday74 '17

1
哦,对了。它标记为C,但实际要求是C ++(如OP的代码和链接的文章所证明)。
melpomene


2

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

在Windows上,您提供服务,注册服务,然后在管理->服务面板中将其设置为在启动时自动启动。


1
谢谢。那么,“守护程序”和普通的Programm之间没有区别吗?我不希望它很容易关闭。
chrisMe

1
不,守护程序只是一个后台进程。更具体地说,您是从父级派生的,运行子进程并终止父级(这样就无法通过终端访问该程序)。成为“守护程序”并不一定需要它:en.wikipedia.org/wiki/Daemon_(计算)
Magn3s1um

1

守护程序模板

我在新样式的守护程序之后编写了一个守护程序模板: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.");
}

守护进程

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::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;
        }
    }
}

守护程序模板服务

[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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.