Fun to Program – Process

Date: 2013/08/06 (initial publish), 2021/08/02 (last update)

Source: en/fun2-00006.md

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.

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

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:

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:

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:

C: new process

Let’s execute a command as a new process from the C program in 3 ways.

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.

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.

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

include::net/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:

Previous Post Top Next Post