How to Daemonize in Linux - silent_wind的专栏 - 博客频道 - CSDN.NET
One of the things I keep running across is Linux daemons that don’t properly daemonize themselves. To properly daemonize, the following steps must be followed.
The fork() call is used to create a separate process. The setsid() call is used to detach the process from the parent (normally a shell). The file mask should be reset. The current directory should be changed to something benign. The standard files (stdin,stdout and stderr) need to be reopened.Failure to do any of these steps will lead to a daemon process that can misbehave. The typical symptoms are as follows.
Starting the daemon and then logging out will cause the terminal to hang. This is particularly nasty with ssh. The directory from which the daemon was launched remains locked. Spurious output appears in the shell from which the daemon was started.Simple Example
The following example program performs the bare minimum steps required to launch a daemon process.
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #define EXIT_SUCCESS 0 #define EXIT_FAILURE 1 static void daemonize(void) { pid_t pid, sid; /* already a daemon */ if ( getppid() == 1 ) return; /* Fork off the parent process */ pid = fork(); if (pid < 0) { exit(EXIT_FAILURE); } /* If we got a good PID, then we can exit the parent process. */ if (pid > 0) { exit(EXIT_SUCCESS); } /* At this point we are executing as the child process */ /* Change the file mode mask */ umask(0); /* Create a new SID for the child process */ sid = setsid(); if (sid < 0) { exit(EXIT_FAILURE); } /* Change the current working directory. This prevents the current directory from being locked; hence not being able to remove it. */ if ((chdir("/")) < 0) { exit(EXIT_FAILURE); } /* Redirect standard files to /dev/null */ freopen( "/dev/null", "r", stdin); freopen( "/dev/null", "w", stdout); freopen( "/dev/null", "w", stderr); } int main( int argc, char *argv[] ) { daemonize(); /* Now we are a daemon -- do the work for which we were paid */ return 0; }
It has been brought to my attention that a second call to fork() may be required to fully detach the process from the controller terminal (in other words: fork, setsid, fork). This does not seem to be required in Linux. A second fork would not cause any problems, although it would complicate the child/parent signalling below.
A More Useful Example
The following program extends the basic daemon by adding the following features.
Logs messages to the system log (via syslog). Creates a lock file to prevent the daemon from being run twice. Changes the effective user (drops privileges). Startup errors are reported to the main process.#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <syslog.h> #include <errno.h> #include <pwd.h> #include <signal.h> /* Change this to whatever your daemon is called */ #define DAEMON_NAME "mydaemon" /* Change this to the user under which to run */ #define RUN_AS_USER "daemon" #define EXIT_SUCCESS 0 #define EXIT_FAILURE 1 static void child_handler(int signum) { switch(signum) { case SIGALRM: exit(EXIT_FAILURE); break; case SIGUSR1: exit(EXIT_SUCCESS); break; case SIGCHLD: exit(EXIT_FAILURE); break; } } static void daemonize( const char *lockfile ) { pid_t pid, sid, parent; int lfp = -1; /* already a daemon */ if ( getppid() == 1 ) return; /* Create the lock file as the current user */ if ( lockfile && lockfile[0] ) { lfp = open(lockfile,O_RDWR|O_CREAT,0640); if ( lfp < 0 ) { syslog( LOG_ERR, "unable to create lock file %s, code=%d (%s)", lockfile, errno, strerror(errno) ); exit(EXIT_FAILURE); } } /* Drop user if there is one, and we were run as root */ if ( getuid() == 0 || geteuid() == 0 ) { struct passwd *pw = getpwnam(RUN_AS_USER); if ( pw ) { syslog( LOG_NOTICE, "setting user to " RUN_AS_USER ); setuid( pw->pw_uid ); } } /* Trap signals that we expect to recieve */ signal(SIGCHLD,child_handler); signal(SIGUSR1,child_handler); signal(SIGALRM,child_handler); /* Fork off the parent process */ pid = fork(); if (pid < 0) { syslog( LOG_ERR, "unable to fork daemon, code=%d (%s)", errno, strerror(errno) ); exit(EXIT_FAILURE); } /* If we got a good PID, then we can exit the parent process. */ if (pid > 0) { /* Wait for confirmation from the child via SIGTERM or SIGCHLD, or for two seconds to elapse (SIGALRM). pause() should not return. */ alarm(2); pause(); exit(EXIT_FAILURE); } /* At this point we are executing as the child process */ parent = getppid(); /* Cancel certain signals */ signal(SIGCHLD,SIG_DFL); /* A child process dies */ signal(SIGTSTP,SIG_IGN); /* Various TTY signals */ signal(SIGTTOU,SIG_IGN); signal(SIGTTIN,SIG_IGN); signal(SIGHUP, SIG_IGN); /* Ignore hangup signal */ signal(SIGTERM,SIG_DFL); /* Die on SIGTERM */ /* Change the file mode mask */ umask(0); /* Create a new SID for the child process */ sid = setsid(); if (sid < 0) { syslog( LOG_ERR, "unable to create a new session, code %d (%s)", errno, strerror(errno) ); exit(EXIT_FAILURE); } /* Change the current working directory. This prevents the current directory from being locked; hence not being able to remove it. */ if ((chdir("/")) < 0) { syslog( LOG_ERR, "unable to change directory to %s, code %d (%s)", "/", errno, strerror(errno) ); exit(EXIT_FAILURE); } /* Redirect standard files to /dev/null */ freopen( "/dev/null", "r", stdin); freopen( "/dev/null", "w", stdout); freopen( "/dev/null", "w", stderr); /* Tell the parent process that we are A-okay */ kill( parent, SIGUSR1 ); } int main( int argc, char *argv[] ) { /* Initialize the logging interface */ openlog( DAEMON_NAME, LOG_PID, LOG_LOCAL5 ); syslog( LOG_INFO, "starting" ); /* One may wish to process command line arguments here */ /* Daemonize */ daemonize( "/var/lock/subsys/" DAEMON_NAME ); /* Now we are a daemon -- do the work for which we were paid */ /* Finish up */ syslog( LOG_NOTICE, "terminated" ); closelog(); return 0; }