Systemy operacyjne

Lista 1 (Lab) Termin oddania do 16.10.2026

Uwaga: Zadania z tej listy proszę nie wysyłać na SVN!

1. (2pt) Instalacja programu do wirtualizacji

  • Wybierz i zainstaluj program do wirtualizacji:
  • Zapoznaj się z interfejsem programu:
    • Tworzenie maszyn wirtualnych
    • Zarządzanie obrazami dysków
    • Podstawowe ustawienia sprzętowe: RAM, CPU, dysk, sieć

2. (5pt) Instalacja dystrybucji Ubuntu

  • Pobierz obraz instalacyjny systemu Ubuntu Desktop.
  • Utwórz nową maszynę wirtualną w wybranym programie i zainstaluj Ubuntu.
  • Po zakończeniu instalacji:
    • Uruchom system i zaloguj się.
    • Naucz się podstawowych komend konsoli:
      • Nawigacja w katalogach: cd, ls, pwd, tree
      • Wyświetlanie zawartości plików: cat, less, head, tail, more
      • Instalacja oprogramowania: apt-get update, apt-get install <pakiet>, apt-get remove <pakiet>, apt-get upgrade
      • Sprawdzanie informacji o systemie: uname -a, df -h, free -h, lsb_release -a, uptime
      • Praca na plikach i katalogach: touch, mkdir, cp, mv, rm, rm -r
      • Zarządzanie procesami: ps aux, ps axjf, top, htop, kill <PID>
      • Użytkownicy i uprawnienia: whoami, id, chmod, chown, who
      • Sieć: ping, wget, curl, ip a, netstat -tulnp
      • Szukanie: find, grep, grep -r, locate

3. (8pt) Instalacja dystrybucji Arch Linux

  • Pobierz obraz systemu Arch Linux.

  • Utwórz nową maszynę wirtualną i zainstaluj Arch Linux zgodnie z:

  • W trakcie instalacji poznaj i umiej wyjaśnić w kilku zdaniach poniższe zagadnienia:

    • BIOS vs UEFI
      • BIOS (Basic Input/Output System) – tradycyjny system startowy komputera, inicjalizujący sprzęt i ładujący system operacyjny.
      • UEFI (Unified Extensible Firmware Interface) – nowoczesny system startowy z graficznym interfejsem i obsługą dużych dysków, szybszym startem i zabezpieczeniami (Secure Boot).
    • MBR vs GPT
      • MBR (Master Boot Record) – starszy styl partycjonowania dysków, obsługujący maksymalnie 2 TB i do 4 partycji podstawowych.
      • GPT (GUID Partition Table) – nowoczesny system partycjonowania, obsługujący dyski powyżej 2 TB i wiele partycji (ile?).
    • Program do partycjonowania (np. fdisk lub cfdisk lub ...)
      • Pozwala tworzyć, usuwać i modyfikować partycje na dysku.
      • Wykonaj podstawowe polecenia w maszynie wirtualnej:
        • fdisk -l – lista dysków
        • Tworzenie nowej partycji
        • Ustawienie typu partycji
        • Zapis zmian
    • System plików
      • Struktura do przechowywania plików i katalogów.
      • Popularne systemy plików w Linux: ext4, ext3, btrfs, xfs.
    • Proces instalacji
      • Tworzenie partycji
      • Montowanie systemu plików
      • Instalacja pakietów podstawowych
      • Konfiguracja bootloadera
      • Ustawienia sieci i konta użytkownika

4. (10pt) Instalacja systemu Xv6 (x86-32, x86-64 oraz RISCV)

  • Zainstaluj system Xv6, który będziemy wykorzystywać jako referencyjną implementację prostego systemu operacyjnego (wersja dla procesora x86 wersja 32-bitowa). W środowisku GNU/Linux (Ubuntu, ArchLinux, ...) pobierz z github system operacyjny Xv6 oraz pobierz łatę xv6.patch. Łata pozwala na kompilację dla nowszych wersji gcc ≥ 12:
$ git clone https://github.com/mit-pdos/xv6-public.git
Cloning into 'xv6-public'...
⋮
$ cd xv6-public
$ wget https://cs.pwr.edu.pl/zawada/so/xv6.patch
$ patch -p1 -i xv6.patch
  • Możesz teraz zbudować i uruchomić ten system. Na początek musisz zainstalować emulator qemu (np. apt install qemu lub pacman -S qemu-desktop):
$ make qemu
  • Można też uruchomić bezpośrednio w terminalu (bez środowiska graficznego) przez emulator qemu tak:
$ make qemu-nox
  • Z emulatora wychodzimy przez kombinację klawiszy ctrl+a x, czyli jednocześnie naciskamy ctrl i a, potem klawisz x. Pomoc skrótów pokazuje kombinacja ctrl+a h np. ctrl+a c - konsola qemu gdzie można zobaczyć dużo informacji o maszynie emulującej przez qemu np. info, info mem, info block, info registers, info qtree. System możemy śledzić przez debugger gdb uruchamiając:
$ make qemu-gdb  # lub make qemu-nox-gdb
  • W drugim terminalu na początek pozwolmy gdb na automatyczne ładownie pliku .gdbinit który jest przygotowany w źródłach xv6:
# komendę uruchamiamy tylko raz!
$ echo "set auto-load safe-path /" >>~/.gdbinit 
  • Uruchamiamy gdb:
$ gdb ./kernel
GNU gdb (GDB)
⋮
Reading symbols from ./kernel...
+ target remote localhost:26000
The target architecture is set to "i8086".
[f000:fff0]    0xffff0: ljmp   $0x3630,$0xf000e05b
0x0000fff0 in ?? ()
+ symbol-file kernel
(gdb) break *0x7c00
Breakpoint 1 at 0x7c00
(gdb) c
Continuing.
[   0:7c00] => 0x7c00:   cli

Thread 1 hit Breakpoint 1, 0x00007c00 in ?? ()
(gdb) 

Szczegóły prawdopodobnie będą się różnić od powyższych w zależności od wersji gdb która została użyta, ale gdb powinien się zatrzymać w punkcie przerwania i powinna to być powyższa instrukcja cli.

  • Następnie zainstaluj i podobnie uruchom wersje Xv6 dla procesorów x86-64 oraz procesorów RISC-V.

5. (*10pt) Napisz własny mini-kontener w języku C (mini-container)

Napiszemy program w języku C, który pobiera jako argument ścieżkę do katalogu pełniącego rolę nowego systemu plików (rootfs), a następnie uruchamia w nim odizolowaną powłokę /bin/sh.

Program musi realizować następujące kroki:

  1. Izolacja procesów i środowiska: Utworzenie nowego procesu potomnego za pomocą wywołania systemowego clone() z odpowiednimi flagami izolacji:
  • CLONE_NEWPID – nowa przestrzeń PID (proces wewnątrz kontenera widzi siebie jako PID 1).
  • CLONE_NEWUTS – osobna nazwa hosta (hostname).
  • CLONE_NEWNS – osobna przestrzeń punktów montowania (mount namespace).
  1. Zmiana nazwy hosta: Ustawienie unikalnej nazwy hosta wewnątrz kontenera za pomocą sethostname().
  2. Zmiana korzenia systemu plików: Przejście do podanej ścieżki za pomocą chroot() oraz zmiana katalogu roboczego na / (chdir("/")).
  3. Izolacja /proc: Zamontowanie wirtualnego systemu plików proc pod ścieżką /proc wewnątrz kontenera (mount("proc", "/proc", "proc", 0, NULL)), aby komendy typu ps i top pokazywały wyłącznie procesy uruchomione w kontenerze.
  4. Uruchomienie procesu: Zamiana obrazu procesu potomnego na powłokę /bin/sh przy użyciu rodziny funkcji exec().

Przykładowy program w C

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024) // 1 MB stosu dla procesu potomnego
static char child_stack[STACK_SIZE];

struct container_config {
    char *rootfs;
};

int child_main(void *arg) {
    struct container_config *config = (struct container_config *)arg;

    // 1. Zmiana nazwy hosta
    if (sethostname("kontener-demo", 13) != 0) {
        perror("sethostname");
        return 1;
    }

    // 2. Izolacja punktów montowania (zabezpieczenie przed propagacją na hosta)
    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL);

    // 3. Zmiana rootfs
    if (chroot(config->rootfs) != 0 || chdir("/") != 0) {
        perror("chroot/chdir");
        return 1;
    }

    // 4. Montowanie wirtualnego systemu plików /proc dla nowych PID-ów
    mount("proc", "/proc", "proc", 0, NULL);

    // 5. Uruchomienie interaktywnej powłoki
    char *args[] = { "/bin/sh", NULL };
    execvp(args[0], args);

    perror("execvp");
    return 1;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Użycie: sudo %s <sciezka_do_rootfs>\n", argv[0]);
        return 1;
    }

    struct container_config config = { .rootfs = argv[1] };

    // Flagi izolacji Linuksa
    int flags = CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNS | SIGCHLD;

    // Utworzenie nowego procesu w osobnej przestrzeni nazw
    pid_t child_pid = clone(child_main, child_stack + STACK_SIZE, flags, &config);
    if (child_pid == -1) {
        perror("clone");
        return 1;
    }

    // Czekanie na zakończenie kontenera
    waitpid(child_pid, NULL, 0);

    return 0;
}

Przygotuj środowisko testowe (rootfs)

Aby przetestować program, potrzebujesz katalogu ze strukturą plików i binariami przygotowanymi pod architekturę docelową (np. statycznie skompilowany BusyBox):

# 1. Tworzenie katalogu dla kontenera
$ mkdir -p mini_rootfs/{bin,proc,sys,dev}

# 2. Pobranie i skopiowanie BusyBoxa (dostarcza podstawowe narzędzia /bin/sh, ps, ls)
$ wget https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox -O mini_rootfs/bin/busybox
$ chmod +x mini_rootfs/bin/busybox

# 3. Utworzenie dowiązań do powłoki
$ ln -s busybox mini_rootfs/bin/sh
$ ln -s busybox mini_rootfs/bin/ps
$ ln -s busybox mini_rootfs/bin/ls

# 4. Kompilacja i uruchomienie programu C
$ gcc -o mini_container mini_container.c
$ sudo ./mini_container ./mini_rootfs

Zadania do zrobienia

  1. Izolacja sieciowa (CLONE_NEWNET): Dodaj flagę CLONE_NEWNET i za pomocą komend ip link lub dedykowanych wywołań stwórz para-interfejs veth łączący hosta z kontenerem.
  2. Ograniczenia zasobów (cgroups v2): Wpisz PID kontenera do nowo utworzonej grupy w /sys/fs/cgroup/ i nałóż limit pamięci (np. memory.max = 50M). Pokaz działanie limitu poprzez uruchomienie alokacji pamięci przekraczającej limit.

6. (*10pt) Wirtualizacja aplikacji w kontenerach Docker i Podman w systemie GNU/Linux

  • Instalacja środowiska kontenerowego
    • Zainstaluj w systemie Linux Docker oraz Podman. Umiej wytłumaczyć do czego wykorzystywane są te narzędzia.

    • Wyjaśnij różnice między Docker a Podman (np. architektura klient-serwer vs. rootless containers).

    • Sprawdź poprawność instalacji:

      $ docker --version
      $ podman --version
  • Tworzenie i uruchamianie kontenerów
    • Pobierz oficjalny obraz systemu Ubuntu:

      $ docker pull ubuntu
      $ podman pull ubuntu
    • Uruchom kontener w trybie interaktywnym:

      $ docker run -it ubuntu bash
      $ podman run -it ubuntu bash
    • Zainstaluj w kontenerze kompilator GCC:

      $ apt update && apt install gcc -y
  • Program testowy „Hello, World”
    • Wewnątrz kontenera utwórz plik hello.c z prostym programem w C wypisującym „Hello, World”.

    • Skompiluj program:

      $ gcc hello.c -o hello
    • Uruchom wynikowy plik wykonywalny i zaprezentuj rezultat:

      $ ./hello
  • Tworzenie własnego obrazu z Dockerfile / Containerfile
    • Utwórz Dockerfile (dla Docker) oraz Containerfile (dla Podman) zawierający:

      • bazę FROM ubuntu:latest
      • instalację GCC
      • skopiowanie pliku hello.c do obrazu
      • kompilację programu w trakcie budowania obrazu
      • ustawienie domyślnego polecenia CMD ["./hello"]
    • Zbuduj własny obraz:

      $ docker build -t hello-docker .
      $ podman build -t hello-podman .
  • Uruchamianie własnych obrazów
    • Uruchom kontener z własnym obrazem:

      $ docker run hello-docker
      $ podman run hello-podman
    • Pokaż wynik działania programu.