Both nohup, setsid and disown can be used to allow programs that need to run for a long time to continue running in the background after exiting the terminal. However, they are used differently for this purpose, and therefore have some differences in their use.

What happens when you exit the terminal

Let’s start by looking at what happens when a terminal exits.

When a terminal is hung or a pseudo-terminal program is shut down, the SIGHUP signal is sent to the controlling process associated with that terminal (i.e. the session head process, usually the shell) if the terminal’s CLOCAL flag is not set. The default behavior of SIGHUP is to terminate the program. When the session head process terminates, the SIGHUP signal is also sent to each process in the foreground process group (depending on the shell’s specific implementation, SIGNHUP may also be sent to the background process group).

nohup,setsid and disown

Then it is natural to see that there are two ways to achieve a process still running in the background after the terminal exits.

  1. the process receives SIGHUP and ignores the signal
  2. the process does not receive the SIGHUP signal at all

nohup

The use of nohup is very simple, just add nohup before the command you want to execute.

1
nohup commands

It works in the first way. Its core code is as follows.

1
2
3
4
signal (SIGHUP, SIG_IGN);

char **cmd = argv + optind;
execvp (*cmd, cmd);

In addition to this, it does the following.

  • Closes the stdin of the process and only gets EOF when the process tries to read the input.
  • Redirects the process’s stdout and stderr to nohup.out.

Since the stdin, stdout and stderr of the process are out of the terminal, it doesn’t seem to make much sense to have it running in the foreground, so we usually add & after it to make it run in the background.

setsid

nohup is used to avoid our process from being interrupted by ignoring the HUP signal, while setsid can avoid the HUP signal by making our process not belong to the session of the first process that accepts the HUP number.

The use of setsid is very similar to nohup, just add setsid before the command to be executed.

1
setsid command

Its core code is the setsid function.

1
pid_t setsid(void);

The process that calls the setsid function will create a new session if it is not the leader of a process group. Specifically, the following 3 things happen.

  • The process becomes the session leader of the new session (the session leader is the process that created the session), which is the only process in the new session.
  • The process becomes the leader process of a new process group, and the new process group ID is the PID of the process.
  • The process is disconnected from the control terminal.

If the process calling the setsid function is the leader of a process group, then the function will return an error. To solve this situation, usually the function needs to fork first, then the parent process exits and the child process performs the setsid. Since the child process inherits the process group ID of the parent process, and its PID is the newly assigned ID, the two cannot be equal, i.e. the child process cannot be the group leader of the process group. In this case, since the parent process exits before the child process, the parent of the child process will have the init process take over. And this is how the sid command is implemented.

The following experiment shows what setsid does.

First compile a test program (assuming that the compiled execution file is s.out) with the following source code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <unistd.h>
#include <stdio.h>

int main()
{
  pid_t sid=getsid(0);          /* 会话 id */
  pid_t pgrp=getpgrp();         /* 进程组id */
  pid_t ppid=getppid();         /* 父进程id */
  pid_t pid=getpid();           /* 进程ID */
  printf("会话id:%d\n进程组id:%d\n父进程id:%d\n进程id:%d\n",sid,pgrp,ppid,pid);
}
1
2
3
4
  会话id:17791
  进程组id:17791
  父进程id:5732
  进程id:17791

The output of the direct execution under the shell is as follows.

1
2
3
4
5
[lujun9972@X61 ~]$ ./s.out 
会话id:5235
进程组id:17095
父进程id:5235
进程id:17095

Where 5235 is the process ID of bash.

And the output of the execution using setsid is as follows.

1
2
3
4
5
[lujun9972@X61 ~]$ setsid ./s.out 
[lujun9972@X61 ~]$ 会话id:17146
进程组id:17146
父进程id:1
进程id:17146

Comparing these two outputs, you will see that setsid has created a brand new session and its parent process has become the init process.

Since both the session and the parent process are no longer relevant to the shell, the shell can’t send a SIGHUP command to that process anyway.

disown

The aforementioned nohup and setsid are external commands that have nothing to do with the specific shell. That is, regardless of the specific implementation of the shell (which is also the control process associated with the terminal), the executing process is guaranteed not to be hung up by SIGHUP. And one limitation of using them is that they must be preceded by nohup or setsid before executing the command.

But if we have already committed the command without any processing, then we have to use the disown command. However, disown is a built-in command in bash, and it can only be used under bash.

There are three more common ways to disown.

1
2
3
disown -h $jobspec               #使某个作业忽略HUP信号。
disown -ah                       #使所有的作业都忽略HUP信号。
disown -rh                       #使正在运行的作业忽略HUP信号。

The disown command removes the command from bash’s jobs list. Thus, when bash receives the SIGHUP signal, it does not send the SIGHUP signal to the command.

However, using disown does not sever the association between the command and the terminal, so that if the command tries to read from stdin or output to stdout after the terminal is closed, it may cause an abnormal exit.

About &

In fact, on newer versions of bash, bash does not send SIGHUP commands to background programs. That means that any process running in the background ending with & will not be exited by the SIGHUP signal for terminal exit.

We can do an experiment.

First prepare a test file with the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void sig_handler(int signo)
{
  if(signo == SIGHUP)
    {
      printf("RECV SIGHUP\n");
    }
  else
    {
      printf("RECV signal %d\n",signo);
    }
}

int main()
{
  signal(SIGHUP,sig_handler);
  sleep(180);
}

After compiling (assuming the compiled executable is a.out), execute it in the terminal:

1
a.out >a.txt &

Let the program run in the background, then close the terminal, then open a new terminal, check a.txt did not find any content, with ps can also find that a.out was not killed, but its parent process became init.

But if you execute the following command in the terminal.

1
a.out >a.txt

Let the program run in the foreground, then close the terminal, then open a new terminal. If you check a.txt, you will find the content of “RECV SIGHUP”, and you can no longer find a.out with ps, which means that a.out is also killed.