5

Can a process background itself?

What would be a Perl and/or C implementation of this?

Chris Davies
  • 116,213
  • 16
  • 160
  • 287

3 Answers3

7

I just want to answer the literal question here: can a process background itself, as opposed to fork itself, continue execution in the child and exit so that the process waiting for it can resume execution already covered by others here.

A note on terminology first here.

Backgrounding is usually referring to job control in interactive shells.

Like when you run a command with & appended. Or press Ctrl+Z and run bg afterwards.

Jobs there are not processes they are process groups. When you run:

$ ps -ej | grep -w "$$" & echo "$!"
35131
  19152   19152   19152 pts/0    00:00:00 zsh
  35130   35130   19152 pts/0    00:00:00 ps
  35131   35130   19152 pts/0    00:00:00 grep

It's the ps | grep job (here implemented via that 35130 process group whose leader is the process running ps but also contains the process running grep) that is put in background.

Backgrounding here means:

  1. The shell is not waiting for the termination of that job. You will return to the prompt and will be able to enter more commands while that job is still running.
  2. The terminal device driver is told that that process group is not in foreground. As such, processes in that process group don't have the right to read from the terminal (all processes in the group will be interrupted if any process in it tries to read from the terminal) and it won't get affected by ^C/^Z/^\, etc.
  3. The shell enters the job in its internal job table and records it as being currently in background.

Now the backgrounding wording is sometimes used outside of terminal job control.

When you do:

cmd1 | cmd2 & pid=$!
somecommand
wait

in a script, there is no job control. If that script is started in a terminal by an interactive shell, it will be itself put in either foreground or background depending on how the script was started like any other command.

but that cmd1 | cmd2 in the script will not be put in background by the shell interpreting the script as it's not an interactive shell.

If you press ^C, cmd1 and cmd2 will be killed just the same alongside the shell running the script, and suspended when you press ^Z. Instead of saying that cmd1 | cmd2 is started in background, it's more correct to say that they are being run asynchronously.

Compared to a job started in background in an interactive shell, only 1 is done. No process group is created to run that pipeline.

Now, with that clarified, can a process put itself in background?

As it's jobs and not processes that are put in background, the questions for that process would be:

  • am I in a session attached to a terminal? (would job control even make sense?)
  • am I alone in my process group or are there other processes?
  • is my process group in foreground or background already?
  • is my process group ultimately being waited for by an interactive shell?

If we can answer yes to all those, that is if we're being called as a simple command (not as part of pipeline or compound command) from an interactive shell in a terminal, then we would need to (1) tell the waiting shell to stop waiting for us, (2) tell the terminal that its process group is no longer the foreground one and (3) tell the shell to update its job table to record the fact that we're now in background.

You can't really tell another process to stop waiting for you other than by terminating or being suspended in which case that process will receive a SIGCHLD signal or the wait*() call it's currently doing will return.

However, you can suspend yourself by sending yourself the SIGTSTP signal (same sent when you press ^Z) or SIGSTOP (which cannot be intercepted), in which case, all of (1), (2) and (3) will happen automatically, except that the job state will be suspended instead of running in background.

Now, since you're suspended, you're no longer running and cannot resume yourself.

You could however fork a child process that will resume yourself (by sending SIGCONT to your pid) in a little while prior to suspending yourself.

When you resume execution, your shell will receive a SIGCHLD again, and (3) where the shell realises that you're now running in background will happen when it gets to process that signal.

As an example, implementing that in sh:

$ sh -c 'echo running in foreground; sleep 1
         (sleep 1; echo resuming my parent; kill -s CONT "$$") &
         echo stopping; kill -s STOP "$$"
         echo resumed
         sleep 30
         echo finished'; echo "$?"
running in foreground
stopping
147
zsh: suspended (signal)  sh -c
$ resuming my parent
resumed
$ jobs
[1]  + running    sh -c
$ finished
[1]  + done       sh -c

It's also possible to suspend your whole job with kill(0, SIGSTOP) (kill -s STOP 0 in sh), but is it right for a process to do that, to affect the run flow of processes it hasn't started and it doesn't know about?

sh -c 'echo running in foreground
       perl -MPOSIX -le "setpgid 0,0; # leave the process group before it is suspended
                         sleep 2;
                         print q(resuming the process group of my parent);
                         kill q(CONT), - shift@ARGV
                        " "$(ps -o pgid= -p "$$")" &
       sleep 1
       echo stopping my process group; kill -s STOP 0
       echo process group resumed
       sleep 30
       echo finished' | cat
5

Only sometimes.

  • A process that is running in the background has a process group ID that is different to the current foreground process group ID in its controlling terminal.
  • This is not to be confused with a process that is running as a dæmon, which is running outwith all login sessions, where the notions of foreground and background simply do not apply because there's no controlling terminal in the first place.

The advice in Ángel's answer is outdated and bad. "daemonization", as it is called, does not really work from login sessions. There are far too many one-way trapdoors that systems pass through in order to set up login sessions; and not only does Ángel's answer ignore things like OpenBSD's setlogin(), AIX's protected environment (see setsenv), and Linux's security contexts and control groups, so too does the old daemon() library function in pretty much all C libraries.

It hasn't worked since the 1980s; because the 1990s saw the introduction of many of these one-way trapdoors. "daemonization" is a fallacy, unfortunately still promoted all of these years later by received wisdom and folklore.

It's not even what dæmons should do. Service management subsystems from the late 1980s (e.g. the AT&T Unix Service Access Facility) and 1990s (e.g. IBM's System Resource Controller) onwards invoke dæmons already in dæmon context. They are not started in login session context in the first place.

Moreover, doing fork-and-exit-parent conflicts with the control semantics that service management subsystems have been using for dæmons for over 3 decades; and closing file descriptors conflicts with the logging mechanisms that service management subsystems set up for dæmons.

  • Closing file descriptors isn't about TTY signals (this being an example of the folklore that I mentioned earlier), and defeats how service management subsystems from daemontools through systemd to runit set up standard output and standard error to be sent to a log. Service management subsystems don't start dæmons with arbitrary file descriptors open in the first place; they start them with a repeatable and consistent, and usually minimal (unless explicitly configured otherwise), set of open file descriptors.
  • Forking and exiting parent is a 1980s thing for back when system administrators started dæmons by adding them to an /etc/rc or /etc/rc.local shell script, or even interactively from a superuser login session, and defeats the fact that service management subsystems use the process ID that it got from fork()ing the service process to both track and control the service. (As you can see from the further reading, IBM has been advising against doing this for almost as long as its System Resource Controller has existed.) Service management subsystems invoke dæmons in sessions that already have no controlling terminal.

Furthermore, "daemonization" isn't what one does to get in the background in a login session. A "job-control" shell puts things into the foreground and background in login sessions by calling the tcsetpgrp() function to alter the controlling terminal's current foreground process group ID value. This has actually nothing whatsoever to do with fork(), chdir(), closing file descriptors, or (kernel) sessions.

A child process of the shell can call tcsetpgrp(), but note that (a) it will be sent a SIGTTOU signal if it is already in the background, and (b) it's non-trivial to find another process group ID to switch to, as the shell might not be the immediate parent process in all circumstances.

  • If you want to start a process in the background, use the shell's & mechanism for doing so.
  • If you want to switch the foreground process group to the background interactively, send the "stop" special character from the terminal.
  • If you want to run something as a dæmon, unrelated to foreground and background, then create a service definition for whatever your system's service management subsystem is.

Further reading

muru
  • 72,889
JdeBP
  • 68,745
2

Yes. The basic way of working o the background would be to fork() itself and fnish the parent, while continuing the work in the child. This often also includes changing the working directory to / (so that the detached process doesn't block unmounting filesystems), closing file descriptors (to avoid TTY signals) and creating a new process session (see setsid(2))

There are library functions like daemon that may simplify doing all of these:

#define _DEFAULT_SOURCE // glibc >= 2.19
#define _BSD_SOURCE // glibc <= 2.19
#include <unistd.h>

int main() { daemon(0, 0); .... }

Ángel
  • 3,589
  • 1
    Thanks. I only don't understand what closing fd's has to do with TTY signals. Can you explain? –  Aug 29 '20 at 02:22
  • 2
    A background process that attempted to read from or write to its controlling terminal could receive a SIGTTIN or SIGTTOU signal. It could also receive a SIGHUP if the terminal was closed. – Ángel Aug 29 '20 at 22:41