130

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

9 回答 9

252

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

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

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


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

  • 分叉父进程并在分叉成功时让它终止。-> 因为父进程已经终止,子进程现在在后台运行。
  • setsid - 创建一个新会话。调用进程成为新会话的领导者和新进程组的进程组领导者。该进程现在已与其控制终端 (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 | 西德 | 文字打字机 | TPGID | 统计 | 识别码 | 时间 | 命令行 |
+-----+-----+------+------+-----+--------+------+ ------+------+------+
| 1 | 3387 | 3386 | 3386 | ? | -1 | 小号 | 1000 | 0:00 | ./ |
+-----+-----+------+------+-----+--------+------+ ------+------+------+

你应该在这里看到的是:

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

读取系统日志:

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

  • 输出应该类似于这个:

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


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

进一步阅读:

于 2013-07-30T18:57:40.253 回答
36

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

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

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

SysV 守护进程

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

  1. 关闭除标准输入输出错误之外的所有打开的文件描述符(即前三个文件描述符 0、1、2)。这确保了不会意外传递的文件描述符留在守护进程中。在 Linux 上,最好通过迭代来实现/proc/self/fd,并从文件描述符 3 迭代到 for 返回的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. 在 daemon 进程中,将 重置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-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。如果日志级别是必要的,可以通过在单个日志行前加上“<4>”之类的字符串(对于 syslog 优先级方案中的日志级别 4 “WARNING”)进行编码,遵循与 Linux 内核printk()级别系统类似的样式。有关详细信息,请参阅sd-daemon(3)systemd.exec(5)

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

于 2016-08-07T20:30:05.733 回答
11

您不能在 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 表示捕获和忽略信号。您可以构建一个可以记录信号接收的信号处理程序,并设置标志(例如指示正常关闭的标志)。

于 2014-01-28T23:36:02.583 回答
9

尝试使用该daemon功能:

#include <unistd.h>

int daemon(int nochdir, int noclose);

手册页

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

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

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

于 2015-03-12T03:49:34.873 回答
6

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

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

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

可以停止所有守护程序。有些人比其他人更容易停止。即使是与伙伴处于按住状态的守护进程对,如果失去伙伴,则重新生成伙伴,也可以停止。你只需要更加努力地工作。

于 2013-07-30T19:20:24.347 回答
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

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

于 2017-01-29T03:17:57.503 回答
3

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

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

于 2013-07-30T18:32:54.193 回答
3

守护进程模板

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

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

主文件

// 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.");
}

守护进程.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);
};

守护进程.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;
        }
    }
}

守护进程-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
于 2019-12-31T16:38:54.917 回答
2

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

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

于 2013-07-30T18:22:57.537 回答