10. Daemon ohjelmointi
Daemon-prosessi yleensä määritellään taustaprosessiksi, joka ei kuulu terminaali-istuntoon. Useita systeemipalveluita suoritaa daemonit, esimerkiksi tietoliikennepalvelut, tulostus jne.
Yksinkertainen taustakäynnistys ei riitä näille pitkäkestoisille ohjelmille. Taustakäynnistys ei oikein irroita prosessia terminaali-istunnosta, joka sen käynnisti. Yleinen tapa käynnistää daemon on joko käynnistää se manuaalisesti tai rc-skriptistä. Daemonin odotetaan itse laittavansa itsensä taustalle.
Seuraavat toimet tehdään daemon-prosessissa:
- fork(), jotta äiti voi suoritaa exit() kutsun. Tällöin kontrolli palaa komentorivile tai shell-ohjelmalle. Tämä vaaditaan, jotta uusi prosessi ei ole prosessiryhmän johtaja.
- setsid(), jotta päästää prosessiryhmän ja sessioryhmän johtajaksi. Koska kontrollipääte on yhdistetty sessioon, tämä uusi sessio ei vielä ole hankkinut kontrolliprosessia. Daemonissa kontrollipäätettä ei tarvita.
- fork() uudestaan, jotta äiti ( siis sessioryhmän johtaja) voi suorittaa exit():n. Tämä tarkoittaa ettei kontrollipäätettä edes enää voi saada.
- chdir("/") varmistamaan ettei prosessi jätä yhtään hakemistoa käyttöön. Ilman tätä ylläpitäjä ei voisi unmountata tiedostojärjestelmää, koska se olisi prosessin nykyhakemisto.
- umask(0). jotta on täysi kontrolli kaikkeen kirjoittamiseen. Käytännössä ei tiedetä minkä umask:n prosessi on perinyt.
- close() tiedostokuvaajat 0, 1, ja 2. Tämä vapauttaa tiedostokuvaajat STDIN, STDOUT, ja STDERR. Käytännössä ei ole mitään tietoa mihin nämä tiedostokuvaajat on ohjattu. Kaikki muutkin tiedostokuvaajat on syytä sulkea.
- Luodaan uudet tiedostokuvaajat STDIN, STDOUT ja STDERR varten. Vaikka niitä ei käyttäisi, on hyvä idea avata. Esimerkiksi lokitiedostoa varten voi avata STDOUT tai STDERR ja /dev/null STDIN:nksi.
Käytännössä mikään näistä ei ole välttämätöntä (tai suositeltavaa) jos daemon käynistetään inetd:ssä. Vain chdir() ja umask() kohdat säilyvät hyödyllisinä.
Esimerkkikoodi daemon.c.
#include < unistd.h>
#include < stdlib.h>
#include < fcntl.h>
#include < signal.h>
#include < sys/types.h>
#include < sys/wait.h>
#include < errno.h>
#define TCP_PORT 8888
/* closeall() -- close all FDs >= a specified value */
void closeall(int fd)
{
int fdlimit = sysconf(_SC_OPEN_MAX);
while (fd < fdlimit)
close(fd++);
}
/* daemon() - detach process from user and disappear into the background
* returns -1 on failure, but you can't do much except exit in that case
* since we may already have forked. This is based on the BSD version,
* so the caller is responsible for things like the umask, etc.
*/
/* believed to work on all Posix systems */
int daemon(int nochdir, int noclose)
{
switch (fork())
{
case 0: break;
case -1: return -1;
default: _exit(0); /* exit the original process */
}
if (setsid() < 0) /* shoudn't fail */
return -1;
/* dyke out this switch if you want to acquire a control tty in */
/* the future -- not normally advisable for daemons */
switch (fork())
{
case 0: break;
case -1: return -1;
default: _exit(0);
}
if (!nochdir)
chdir("/");
if (!noclose)
{
closeall(0);
open("/dev/null",O_RDWR);
dup(0); dup(0);
}
return 0;
}
/* fork2() -- like fork, but the new process is immediately orphaned
* (won't leave a zombie when it exits)
* Returns 1 to the parent, not any meaningful pid.
* The parent cannot wait() for the new process (it's unrelated).
*/
/* This version assumes that you *haven't* caught or ignored SIGCHLD. */
/* If you have, then you should just be using fork() instead anyway. */
int fork2()
{
pid_t pid;
int rc;
int status;
if (!(pid = fork()))
{
switch (fork())
{
case 0: return 0;
case -1: _exit(errno); /* assumes all errnos are <256 */
default: _exit(0);
}
}
if (pid < 0 || waitpid(pid,&status,0) < 0)
return -1;
if (WIFEXITED(status))
if (WEXITSTATUS(status) == 0)
return 1;
else
errno = WEXITSTATUS(status);
else
errno = EINTR; /* well, sort of :-) */
return -1;
}
void errexit(const char *str)
{
syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
exit(1);
}
void errreport(const char *str)
{
syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
}
/* the actual child process is here. */
void run_child(int sock)
{
FILE *in = fdopen(sock,"r");
FILE *out = fdopen(sock,"w");
int ch;
setvbuf(in, NULL, _IOFBF, 1024);
setvbuf(out, NULL, _IOLBF, 1024);
while ((ch = fgetc(in)) != EOF)
fputc(toupper(ch), out);
fclose(out);
}
/* This is the daemon's main work -- listen for connections and spawn */
void process()
{
struct sockaddr_in addr;
int addrlen = sizeof(addr);
int sock = socket(AF_INET, SOCK_STREAM, 0);
int flag = 1;
int rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
&flag, sizeof(flag));
if (rc < 0)
errexit("setsockopt");
addr.sin_family = AF_INET;
addr.sin_port = htons(TCP_PORT);
addr.sin_addr.s_addr = INADDR_ANY;
rc = bind(sock, (struct sockaddr *) &addr, addrlen);
if (rc < 0)
errexit("bind");
rc = listen(sock, 5);
if (rc < 0)
errexit("listen");
for (;;)
{
rc = accept(sock, (struct sockaddr *) &addr, &addrlen);
if (rc >= 0)
switch (fork2())
{
case 0: close(sock); run_child(rc); _exit(0);
case -1: errreport("fork2"); close(rc); break;
default: close(rc);
}
}
}
int main()
{
if (daemon(0,0) < 0)
{
perror("daemon");
exit(2);
}
openlog("test", LOG_PID, LOG_DAEMON);
process();
return 0;
}
Jan Lindström (Jan.Lindstrom@cs.Helsinki.FI)

