📜 ⬆️ ⬇️

Operating Systems: Three Easy Pieces. Part 3: Process API (Translation)

Introduction to operating systems


Hi, Habr! I want to bring to your attention a series of articles-translations of one interesting in my opinion literature - OSTEP. This material takes a rather in-depth look at the work of unix-like operating systems, namely, working with processes, various schedulers, memory, and other similar components that make up a modern OS. The original of all materials you can see here . Please note that the translation was made unprofessionally (fairly freely), but I hope I saved the general meaning.

Laboratory work on this subject can be found here:


Other parts:
')

And you can also look to me on the channel in the telegram =)

Alarm! There is a lab for this lecture! watch githab

Process API



Consider an example of creating a process in a UNIX system. It occurs through two system calls fork () and exec () .

Call fork ()


image

Consider a program that performs a fork () call. The result of its implementation will be as follows.

image

First of all, we enter the function main () and perform the output of the string on the screen. The string contains the process identifier that is originally called the PID or process identifier. This identifier is used in UNIX to refer to a process. The next command will call fork (). At this point, an almost exact copy of the process is created. For the OS, it looks like the system runs as if 2 copies of the same program, which, in turn, will exit the fork () function. The newly created child process (relative to the parent process that created it) will no longer be executed, starting with the main () function. It should be remembered that the child process is not an exact copy of the parent process, in particular, it has its own address space, its own registers, its own pointer to executable instructions, and the like. Thus, the value returned by the caller of the fork () function will be different. In particular, the parent process will receive as a return the PID value of the child’s process, and the child will receive a value of 0. These return codes can later be used to separate the processes and force each of them to do their work. At the same time, the implementation of this program is not strictly defined. After the division into 2 processes, the OS begins to monitor them, as well, and plan their work. In the case of execution on a single-core processor, one of the processes will continue the work, in this case the parent, and then the child process will receive control. When you restart, the situation may be different.

Call wait ()


image

Consider the following program. In this program, due to the presence of a wait () call, the parent process will always wait for the child process to terminate. In this case, we get a strictly defined text output on the screen.

image

Call exec ()


image

Consider the exec () call. This system call is useful when we want to run a completely different program. Here we will call execvp () to run the wc program, which is a word counting program. What happens when you call exec ()? The name of the executable file and some parameters are passed as arguments to this call. After that, the code and static data is loaded from this executable file and its own segment is rubbed with the code. The remaining areas of memory, such as the stack and the heap, are reinitialized. After that, the OS simply executes the program, passing it a set of arguments. Thus, we did not create a new process; we simply transformed the current running program into another running program. After the execution of the exec () call in the descendant, it seems that the original program did not seem to start up in principle.

This launch complication is completely normal for the Unix shell, and allows the shell to execute code after calling fork () , but before calling exec () . An example of such a code could be the adjustment of the environment of the shell to the needs of the program being run, before its immediate launch.

Shell is just a user program. She shows you the prompt and waits for you to write something to it. In most cases, if you write a program name there, the shell will find its location, call fork (), and then, to create a new process, call any of the exec () types and wait for it to execute by calling wait (). When the child process is completed, the shell returns from the wait () call and displays the prompt again and waits for the next command to be entered.

Splitting fork () & exec () allows the shell to do the following things, for example:
wc file> new_file.

In this example, the output of the wc program is redirected to a file. The way that the shell achieves this is quite simple - when creating a child process before calling exec () , the shell closes the standard output stream and opens the new_file file, so all output from the wc program that is further started will be redirected to the file instead of the screen.

Unix pipes are implemented in a similar way, with the difference that they use the pipe () call. In this case, the output stream of the process will be connected to the pipe queue located in the kernel to which the input stream of another process will be attached.

Source: https://habr.com/ru/post/447182/


All Articles