---
# System prepended metadata

title: SO Lista 0

---

# SO Lista 0

Należy przygotować się do zajęć czytając następujące rozdziały książek:
• Operating Systems: Three Easy Pieces – [Introduction$^1$](http://pages.cs.wisc.edu/~remzi/OSTEP/intro.pdf)
• Computer Systems: A Programmer’s Perspective (3rd edition) – 7.8, 7.9, 8.1, 9.7
**UWAGA!** W trakcie prezentacji należy zdefiniować pojęcia oznaczone **wytłuszczoną** czcionką

### Zadanie 1
:::info
* Opisz różnice między **przerwaniem sprzętowym** (ang. hardware interrupt), **wyjątkiem procesora** (ang. exception) i **pułapką** (ang. trap). 
* Dla każdego z nich podaj co najmniej trzy przykłady zdarzeń, które je wyzwalają. 
* W jakim scenariuszu wyjątek procesora nie oznacza błędu czasu wykonania programu?
* Kiedy pułapka jest generowana w wyniku prawidłowej pracy programu?
:::
* **wyjątek procesora** nagła zmiana w przepływie sterowania w odpowiedzi na zdarzenie (zmianę stanu procesora). Przykłady to: błąd dzielenia przez 0, błąd dostępu do strony pamieci lub przepełnienie arytmetyczne.
Dzielą się one na **przerwania**, **pułapki**, *błędy* i *zatrzymania*
![](https://i.imgur.com/9zx59Ei.png)
* **przerwanie sprzętowe** wywoływane jest przez sygnał z urządzeń I/O, są asnychroniczne - nie są spowodowane wykonywaniem instrukcji w CPU, są wywoływane przez hardware. Wyzwalają je wciśnięcie klawisza klawiatury, działania na drukarce lub skanerze lub sygnały układu scalonego (np. timer)
![](https://i.imgur.com/MsdtL3c.png)
![](https://i.imgur.com/5Jumn2a.png)
* **pułapka** to wyjątek uzyskany celowo, synchroniczny (jest wynikiem wykonanej instrukcji). Najważniejszym zastosowaniem pułapek jest zapewnienie interfejsu między programami użytkownika a jądrem, znanego jako wywołanie systemowe. Programy użytkownika często muszą zażądać od jądra usług takich jak odczyt pliku (read), utworzenie nowego procesu (fork), załadowanie nowego programu (execve) i zakończenie bieżącego procesu (exit). Aby umożliwić kontrolowany dostęp do takich usług jądra, procesory udostępniają specjalną instrukcję ``syscall n``, którą programy użytkownika mogą wykonać, gdy chcą zażądać usługi ``n``. Wykonanie instrukcji *syscall* powoduje wysłanie pułapki do programu obsługi wyjątków, który dekoduje argument i wywołuje odpowiednią procedurę jądra. Korzysta z nich też każdy debugger, np. dla breakpointów. Różnica między zwykłą funkcją a syscall jest taka, że zwykła funkcja wykonywana jest w **user mode**, a ``syscall`` w **kernel mode**. 
![](https://i.imgur.com/GXJ5Zbj.png)

> * Wyjątki asynchroniczne (a więc przerwania sprzętowe) nie powodują przerwania działania programu.
> * Pułapka jest generowana w wyniku prawidłowej pracy programu gdy jest wywołaniem systemowym.

:::spoiler Wycinki z CS:APP
>![](https://i.imgur.com/OSsEci3.png)
![](https://i.imgur.com/A0xFhat.png)
![](https://i.imgur.com/vklQbeK.png)
![](https://i.imgur.com/c0kRUK9.png)
![](https://i.imgur.com/2gDcZiy.png)
![](https://i.imgur.com/Y2zLCKD.png)
![](https://i.imgur.com/YrOMhZh.png)
![](https://i.imgur.com/H17wfPk.png)
:::

### Zadanie 2
:::info
* Opisz mechanizm **obsługi przerwań** bazujący na **wektorze przerwań** (ang. interrupt vector table). 
* Co robi procesor przed pobraniem pierwszej instrukcji **procedury obsługi przerwania** (ang. interrupt handler) i po natrafieniu na instrukcję powrotu z przerwania? 
* Czemu procedura obsługi przerwania powinna być wykonywana w **trybie jądra** (ang. kernel mode) i używać stosu odrębnego od stosu użytkownika?
:::
**Obsługa przerwania**
Każdemu typowi przerwania przypisuje się nieujemną liczbę. System po starcie, inicjalizuje **tablicę wektorów przerwań** (numer przerwania -> mechanizm przetwarzania). Przy wystąpieniu event'u procesor zapamiętuje na stosie jądra stan oraz instrukcję do której powinien wrócić po wykonaniu odpowiedniego handler'a. Procesor przerywa pracę i zaczyna wykonywać **procedurę obsługi przerwania** spod odpowiedniego indeksu z tablicy wektorów przerwań.

**Procedura obsługi przerwań** to kod mający na celu obsługę konkretnego sygnału żądania przerwania.
Jest ona bardzo podobna do zwykłej procedury, działa jednak w **trybie jądra**, aby mieć dostęp do wszystkich zasobów systemowych. Bez tego nie zadziałałby page fault, ponieważ wpisuje on na dysk fizyczny, a user mode nie może tego zrobić.

Jeżeli do zapamiętania stanu zostałby wykorzystany bieżący stos, może być to stos procesu użytkownika, a wartość wskaźnika stosu może być nieprawidłowa. Dlatego korzysta się ze **stosu jądra** ponieważ istnieje większa szansa na to, że wskaźnik stosu jądra będzie miał prawidłową wartość i będzie wskazywał na stronę dostępną w pamięci.

**Tryb jądra** (ang. kernel mode) zezwala na zrobienie wszystkiego w systemie, daje dostęp do hardware’u, natomiast **user mode** na to nie pozwala, nie możemy np. czytać z pamięci czy do niej zapisywać, uruchomiony kod musi korzystać z API systemu, aby wykonać wspomniane zadania.
![](https://i.imgur.com/CNwb5cJ.png)
![](https://i.imgur.com/OplvdSK.png)



---
### Zadanie 3
:::info
* Bazując na formacie ELF (ang. Executable and Linkable Format) opisz składowe pliku wykonywalnego. 
* Czym różni się **sekcja** od **segmentu**? 
* Co opisują **nagłówki programu**? 
* Skąd system operacyjny wie, pod jakim adresem ma umieścić segmenty programu i gdzie położona jest pierwsza instrukcja programu?
:::
**TW**i : *"Sekcje siedzą statycznie w pliku", "Segmenty siedzą w RAM-ie w trakcie wykonania"*
**Sekcje** zawierają informacje potrzebne w trakcie linkowania, a **segmenty** informacje wykorzystywane w trakcie runtime. Często sobie odpowiadają w pamięci wirtualnej procesu.

Plik relokowalny w formacie ELF wygląda następująco:
![](https://i.imgur.com/uDqHX1b.png)

Rozpoczyna się on 16-bajtową sekwencją zwaną ELF header opisującą wielkość słowa i kolejność bajtów (ang. endianness) systemu, który wygernerował plik. ELF header zawiera również informacje umożliwiające linkerowi spasowanie i zinterpetowanie pliku obiektowego - są to między innymi typ pliku obiektowego, rodzaj architektury, czy rozmiar i liczbę sekcji.

``> readelf -t -s Zad3.o``

Zwykle plik ELF składa się z następujących sekcji:

**.text** -> Kod maszynowy skompilowanego programu.

**.rodata**	-> Dane tylko do odczytu, np. sekwencje znaków niezmienialnych string używanych w funkcji printf oraz tabele skoków dla instrukcji switch.

**.data** -> Zainicjalizowane zmienne globalne oraz static. Lokalne zmienne są trzymane na stosie w trakcie uruchomienia programu (run time) i nie pojawiają się ani w sekcji .data, ani w .bss.

**.bss** -> Niezainicjalizowane zmienne globalne oraz static, włącznie ze zmiennymi zainicjalizowanymi na 0, ta sekcja nie zajmuje miejsca w pliku relokowalnym, jest jedynie placeholderem.

**.symtab**	-> Tabela symboli z informacją o funkcjach i zmiennych globalnych, które są zdefiniowane i do których odwołuje się w programie.

**.rel.text** -> Lista lokalizacji z sekcji .text, która zostanie zmodyfikowana w trakcie linkowania tego pliku relokowalnego z innymi. W ogólności instrukcje, które wywołują “zewnętrzne” (ang. external) funkcje lub odwołują się do zmiennych globalnych, będą musiały być zmodyfikowane, jednak funkcje wywoływane lokalnie nie muszą być modyfikowane.

**.rel.data** -> Informacje o relokacji dla zmiennych globalnych, do których występują odwołania lub są zdefiniowane w tym module. W ogólności są to zainicjalizowane zmienne globalne, których wartością początkową jest adres oraz funkcje zdefiniowane “zewnętrznie”.

**.debug** -> Lista symboli do debugowania, zawiera lokalne zmienne i definicje typów (typedef), zmienne globalne zdefiniowane przez program i te, do których się odwołuje, jak i oryginalny kod źródłowy w języku C. Aby sekcja ta powstała, należy skompilować program z flagą -g.

**.line** -> Mapowanie pomiędzy liniami z pliku źródłowego a instrukcjami kodu maszynowego z sekcji .text, podobnie pojawia się po użyciu flagi -g.

**.strtab**	-> Tablica stringów dla sekcji tabeli symboli .symtab oraz .debug, jak i nazw sekcji w nagłówkach.

---
System operacyjny wie pod jakim adresem ma umieścić segmenty programu dzięki segment header table. Adres pierwszej instrukcji znajduje się w headerze ELF.

---
### Zadanie 4
:::info
Zapoznaj się z rozdziałami 3.4 i A.2 dokumentu [System V Application Binary Interface AMD64](https://www.uclibc.org/docs/psABI-x86_64.pdf) Architecture Processor Supplement2 i odpowiedz na następujące pytania:
* W jaki sposób jądro musi przygotować **przestrzeń adresową** procesu? Co musi znajdować się na stosie w momencie wywołania procedury «_start»? Do czego służy auxiliary vector? Można go wyświetlić wydając przykładowe polecenie «LD_SHOW_AUXV=1 /bin/true».
* W jaki sposób wywołać funkcję jądra? W których rejestrach należy umieścić argumenty? Gdzie można spodziewać się wyników i jak jądro sygnalizuje niepowodzenie **wywołania systemowego**?
:::
Disclaimer: Warto pamiętać o tym że ABI (Application Binary Interface) i API (Application Programming Interface) to dwie zupełnie różne rzeczy, w ABI są rzeczy takie jak obsługa ramki stosu i inne niskopoziomowe sprawy, a w API są bardziej wysokopoziomowe wywołania, takie jak syscalle do porozumiewania się z innymi programami. ABI x32 pozwala na pracowanie na architekturze 64-bitowej na adresach 32-bitowych, dzięki temu zwiększamy szybkość działania o około 30%.

Przestrzeń adresowa procesu jest przygotowywana w następny sposób: procesor jest resetowany do “czystego” stanu, tzn. wszystkie flagi (CF, ZF, IF, itp.) są wyzerowane.

Auxiliary vector w trakcie uruchamiania programu otrzymuje od systemu operacyjnego informacje o środowisku, w którym ten program będzie działać.

Procedura _start jest entry pointem programu, tzn. adres tego symbolu jest adresem, do którego wykonywany jest skok na początku wykonania programu. Zwykle jest ona zawarta w pliku crt0.o, który zawiera kod startowy dla środowiska C runtime.

syscall to procedura jądra, działa ona podobnie do standardowych funkcji, jednak zamiast jump, używa procedur dla jądra. Jeżeli wynik w %rax będzie w przedziale <-4095, -1>, to syscall zwróci błąd wywołania systemowego.

---
### Zadanie 5
:::info
Opisz znaczenie słowa kluczowego «volatile» w języku C. Wymień co najmniej dwa scenariusze, w których użycie wskaźników do ulotnej zawartości pamięci jest niezbędne dla poprawności działania programu.
:::
There are at least three common reasons to use it, all involving situations where the value of the variable can change without action from the visible code: When you interface with hardware that changes the value itself; when there's another thread running that also uses the variable; or when there's a signal handler that might change the value of the variable.
https://pl.wikipedia.org/wiki/Zmienna_ulotna
https://barrgroup.com/embedded-systems/how-to/c-volatile-keyword

---

### Zadanie 6

Przypomnij jak wygląda mechanizm **tłumaczenia adresów** bazujący na wielopoziomowej tablicy stron procesorów z rodziny x86-64. Przedstaw algorytm obliczania **adresu fizycznego** na podstawie **adresu wirtualnego** z uwzględnieniem uprawnień dostępu. Jaką rolę w procesie tłumaczenia odgrywa **pamięć TLB**?

---
:::    info

**Ściągnij ze strony przedmiotu archiwum «so21_lista_0.tar.gz», następnie rozpakuj i skompiluj źródła poleceniem «make»**

:::
___
### Zadanie 7
Uruchom program «1_ls» pod kontrolą narzędzia «ltrace -S». Na podstawie śladu wykonania programu zidentyfikuj, które z **wywołań systemowych** są używane przez procedury: «opendir», «readdir», «printf» i «closedir». Do czego służy wywołanie systemowe «brk»? Używając debuggera «gdb» i polecenia «catch syscall brk» zidentyfikuj, która funkcja używa «brk».

---

### Zadanie 8
Pod kontrolą narzędzia «strace» uruchom program «2_cat» korzystający bezpośrednio z wywołań systemowych do interakcji ze **standardowym wejściem i wyjściem**. Pokaż, że program oczekuje na odczyt na deskryptorze pliku 0 i pisze do pliku o deskryptorze 1. Naciśnij kombinację klawiszy «CTRL+D» kończąc wejściowy strumień danych – co zwróciło «read»? Zmodyfikuj program tak, by czytał z pliku podanego w linii poleceń. Co się stanie, jeśli przekażesz **ścieżkę** do katalogu zamiast do pliku zwykłego?

---


