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

Introduction to Operating Systems

Hey Habr! I would like to bring to your attention a series of articles-translations of one interesting literature in my opinion - OSTEP. This material discusses quite deeply the work of unix-like operating systems, namely, work with processes, various schedulers, memory, and other similar components that make up a modern OS. You can see the original of all materials here here. Please note that the translation was made unprofessionally (quite freely), but I hope I retained the general meaning.

Lab work on this subject can be found here:

Other parts:

You can also check out my channel at telegrams =)

Alarm! there is a lab for this lecture! Look github

process API

Consider an example of creating a process in a UNIX system. It happens through two system calls fork () ΠΈ exec ().

call fork()

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

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

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

First of all, we enter the main() function and print the string to the screen. The string contains the process ID, which in the original is called PID or process identifier. This identifier is used in UNIX to refer to a process. The next command will execute the fork() call. At this point, an almost exact copy of the process is created. For the OS, it looks like 2 copies of the same program are running in the system, which in turn will exit the execution of the fork () function. The newly created child process (with respect to the parent process that created it) will no longer be executed, starting from 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 to the caller of the fork() function will be different. In particular, the parent process will return the PID value of the child process, and the child will receive a value equal to 0. Using these return codes, it is already possible to separate processes in the future and force each of them to do its work. However, the execution of this program is not strictly defined. After splitting into 2 processes, the OS starts monitoring them in the same way and scheduling their work. In the case of execution on a single-core processor, one of the processes, in this case, the parent, will continue to work, and then the child process will take control. When restarting, the situation may be different.

call to wait()

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

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

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

exec() call

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

Consider the challenge exec (). 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 exec() is called? This call is passed as arguments the name of the executable file and some parameters. After that, the code and static data are loaded from this executable file and the own segment with the code is overwritten. The rest of the memory, such as the stack and heap, is reinitialized. After that, the OS simply executes the program, passing it a set of arguments. So we didn't create a new process, we just transformed the currently running program into another running program. After an exec() call is made in the child, it looks like the original program didn't run at all.

This complication of startup is absolutely normal for the Unix shell, and allows this shell to execute code after calling fork (), but before calling exec (). An example of such code would be to adjust the shell environment to the needs of the program being run, before it is actually launched.

Shell is just a user program. She shows you a prompt and waits for you to type something into it. In most cases, if you write a program name there, the shell will find its location, call the fork() method, and then call one of the exec() types to create a new process and wait for it to complete with a wait() call. When the child process terminates, the shell will return from the wait() call and print the prompt again and wait for the next command to be entered.

The separation of 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 the shell achieves this is quite simple - by creating a child process before calling exec (), the shell closes standard output and opens the file new_file, so all output from the next running program wc will be redirected to a file instead of a 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: habr.com

Add a comment