We write our OS: Issue 1
This series of articles is devoted to low-level programming, that is, computer architecture, the design of operating systems, assembly language programming and related fields. So far, two habrayuzers are engaged in writing - iley and pehat . For many high school students, students, and professional programmers, these topics are very difficult to learn. There is a lot of literature and courses on low-level programming, but it’s hard to get a complete and comprehensive picture. It is difficult to read one or two books on assembler and operating systems, at least in general terms, to imagine how this complex system of iron, silicon and many programs actually works - a computer.
Each solves the problem of learning in his own way. Someone reads a lot of literature, someone tries to quickly switch to practice and understand along the way, someone tries to explain to friends everything that he studies. And we decided to combine these approaches. So, in this course of articles, we will demonstrate step by step how to write a simple operating system. Articles will be of an overview nature, that is, they will not have comprehensive theoretical information, however we will always try to provide links to good theoretical materials and answer all questions that arise. We do not have a clear plan, so many important decisions will be made along the way, taking into account your feedback.
Perhaps we will deliberately lead the development process to a standstill in order to allow you and ourselves to fully understand all the consequences of an incorrectly made decision, and also hone some technical skills on it. So do not take our decisions as the only right ones and blindly believe us. We emphasize once again that we expect readers to be active in discussing articles, which should greatly influence the overall development and writing of subsequent articles. Ideally, I would like some of the readers to join the development of the system over time.
We will assume that the reader is already familiar with the basics of assembler and C languages, as well as with the basic concepts of computer architecture. That is, we will not explain what a register or, say, random access memory is. If you do not have enough knowledge, you can always refer to additional literature. A short list of references and links to sites with good articles are at the end of the article. It is also advisable to be able to use Linux, since all compilation instructions will be provided specifically for this system.
And now - closer to the point. In the remainder of this article, we will write the classic Hello World program. Our Hello World will be a little specific. It will not be launched from any operating system, but directly, so to speak, on bare metal. Before we start directly writing code, let's figure out how exactly we are trying to do this. And for this you need to consider the process of loading the computer.
So, we take our favorite computer and press the largest button on the system unit. We see a funny screensaver, the system unit joyfully beeps with the speaker and after some time the operating system boots up. As you know, the operating system is stored on the hard drive, and here the question arises: how did the operating system magically boot into RAM and start to run?
Know: the system that exists on any computer is responsible for this, and its name is no, not Windows, your language’s tip - it’s called the BIOS. Its name is decoded as Basic Input-Output System, that is, the basic input-output system. The BIOS is located on a small microcircuit on the motherboard and starts immediately after pressing the large ON button. The BIOS has three main tasks:
The sad news: the size of the bootloader should be only 512 bytes. Why so few? To do this, we need to get acquainted with the device floppy disk. Here is an informative picture:
The picture shows the surface of the disk drive. The floppy disk has 2 surfaces. On each surface there are ring-shaped tracks (tracks). Each track is divided into small arcuate pieces called sectors. So, historically, the sector of a diskette has a size of 512 bytes. The very first sector on the disk, the boot sector, is read by the BIOS into the zero memory segment at offset 0x7C00, and control is passed on to this address. The bootloader usually loads into memory not the OS itself, but another bootloader program stored on disk, but for some reason (most likely this reason is the size) does not fit into one sector. And since so far the role of our OS has been played by the banal HelloWorld, our main goal is to make the computer believe in the existence of our OS, even on one sector, and start it.
How is the boot sector arranged? On PCs, the only requirement for the boot sector is to contain in its last two bytes the values 0x55 and 0xAA - the signature of the boot sector. So, it’s more or less clear what we need to do. Let's write the code! The above code is written for the yasm assembler .
This short program requires a number of important explanations. The string is
In lines
the data segment (
Then in the cycle the message “Hello World!” Is displayed character by character. To do this, use the
In the "
In line "
We sort of figured out the program code, let's now try to compile this happiness. For compilation, we need, in fact, an assembler - the aforementioned yasm . It is found in most Linux repositories. The program can be compiled as follows:
The resulting hello.bin file must be written to the boot sector of the diskette. This is done like this (of course, you need to substitute the name of your drive instead of fd).
Since not everyone has floppy drives and floppy disks, you can use a virtual machine, for example, qemu or VirtualBox . To do this, you will have to make a disk image with our bootloader and insert it into the "virtual drive".
Create a disk image and fill it with zeros:
We write our program at the very beginning of the image:
Run the resulting image in qemu:
After starting, you should see a qemu window with a happy line “Hello World!” This is where the first article ends. We will be glad to see your feedback and suggestions.
Literature
Each solves the problem of learning in his own way. Someone reads a lot of literature, someone tries to quickly switch to practice and understand along the way, someone tries to explain to friends everything that he studies. And we decided to combine these approaches. So, in this course of articles, we will demonstrate step by step how to write a simple operating system. Articles will be of an overview nature, that is, they will not have comprehensive theoretical information, however we will always try to provide links to good theoretical materials and answer all questions that arise. We do not have a clear plan, so many important decisions will be made along the way, taking into account your feedback.
Perhaps we will deliberately lead the development process to a standstill in order to allow you and ourselves to fully understand all the consequences of an incorrectly made decision, and also hone some technical skills on it. So do not take our decisions as the only right ones and blindly believe us. We emphasize once again that we expect readers to be active in discussing articles, which should greatly influence the overall development and writing of subsequent articles. Ideally, I would like some of the readers to join the development of the system over time.
We will assume that the reader is already familiar with the basics of assembler and C languages, as well as with the basic concepts of computer architecture. That is, we will not explain what a register or, say, random access memory is. If you do not have enough knowledge, you can always refer to additional literature. A short list of references and links to sites with good articles are at the end of the article. It is also advisable to be able to use Linux, since all compilation instructions will be provided specifically for this system.
And now - closer to the point. In the remainder of this article, we will write the classic Hello World program. Our Hello World will be a little specific. It will not be launched from any operating system, but directly, so to speak, on bare metal. Before we start directly writing code, let's figure out how exactly we are trying to do this. And for this you need to consider the process of loading the computer.
So, we take our favorite computer and press the largest button on the system unit. We see a funny screensaver, the system unit joyfully beeps with the speaker and after some time the operating system boots up. As you know, the operating system is stored on the hard drive, and here the question arises: how did the operating system magically boot into RAM and start to run?
Know: the system that exists on any computer is responsible for this, and its name is no, not Windows, your language’s tip - it’s called the BIOS. Its name is decoded as Basic Input-Output System, that is, the basic input-output system. The BIOS is located on a small microcircuit on the motherboard and starts immediately after pressing the large ON button. The BIOS has three main tasks:
- Detect all connected devices (processor, keyboard, monitor, RAM, video card, head, arms, wings, legs and tails ...) and check for proper operation. The POST program (Power On Self Test) is responsible for this. If vital iron is not found, then no software can help, and at this point the system speaker will squeak something sinister and it won’t get to the OS at all. Let's not talk about the sad, suppose that we have a fully working computer, rejoice and move on to the second BIOS function:
- Providing the operating system with a basic set of functions for working with hardware. For example, through the BIOS functions, you can display text on the screen or read data from the keyboard. Therefore, it is called the basic input-output system. Typically, the operating system accesses these functions through interrupts.
- Starting the bootloader of the operating system. In this case, as a rule, the boot sector is read - the first sector of the storage medium (diskette, hard drive, CD, flash drive). The media polling order can be set in BIOS SETUP. The boot sector contains a program, sometimes called a bootloader. Roughly speaking, the task of the bootloader is to start the launch of the operating system. The process of loading the operating system can be very specific and highly dependent on its features. Therefore, the primary bootloader is written directly by the developers of the OS and during installation is written to the boot sector. When the bootloader starts, the processor is in real mode.
The sad news: the size of the bootloader should be only 512 bytes. Why so few? To do this, we need to get acquainted with the device floppy disk. Here is an informative picture:
The picture shows the surface of the disk drive. The floppy disk has 2 surfaces. On each surface there are ring-shaped tracks (tracks). Each track is divided into small arcuate pieces called sectors. So, historically, the sector of a diskette has a size of 512 bytes. The very first sector on the disk, the boot sector, is read by the BIOS into the zero memory segment at offset 0x7C00, and control is passed on to this address. The bootloader usually loads into memory not the OS itself, but another bootloader program stored on disk, but for some reason (most likely this reason is the size) does not fit into one sector. And since so far the role of our OS has been played by the banal HelloWorld, our main goal is to make the computer believe in the existence of our OS, even on one sector, and start it.
How is the boot sector arranged? On PCs, the only requirement for the boot sector is to contain in its last two bytes the values 0x55 and 0xAA - the signature of the boot sector. So, it’s more or less clear what we need to do. Let's write the code! The above code is written for the yasm assembler .
section .text
use16
org 0x7C00 ; наша программа загружается по адресу 0x7C00
start:
mov ax, cs
mov ds, ax ; выбираем сегмент данных
mov si, message
cld ; направление для строковых команд
mov ah, 0x0E ; номер функции BIOS
mov bh, 0x00 ; страница видеопамяти
puts_loop:
lodsb ; загружаем очередной символ в al
test al, al ; нулевой символ означает конец строки
jz puts_loop_exit
int 0x10 ; вызываем функцию BIOS
jmp puts_loop
puts_loop_exit:
jmp $ ; вечный цикл
message:
db 'Hello World!', 0
finish:
times 0x1FE-finish+start db 0
db 0x55, 0xAA ; сигнатура загрузочного сектора
This short program requires a number of important explanations. The string is
org 0x7C00
needed so that the assembler (meaning the program, not the language) correctly calculates the addresses for labels and variables ( puts_loop, puts_loop_exit, message
). So we inform him that the program will be loaded into memory at 0x7C00. In lines
mov ax, cs
mov ds, ax
the data segment (
ds
) is set equal to the code segment ( cs
), since in our program both the data and the code are stored in one segment. Then in the cycle the message “Hello World!” Is displayed character by character. To do this, use the
0x0E
interrupt function 0x10
. It has the following parameters: AH = 0x0E
(function number) BH =
video page number (until we bother, specify 0) AL =
ASCII code of the character In the "
jmp $
" line , the program freezes. And rightly so, there is no need for her to execute the extra code. However, for the computer to work again, you will have to reboot. In line "
times 0x1FE-finish+start db 0
»The remainder of the program code (with the exception of the last two bytes) is filled with zeros. This is done so that after compilation, the signature of the boot sector appears in the last two bytes of the program. We sort of figured out the program code, let's now try to compile this happiness. For compilation, we need, in fact, an assembler - the aforementioned yasm . It is found in most Linux repositories. The program can be compiled as follows:
$ yasm -f bin -o hello.bin hello.asm
The resulting hello.bin file must be written to the boot sector of the diskette. This is done like this (of course, you need to substitute the name of your drive instead of fd).
$ dd if=hello.bin of=/dev/fd
Since not everyone has floppy drives and floppy disks, you can use a virtual machine, for example, qemu or VirtualBox . To do this, you will have to make a disk image with our bootloader and insert it into the "virtual drive".
Create a disk image and fill it with zeros:
$ dd if=/dev/zero of=disk.img bs=1024 count=1440
We write our program at the very beginning of the image:
$ dd if=hello.bin of=disk.img conv=notrunc
Run the resulting image in qemu:
$ qemu -fda disk.img -boot a
After starting, you should see a qemu window with a happy line “Hello World!” This is where the first article ends. We will be glad to see your feedback and suggestions.
Literature
- In assembly language:
- Zubkov S. V. "Assembler for DOS, Windows and Unix"
- Introduction to Machine Code
- Assembly language programming under DOS
- C language:
- Kernigan B., Ritchie D. “C Programming Language”
- Schildt G. "The Complete C Guide"
- By device operating systems:
- Tanenbaum E. "Modern operating systems"
- Tanenbaum E. "Operating Systems: Development and Implementation"
- Olifer V., Olifer N. "Network Operating Systems"
- http://osdev.org
- By computer architecture:
- Tanenbaum E. "Computer Architecture"
- Guk M. "Hardware IBM PC. Encyclopedia"
- Petzold C. "Code. Secret language of computer science "
- reference Information