Page 1 of 1

Spawning independent (background) processes: the SIGHUP mechanism Rate Topic: -----

#1 JWHSmith  Icon User is offline

  • New D.I.C Head
  • member icon

Reputation: 2
  • View blog
  • Posts: 9
  • Joined: 13-November 14

Posted 14 November 2014 - 12:36 PM

After reading Backgrounding, Foregrounding And Job Control In Bash, another tutorial from DIC, I thought I'd post my first tutorial and provide more information about the processes' tree organisation and control on a UNIX system. In his tutorial, wordswords covered bash's job control (through the ampersand and the jobs (& Cie) builtin), so if you're working in Bash, I'd recommend you read it first, just so you get at ease with job control in your own shell.

Another thing: if you want to be able to see how things are organised, you may want to install a small/clean (virtual) copy of your favourite lightweight distribution first. This way, your system will keep running with a minimal number of processes, hence making the processes' tree a little easier to read. I would recommend Arch Linux, but a GUI-less Debian would do just fine.

In this tutorial, I'll cover:

  • The structure of the UNIX process tree.
  • Controlling sessions, processes and terminals.
  • The SIGHUP mechanism.
  • The nohup utility.
  • Bash's disown builtin.
  • Extra: programmatically making a process independent.


How are processes organised?

If you have already paid some attention to the ps program, you may know the basics about process hierarchy:
  • A process can create (spawn) several other processes called child processes.
  • When A spawns B, A is called B's parent.
  • If two processes share the same parent, they may be referred to as siblings.
  • The parent's parent isn't called a parent, but an ancestor. A process may have several ancestors, but no more than one parent.
  • Similarly, a child's child is called a descendant.

As a bonus, let me add:
  • When a parent terminates, its children become orphan processes.
  • On most UNIX systems, orphans are adopted by the parent of all processes, init (PID 1). Therefore, a process will never remain parent-less for too long.

If you want a minimised view of your processes' hierarchy, you may type:

$ ps -o "pid,ppid,comm"

If you'd like a more "graphical" approach, you may want to install pstree. You'll probably find it in your distribution's repositories.

However, aside from these "family" relations, processes also share another kind of organisation: sessions and groups. While this organisation isn't visible in the process tree, it is important to know that it's here, since your shell relies on it a lot.

  • Several processes may share a process group.
  • The first process spawned in a group is the group leader.
  • Several groups may share a process session. Processes from the same group necessarily belong to the same session.
  • The first process spawned in a session is the session leader.
  • Each group has an ID, called the PGID (Process Group ID). A group's PGID is its first leader's PID.
  • Each session has an ID, called the (P)SID (Session ID). A session's SID is its first leader's PID.

This may seem complicated at first, but it's nothing but a nesting doll actually: sessions contain groups, which contain processes. Now, if you want to see this information with ps, just type:

$ ps -o "pid,ppid,pgid,sid,comm"

Hint: if you got a sharp eye, you'll notice that your ps process shares your shell's session... Additionally, if you spawn two long-lasting processes in your shell... they'll share the same group as well (try calling sleep 10 & twice) !


Controlling sessions, processes and terminals

The main purpose of this groups/sessions organisation is that it allows some processes to be somehow "responsible" of others in their own group. This behaviour is applied between terminals and their processes through controlling sessions and terminals :

  • When a terminal is opened, it spawns its first child process (usually, your shell).
  • Your shell is called your terminal's controlling process.
  • Your terminal is called your shell's controlling terminal.

This is the reason why your keyboard inputs reach your processes in a terminal. We call these processes controlling processes because they have control over the terminal's I/O. This "control" is somehow relayed to whatever process you create from your shell (simply put, your commands) as the shell buffers your inputs and transmits them to the foreground process.


The SIGHUP mechanism

Signals are one way processes have to communicate. Among the UNIX set of signals, you'll find one called SIGHUP, short for SIGnal Hang UP. While there's a lot of historical background behind this signal, the purpose is quite simple: terminals are expected to send this signal to their controlling process whenever they are about to close. This allows the shell to clean up before exiting.

  • When a process receives a SIGHUP, its default reaction is to terminate.
  • It is possible to reroute signals to handle them differently. Your shells do not terminate immediately after receiving a SIGHUP (they clean up first).

Now, when your shell receives a SIGHUP from your terminal, its reaction will be to send another SIGHUP to every process it has spawned, that is, every process that belongs to the same session as it does. Now, since most processes don't customise their reaction to SIGHUPs, they just terminate at the same time your terminal closes. For instance, if you spawn two sleep 10 & processes in your shell, and close your terminal, they will get terminated.

 $ sleep 10 &
$ sleep 10 &
# (closing the terminal...)
$ ps -e
# (no "sleep" process)

However, since the objective here is to create a background process which does not depend on any shell, and keeps running despite you closing the terminal, we'll see about two ways of handling this SIGHUP mechanism from your shell.


The nohup utility

As I said earlier, a program can customise the way it reacts to a signal. For instance, it can decide to ignore SIGHUPs. When this happens, and the terminal closes, the SIGHUP emitted by the shell will be ignored by your program, which will keep running. Since its parent (the shell) dies, it'll just be adopted by init.

Since you probably don't want to edit your favourite tool's source code to change its behaviour, you'll be happy to know that this can be handled externally when spawning your process. This is where nohup comes in:

$ nohup sleep 20 &
# (closing the terminal)
$ ps -e
-    -    -    sleep
# (ooh, there it is!)

As you can see, despite you closing the terminal, the process survived by ignoring your shell's termination.


Bash's disown builtin

If you're running bash, nohup isn't your only alternative. Actually, there is another tool which allows you to "release" a process after spawning it: the disown builtin. It works quite like the kill builtin (which you may know from the tutorial I linked earlier).

$ sleep 20 &
$ jobs
[1]+  Running                 sleep 20 &
$ disown %1

Now, if you close your terminal, and open it again... Tadam, sleep is still here. This is because bash marked it as "independent", and skipped it when emitting the SIGHUP broadcast. The process was then given its own session and adopted by init.


Extra: programmatically making a process independent

I know this isn't the C/C++ tutorials forum, but I'd like to give you a way to make your program independent without relying on external utilities such as nohup and disown. For that, we'll use a few C API system calls. Here are the steps:

  • Fork a new process and kill the parent. The key system call here is fork. Remember: the parent is in the shell's jobs list, and we don't want to stay there. Also note that a shell does not keep tracks of its jobs children.
  • Obtain a new session and group in order to get independent from any other process. The key system call here is setsid.

There is a minimal version of the program, with some comments.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[]){
    /*
     * fork() returns 0 in the child process, something else otherwise.
     * If we're not in the child (parent process, or error), just exit.
     */
    if(fork() != 0) exit(0);

    /*
     * setsid() returns 0 on success, -1 otherwise. perror() will tell us why in this case.
     */
    if(setsid() < 0) perror("setsid");

    // Sleeping 20s so you have time to close the terminal and come back.
    sleep(20);

    return EXIT_SUCCESS;
}

Now, compile it, execute it, close your terminal and come back. You'll see that your program is still alive, in its brand new session. Now, if you use a controlled infinite loop instead of just sleeping, you can get yourself a minimal daemon!

Anyway, I think I've covered what I wanted to cover here. I hope you'll find it useful when using your Linux machine, or when developing background programs ;)/>

Is This A Good Question/Topic? 0
  • +

Page 1 of 1