The term 'normally' used in this context typically means that an executing process exits when having completed all its instructions successfully, without e.g. being killed by signals or crashing unexpectedly. For example: Ctrl+C
is hit while the command find
is running and the find
process recevives the SIGINT
signal. Handling this signal is implementation specific, but usually this means killing the process normally. A normal termination of a C
program happens upon successfully returning from the function main
being the last executed statement in the program.
This contrasts a process that for instance does not complete all the instructions and exits in non-deterministic manner. This can be caused by programmatic errors or it can be caused by signals issued by other processes to kill it. An abnormal termination is often determined in the logic implemented in the program e.g. a child process aborts/exits to the parent process with some return value x
, indicating some state or event in execution that is not 'normal'. For example: a process recevives the SIGKILL
signal. This signal can not be handled and kills the process.
Here the process abstraction is similar to a programmatic function in that they both share the ability to return a value to the calling process e.g. a shell. The semantics of these return values defines what qualifies a "successful" or a "unsuccessful" call. So in this case it's the programmer who is left with the responsibility for defining or using these semantics.
To make a concrete example: a command executed from bash
will eventually return a value to it's caller upon termination. Here, any value other than 0
indicates abnormal termination of the command. In bash
you can inspect the variable $?
to see the return value of the last executed statement. Using this return value can be useful when controlling the flow of execution in a script e.g. to prevent reading from a non-existing file or such.
There are also other things to consider when talking terminating processes, such as freeing allocated address space with associated resources in memory, plus cpu scheduling of jobs, but that's another chapter.
This was implemented to the Linux kernel 3.4 as a flag of the system call prctl().
From the prctl(2)
manpage:
[...] A subreaper fulfills the role of init(1)
for its descendant
processes. Upon termination of a process that is orphaned (i.e., its
immediate parent has already terminated) and marked as having a
subreaper, the nearest still living ancestor subreaper will receive a
SIGCHLD
signal and be able to wait(2)
on the process to discover its
termination status.
A process can define itself as a subreaper with prctl(PR_SET_CHILD_SUBREAPER)
. If so, it's not init
(PID 1) that will become the parent of orphaned child processes, instead the nearest living grandparent that is marked as a subreaper will become the new parent. If there is no living grandparent, init
does.
The reason to implement this mechanism was that userspace service managers/supervisors (like upstart
, systemd
) need to track their started services. Many services daemonize by double-forking and get implicitly re-parented to PID 1. The service manager will no longer be able to receive the SIGCHLD
signals for them, and is no longer in charge of reaping the children with wait()
. All information about the children is lost at the moment PID 1 cleans up the re-parented processes. Now, a service manager process can mark itself as a sort of "sub-init", and is now able to stay as the parent for all orphaned processes created by the started services. All SIGCHLD
signals will be delivered to the service manager.
In Linux, a daemon is typically created by forking twice with the intermediate process exiting after forking the grandchild. This is a common technique to avoid zombie processes. The init script calls a child. That child forks again and thus exits immediately. The grandchild will be adopted by init
, which continuously calls wait()
to collect the exit status of his children to avoid zombies. With the concept of subreapers, the userspace service manager now becomes the new parent, instead of init
.
Best Answer
In the context of a Unix or linux process, the phrase "the stack" can mean two things.
First, "the stack" can mean the last-in, first-out records of the calling sequence of the flow of control. When a process executes,
main()
gets called first.main()
might callprintf()
. Code generated by the compiler writes the address of the format string, and any other arguments toprintf()
to some memory locations. Then the code writes the address to which flow-of-control should return afterprintf()
finishes. Then the code calls a jump or branch to the start ofprintf()
. Each thread has one of these function activation record stacks. Note that a lot of CPUs have hardware instructions for setting up and maintaining the stack, but that other CPUs (IBM 360, or whatever it's called) actually used linked lists that could potentially be scattered about the address space.Second, "the stack" can mean the memory locations to which the CPU writes arguments to functions, and the address that a called-function should return to. "The stack" refers to a contiguous piece of the process' address space.
Memory in a Unix or Linux or *BSD process is a very long line, starting at about 0x400000, and ending at about 0x7fffffffffff (on x86_64 CPUs). The stack address space starts at the largest numerical address. Every time a function gets called, the stack of function activatio records "grows down": the process code puts function arguments and a return address on the stack of activatio records, and decrements the stack pointer, a special CPU register used to keep track of where in the address space of the stack, the process current variables' values reside.
Each thread gets a piece of "the stack" (stack address space) for its own use as a function activation record stack. Somewhere between 0x7fffffffffff and a lower address, each thread has an area of memory reserved for use in function calls. Usually this is only enforced by convention, not hardware, so if your thread calls function after nested function, the bottom of that thread's stack can overwrite the top of another thread's stack.
So each thread has a piece of "the stack" memory area, and that's where the "shared stack" terminology comes from. It's a consequence of a process address space being a single linear chunk of memory, and the two uses of the term "the stack". I'm pretty sure that some older JVMs (really ancient) in reality only had a single thread. Any threading inside the Java code was really done by a single real thread. Newer JVMs, JVMs who invoke real threads to do Java threads, will have the same "shared stack" I describe above. Linux and Plan 9 have a process-starting system call (clone() for Linux, rfork() in Plan 9) that can set up processes that share parts of the address space, and maybe different stack address spaces, but that style of threading never really caught on.