Previous Post | Top | Next Post |
TOC
This was originally written and created around 2013 and may require to be updated. (2021)
Process
Here are some practice results to play with process and inter process communication (IPC) (signal and network socket) on Debian wheezy.
Signal
Signal is explained in signal
(7).
Here are default actions for notable signals.
- Default action is to terminate the process.
SIGHUP
= 1 : Death of controlling processSIGINT
= 2 : Interrupt from keyboard (Ctrl-C
)SIGKILL
= 9 : Kill signal (non-trappable)SIGALRM
= 14 : Timer signal from alarm(2)SIGTERM
= 15 : Termination signal (default forkill
)SIGUSR1
= 10 : User-defined signal 1SIGUSR2
= 12 : User-defined signal 2
- Default action is to terminate the process and dump core.
SIGQUIT
= 3 : Quit from keyboard (Ctrl-\
)SIGTRAP
= 5 : Trace/breakpoint trapSIGSEGV
= 11 : Invalid memory reference
- Default action is to continue the process
SIGCONT
= 18 : Continue if stopped
- Default action is to stop the process.
SIGSTOP
= 19 : Stop process (non-trappable)SIGTSTP
= 20 : Stop typed at tty (Ctrl-Z
)
- Default action is to ignore the signal.
SIGWINCH
= 28 : Gracefully shutdown the worker processes (httpd, non-POSIX)
I can check signal assignment situation quickly with the Bash builtin “kill -l
”
command. (Not with other shells and command!)
List of signal names and signal numbers with the Bash builtin “kill -l
”
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
TIP: Please note that SIGABRT
and SIGIOT
share the same signal value 6; and
SIGIO
and SIGLOST
share the same signal value 29.
Shell: kill
The kill
commands behave differently depending on which one used. Let’s
check their syntax to send signals.
The example of kill
commands
$ bash -c kill
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill
-l [sigspec]
$ dash -c kill
dash: 1: kill: Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or
kill -l [exitstatus]
$ /bin/kill
Usage:
kill [options] <pid> [...]
Options:
<pid> [...] send signal to every <pid> listed
-<signal>, -s, --signal <signal>
specify the <signal> to be sent
-l, --list=[<signal>] list all signal names, or convert one to a name
-L, --table list all signal names in a nice table
-h, --help display this help and exit
-V, --version output version information and exit
For more details see kill(1).
$ bash -c "kill -l 15"
TERM
$ dash -c "kill -l 15"
TERM
$ /bin/kill -l15
TERM
- Only the Bash builtin
kill
can use jobspec such as%1
as its argument. - If signal number argument is given to “
/bin/kill -l
”, there should be no space before it.
Shell: trap
The trap
command can trap signals.
Example shell code with trap and infinite loop.
#!/bin/sh
trap_hup() {
echo "SIGHUP detected."
}
trap trap_hup HUP
trap "echo 'SIGTSTP detected.'" TSTP
trap "" TERM
while true; do sleep 0.01;done
Let’s play with this on the Bash shell.
Example of kill used with ./sigtrap
$ ./sigtrap &
[1] 12692
$ kill -s HUP %1
SIGHUP detected.
$ kill -s TSTP %1
SIGTSTP detected.
$ kill -n 1 %1
SIGHUP detected.
$ kill -1 %1
SIGHUP detected.
$ ps
PID TTY TIME CMD
12589 pts/9 00:00:00 bash
12692 pts/9 00:00:31 sigtrap
12697 pts/9 00:00:00 ps
$ jobs
[1]+ Running ./sigtrap &
$ kill -s INT %1
$ jobs
[1]+ Interrupt ./sigtrap
$ jobs
$ dash
$ ./sigtrap &
$ ps
PID TTY TIME CMD
12589 pts/9 00:00:00 bash
12705 pts/9 00:00:00 dash
12710 pts/9 00:00:10 sigtrap
12712 pts/9 00:00:00 ps
$ kill -s INT 12710
$ ps
PID TTY TIME CMD
12589 pts/9 00:00:00 bash
12705 pts/9 00:00:00 dash
12733 pts/9 00:00:00 ps
[1] + Interrupt ./sigtrap
$ ps
PID TTY TIME CMD
12589 pts/9 00:00:00 bash
12705 pts/9 00:00:00 dash
12734 pts/9 00:00:00 ps
If you are not on the Bash shell, you have to use PID “10951
”, instead of jobspec “%1
”.
When writing a shell script which is usually the Dash shell, PID changes every
invocation. The use of kill
is non-trivial for such case. So we use the
killall
(1) command instead.
Example of killall
used with ./sigtrap
$ ./sigtrap &
$ ps
PID TTY TIME CMD
6764 pts/4 00:00:00 sh
6765 pts/4 00:00:00 sh
6766 pts/4 00:00:00 sigtrap
6767 pts/4 00:00:00 ps
6768 pts/4 00:00:00 sleep
$ jobs
[1] + Running
$ killall -s HUP sigtrap
SIGHUP detected.
$ killall -s TSTP sigtrap
SIGTSTP detected.
$ killall -s 1 sigtrap
SIGHUP detected.
$ killall -HUP sigtrap
$ ps
SIGHUP detected.
PID TTY TIME CMD
6764 pts/4 00:00:00 sh
6765 pts/4 00:00:00 sh
6766 pts/4 00:00:00 sigtrap
6825 pts/4 00:00:00 ps
6826 pts/4 00:00:00 sleep
$ jobs
[1] + Running
$ killall -s TERM sigtrap
$ ps
PID TTY TIME CMD
6764 pts/4 00:00:00 sh
6765 pts/4 00:00:00 sh
6766 pts/4 00:00:00 sigtrap
6829 pts/4 00:00:00 ps
$ jobs
[1] + Running
$ killall -s KILL sigtrap
$ ps
PID TTY TIME CMD
6764 pts/4 00:00:00 sh
6765 pts/4 00:00:00 sh
6832 pts/4 00:00:00 ps
$ jobs
[1] + Killed
The most common use of the trap
command is to set up a hook script for the
program exit. Here a special signal EXIT
is used in addition to other normal
signals with the trap
command. This EXIT
signal is raised when shell
script itself exits without external signals.
Example shell code with trap and remove a file.
#!/bin/sh
set -x
trap 'echo "Exit! Removing foo.tmp."; rm foo.tmp' EXIT HUP INT TERM
touch foo.tmp
# actual code to use foo.tmp
ls -l foo.tmp
sleep 1 # timing to ensure ls output
Let’s play with this.
Example of ./traprm
$ ls -l foo.tmp
ls: cannot access foo.tmp: No such file or directory
$ ./traprm
+ trap echo "Exit! Removing foo.tmp."; rm foo.tmp EXIT HUP INT TERM
+ touch foo.tmp
+ ls -l foo.tmp
-rw-rw-r-- 1 osamu osamu 0 Aug 17 23:43 foo.tmp
+ sleep 1
+ echo Exit! Removing foo.tmp.
Exit! Removing foo.tmp.
+ rm foo.tmp
$ ls -l foo.tmp
ls: cannot access foo.tmp: No such file or directory
Shell: new process
Shell can start a new process easily by:
- starting another shell program as a command.
- starting a block of shell code list enclosed in
( ... )
as subshell.
TIP: Use of &
after starting the new process enables the current shell to
continue processing the subsequent part of the current shell and the new
process in parrallel. It moves the new process to the background.
TIP: Use the alternative syntax of a block of shell code list enclosed in
{ ... ; }
to execute them in the current shell environment.
Python: new process
Python can start a new process easily by:
- starting a command “
foo
” using theos.system("foo")
function after “import os
”. - starting a command “
foo
” using theos.popen("foo")
function after “import os
”.
TIP: The new subprocess module can be used to replace many process related modules and functions to make them more secure easily. See subprocess — Subprocess management
Lua: new process
Lua can start a new process easily by:
- starting a command “
foo
” using theos.execute("foo")
. - starting a command “
foo
” using theio.popen("foo")
.
C: new process
Let’s execute a command as a new process from the C program in 3 ways.
system
(3) : Thesystem()
function starts a command by calling “/bin/sh -c
” command, and returns after its completion. During execution of the command,SIGCHLD
will be blocked, andSIGINT
andSIGQUIT
will be ignored.popen
(3) : Thepopen()
function opens a process by creating a pipe(read-only or write-only), forking, and invoking the shell.fork
(2) +exec
(3) : Thefork()
function creates a new process by duplicating the calling process. Theexec()
family of functions replaces the current process image with a new process image.
C: system
Let’s start a command from the C program with system
(3) while checking environment variables.
Command execution: system.c
/* vi:set ts=4 sts=4 expandtab: */
#include <stdlib.h> /* exit getenv system */
#include <stdio.h> /* [f]printf perror */
int
main(int argc, char* argv[])
{
printf("main HOME = %s\n", getenv("HOME"));
printf("main PATH = %s\n", getenv("PATH"));
printf("main TERM = %s\n", getenv("TERM"));
printf("main DISPLAY = %s\n", getenv("DISPLAY"));
printf("--- Let's fake shell command prompt with system\n");
printf("$ ls -li system.c\n");
system("ls -li system.c");
return EXIT_SUCCESS;
}
Run ./system
$ gcc -Wall -o system system.c
$ ./system
main HOME = /home/osamu
main PATH = /path/to/c/../../bin:(CURDIR)/bin:/home/osamu/bin:/home/osamu/....
main TERM = dumb
main DISPLAY = :0
--- Let's fake shell command prompt with system
$ ls -li system.c
3558352 -rw-rw-r-- 1 osamu osamu 545 Mar 12 21:26 system.c
C: popen
Let’s start a child process with popen
(3) and pass messages via pipe from the child process to the parent process.
Command execution: popen.c
/* vi:set ts=4 sts=4 expandtab: */
#include <stdlib.h> /* exit getenv */
#include <stdio.h> /* [f]printf perror */
#include <unistd.h> /* getpid getppid fork exec sleep */
#include <sys/types.h> /* getpid getppid */
int
main(int argc, char* argv[])
{
FILE* f;
char * p;
size_t size;
p = malloc(1024);
size = (size_t) 1024;
fprintf(stderr, "main PID = %d\n", getpid());
fprintf(stderr, "main PPID = %d\n", getppid());
fprintf(stderr, "--- Let's fake shell command prompt with popen.\n");
fprintf(stderr, "$ ls -li *.c; echo PPID=$PPID; echo PID=$$\n");
f = popen("ls -li *.c; echo PPID=$PPID; echo PID=$$", "r");
if (f < 0) {
perror("E: popen");
exit(EXIT_FAILURE);
}
fprintf(stderr, "--- Let's print result of popen.\n");
while (getline(&p, &size, f) >= 0) {
fprintf(stderr, "%s", p);
}
pclose(f);
return EXIT_SUCCESS;
}
Run ./popen
$ gcc -Wall -o popen popen.c
$ echo PID=$$
PID=6435
$ echo PPID=$PPID
PPID=6434
$ ./popen
main PID = 6446
main PPID = 6435
--- Let's fake shell command prompt with popen.
$ ls -li *.c; echo PPID=$PPID; echo PID=$$
--- Let's print result of popen.
3546581 -rw-rw-r-- 1 osamu osamu 2056 Mar 12 22:12 forkexec.c
3546585 -rw-rw-r-- 1 osamu osamu 942 Mar 12 22:16 popen.c
3561347 -rw-rw-r-- 1 osamu osamu 1292 Mar 17 00:58 sigaction.c
3545799 -rw-rw-r-- 1 osamu osamu 723 Mar 16 23:00 signal.c
3558352 -rw-rw-r-- 1 osamu osamu 545 Mar 12 21:26 system.c
PPID=6446
PID=6447
$ ./popen 2>/dev/null
$ ./popen >/dev/null
main PID = 6452
main PPID = 6435
--- Let's fake shell command prompt with popen.
$ ls -li *.c; echo PPID=$PPID; echo PID=$$
--- Let's print result of popen.
3546581 -rw-rw-r-- 1 osamu osamu 2056 Mar 12 22:12 forkexec.c
3546585 -rw-rw-r-- 1 osamu osamu 942 Mar 12 22:16 popen.c
3561347 -rw-rw-r-- 1 osamu osamu 1292 Mar 17 00:58 sigaction.c
3545799 -rw-rw-r-- 1 osamu osamu 723 Mar 16 23:00 signal.c
3558352 -rw-rw-r-- 1 osamu osamu 545 Mar 12 21:26 system.c
PPID=6452
PID=6453
C: fork + exec
Let’s start a child process with fork
(2) and pass messages from the child process to the parent process via pipe.
Fork and exec example: forkexec.c
/* vi:set ts=4 sts=4 expandtab: */
#include <stdlib.h> /* exit getenv */
#include <stdio.h> /* [f]printf perror */
#include <string.h> /* strlen */
#include <unistd.h> /* getpid getppid fork exec sleep */
#include <sys/types.h> /* getpid getppid wait */
#include <sys/wait.h> /* wait */
int
main(int argc, char* argv[])
{
pid_t p;
int pipefd[2], e;
char buf;
char * b = "MESSAGE: passed from child to parent via pipe\n";
fprintf(stderr, "parent PID = %d\n", getpid());
fprintf(stderr, "parent PPID = %d\n", getppid());
if (pipe(pipefd) == -1) { /* create pipe */
perror("E: pipe");
exit(EXIT_FAILURE);
}
fprintf(stderr, "--- Let's fork ...\n");
p = fork();
if (p == -1) {
perror("E: Fork error\n");
exit(EXIT_FAILURE);
} else if (p == 0) { /* child process */
close(pipefd[0]); /* Close unused read end fd */
printf("child PID = %d\n", getpid());
printf("child PPID = %d\n", getppid());
printf("child write to pipe\n");
write(pipefd[1], b, strlen(b));
dup2(pipefd[1], STDOUT_FILENO);
printf("child> ls -l forkexec.c\n");
e = execlp("/bin/ls", "ls", "-l", "forkexec.c", NULL); /* execute ls -l */
close(pipefd[1]); /* Close used write end fd */
_exit(e); /* idiom to end child process "immediately" */
} else { /* parent process */
close(pipefd[1]); /* Close unused write end fd */
fprintf(stderr, "parent PID = %d\n", getpid());
fprintf(stderr, "parent PPID = %d\n", getppid());
fprintf(stderr, "parent forked = %d\n", p);
fprintf(stderr, "parent read from pipe\n");
while (read(pipefd[0], &buf, 1) > 0) {
write(STDERR_FILENO, &buf, 1);
}
close(pipefd[0]); /* Close used read end fd */
wait(NULL); /* Wait for child */
fprintf(stderr, "parent child exited.\n");
}
return EXIT_SUCCESS;
}
Run ./forkexec
$ gcc -Wall -o forkexec forkexec.c
$ ./forkexec
parent PID = 6417
parent PPID = 6406
--- Let's fork ...
parent PID = 6417
parent PPID = 6406
parent forked = 6418
parent read from pipe
child PID = 6418
child PPID = 6417
child write to pipe
MESSAGE: passed from child to parent via pipe
child> ls -l forkexec.c
-rw-rw-r-- 1 osamu osamu 2056 Mar 12 22:12 forkexec.c
parent child exited.
$
$ ./forkexec 2>/dev/null
child PID = 6420
child PPID = 6419
child write to pipe
$
$ ./forkexec >/dev/null
parent PID = 6421
parent PPID = 6406
--- Let's fork ...
parent PID = 6421
parent PPID = 6406
parent forked = 6422
parent read from pipe
MESSAGE: passed from child to parent via pipe
-rw-rw-r-- 1 osamu osamu 2056 Mar 12 22:12 forkexec.c
parent child exited.
This last method uses the exec
(3) family of functions with various argument usages.
l
-suffix:execl
,execlp
,execle
- The argument list available to the new program is directly used as the later part of the arguments.
v
-suffix:execv
,execvp
,execvpe
- An array of pointers to null-terminated strings are used to represent the argument list available to the new program.
p
-suffix:execlp
execvp
,execvpe
- This indicates that the function duplicates the shell function for searching an executable file indicated by the
$PATH
environment variable.
- This indicates that the function duplicates the shell function for searching an executable file indicated by the
e
-suffix :execle
execvpe
- This indicates that the function specifys the environment of the executed program via the argument
envp
.
- This indicates that the function specifys the environment of the executed program via the argument
C: signal macro
Since the signal number assignment is system dependent, we should use macro to specify signal in the C program.
Signal macros defined in /usr/include/asm/signal.h
.
$ grep "# *define *SIG.* [ 0-9][0-9]$" /usr/include/asm/signal.h
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGLOST 29
#define SIGPWR 30
#define SIGSYS 31
#define SIGRTMIN 32
C: signal(2)
For the simplicity, I start with the traditional signal
(2).
TIP: The POSIX.1 sigaction
(2) is more portable than the signal
(2).
The effects of signal
(2) in a multithreaded process are unspecified. See
signal
(2) for details.
Signal example: signal.c
/* vi:set ts=4 sts=4 expandtab: */
#include <stdlib.h> /* exit */
#include <stdio.h> /* [f]printf perror */
#include <signal.h> /* signal */
#include <unistd.h> /* getpid pause */
void
handler(int signum)
{
printf("\n=== SIGNAL %d handler ===\n", signum);
printf("First round ... ignored. Next round activated.\n");
(void) signal(SIGINT, SIG_DFL); /* set signal behavior to default */
}
int
main(int argc, char* argv[])
{
printf("PID=%d", getpid());
(void) signal(SIGINT, handler); /* set signal behavior to handler */
(void) signal(SIGHUP, SIG_IGN); /* set signal behavior to ignore */
while(1) {
pause(); /* waiting for signal */
}
return EXIT_SUCCESS;
}
Run ./signal
, etc.
$ gcc -Wall -o signal signal.c
$ ./signal &
$ jobs
[1] + Running
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6557 6556 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6558 6557 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6569 6558 0 80 0 - 1019 - pts/4 00:00:00 signal
0 R 1000 6576 6574 0 80 0 - 2655 - pts/4 00:00:00 ps
$ killall -v -s INT signal
PID=6569
=== SIGNAL 2 handler ===
First round ... ignored. Next round activated.
Killed signal(6569) with signal 2
$ jobs
[1] + Running
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6557 6556 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6558 6557 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6569 6558 0 80 0 - 1019 - pts/4 00:00:00 signal
0 R 1000 6589 6587 0 80 0 - 2655 - pts/4 00:00:00 ps
$ killall -v -s INT signal
Killed signal(6569) with signal 2
$ jobs
[1] + Interrupt
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6557 6556 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6558 6557 0 80 0 - 1079 - pts/4 00:00:00 sh
0 R 1000 6602 6600 0 80 0 - 2655 - pts/4 00:00:00 ps
$ ./signal &
$ jobs
[1] + Running
$ ./signal &
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6557 6556 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6558 6557 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6603 6558 0 80 0 - 1019 - pts/4 00:00:00 signal
0 R 1000 6612 6610 0 80 0 - 2655 - pts/4 00:00:00 ps
$ killall -v -s HUP signal
Killed signal(6603) with signal 1
$ jobs
[1] + Running
$ ./signal &
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6557 6556 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6558 6557 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6603 6558 0 80 0 - 1019 - pts/4 00:00:00 signal
0 R 1000 6625 6623 0 80 0 - 2655 - pts/4 00:00:00 ps
$ killall -v -s KILL signal
Killed signal(6603) with signal 9
$ jobs
[1] + Killed
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6557 6556 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6558 6557 0 80 0 - 1079 - pts/4 00:00:00 sh
0 R 1000 6638 6636 0 80 0 - 2655 - pts/4 00:00:00 ps
C: sigaction(2)
Let’s try basically the same with the new sigaction
(2) while preventing the
reentrant signal situation.
Signal example: sigaction.c
/* vi:set ts=4 sts=4 expandtab: */
#include <stdlib.h> /* exit */
#include <stdio.h> /* [f]printf perror */
#include <signal.h> /* signaction */
#include <unistd.h> /* getpid pause */
static struct sigaction saint, sahup;
void
handler(int signum)
{
printf("\n=== SIGNAL %d handler ===\n", signum);
printf("First round ... ignored. Next round activated.\n");
saint.sa_handler = SIG_DFL; /* signal behavior to default */
sigaction(SIGINT, &saint, 0); /* set signal */
}
int
main(int argc, char* argv[])
{
printf("PID=%d", getpid());
saint.sa_flags = 0; /* default */
sigemptyset(&saint.sa_mask); /* create a open mask */
/* sigaddset(&saint.sa_mask, SIGINT); no need since no SA_NODEFER */
saint.sa_handler = handler; /* signal behavior to handler */
sigaction(SIGINT, &saint, 0); /* set signal */
sahup.sa_flags = SA_NODEFER; /* set SA_NODEFER */
sigemptyset(&sahup.sa_mask); /* create a open mask */
sigaddset(&sahup.sa_mask, SIGHUP); /* mask SIGHUP since SA_NODEFER */
sahup.sa_handler = SIG_IGN; /* signal behavior to ignore */
sigaction(SIGHUP, &sahup, 0); /* set signal */
while(1) {
pause(); /* waiting for signal */
}
return EXIT_SUCCESS;
}
Run ./sigaction
, etc.
$ gcc -Wall -o sigaction sigaction.c
$ ./sigaction &
$ jobs
[1] + Running
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6466 6465 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6467 6466 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6478 6467 0 80 0 - 1019 - pts/4 00:00:00 sigaction
0 R 1000 6485 6483 0 80 0 - 2655 - pts/4 00:00:00 ps
$ killall -v -s INT sigaction
PID=6478
=== SIGNAL 2 handler ===
First round ... ignored. Next round activated.
Killed sigaction(6478) with signal 2
$ jobs
[1] + Running
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6466 6465 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6467 6466 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6478 6467 0 80 0 - 1019 - pts/4 00:00:00 sigaction
0 R 1000 6498 6496 0 80 0 - 2655 - pts/4 00:00:00 ps
$ killall -v -s INT sigaction
Killed sigaction(6478) with signal 2
$ jobs
[1] + Interrupt
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6466 6465 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6467 6466 0 80 0 - 1079 - pts/4 00:00:00 sh
0 R 1000 6511 6509 0 80 0 - 2655 - pts/4 00:00:00 ps
$ ./sigaction &
$ jobs
[1] + Running
$ ./sigaction &
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6466 6465 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6467 6466 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6512 6467 0 80 0 - 1019 - pts/4 00:00:00 sigaction
0 R 1000 6519 6517 0 80 0 - 2655 - pts/4 00:00:00 ps
$ killall -v -s HUP sigaction
Killed sigaction(6512) with signal 1
$ jobs
[1] + Running
$ ./sigaction &
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6466 6465 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6467 6466 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6512 6467 0 80 0 - 1019 - pts/4 00:00:00 sigaction
0 R 1000 6532 6530 0 80 0 - 2655 - pts/4 00:00:00 ps
$ killall -v -s KILL sigaction
Killed sigaction(6512) with signal 9
$ jobs
[1] + Killed
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 6466 6465 0 80 0 - 1079 - pts/4 00:00:00 sh
0 S 1000 6467 6466 0 80 0 - 1079 - pts/4 00:00:00 sh
0 R 1000 6545 6543 0 80 0 - 2655 - pts/4 00:00:00 ps
In above example, SA_NODEFER
was set for the SIGHUP
side of code just to
show how to prevent reentrant signal situation explicitly by the sigaddset
(3)
command.
TIP: You can set up fine grained signal mask with the sigprocmask
(3).
httpd in C
Let’s try coding a simple httpd server program, httpd6
. This can provide good
insight into how the httpd server works with the bottom up approach. Here is the
design guide line I used.
- IPv6 protocol.
- the port number as the first argument (required, range=1024-60000)
- the document root path as the optional second argument.
- the default document root path as “~/public_html”
- no CGI script support
- no “
..
” in the URL string for security. - write simple for the easier to understand code.
TIP: See http://www.ibm.com/developerworks/systems/library/es-nweb/index.html[nweb: a tiny, safe Web server (static pages only)] for IPv4 protocol example code. It comes with detailed explanation on how things work. (My example is basically a rewrite for IPv6 in my coding style.)
Common header : common.h
/* vi:set ts=4 sts=4 expandtab: */
#include <stdio.h> /* [fs]printf */
#include <stdlib.h> /* exit getpid getenv */
#include <string.h> /* strncmp */
#include <unistd.h> /* lseek read write fork sleep */
#include <sys/types.h> /* lseek getpid BSD_portability */
#include <errno.h> /* errno */
#include <fcntl.h> /* open close */
#include <signal.h> /* sigaction */
#include <sys/socket.h> /* socket bind listen accept */
#include <arpa/inet.h> /* htonl htons */
#define WEB_ROOT "public_html/"
#define LOG_FILE "httpd6.log"
#define MAXFD 32
#define MAXBACKLOG 64
#define PIPE_BUF (8*1024)
/* provide "FILE *f;" by the caller routine */
/* ## is GNU extension to allow zero __VA_ARGS__ */
#define log_printf(fmt, ...) \
f = fopen(LOG_FILE, "a");\
(void) fprintf(f, fmt, ##__VA_ARGS__);\
(void) fflush(f);\
fclose(f);\
sync();
void httpd6(int fdsock, int hit);
int main (int argc, char **argv);
This is a common header file. Most notable is a macro log_printf
which
enables to write to the log file with printf
syntax.
Routine to daemonize httpd6: main.c
/* vi:set ts=4 sts=4 expandtab: */
#include "common.h"
int main (int argc, char **argv)
{
FILE *f; /* for log_printf */
int port, fdbl, hit, fdsock, pid, i;
socklen_t addrlen;
static struct sockaddr_in6 client_address, server_address;
static struct sigaction sacld, sahup;
if (argc < 1 || argc > 3) {
(void) fprintf(stderr, "Mini-httpd6:\nUsage: httpd6 [port [PATH]]\n");
exit(EXIT_FAILURE);
} else if (argc == 1) {
port = 8080;
} else {
if ((int) strlen(argv[1]) > 6) {
(void) fprintf(stderr, "E: port is more than 5 digits.\n");
exit(EXIT_FAILURE);
}
port = atoi(argv[1]);
}
if (port < 1024 || port > 60000) {
(void) fprintf(stderr, "E: Invalid port number as argument. "
"Try 1024-60000.\n");
exit(EXIT_FAILURE);
}
if (argc == 3) {
if ((int) strlen(argv[2]) > 81) {
(void) fprintf(stderr, "E: PATH is more than 80 chars.\n");
exit(EXIT_FAILURE);
}
if (chdir(argv[2])) {
(void) fprintf(stderr, "E: Can not change to directory %s.\n", argv[2]);
exit(EXIT_FAILURE);
}
} else { /* default argc = 1 or 2 */
if (chdir(getenv("HOME"))) {
(void) fprintf(stderr, "E: Can not change to directory %s.\n",
getenv("HOME"));
exit(EXIT_FAILURE);
}
if (chdir(WEB_ROOT)) {
(void) fprintf(stderr, "E: Can not change to directory %s.\n",
WEB_ROOT);
exit(EXIT_FAILURE);
}
}
sacld.sa_flags = 0; /* default */
sigemptyset(&sacld.sa_mask); /* create a open mask */
sacld.sa_handler = SIG_IGN; /* ignore child death */
sigaction(SIGINT, &sacld, 0); /* set signal */
sahup.sa_flags = 0; /* default */
sigemptyset(&sahup.sa_mask); /* create a open mask */
sahup.sa_handler = SIG_IGN; /* ignore terminal hungup */
sigaction(SIGINT, &sahup, 0); /* set signal */
for (i = 0; i <= MAXFD; i++) {
(void) close(i); /* close STDIN, STDOUT, ... */
}
pid = fork();
if (pid == -1) {
log_printf("ERROR: system call: fork, %s, exiting pid=%d.\n",
strerror(errno), getpid());
exit(EXIT_FAILURE);
} else if (pid != 0) {
(void) fprintf(stderr, "I: Sucessfully daemonize httpd6.\n");
return EXIT_SUCCESS; /* parent exit and deamonize */
}
if (setpgrp()) { /* set PGID of child to PGID of parent */
log_printf("ERROR: system call: setgrp, %s, exiting pid=%d.\n",
strerror(errno), getpid());
exit(EXIT_FAILURE);
}
log_printf("LOG: httpd starting with parent pid=%d.\n", getpid());
fdbl = socket(AF_INET6, SOCK_STREAM, 0);
if (fdbl == -1) {
log_printf("ERROR: system call: socket, %s, exiting pid=%d.\n",
strerror(errno), getpid());
exit(EXIT_FAILURE);
}
server_address.sin6_family = AF_INET6; /* IPV6 , see ip(7)*/
server_address.sin6_addr = in6addr_any;
server_address.sin6_port = htons(port); /* host to network */
if (bind(fdbl, (struct sockaddr *) &server_address,
sizeof(server_address)) == -1) {
log_printf("ERROR: system call: bind, %s, exiting pid=%d.\n",
strerror(errno), getpid());
exit(EXIT_FAILURE);
} else {
log_printf("LOG: bind: with IPv6 accept, port=%d.\n", port);
}
if (listen(fdbl, MAXBACKLOG) == -1) {
log_printf("ERROR: system call: listen, %s, exiting pid=%d.\n",
strerror(errno), getpid());
exit(EXIT_FAILURE);
}
log_printf("LOG: httpd successfuly bind and listen at port %i.\n", port);
for (hit = 1; ; hit++) {
addrlen = sizeof(client_address);
fdsock = accept(fdbl, (struct sockaddr *) &client_address, &addrlen);
if (fdsock == -1) {
log_printf("ERROR: system call: accept, %s, exiting pid=%d.\n",
strerror(errno), getpid());
exit(EXIT_FAILURE);
}
log_printf("LOG: httpd hit=%i.\n", hit);
pid = fork();
if (pid == -1) {
log_printf("ERROR: system call: fork, %s, exiting pid=%d.\n",
strerror(errno), getpid());
exit(EXIT_FAILURE);
} else if (pid != 0) {
(void) close(fdsock); /* parent */
} else {
(void) close(fdbl); /* child */
log_printf("LOG: httpd forked child pid=%i.\n", getpid());
httpd6(fdsock, hit); /* no return */
}
}
}
This essentially starts a daemonized httpd6
process. The daemonized httpd6
process is the infinite loop routine. It creates a
network socket and listens to the
TCP connection port specified on the command line and assume IPv6 connection.
Every time httpd6
is hit, it starts a child process to handle the HTTP
request.
Main routine providing httpd6: httpd6.c
This is the routine to handle the HTTP request in the child process of the daemon. Since I put HTML protocol related text data in the codes, this is very top heavy. But this is relatively simple :-)
You can see this httpd6
can serve as IPv6 web server.
$ ./httpd6 8080 ../01_static
$ wget localhost:8080
--2013-03-25 21:57:02-- http://localhost:8080/
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2175 (2.1K) [text/html]
Saving to: ‘index.html’
0K .. 100% 86.8M=0s
2013-03-25 21:57:02 (86.8 MB/s) - ‘index.html’ saved [2175/2175]
httpd in Python
Coding in the Python environment provides us with bigger building blocks to work with than that in the C environment.
The http.server
module in the Python3 library contains basic HTTP server classes. Let’s use this.
Just like the argparse
module in CLI programs: Python, I read
the library source comments to get good idea.
$ /usr/bin/python3 -q
>>> print(sys.path)
['', '/usr/lib/python3.2', '/usr/lib/python3.2/plat-linux2', '/usr/lib/python3.2
/lib-dynload', '/usr/local/lib/python3.2/dist-packages', '/usr/lib/python3/dist-
packages']
>>> import http.server
>>> print (http.server.__file__)
/usr/lib/python3.2/http/server.py
>>> exit()
$ head /usr/lib/python3.2/http/server.py
"""HTTP server classes.
Note: BaseHTTPRequestHandler doesn't implement any HTTP request; see
SimpleHTTPRequestHandler for simple implementations of GET, HEAD and POST,
and CGIHTTPRequestHandler for CGI scripts.
It does, however, optionally implement HTTP/1.1 persistent connections,
as of version 0.3.
Notes on CGIHTTPRequestHandler
The test
function towards the end of the /usr/lib/python3.2/http/server.py
file seems to be good template to start with if its HTTP handler is changed to
SimpleHTTPRequestHandler
.
Simple Python HTTP server: httpd
#!/usr/bin/python3
import http.server
import sys
def run(HandlerClass = http.server.BaseHTTPRequestHandler,
ServerClass = http.server.HTTPServer, protocol="HTTP/1.0"):
"""Test the HTTP request handler class.
This runs an HTTP server on port 8080 (or the first command line
argument).
"""
if sys.argv[1:]:
port = int(sys.argv[1])
else:
port = 8080
server_address = ('', port)
HandlerClass.protocol_version = protocol
httpd = ServerClass(server_address, HandlerClass)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.")
httpd.server_close()
sys.exit(0)
if __name__ == '__main__':
run(HandlerClass=http.server.SimpleHTTPRequestHandler)
You can see this httpd
can serve as IPv4 web server.
$ ./httpd 8080 &
Serving HTTP on 0.0.0.0 port 8080 ...
$ wget -O index2.html localhost:8080
--2013-03-25 22:03:26-- http://localhost:8080/
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:8080... failed: Connection refused.
Connecting to localhost (localhost)|127.0.0.1|:8080... connected.
127.0.0.1 - - [25/Mar/2013 22:03:27] "GET / HTTP/1.1" 200 -
HTTP request sent, awaiting response... 200 OK
Length: 2175 (2.1K) [text/html]
Saving to: ‘index2.html’
0K .. 100% 5.23M=0s
2013-03-25 22:03:26 (5.23 MB/s) - ‘index2.html’ saved [2175/2175]
See:
- “http.server — HTTP servers” in “The Python Standard Library “
- “BaseHttpServer” in “The Python Wiki”
Previous Post | Top | Next Post |