Signals and Daemon Processes: UNIX Programming
Signals and Daemon Processes: UNIX Programming
When a signal is sent to a process, it is pending on the process to handle it. The process can react to pending
signals in one of three ways:
Accept the default action of the signal, which for most signals will terminate the process.
Ignore the signal. The signal will be discarded and it has no affect whatsoever on the recipient process.
Invoke a user-defined function. The function is known as a signal handler routine and the signal is said to be
caught when this function is called.
The formal argument of the API are: sig_no is a signal identifier like SIGINT or SIGTERM. The handler
argument is the function pointer of a user-defined signal handler function.
The following example attempts to catch the SIGTERM signal, ignores the SIGINT signal, and accepts the
default action of the SIGSEGV signal. The pause API suspends the calling process until it is interrupted by a
signal and the corresponding signal handler does a return:
#include<iostream.h>
#include<signal.h>
/*signal handler function*/
void catch_sig(int sig_num)
{
signal (sig_num,catch_sig);
cout<<”catch_sig:”<<sig_num<<endl;
}
/*main function*/
int main()
{
signal(SIGTERM,catch_sig);
signal(SIGINT,SIG_IGN);
signal(SIGSEGV,SIG_DFL);
pause( ); /*wait for a signal interruption*/
}
The SIG_IGN specifies a signal is to be ignored, which means that if the signal is generated to the process, it will
be discarded without any interruption of the process.
The SIG_DFL specifies to accept the default action of a signal.
SIGNAL MASK
A process initially inherits the parent’s signal mask when it is created, but any pending signals for the parent process
are not passed on. A process may query or set its signal mask via the sigprocmask API:
#include <signal.h>
int sigprocmask(int cmd, const sigset_t *new_mask, sigset_t *old_mask);
The BSD UNIX and POSIX.1 define a set of API known as sigsetops functions:
#include<signal.h>
The sigemptyset API clears all signal flags in the sigmask argument.
The sigaddset API sets the flag corresponding to the signal_num signal in the sigmask argument.
The sigdelset API clears the flag corresponding to the signal_num signal in the sigmask argument.
The sigfillset API sets all the signal flags in the sigmask argument.
[ all the above functions return 0 if OK, -1 on error ]
The sigismember API returns 1 if flag is set, 0 if not set and -1 if the call fails.
The following example checks whether the SIGINT signal is present in a process signal mask and adds it to the mask if
it is not there.
#include<stdio.h>
#include<signal.h>
int main()
{
sigset_t sigmask;
sigemptyset(&sigmask); /*initialise set*/
A process can query which signals are pending for it via the sigpending API:
#include<signal.h>
int sigpending(sigset_t* sigmask);
Returns 0 if OK, -1 if fails.
The sigpending API can be useful to find out whether one or more signals are pending for a process and to
set up special signal handling methods for these signals before the process calls the sigprocmask API to
unblock them.
The following example reports to the console whether the SIGTERM signal is pending for the process:
#include<iostream.h>
#include<stdio.h>
In addition to the above, UNIX also supports following APIs for signal mask manipulation:
#include<signal.h>
SIGACTION
The sigaction API blocks the signal it is catching allowing a process to specify additional signals to be blocked
when the API is handling a signal.
The sigaction API prototype is:
#include<signal.h>
int sigaction(int signal_num, struct sigaction* action, struct sigaction* old_action);
Returns: 0 if OK, 1 on error
The struct sigaction data type is defined in the <signal.h> header as:
struct sigaction
{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flag;
}
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask,SIGSEGV);
action.sa_handler=callme;
action.sa_flags=0;
if(sigaction(SIGINT,&action,&old_action)==-1)
perror(“sigaction”);
pause();
cout<<argv[0]<<”exists\n”;
return 0;
}
The sigsetjmp and siglongjmp are created to support signal mask processing.
Specifically, it is implementation-dependent on whether a process signal mask is saved and restored when it
invokes the setjmp and longjmp APIs respectively.
The only difference between these functions and the setjmp and longjmp functions is that sigsetjmp has an
additional argument.
If savemask is nonzero, then sigsetjmp also saves the current signal mask of the process in env. When
siglongjmp is called, if the env argument was saved by a call to sigsetjmp with a nonzero savemask, then
siglongjmp restores the saved signal mask.
The siglongjmp API is usually called from user-defined signal handling functions. This is because a process signal
mask is modified when a signal handler is called, and siglongjmp should be called to ensure the process signal
mask is restored properly when “jumping out” from a signal handling function.
7 BrCE, Dept of CSE, Bengaluru
Module 5 UNIX Programming
The following program illustrates the uses of sigsetjmp and siglongjmp APIs.
#include<iostream.h>
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<setjmp.h>
sigjmp_buf env;
void callme(int sig_num)
{
cout<< “catch signal:” <<sig_num <<endl;
siglongjmp(env,2);
}
int main()
{
sigset_t sigmask;
struct sigaction action,old_action;
sigemptyset(&sigmask);
if(sigaddset(&sigmask,SIGTERM)==-1) || sigprocmask(SIG_SETMASK,&sigmask,0)==-1)
perror(“set signal mask”);
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask,SIGSEGV);
action.sa_handler=(void(*)())callme;
action.sa_flags=0;
if(sigaction(SIGINT,&action,&old_action)==-1)
perror(“sigaction”);
if(sigsetjmp(env,1)!=0)
{
cerr<<”return from signal interruption”;
return 0;
}
else
cerr<<”return from first time sigsetjmp is called”;
pause();
}
KILL
A process can send a signal to a related process via the kill API. This is a simple means of inter-process
communication or control. The function prototype of the API is:
#include<signal.h>
int kill(pid_t pid, int signal_num); Returns: 0 on success, -1 on failure.
The signal_num argument is the integer value of a signal to be sent to one or more processes designated by
pid. The possible values of pid and its use by the kill API are:
pid > 0 The signal is sent to the process whose process ID is pid.
pid == 0 The signal is sent to all processes whose process group ID equals the process group ID of
the sender and for which the sender has permission to send the signal.
pid < 0 The signal is sent to all processes whose process group ID equals the absolute value of pid
and for which the sender has permission to send the signal.
pid == 1 The signal is sent to all processes on the system for which the sender has permission to send
the signal.
The following program illustrates the implementation of the UNIX kill command using the kill API:
#include<iostream.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
int main(int argc,char** argv)
{
int pid, sig =
SIGTERM;
if(argc==3)
{
if(sscanf(argv[1],”%d”,&sig)!=1)
{
cerr<<”invalid number:” << argv[1] <<
endl; return -1;
}
argv++,argc--;
}
while(--argc>0)
if(sscanf(*++argv, “%d”, &pid)==1)
{
if(kill(pid,sig)==-1)
perror(“kill”);
}
else
cerr<<”invalid pid:” << argv[0] <<endl;
return 0;
}
ALARM
The alarm API can be called by a process to request the kernel to send the SIGALRM signal after a certain
number of real clock seconds. The function prototype of the API is:
#include<signal.h>
Unsigned int alarm(unsigned int time_interval); Returns: 0 or number of seconds until previously set alarm
INTERVAL TIMERS
The interval timer can be used to schedule a process to do some tasks at a fixed time interval, to time the
execution of some operations, or to limit the time allowed for the execution of some tasks.
The following program illustrates how to set up a real-time clock interval timer using the alarm API:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#define INTERVAL 5
void callme(int sig_no)
{
alarm(INTERVAL);
/*do scheduled tasks*/
}
In addition to alarm API, UNIX also invented the setitimer API, which can be used to define up to three different
types of timers in a process:
Real time clock timer
Timer based on the user time spent by a process
Timer based on the total user and system times spent by a process
The getitimer API is also defined for users to query the timer values that are set by the setitimer API.
The setitimer and getitimer function prototypes are:
#include<sys/time.h>
int setitimer(int which, const struct itimerval * val, struct itimerval * old);
int getitimer(int which, struct itimerval * old);
The which arguments to the above APIs specify which timer to process. Its possible values and the corresponding
timer types are:
Example program:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#define INTERVAL 5
void callme(int sig_no)
{
/*do scheduled tasks*/
}
int main()
{
struct itimerval val;
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_handler=(void(*)( )) callme;
action.sa_flags=SA_RESTART;
if(sigaction(SIGALARM,&action,0)==-1)
{
perror(“sigaction”);
return 1;
}
val.it_interval.tv_sec =INTERVAL;
val.it_interval.tv_usec =0;
val.it_value.tv_sec =INTERVAL;
val.it_value.tv_usec =0;
else while(1)
{
/*do normal operation*/
}
return 0;
}
The setitimer and getitimer APIs return a zero value if they succeed or a -1 value if they fail.
POSIX.1b TIMERS
POSIX.1b defines a set of APIs for interval timer manipulations. The POSIX.1b timers are more flexible and powerful
than are the UNIX timers in the following ways:
Users may define multiple independent timers per system clock.
The timer resolution is in nanoseconds.
Users may specify the signal to be raised when a timer expires.
The time interval may be specified as either an absolute or a relative time.
DAEMON PROCESSES
INTRODUCTION
Daemons are processes that live for a long time. They are often started when the system is bootstrapped and
terminate only when the system is shut down.
DAEMON CHARACTERISTICS
The characteristics of daemons are:
Daemons run in background.
Daemons have super-user privilege.
Daemons don’t have controlling terminal.
Daemons are session and group leaders.
CODING RULES
Call umask to set the file mode creation mask to 0. The file mode creation mask that's inherited could be set to
deny certain permissions. If the daemon process is going to create files, it may want to set specific permissions.
Call fork and have the parent exit. This does several things. First, if the daemon was started as a simple shell
command, having the parent terminate makes the shell think that the command is done. Second, the child inherits
the process group ID of the parent but gets a new process ID, so we're guaranteed that the child is not a process
group leader.
Call setsid to create a new session. The process (a) becomes a session leader of a new session, (b) becomes the
process group leader of a new process group, and (c) has no controlling terminal.
Change the current working directory to the root directory. The current working directory inherited from the
parent could be on a mounted file system. Since daemons normally exist until the system is rebooted, if the
daemon stays on a mounted file system, that file system cannot be unmounted.
Unneeded file descriptors should be closed. This prevents the daemon from holding open any descriptors that it
may have inherited from its parent.
Some daemons open file descriptors 0, 1, and 2 to /dev/null so that any library routines that try to read from
standard input or write to standard output or standard error will have no effect. Since the daemon is not
associated with a terminal device, there is nowhere for output to be displayed; nor is there anywhere to receive
input from an interactive user. Even if the daemon was started from an interactive session, the daemon runs in the
background, and the login session can terminate without affecting the daemon. If other users log in on the same
terminal device, we wouldn't want output from the daemon showing up on the terminal, and the users wouldn't
expect their input to be read by the daemon.
Example Program:
#include <unistd,h>
#include
<sys/types.h>
#include <fcntl.h>
int daemon_initialise( )
{
pid_t pid;
if (( pid = for() ) < 0)
return –1;
else if ( pid != 0)
exit(0); /* parent exits */
/* child continues */
setsid( );
chdir(“/”);
umask(0);
return 0;
}
ERROR LOGGING
One problem a daemon has is how to handle error messages. It can't simply write to standard
error, since it shouldn't have a controlling terminal. We don't want all the daemons writing to the
14 BrCE, Dept of CSE, Bengaluru
Module 5 UNIX Programming
console device, since on many workstations, the console device runs a windowing system. A
central daemon error-logging facility is required.
Normally, the syslogd daemon reads all three forms of log messages. On start-up, this daemon reads a
configuration file, usually /etc/syslog.conf, which determines where different classes of messages are to
be sent. For example, urgent messages can be sent to the system administrator (if logged in) and printed
on the console, whereas warnings may be logged to a file. Our interface to this facility is through the
syslog function.
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
SINGLE-INSTANCE DAEMONS
Some daemons are implemented so that only a single copy of the daemon should be running at a
time for proper operation.
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>
int already_running(void)
{
int fd;
char buf[16];
DAEMON CONVENTIONS
If the daemon uses a lock file, the file is usually stored in /var/run. Note, however, that the daemon might
need superuser permissions to create a file here. The name of the file is usually name .pid, where name is the
name of the daemon or the service. For example, the name of the cron daemon's lock file is
/var/run/crond.pid.
If the daemon supports configuration options, they are usually stored in /etc. The configuration file
is named name.conf, where name is the name of the daemon or the name of the service. For
example, the configuration for the syslogddaemon is /etc/syslog.conf.
Daemons can be started from the command line, but they are usually started from one of the
system initialization scripts (/etc/rc* or /etc/init.d/*). If the daemon should be restarted
automatically when it exits, we can arrange for initto restart it if we include a respawnentry for it in
/etc/inittab.
If a daemon has a configuration file, the daemon reads it when it starts, but usually won't look at it
again. If an administrator changes the configuration, the daemon would need to be stopped and
restarted to account for the configuration changes. To avoid this, some daemons will catch SIGHUP
and reread their configuration files when they receive the signal. Since they aren't associated with
terminals and are either session leaders without controlling terminals or members of orphaned
process groups, daemons have no reason to expect to receive SIGHUP. Thus, they can safely reuse it
CLIENT-SERVER MODEL
In general, a server is a process that waits for a client to contact it, requesting some type of
service. In Figure 13.2 [REFER PAGE 10], the service being provided by the syslogd server is the
logging of an error message.
In Figure 13.2, the communication between the client and the server is one-way. The client sends its
service request to the server; the server sends nothing back to the client. In the upcoming chapters,
we'll see numerous examples of two-way communication between a client and a server. The client
sends a request to the server, and the server sends a reply back to the client.