在Linux中创建守护进程

时间:2013-07-30 18:17:05

标签: c linux daemon

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

9 个答案:

答案 0 :(得分:184)

  

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

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

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


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

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

为您提供一个起点:查看显示基本步骤的骨架代码:

/*
 * 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 (初始流程)
  • PID!= SID 表示我们的流程不是会话负责人 (因为第二个fork())
  • 因为PID!= SID我们的流程无法再次控制TTY

阅读系统日志:

  • 找到您的syslog文件。我在这里:/var/log/syslog
  • 执行:grep firstdaemon /var/log/syslog

  • 输出应与此类似:

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


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

进一步阅读:

答案 1 :(得分:28)

man 7 daemon描述了如何非常详细地创建守护进程。我的回答只是摘自本手册。

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

  1. 传统SysV守护进程(old-style),
  2. systemd守护进程(new-style)。
  3. SysV守护进程

    如果您对传统SysV守护程序感兴趣,则应实施following steps

      
        
    1. 关闭除标准输入输出错误之外的所有打开文件描述符(即前三个文件描述符0,1,2) 。这可确保在守护进程中不会意外传递文件描述符。在Linux上,最好通过迭代/proc/self/fd来实现,其中从文件描述符3迭代到getrlimit() RLIMIT_NOFILE返回的值。
    2.   
    3. Reset所有信号处理程序都是默认值。最好通过迭代可用信号,最高限度为_NSIG并将其重置为SIG_DFL
    4.   
    5. 使用sigprocmask()重置信号掩码。
    6.   
    7. 清理环境块,删除或重置可能对守护程序运行时产生负面影响的环境变量。
    8.   
    9. 致电fork(),创建后台流程。
    10.   
    11. 在孩子中,请致电setsid()以从任何终端分离并创建独立的session
    12.   
    13. 在孩子中,再次呼叫fork(),以确保守护程序永远不会再次重新获得终端。
    14.   
    15. 在第一个孩子中调用exit(),以便只有第二个孩子(实际的守护程序进程)停留在周围。这可以确保守护进程重新成为init / PID 1的父级,因为所有守护进程都应该是。
    16.   
    17. 在守护进程中,将/dev/null连接到标准输入输出错误
    18.   
    19. 在守护程序进程中,将umask重置为0,以便传递给open()mkdir()等文件模式直接控制所创建文件和目录的访问模式。
    20.   
    21. 在守护进程中,change当前目录到根目录(/),以避免守护进程不自觉地阻止挂载点被卸载。
    22.   
    23. 在守护程序进程中,将守护程序PID(由getpid()返回)写入PID文件,例如/run/foobar.pid(对于假设的守护程序&#34; foobar&#34 ;)确保守护程序不能多次启动。这必须以无竞争的方式实现,以便PID文件仅在先前存储在PID文件中的PID不再存在或属于外部进程的同时进行验证时更新。
    24.   
    25. 在守护程序进程中,删除权限(如果可能且适用)。
    26.   
    27. 从守护程序进程,通知原始进程启动初始化完成。这可以通过在第一个fork()之前创建的未命名管道或类似通信通道来实现,因此可以在原始进程和守护进程中使用。
    28.   
    29. 在原始流程中调用exit()。调用该守护进程的进程必须能够依赖于{/ 3}}在初始化完成后发生并且所有外部通信通道都已建立并可访问。
    30.   

    请注意此警告:

      

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

         

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

    请注意,daemon()不符合daemon()

    新式守护进程

    对于新式守护进程,建议使用POSIX

      
        
    1. 如果收到SIGTERM,请关闭守护程序并彻底退出。
    2.   
    3. 如果收到SIGHUP,请重新加载配置文件(如果适用)。
    4.   
    5. 从主守护程序进程提供正确的退出代码,因为init系统使用它来检测服务错误和问题。建议遵循following steps
    6. 中定义的退出代码方案   
    7. 如果可能且适用,请通过LSB recommendations for SysV init scripts系统公开守护程序的控制界面,并在初始化的最后一步获取总线名称。
    8.   
    9. 要在systemd中进行集成,请提供一个D-Bus IPC .service文件,其中包含有关启动,停止和维护守护程序的信息。有关详细信息,请参阅unit
    10.   
    11. 尽可能依赖init系统的功能来限制守护进程对文件,服务和其他资源的访问,即在systemd的情况下,依赖于systemd的{{3而不是实现自己的代码,依赖于systemd的systemd.service(5)代码,而不是在守护进程中实现它,类似。有关可用的控件,请参阅resource limit control
    12.   
    13. 如果使用privilege dropping,请通过提供D-Bus服务激活systemd.exec(5)使您的守护程序总线可激活。这有多个优点:您的守护进程可以按需启动;它可以与其他需要它的守护进程并行启动 - 最大化并行化和D-Bus;您的守护程序可以在失败时重新启动而不会丢失任何总线请求,因为总线会对可激活服务的请求进行排队。有关详细信息,请参阅configuration file
    14.   
    15. 如果您的守护程序通过套接字向其他本地进程或远程客户端提供服务,则应按照指出boot-up speed的方案进行below。与D-Bus激活一样,这可以按需启动服务,并且可以改善服务启动的并行化。此外,对于无状态协议(例如syslog,DNS),可以重新启动实现基于套接字的激活的守护程序,而不会丢失单个请求。有关详细信息,请参阅socket-activatable
    16.   
    17. 如果适用,守护程序应通过below接口通知init系统有关启动完成或状态更新的信息。
    18.   
    19. 新型守护程序可以选择只通过below简单地登录到标准错误,而不是使用sd_notify(3)调用直接登录到系统syslog服务,然后由syslog()转发到syslog。 init系统。如果需要日志级别,则可以通过在各个日志行前面添加诸如&#34;&lt; 4&gt;&#34;之类的字符串来对这些日志级别进行编码。 (对于日志级别4和#34;警告&#34;在syslog优先级方案中),遵循与Linux内核的fprintf()级别系统类似的样式。有关详细信息,请参阅printk()sd-daemon(3)
    20.   

    要了解详情,请阅读整篇systemd.exec(5)

答案 2 :(得分:7)

你不能在linux中创建一个无法杀死的进程。 root用户(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表示捕获并忽略该信号。您可以构建一个可以记录信号接收的信号处理程序,并设置标志(例如标志以指示正常关闭)。

答案 3 :(得分:5)

我可以停止第一个要求“一个无法停止的守护进程 ......”

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

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

可以停止所有守护进程。有些人比其他人更容易被阻止。即使与伴侣保持一对守护进程,如果丢失也会重新生成伴侣,可以停止。你只需要更努力地工作。

答案 4 :(得分:5)

尝试使用daemon功能:

#include <unistd.h>

int daemon(int nochdir, int noclose);

来自man page

  

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

     

如果nochdir为零,则daemon()会更改调用进程的当前值   工作目录到根目录(&#34; /&#34;);否则,目前   工作目录保持不变。

     

如果noclose为零,则守护进程()重定向标准输入,标准   输出和标准错误到/ dev / null;否则,没有变化   对这些文件描述符做了。

答案 5 :(得分:5)

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

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

(也很容易让您在应用目录中观察代码更改,并在代码更改时自动重启应用进程)

答案 6 :(得分:2)

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

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

答案 7 :(得分:1)

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

在Windows上,您创建服务,注册服务,然后将其设置为在管理中启动时自动启动 - &gt;服务小组。

答案 8 :(得分:0)

守护程序模板

我在新样式的守护程序之后编写了一个守护程序模板:link

您可以在GitHub上找到完整的模板代码:here

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