10 Programs and processes — what it is to be run
What to know first
Looking back
Chapter 5 said a program’s memory divides into code, static, stack and heap. But when, and by whom, are those regions prepared?
A. At the moment the program is run, mostly by the operating system. The executable file lying on the disk has no memory yet — it is merely a blueprint saying “the code is like this, and the static region needs this much”. Reading that blueprint, spreading out the real memory, setting up the workbench, and leaping to the first instruction — that is running, and one set brought to life that way is called a process.
The need for this chapter, and its context
This chapter sits right after streams for two reasons. One — chapter 9 showed a band along which characters flow, so who hands us that band is the very next question. The answer is the operating system: standard input, output and error are channels already open when a process is born. Two — chapter 5 said memory divides like this, and what remained was who makes those regions, and when; that answer is here too.
Having this chapter also keeps the word “runs” from being empty when the first program arrives in chapter 16. And since the very next chapter shows how the hardware implements the isolation described here, this is the seam that joins the two stories.
By the end of this chapter
fork and Windows’ way — seen briefly. What appears here is not the C standard. It is also the chapter that first draws the boundary between what the standard promises and what the operating system gives.The questions this chapter answers
- “Memory of its own” sounds odd — is there not only one memory attached to the machine?
- Then what is the reason to know about processes now?
- Why is there an “ended” state at all? Once it is over, why not just disappear?
- How is a system call different from a function call? In the code they look the same.
10.1 A program is a noun, a process is a verb#
The distinction is simple.
- Program — a file lying on the disk. Dead. It can be copied and moved.
- Process — one set run and alive. It has memory, has a position it has reached, has open files, ends one day and leaves an exit status.
Run the same program three times and three processes arise. The three share the same code but their memory is each their own — change a value on one side and the other does not know.
| what the OS gives a process | what it is | in this book |
|---|---|---|
| address space | a memory map of its own (chapter 5′s four regions) | chapter 88 |
| execution position | which instruction it is in the middle of | — |
| the list of open files | the passages including standard input and output | chapters 9 and 68 |
| command-line arguments and environment | the values handed over at running | chapter 55 |
| exit status | the one number it leaves as it ends | chapters 16 and 55 |
Table 10.1 — What the operating system gives a process
Two things in this list connect straight to later chapters. First, the standard input, output and error we shall see in chapter 9 are passages the operating system opened for the process in advance — it is not the program that opens them. Second, the 0 that chapter 16′s hello world returns as it finishes is exactly the exit status, and the side receiving that number is whoever ran this process.
Q. “Memory of its own” sounds odd — is there not only one memory attached to the machine?
A. Physically there is one, but the operating system shows each process its own address space. The addresses a process sees are not physical addresses but numbers of that process alone, and hardware moves them to the real places (virtual memory). So even if two processes use identical addresses they touch different places, and if one collapses the other is unharmed.
There is a world without this isolation too — on a small chip running with no operating system there is no concept of a process at all, and one piece of code uses the whole machine (chapter 88). So this chapter’s story is not the story of “everywhere C runs” but the story of when it runs on an operating system.
Seeing it once in the flesh makes “a program is a noun, a process is a verb” concrete.
examples/ch49/process.c
// 프로세스는 실행할 때마다 새로 태어난다 --- 지난번의 기억은 남지 않는다.
#include <stdio.h>
#include <stdlib.h>
// 프로그램이 통째로 가지는 값. 「지난 실행」의 값이 남아 있을 것 같지만,
// 프로그램이 끝나면 이 값이 놓였던 기억은 운영체제가 거두어 간다.
static int run_count = 0;
static void at_the_end(void)
{
puts(" the process is ending --- everything it owned goes back");
}
int main(int argc, char **argv)
{
atexit(at_the_end); // 프로세스가 끝날 때 부를 함수를 등록한다
run_count += 1;
// argv[0] 은 이 프로그램이 불린 이름이다. 자리마다 값이 다르므로 여기서는
// 「있다/없다」만 본다.
printf("the program received %d argument(s), and argv[0] is %s\n",
argc, (argc > 0 && argv[0] != NULL) ? "present" : "absent");
printf(" run_count in this process: %d\n", run_count);
puts(" run it again and it prints 1 again --- a new process starts fresh");
return 0;
}
Output
the program received 1 argument(s), and argv[0] is present
run_count in this process: 1
run it again and it prints 1 again --- a new process starts fresh
the process is ending --- everything it owned goes back
The run_count printed is always 1. Run it twice, run it ten times: still 1 — the program file stays put, but a process is born anew each time. The memory the last run used has already been taken back by the operating system. The function registered with atexit being called at the end is the other side of the same story: a process has a beginning and an end, and at that end it gives back everything it held.
10.2 How a process is born#
From here it differs by operating system. We learn the faces of two branches.
Platform note. The Unix family — fork and exec
The Unix family (Linux, macOS, BSD) divides the making of a process into two steps. This design is the root of the shell and of pipelines (chapter 9).
fork()— it duplicates the present process. The duplicated side (the child) starts with the same code, the same memory contents and the same open files as the parent. The strange thing is that this function returns twice — to the parent it returns the child’s number (its PID), to the child 0. From that return value each knows who it is.- The
execfamily — it replaces the present process’s contents with another program. The memory is turned wholly into the new program’s and it does not return (if it succeeds). - The
waitfamily — the parent waits for the child to end and receives the exit status.
pid_t pid = fork();
if (pid == 0) { /* the child */
execl("/bin/ls", "ls", (char *)0);
_exit(127); /* it comes here only if exec failed */
} else if (pid > 0) { /* the parent */
int status;
waitpid(pid, &status, 0);
}Type one command in a terminal and the shell does exactly this — duplicates itself, replaces the duplicated side with that command, and waits for it to end. The redirection and pipes seen in chapter 9 happen in between as well: after duplicating and before replacing, the child’s input and output passages are changed. Dividing the design into two steps is what made that place.
Duplication does not mean copying the memory whole. Today’s implementations put it off until writing (copy-on-write) — parent and child share the same place, and only the part one of them writes to is copied at that moment.
Platform note. Windows — CreateProcess
Windows has no fork. Instead it is one step — CreateProcess instructs in one go, “start this program as a new process”. There being no duplication step, the child does not inherit the parent’s memory, and what the parent wishes to hand down (open passages, the environment, the working directory) is stated in the arguments.
The difference in character between the two ways shows itself in porting. The Unix pattern of “duplicate, mend a little, then replace” does not carry over to Windows as it is and must be rewritten in the form of writing what is needed into the arguments. On the POSIX side — the standard shared by the Unix family, a standard of the operating system rather than of C — there is posix_spawn, which makes this pattern one step.
A common misconception. “Surely the functions for handling processes are in the C standard too”
It is not. Neither fork nor exec nor CreateProcess is C standard. What the C standard says about processes is surprisingly little — about as much as that the program starts, main runs, and it ends leaving an exit status. How to make a new process, run another program, or wait for a child is not in the standard (there is system alone, settled only as “it executes a command string”, and what that means is left to the implementation).
Why so stingy — because C must run on machines with no operating system too (chapter 67′s freestanding implementation). C must exist on chips that have no processes, so processes remained outside the standard, in the operating system’s territory.
This sense of the boundary matters through the whole book. What is in the standard works everywhere; what is outside it works only on that operating system. It is also why this book puts the stories tied to an operating system into separate “platform note” boxes.
Q. Then what is the reason to know about processes now?
A. Because three things become needed at once.
First, where chapter 9′s streams come from — standard input and output are passages already open when the process is born. Second, to whom the exit status 0 that chapter 16′s hello world leaves goes. Third, that chapter 5′s four regions are separate per process — saying a global variable is “one throughout the program” means one within one process.
The deeper stories — programs running along several strands (threads), communication between processes, signals — are beyond this book’s scope, but the pieces of them the C standard treats are met in their places (signals in chapter 80, threads and atomic operations in chapter 85).
10.3 The life of a process#
A process does not stay in one state from birth to end. Running, able to run and unable to run are different things, and that difference is the ground of the next chapter.
| State | What it is | Waiting for what | How it leaves |
|---|---|---|---|
| Running | it is using the core right now | — | it stops by itself, its time runs out, or it ends |
| Ready | it could run right now but it is not its turn | a turn on the core | the scheduler picks it |
| Waiting | it cannot run — it waits for something | a disk, input, a time, another process | the thing it waited for happens |
| Ended | the work is done; only the exit status is left | its parent to collect that value | the parent collects it with wait |
Table 10.2 — The states a process sits in
★ The third row is the state most often met in practice. When a program is “slow” it is usually not short of cores but waiting — for the disk, for a key press, for the network. And since there is no reason to hold the core while waiting, this is where the operating system hands it to another process.
Q. Why is there an “ended” state at all? Once it is over, why not just disappear?
A. Because one thing is left behind: the number called the exit status. If the side that should receive it (the parent) has not yet done so, the body of the process may go but that one line of record must remain. When the parent collects it, it disappears entirely. If the parent never collects, only that record drifts on — in the Unix family it is called a zombie, which sounds alarming but does nothing at all.
10.4 Two modes — what may be done and what may not#
One thing remains. If processes cannot touch one another’s memory, who makes that rule hold? If each program were to keep it voluntarily, nothing could stop a program that did not.
So the machine has two modes.
| User mode | Kernel mode | |
|---|---|---|
| Who runs there | ordinary programs | the operating system |
| Memory | only its own address space is visible | all of it is visible |
| Devices | cannot be touched directly | can be touched |
| Doing what may not be done | it stops right there — the operating system steps in | — |
| The door across | a system call — ask at an appointed place | returns to the previous mode |
Table 10.3 — The two modes
This makes what printf really does in chapter 16 a little more precise. Putting characters on a screen means touching a device, which user mode may not do. printf formats the characters, holds them in a tub, and in the end asks the operating system. That asking is the system call.
Q. How is a system call different from a function call? In the code they look the same.
A. They do look the same — you write write(…). What happens inside differs. An ordinary call jumps to another address within the same mode; a system call crosses the boundary between modes. So it costs more: the request is handed over in an appointed way, the mode changes, and the operating system checks “may this be granted?” before anything happens.
This adds another layer to why the tub of chapter 9 exists. Crossing the boundary for every single character would mean paying that cost every time, so the characters are gathered and handed over in one go.
Recap
| to remember | the point |
|---|---|
| program / process | a file / one set run and alive |
| what the OS gives | an address space, open passages, an exit status |
| memory is each its own | run the same program several times and they do not know each other |
| Unix | two steps, fork (duplicate) + exec (replace) |
| Windows | one step, CreateProcess |
| outside the standard | processes are not the C standard’s territory — the OS gives them |
Table 10.4 — Program and process — what to remember
The stage is set. A program is a file; run it and it becomes a process; the operating system hands it memory, channels and an exit status — that is what actually happens when chapter 16′s first program is “run”.
Now to see what happens on that stage. When there are several processes, how is one core shared out; who decides each switch and what announces it — that is the next chapter. The “waiting” state and the boundary between modes, both just seen, are used there at once. How bold a lie the simple picture of the machine has been all along — memory splitting apart (chapter 12), execution overlapping (chapter 13), the compiler stepping in (chapter 14), and therefore why C cannot help being “an abstract language” (chapter 15) — is what remains of this part.