📜 ⬆️ ⬇️

Operating systems from scratch; Level 0

Good afternoon / evening / night / morning! There is one experimental course on operating systems. There he is at Stanford University. But some materials are available to everyone. In addition to the slides, full descriptions of practical exercises are available.


How does this course differ from others? Most of the code is written independently and runs on a very real modern hardware. The target platform is Raspberry Pi 3 model B. Those. sufficiently current architecture AArch64. ARMv8 Cortex-A53, four cores, 64-bit and that's it. Rust is selected as the main programming language. Which is safe, fast, without GC and so on. He, Rust, is supposed to be studied during the course.


There are about disks, file systems, input-output operations, threads / processes, scheduling, virtual memory, protection and security, interrupts, concurrency and synchronization. As in any other self-respecting course. The difference in the relevance of the material and the amount of practice. Coddit will have a lot .


Translator's Note


If you wanted to see a literal translation, then it will not be. Instead, I will try to make the text useful and understandable. For example, in places that are relevant only to Stanford students, I will put the information useful to others. There may be a little slang, a little unrelated to the original illustrations and a small number of additional comments. For the sake of readability, there will be no obvious Translator Notes ™. The text can be considered a literary translation or an article based on. I'm not a welder - I will not be offended.


Where did I learn about this course? Someone posted a reference on Hacker News . I accidentally saw and penetrated. A little he poked the course materials and eventually decided to translate this matter.


Overview


In this part we will customize the raspberry and necessary tools. As a result, we will have a raspberry flashing LED. There are four main stages. First of all, we need to make sure that the connection between Pi and the computer works for itself. Run the pre-prepared program. In the second stage, let's figure out how to connect LEDs. Pro breadboard and wiring. At the third stage we will collect nyashny call code and run it on Pi. Install the aarch64-none-elf and try it out. And at the fourth stage we will rewrite this whole thing on Rust.


A couple of useful links:



Phase 0: Getting Started


Before completing the course, you should get yourself a unix-like operating system for your immediate use. It can be Linux, BSD or macOS with git , wget , tar , screen and make installed. Theoretically, it can work in Windows 10 with the linux subsystem, but no one has verified for sure. At least this configuration is not supported. Those. There are no ready-made recipes for Windows and it is recommended to install Ubuntu LST or Fedora .



From iron we will need:



In the discussion on reddit there are references on amazon with what may be required. However, all this can be bought in any other store. Including offline. In addition, all you can buy more any components to your taste.


Caution : Malinka is electrostatically sensitive. Try not to touch the contacts with your bare hands. You will not be killed by a current or even scratched, but Malinka itself may be completely incapacitated. Ground yourself.


When all this is available, you can tighten the job code:


 git clone https://web.stanford.edu/class/cs140e/assignments/0-blinky/skeleton.git 0-blinky cd assignment0 make fetch 

Feel free to explore the content yourself.


Phase 1: Cooking Raspberries



The first thing we need to do is configure the CP2102 adapter. It is needed for communication between a computer and Pi. In addition to this, the raspberry gets through it the vital 5 volts. On the one hand there is USB, on the other there are five pins, in the middle of the handkerchief.


Driver setup


On Linux, everything should work right out of the box. You need to install a driver on Macs. Download this archive and unpack. We start SiLabsUSBDriverDisk.dmg and agree with the points on the sale of the soul under a license. After that, on the mounted volume, launch Silicon Labs VPC Driver.pkg . Install and reboot.


Try inserting the CP2102 into a free USB slot. If everything works, the corresponding files should appear in /dev . In the case of poppy /dev/tty.SLAB_USBtoUART . In the case of Linux, something like /dev/ttyUSB0 . Write it down - useful. Take out the adapter.


Connect raspberry


Now connect the Raspberry Pi to the CP2102. Here is the connector mapping table:


CP2102 connectorsRaspberry Pi connectors
+ 5vfour
GND6
Rxdeight
Txdten

Numbering of pins on the raspberry (still there is an interactive version):



All together it will look like this (the colors of the wires can be chosen arbitrarily):




Important : check and recheck the connections before connecting all this to the computer. We need fresh raspberry, not burnt jam.


If there is confidence in the correctness of pairing the raspberry and adapter - you can still connect the CP2102 to the computer.


Launch


Raspberry Pi loads programs from a microSD card during power up. Right now we will figure out how to cook it.


First of all, we need to drop files from the cloned repository onto the microSD box. Namely, those that are in the files/firmware folder. Those. bootcode.bin , config.txt and start.elf . Copy them to the root of the flash card. If suddenly there are no these files in the repository repository - you forgot about make fetch .


Why do we need bootcode.bin , config.txt and start.elf ?
This is all a bootloader for the raspberry. bootcode.bin is the first boot loader. His task is to load start.elf . Which configures the processor in accordance with the contents of the config.txt file. After that, it loads kernel8.img and passes control to it. By the way where is he?

Now copy the files/activity-led-blink.bin from the repo to the root of the flash card and give this file the name kernel8.img . Unmount the card and pull out. Make sure that the raspberry is disabled. Then we insert the card into the raspberry and connect the raspberry to the power supply. We should see a flashing LED on the raspberry and on the CP2102 adapter. Blinking of the latter means that some data is being transferred there.


Data? What data? In order to see them, we need to connect a serial terminal emulator to the CP2102 and read what is happening there. We will use the screen for it is installed on both Linux and macOS. Remember the device path from the /dev folder and run


 screen /dev/<> 115200 

On Linux, you may need to use sudo to run this command. However, you can add your user to the dialout group and not constantly write to this sudo command:


 sudo gpasswd --add <-> dialout 

One way or another, but we should see greetings from the raspberry. To exit the screen , press <ctrl-a> k , and then answer y to the exit prompt.


Phase 2: LED flashing


At this stage we will connect the 16th pin of the GPIO (physical contact No. 36) of the raspberry to the LED on the breadboard. Check out his work using a pre-prepared binary with firmware. Make sure the raspberry is disabled.


GPIO: General Purpose I / O (General Purpose I / O)


As the name implies, GPIO is a common mechanism for transferring data / signals between any two devices through electrical contacts.


The GPIO pins on the malinka can work as inputs or as outputs . When a contact is a holiday , it can be turned on or off. The inclusion of a contact means that you can take 3.3 volts from it. By turning off, it means that there is no current through this contact. When the GPIO-contact is input , the Malinka checks whether there are 3.3 volts on it, or not.


These contacts are incredibly, breathtakingly versatile and can be used to implement a huge range of different functions. Details can be found in the documentation . Documentation is not just attached. It is possible, and sometimes necessary, to read along the course.


LED connection.


Let's start with the construction of such a scheme:


LED on the constant


If you have never used a breadboard, then it is recommended to read (or at least see the pictures) here in this guide . In our scheme, we connect the LED to the contact 3.3 volts (pin number 1) and to the contact with zero potential (number 14). Pay attention to the correct connection of the LED. His shorter leg must be connected through a resistor to pin 14 (zero potential, or ground differently). After that, you can connect the malinka to the power. The LED will be on (if everything is connected correctly). If you turn the LED, it simply will not light up. He is finally the same diode as any of his friends.


If everything worked with a uniformly lit LED, then you can try to blink it. Cut off the raspberry from food. Now we reconnect the LED from pin 1 to pin 36 (GPIO 16) like this:


LED flashing circuit


Again, remove the memory card. Copy files/gpio16-blink.bin with the name kernel8.img instead of the old one with the same name. Put the card back and connect the malinka to the power supply. Now the LED should blink uncontrollably.


Phase 3: Nice Si


This time we will be writing a prog, which will do the same as gpio16-blink.bin . In order to be able to compile the nude sishechka under the raspberry we need a cross-compiler under aarch64-none-elf .


Cross-compiler installation


We need to install a GNU toolchain for the aarch64-none-elf architecture (the gcc compiler and its company are like objcopy).


Under macOS


First you need to install the homebrew package manager. If already installed, then this part can be skipped.


  1. Install the Xcode tools for the command line. A dialog box will appear. When it appears - click on "Install", "Continue" or whatever it usually is.
     xcode-select --install 
  2. Run the Homebrew installation script. It will guide you through the rest of the installation process.
     /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 

Now install the aarch64-none-elf toolchain using homebrew.


 brew tap SergioBenitez/osxct brew install aarch64-none-elf 

Check whether everything is correctly installed:


 $ aarch64-none-elf-gcc --version aarch64-none-elf-gcc (GCC) 7.2.0 Copyright (C) 2017 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

Under linux


  1. Download and unzip aarch64-none-elf-linux-x64.tar.gz . After that move arch64-none-elf to /usr/local/bin :
     wget https://web.stanford.edu/class/cs140e/files/aarch64-none-elf-linux-x64.tar.gz tar -xzvf aarch64-none-elf-linux-x64.tar.gz sudo mv aarch64-none-elf /usr/local/bin 
  2. Add /usr/local/bin/aarch64-none-elf/bin to the PATH environment variable. How exactly - it depends on your particular Linux distribution. In most cases, you should add the following to ~/.profile :
     PATH="/usr/local/bin/aarch64-none-elf/bin:$PATH" 
  3. Check if everything is normal. As output, we have to get the gcc version and all that.
     aarch64-none-elf-gcc --version 

You can build yourself from the source if such a desire arises. Read more here .


Now a little about iron


The interaction of the vast majority of modern hardware devices with software is carried out through its mapping into memory Memory-mapped I / O. The bottom line is this: you can communicate with devices as if it were just some part of the memory. This provides a specification of what will happen when you write or read certain addresses in memory. Addresses are usually divided into pieces of 32 or 64 bits, which are called registers. Registers can only be read from, written to, or both.


How do we know which registers and what to use, and where are they in memory? Manufacturers of various devices write documentation for these devices. Usually they are called datasheets (data sheet), manuals (device manual), well, or just documentation. There is no common widespread format for documenting devices. Sometimes the documentation may be insufficient or it may not be at all. The ability to read and understand the hardware documentation is quite a useful skill, and in some ways even art.


GPIO interface in memory


Documentation for many of the peripherals that the Rasbperry Pi has can be found in BCM2837 ARM Peripherals Manual . About GPIO can be read on page 89.


Pajazhi, in the same place about BCM2835, and we have BCM2837. Is that normal?

If you open the manual, then there can be seen in many places mentioning BCM2835. We just took a guide to it and corrected some errors. Well, the title was changed to BCM2837. BCM2837 and BCM2835 have the same peripheral devices with the same relative addresses in memory. The main difference in the overall configuration of physical memory. The base physical address of the peripherals on the BCM2837 is 0x3F000000 , as opposed to 0x20000000 in the BCM2835. However, both chips display these addresses at 0x7E000000 . Briefly on the BCM2837, the "peripheral" address 0x7EXXXXXX will be located at the physical address 0x3FXXXXXX . This documentation has been modified to reflect this.

For our task, the following registers are enough for us:


nameaddressdescriptionthe sizeread / write
GPFSEL10x7E200004GPIO Function Select 132 bitsboth
GPSET00x7E20001CGPIO Pin Output Set 032 bitsonly record
GPCLR00x7E200028GPIO Pin Output Clear 032 bitsonly record

This is directly copied directly from the documentation on page 90.


Now read the documentation for the GPFSELn register on pages 91 and 92. We write to this register to configure the pins as output or input. What should be the value in each field of the register GPFSEL1 to configure the output number 16 GPIO, so that it becomes an output?


Now, again, read the documentation on page 95. About the registers GPSET0 and GPCLR0 . We write to the GPSET0 register to enable the contact. And in GPCLR0 for shutdown. What value do we need to write to these registers to enable / disable pin 16?


Code writing


In the phase3/ turnips directory there is a procurement code for building a binary file for Malinka. For now, we can do without explaining why we need crt0.S , layout.ld and Makefile . Instead, focus on blinky.c . In it you will find that the addresses of all three registers we need at the top are already indicated. In addition, there are a couple of functions that can create a time delay. The task is to supplement the main function so that pin 16 of the GPIO is configured as an output, and then it turns on and then turns off to flash with a LED.


When the code is ready - it should be tested. First, compile it by running make , being in the phase3/ directory. If everything is good and there are no errors, then the blinky.bin blinky.bin will be blinky.bin . Rename it to kernel8.img , copy it to a microSD card and run it all on a raspberry. If you already have a working kernel8.img , you can proceed to the next phase.


Tips:

Setting / on / off pins can be implemented in a single line of code.
')
Here operators << , | , & and ~ .

Hex and binary forms can be used in sishechke. For the number three, something like 0x03 and 0b011 respectively.

Phase 4: Rust


This time we will be writing a program like gpio16-blink.bin , but already in Rust. The code is written in phase4/src/lib.rs


Install Rust and Xargo


In order to compile programs on Rust, we should install this compiler. In addition, we will install xargo , which is a wrapper associated with the package manager cargo . Xargo allows us to compile our code for Rasbperry Pi and all that.


  1. We go to https://rustup.rs/ and follow the instructions for installing rustup . Verify that Rust was installed correctly by running rustc --version .
  2. Now use rustup and cargo (which was installed with rustc in the last step) to install the Rust night assembly. At the same time and install the source code of the standard library. And xargo course.
     rustup default nightly-2018-01-09 rustup component add rust-src cargo install xargo 
  3. We check the installed commands and make sure that the versions of all this correspond to what we want from them:
     $ rustc --version rustc 1.25.0-nightly (b5392f545 2018-01-08) $ xargo --version xargo 0.3.10 cargo 0.25.0-nightly (a88fbace4 2017-12-29) 

Now we have quite a working compiler Rust.


Code writing


To write code in the file phase4/src/lib.rs you need to know at least the following constructs:


1) You can read from and write to what is behind the bare pointers ( *mut T ) using the read_volatile() and write_volatile() methods. For example, we have declared this:


 const A: *mut u32 = 0x12 as *mut u32; const B: *mut u32 = 0x34 as *mut u32; 

We can write a 32-bit unsigned integer with the address 0x12 into the cell with the address 0x34 like this:


 B.write_volatile(A.read_volatile()); 

2) Local variables are declared using the let _ = _; .
You can read A from the previous example (i.e. the value located at address 0x12 ) into a variable like this:


 let value = A.read_volatile(); 

3) Call the function fn f(param: usize); can be like this: f(123); .
4) The loop block can be used to repeat infinitely something:


 loop { do_this_again_and_again(); } 

5) Rust has the following bitwise operators:



Now you are ready to flash the LED from the code on Rust. The code is written in phase4/src/lib.rs Translate the code into a similar rust code (in the kmain function). There are already announced the necessary registers and the function of "sleep", which creates a delay for some time. Use it all.


When you are ready to test your program, compile it by running make in the phase4 directory. If everything is fine, then the file build/blinky.bin will be created, which we rename to kernel8.img and put it on the microSD card, which we then insert into the raspberry. When the LED flashes again, we can assume that this part of the tutorial is complete.


UPD Next series

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


All Articles