Operating Systems: Three Easy Pieces. Part 1: Intro (translation)

Introduction to Operating Systems

Hello, Habr! I would like to present to you a series of translated articles on a piece of literature that I find interesting — OSTEP. This material delves deeply into the workings of Unix-like operating systems, specifically focusing on processes, various schedulers, memory, and other similar components that constitute modern OS. You can view the original materials here here. Please note that the translation is done non-professionally (rather freely), but I hope I preserved the overall meaning.

Laboratory work on this subject can be found here:
— original: pages.cs.wisc.edu/~remzi/OSTEP/Homework/homework.html
— original: github.com/remzi-arpacidusseau/ostep-code
— my personal adaptation: github.com/bykvaadm/OS/tree/master/ostep

You can also check out my channel on Telegram =)

Program Operation

What happens when a program is running? A launched program performs one simple task — it executes instructions. Every second, millions, and possibly billions, of instructions are fetched by the processor from RAM, which then decodes them (for example, recognizes what type these instructions belong to) and executes them. This can involve adding two numbers, accessing memory, checking a condition, jumping to a function, and so on. After completing one instruction, the processor moves on to the next. And so it goes, instruction by instruction, until the program finishes.
This example is naturally a simplified view — in reality, to speed up processor performance, modern hardware allows for executing instructions out of order, predicting possible results, executing instructions simultaneously, and other similar tricks.

Von Neumann model of computation

The simplified method we described is similar to the Von Neumann model of computation. Von Neumann is one of the pioneers of computer systems and also one of the authors of game theory.While a program is running, a multitude of other events occur, with numerous other processes and external logic at work, all aimed at simplifying the startup, functioning, and maintenance of the system.
There is a set of software responsible for the ease of launching programs (or even allowing multiple programs to run simultaneously). It enables programs to share the same memory and interact with various devices. This set of software is essentially called the operating system, and its tasks include monitoring that the system functions correctly and efficiently, as well as ensuring the simplicity of managing this system.

Operating system

An operating system, abbreviated as OS, is a complex of interconnected programs designed to manage computer resources and organize user interaction with the computer..
The OS achieves its efficiency primarily through the most important technique — the technique of virtualization.The OS interacts with physical resources (such as the processor, memory, disk, and so on) and transforms them into a more general, more capable, and easier-to-use form of itself. Therefore, for a general understanding, one can very roughly compare an operating system to a virtual machine.
In order to allow users to issue commands to the operating system and thus utilize the capabilities of the virtual machine (such as launching a program, allocating memory, accessing a file, and so on), the operating system provides an interface called API (application programming interface) to which calls can be made. A typical operating system allows for hundreds of system calls.
Finally, since virtualization enables multiple programs to operate (thereby sharing the CPU) and simultaneously access their instructions and data (thus sharing memory), as well as access disks (thereby sharing input-output devices), the operating system is also referred to as a resource manager. Each processor, disk, and memory is a resource of the system, and thus one of the roles of the operating system becomes the task of managing these resources efficiently, fairly, or conversely, depending on the task for which the operating system is designed.

CPU virtualization

Let's consider the following program:
(https://www.youtube.com/watch?v=zDwT5fUcki4&feature=youtu.be)

Operating Systems: Three Easy Pieces. Part 1: Intro (translation)

It doesn't perform any special actions; essentially, what it does is call the function spin(), which cyclically checks the time and returns after one second has passed. Thus, it infinitely repeats the string that the user passed as an argument.
We'll run this program and pass it the character 'A' as an argument. The result is not particularly interesting — the system simply executes the program, which periodically outputs the character 'A' on the screen.
Now let's try a variant where multiple instances of the same program are running, but outputting different letters to make it clearer. In this case, the result will be somewhat different. Despite having only one processor, the program executes simultaneously. How is this possible? The operating system, with the help of hardware capabilities, creates an illusion. An illusion that there are multiple virtual processors in the system, transforming one physical processor into theoretically endless quantities, thereby allowing programs to appear to execute simultaneously. This illusion is called CPU Virtualization.
Such a picture raises many questions; for example, if several programs want to run simultaneously, which one will actually be executed? This question is answered by the operating system's 'policies'. Policies are used in many parts of the OS and address such questions, and they are also basic mechanisms that the OS implements. Hence the role of the OS as a resource manager.

Memory Virtualization

Now let's look at memory. The physical model of memory in modern systems is represented as an array of bytes.To read from memory, you need to specify the address of the cell, to gain access to it. To write or update data, you also need to specify the data and the address of the cell where they will be written.
Accessing memory happens constantly during program execution. The program keeps its entire data structure in memory and accesses it while executing various instructions. In turn, the instructions are also stored in memory, so access to it occurs with every request for the next instruction.

Call malloc()

Let's consider the following program that allocates memory using a call malloc() (https://youtu.be/jnlKRnoT1m0):

Operating Systems: Three Easy Pieces. Part 1: Intro (translation)

The program does several things. First, it allocates a certain amount of memory (line 7), then prints the address of the allocated cell (line 9), and writes zero into the first slot of the allocated memory. Next, the program enters a loop where it increments the value stored in memory at the address in the variable "p". It also outputs its own process ID. The process ID is unique for each running process. If we run several copies, we encounter an interesting result: In the first case, if we do nothing and just run several copies, the addresses will be different. But this does not support our theory! Correct, because in modern distributions, memory randomization is enabled by default. If we disable it, we will get the expected result— the memory addresses of two concurrently running programs will match.

Operating Systems: Three Easy Pieces. Part 1: Intro (translation)

As a result, it turns out that two independent programs operate with their own private address spaces, which in turn are mapped by the operating system to physical memory. Therefore, using memory addresses within one program will not affect others, and each program believes it has its own chunk of physical memory completely at its disposal. The reality, however, is that physical memory is a shared resource managed by the operating system.

Consistency

Another important topic within operating systems is consistency. This term is used when discussing issues in the system that may arise when dealing with multiple tasks simultaneously within one program. Consistency issues can even occur within the operating system itself. In previous examples with memory and processor virtualization, we understood that the OS manages many tasks at once— launching the first process, then the second, and so on. As it turns out, such behavior can lead to certain problems. For instance, modern multithreaded programs experience such difficulties.

Let's consider the following program:

Operating Systems: Three Easy Pieces. Part 1: Intro (translation)

The program in the main function creates two threads using a call Pthread_create()In this example, a thread can be thought of as a function running in the same memory space alongside other functions, with the number of functions running simultaneously clearly being more than one. In this example, each thread starts and executes the function worker(), which simply increments a variable,.

Let's run this program with the argument 1000. As you might have guessed, the result should be 2000 since each thread increments the variable 1000 times. However, it's not that straightforward. Let's try running the program with a number of repetitions significantly larger.

Operating Systems: Three Easy Pieces. Part 1: Intro (translation)

By providing an input number, for instance, 100000, we expect to see an output of 200000. However, running the number 100000 multiple times, we not only fail to see the correct answer but also receive different incorrect answers. The mystery lies in the fact that increasing the number requires three operations — fetching the number from memory, incrementing, and then writing the number back. Since all these instructions are not carried out atomically (simultaneously), strange things like this can happen. This problem is known in programming as race condition. When unknown forces might affect the execution of your operations at an unknown time.

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster