Blog » Linux

RAMDiski w systemie Linux

RAMDiski w systemie Linux

Dziś poruszymy temat przetrzymywania plików, w szczególności tymczasowych, w pamięci systemu operacyjnego. Jest to rzadko omawiany temat mogący jednak znacznie podnieść wydajność i/lub bezpieczeństwo niektórych rozwiązań.

Większości administratorów słowo RAMDisk, często zapisywane po prostu jako ramdisk, słusznie kojarzy się z plikami initramfs, które są umieszczone w katalogu/partycji boot.

[[email protected] ~]$ ll /boot/initramfs-* | head -2
-rw-------. 1 root root 73562106 Nov 14 2018 /boot/initramfs-0-rescue-d83e252f8d4a4e838692e59dbf0d7a9f.img
-rw-------. 1 root root 33280664 Nov 23 20:06 /boot/initramfs-3.10.0-1062.4.3.el7.x86_64.img

Pliki te są montowane jako system plików w pamięci, czyli de facto (RAMDisk), i zawierają oprogramowanie niezbędne do dalszego startu systemu podczas jego rozruchu. Znajduje się tam między innymi okrojony init (w nowszych wersjach Enterprise Linuxa okrojoną wersję systemd). Po przygotowaniu systemu następuje wywołanie programu switch_root, który sprawia między innymi, że dotychczasowy (initramfs) RAMDisk zostaje z pamięci zwolniony (rekursywnie usunięty), a system działa w nowym głównym drzewie katalogów.

W tym artykule przyjrzymy się jednak pojęciu RAMDisk w znacznie szerszej perspektywie. Na sam koniec przeprowadzimy również krótki benchmark.

Na końcu wstępu warto wspomnieć, iż samo pojęcie RAMDisk jest zapisywane na co najmniej 4 sposoby:

  • RAMDisk (taki sposób zapisu znajdziemy np. na stronie AMD)
  • RAM Disk (taki sposób zapisu znajdziemy np. w Wikipedii)
  • ramdisk (taki sposób zapisu w naturalny sposób używany jest w poleceniach, ale pojawia się także w dokumentacji i w artykułach)
  • tmpfs – taki zapis pojawia się w artykułach dotyczących systemów stricte linuxowych/unixowych (np. BSD) i jest zarówno zbiorczą nazwą dla idei RAMDisk, jak i jedną z jego implementacji.

W celu zachowania czytelności artykułu będę używać nazwy RAMDisk.

Czym jest RAMDisk?

Jak już wspomniałem, RAMDisk to system plików przechowywany w pamięci. Większość użytkowników systemu Linux zupełnie nieświadomie posiada kilka RAMDisków działających w swoim systemie.

[[email protected] ~]$ df -h | grep tmpfs
devtmpfs 7.7G 0 7.7G 0% /dev
tmpfs 7.8G 107M 7.7G 2% /dev/shm
tmpfs 7.8G 2.4M 7.8G 1% /run
tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup
tmpfs 1.6G 44K 1.6G 1% /run/user/1000

Wszystkie te RAMDiski zostały stworzone automatycznie bez udziału użytkownika. Co więcej, nie posiadają swoich wpisów w fstab. Więcej na temat automatycznie tworzonych RAMDisków znajduje się w dalszej części artykułu.

Zalety używania RAMDisku:

  • szybkość – jest to najszybszy interfejs plikowy, jaki możemy posiadać
  • elastyczność – tworzenie RAMDisku nie wymaga żadnych alokacji. Można więc tworzyć overcommit, czyli zgłaszać i udostępniać więcej zasobów niż w rzeczywistości się posiada. W powyższym przykładzie posiadamy RAMDiski na łączną sumę około 32 GB, mimo iż system posiada 16 GB pamięci RAM
  • tymczasowość danych – po wyłączeniu komputera lub odmontowaniu RAMDisku (i nadpisaniu lub skasowaniu danych) są bezpowrotnie tracone.

Wady używania RAMDisku:

  • wykorzystanie „drogiej” pamięci RAM. W przypadku gdy procesy w systemie nie zużywają znacznej części pamięci, RAMDisk może przyspieszyć działanie wybranych operacji oraz procesów. Należy pamiętać, iż Linux działa najlepiej, gdy ma możliwość cachowania danych w pamięci RAM. Zabranie tej pamięci w celu użycia RAMDisku może finalnie spowolnić system
  • tymczasowość danych – ogranicza ilość zastosowań do danych, z których ewentualną nagłą utratą jesteśmy pogodzeni.

Jako ciekawostkę należy podać, iż niektóre konfiguracje systemów unixowych, szczególnie było to popularne w przypadku Solarisa, domyślnie montują /tmp jako tmpfs. Dzięki temu w teorii nie potrzebują dodatkowych prac w celu usuwania plików tymczasowych. Odbywa się to jednak kosztem użycia pamięci RAM i potencjalnego zapełnienia pamięci systemu (zarówno pamięci RAM, jak i swap) niepotrzebnymi plikami tymczasowymi.

Typy RAMDisków w systemie Linux

RAMDyski w Linuxie można podzielić co najmniej ze względu na:

  • implementacje (typ)
  • sposób tworzenia i przeznaczenie.

Najpierw pozwolę sobie omówić implementacje RAMDisku w Linuxie:

  • RAMDisk typu ramfs. Jest to stosunkowo prosty mechanizm tworzący syntetyczny system plików. Wykorzystuje on fakt, iż jądro odczytując lub zapisując plik, używa mechanizmów cache/buforowania. Pliki i katalogi zapisywane do ramfs także wpisują swoje dane do cache (lub dentry cache w przypadku katalogów), jednak nie zapisują one swoich danych na urządzeniu. Takie podejście eliminuje między innymi używanie systemu plików oraz konieczność prealokacji zasobów. Do jego bolączek należy:
    • brak możliwości kontrolowania wielkości
    • możliwość zapisu aż do wyczerpania pamięci fizycznej
    • strony pamięci używane przez ramfs nie mogą zostać przechowywane w pamięci swap
    • w przypadku braku pamięci w systemie zostanie uruchomiony OOM Killer (Out-Of-Memory), jednak pamięć używana przez RAMDisk nie zostanie zwolniona tak długo, aż nie zostanie on odmontowany
    • z wyżej wymienionych powodów nie powinien być użytkowany przez nieuprzywilejowanych użytkowników;
  • RAMDisk typu tmpfs tmpfs został stworzony na podstawie ramfs i jest jego bardziej rozbudowaną wersją. Wykorzystując tmpfs, uzyskuje się między innymi kontrolę nad maksymalnym rozmiarem oraz możliwość przejścia z pamięci fizycznej (RAM) na pamięć rozszerzoną (swap). Dzięki temu system może lepiej zarządzać pamięcią z uwzględnieniem rzeczywistych potrzeb. Niemniej jeśli zależy nam na zwiększonym bezpieczeństwie oraz na tym, by dane z RAMDisku nie zostały zapisane na dysku przy pomocy mechanizmu swap, nie należy go stosować
  • RAMDisk typu ramdisk. Nieużywany już typ RAMDisków. Tworzy syntetyczne urządzenie blokowe. Używa prawdziwego systemu plików i jest niewydajny. Wymaga prealokacji części zasobów, a na dodatek dubluje wiele operacji. Został wyparty przez ramfs
  • RAMDisk typu cramfs i squashfs. Ostatnim typem RAMDisków są skompresowane RAMDiski, pozwalające tylko na odczyt (read-only) zawartych w nich danych. Pozwalają one na swobodny dostęp do skompresowanych danych, które są dekompresowane „w locie”. W związku z trudnością dodawania nowych danych do już skompresowanego obrazu/pliku są one wykorzystywane tylko do odczytu. Przykładowo squashfs jest używany między innymi w obrazach „live” dystrybucji Linuxowych, dystrybucji dla routerów OpenWRT czy w Google Chromecast.

Następnym zaproponowanym podziałem RAMDisków jest podział ze względu na użytkownika tworzącego RAMDisk wraz z jego użyciem.

1. Ramdiski tworzone automatycznie przez system:

  • shm – /dev/shm implementuje idee pamięci współdzielonej. Przestrzeń ta jest wykorzystywana między innymi przez wywołania systemowe z rodziny shm_open i shm_unlink, które są częścią standardu POSIX
  • devtmpfs – RAMDisk, w którym Linux tworzy między innymi pliki urządzeń
  • cgroups – RAMDisk tworzony dla cgroup w wersji 1. Znajdują się w nim poszczególne kontrolery cgroup.

2. RAMDisk tworzony przez użytkownika:

  • RAMDisk tworzony dla procesów intensywnie wykorzystujących operacje I/O, podczas gdy dane przetrzymywane w RAMDisku nie posiadają znacznej wartości. Z reguły używa się RAMDisku typu tmpfs. Przykładowym zastosowaniem jest kompilowanie programu lub budowanie paczek/paczki. Dzięki zastosowaniu RAMDisku proces budowania może być nawet o rząd wielkości szybszy. W przypadku utraty zasilania przez komputer nie ponosimy żadnych strat, gdyż procesy te są łatwo powtarzalne
  • RAMDiski służące do przetrzymywania sekretów. Z reguły używa się RAMDisku typu ramfs. Po wyłączeniu komputera zawarte w RAMDisku sekrety (np. klucz GPG, klucz do certyfikatu, klucze ssh, klucz do portfela bitcoin) przestają być dostępne.

Wykorzystanie /dev/shm

RAMDisk /dev/shm możemy wykorzystywać w dowolny sposób. Domyślnie nie wymaga on żadnych dodatkowych uprawnień, aczkolwiek na zabezpieczonych systemach nakłada się na niego obostrzenia. W poniższym przykładzie w katalogu domowym tworzone jest miękkie dowiązanie (ang. softlink), które następnie jest wykorzystywane do składowania informacji w RAMDisku.

[[email protected] ~]$ ln -s /dev/shm/ my_ramdisk
[[email protected] ~]$ df -h | grep shm
tmpfs 7.8G 86M 7.7G 2% /dev/shm
[[email protected] ~]$ dd -if=/dev/zero -of=my_ramdisk/4GB_PLIK -count=1024 -bs=4M
dd: invalid option -- 'i'
Try 'dd --help' for more information.
[[email protected] ~]$ dd if=/dev/zero of=my_ramdisk/4GB_PLIK count=1024 bs=4M
1024+0 records in
1024+0 records out
4294967296 bytes (4.3 GB) copied, 1.06584 s, 4.0 GB/s
[[email protected] ~]$ df -h | grep shm
tmpfs 7.8G 4.1G 3.7G 53% /dev/shm

Należy pamiętać także o fakcie, iż tak użyty RAMDisk domyślnie pozwala na odczyt danych przez innych użytkowników programu (w końcu jest to pamięć współdzielona).

Tworzenie RAMDisku tmpfs

tmpfs jest zaawansowanym mechanizmem RAMDisku pozwalającym między innymi na:

  • ograniczenia dotyczące rozmiaru, ilości wpisów inode lub bloków pamięci
  • ustawienie domyślnych praw dostępu oraz użytkownika
  • ustawienie polityki alokacji pamięci ze względu na NUMA.

W poniższym przykładzie na 6 GB RAMDisku został zapisany 4GB plik:

[[email protected] ~]# mount -t tmpfs tmpfs /mnt -o size=6G,uid=1000,gid=1000
[[email protected] ~]# free -m
total used free shared buff/cache available
Mem: 15777 2287 8922 329 4567 12848
Swap: 7935 0 7935
[[email protected] ~]# dd if=/dev/zero of=/mnt/4GB_zero bs=4M count=1024
1024+0 records in
1024+0 records out
4294967296 bytes (4.3 GB) copied, 1.34219 s, 3.2 GB/s
[[email protected] ~]# free -m
total used free shared buff/cache available
Mem: 15777 2285 4818 4426 8673 8753
Swap: 7935 0 7935

Miejsce zajęte przez plik jest raportowane w kolumnie „buff/cache”. W przypadku obciążonego systemu część stron pamięci mogłaby zostać zapisana w pamięci swap.

Zarówno komenda df, jak i mount zwraca informację o RAMDisku.

[[email protected] ~]# df -h | grep mnt
tmpfs 6.0G 4.0G 2.0G 67% /mnt
[[email protected] ~]# mount | grep mnt
tmpfs on /mnt type tmpfs (rw,relatime,seclabel,size=6291456k,uid=1000,gid=1000)

W celu zwolnienia miejsca w pamięci, czego konsekwencją jest utrata danych będących na RAMDisku, należy go po prostu odmontować. Warto przy tym pamiętać, iż część danych, jeśli trafiły do pamięci swap, wciąż może być zapisanych na dysku. Jądro ze względów wydajnościowych nie nadpisuje danych zwolnionych stron pamięci.

[[email protected] mnt]# umount /mnt/
[[email protected] ~]# free -m
total used free shared buff/cache available
Mem: 15777 3248 7663 481 4865 11736
Swap: 7935 0 7935

Tworzenie RAMDisku ramfs

Ponieważ ramfs nie posiada żadnych opcji montowania, stworzenie RAMDisku typu ramfs jest tak proste jak:

mount -t ramfs ramfs /punkt/montowania

W poniższym przykładzie na RAMDisku został zapisany 4GB plik:

[[email protected] mnt]# mount -t ramfs ramfs /mnt
[[email protected] mnt]# free -m
total used free shared buff/cache available
Mem: 15815 3580 6381 4198 5854 7703
Swap: 7999 1326 6673
[[email protected] mnt]# dd if=/dev/zero of=/mnt/4GB_zero bs=4M count=1024
1024+0 records in
1024+0 records out
4294967296 bytes (4.3 GB) copied, 0.913067 s, 4.7 GB/s
[[email protected] mnt]# free -m
total used free shared buff/cache available
Mem: 15815 3580 2275 4198 9959 7703
Swap: 7999 1326 6673

Jak widać, miejsce zajęte przez 4 GB plik zostało zaraportowane w kolumnie „buff/cache”. Jest to zgodne z opisem ramfs mówiącym o tym, iż wykorzystuje on mechanizm cachowania jądra, jednak bez zapisu danych na urządzenie fizyczne.

Następnie warto zwrócić uwagę, iż RAMDisk typu ramfs nie jest uwzględniony w wyjściu komendy df -h.

[[email protected] mnt]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 7.8G 0 7.8G 0% /dev
tmpfs 7.8G 4.2G 3.7G 54% /dev/shm
tmpfs 7.8G 18M 7.8G 1% /run
tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup
/dev/dm-2 230G 184G 46G 81% /
/dev/nvme0n1p2 1014M 398M 617M 40% /boot
/dev/nvme0n1p1 200M 9.9M 190M 5% /boot/efi
tmpfs 1.6G 48K 1.6G 1% /run/user/1000

Na szczęście komenda mount uwzględnia RAMDiski ramfs.

[[email protected] ~]# mount | grep ramfs
none on /mnt type ramfs (rw,relatime)

Analogicznie do tmpfs, w celu zwolnienia miejsca w pamięci, czego konsekwencją jest utrata danych będących na RAMDisku, należy go po prostu odmontować.

[[email protected] mnt]# umount /mnt/
[[email protected] mnt]# free -m
total used free shared buff/cache available
Mem: 15815 3583 6377 4198 5855 7700
Swap: 7999 1325 6674

Na samym końcu warto wspomnieć, że tak stworzony RAMDisk domyślnie może być użyty tylko przez użytkownika root.

Tworzenie własnego RAMDisku przy starcie systemu

By RAMDisk został stworzony automatycznie podczas startu systemu, należy go wpisać do pliku /etc/fstab.

Przykładowy wpis tworzący 18 GB RAMDisk typu tmpfs:

tmpfs /mnt tmpfs size=18G

Krótkie porównanie prędkości RAMDisku z dyskami NVMe

Przedstawione niżej porównanie zostało stworzone na dwóch lokalnych maszynach, które posiadają następujące parametry:

Host 1 (SpaceShip)Host 2 (SpaceStation)
KomputerThinkpad T480Była stacja do grania autora.
CPUIntel(R) Core(TM) i7-8550UIntel(R) Core(TM) i7-6700K
Pamięć:2 x Samsung 8GB M471A1K43CB1-CRC DDR4-24002 x G.SKILL 16GB Ripjaws V DDR4 3200MHz
Dysk:Samsung SSD 970 EVO 500GBSAMSUNG PM961 MZVLB256HAHQ-000L7
Jądro:5.5.0-1.el7.elrepo.x86_643.10.0-1062.12.1.el7.x86_64
System:EuroLinux 7EuroLinux 7

Jak widać, różnice w hostach są znaczne. Na dodatek Host 2 posiada o wiele słabszy dysk, który, co gorsza jest w dużym stopniu zapełniony (dyski SSD z reguły tracą na wydajności powyżej 80% zapełnienia). Posiada on jednak szybszy procesor i pamięć. Na niekorzyść Hosta 1 działa fakt, iż dyski pracujące w ThinkPadach z serii T480 są połączone tylko dwiema liniami PCIe. Należy zwrócić też uwagę na różnice w wersjach jądra.

Do testowania użyłem programu gobonnie, który jest reimplementacją programu bonnie. Zaznaczę, że tego testu nie należy traktować jako wykładni. Nie jest to bowiem szczegółowe studium przypadku wraz z rekomendacjami. Na koniec warto wspomnieć, iż jądra systemów nie były w żaden istotny sposób zestrajane.

Testy dysku zostały przeprowadzone przy pomocy następującej komendy:

./gobonniego -dir /mnt/ -size 32.0 -runs 3

Wybór próby na 32 GB nie jest przypadkowy. Jest to bowiem dwukrotność pamięci operacyjnej, co zmniejsza wpływ mechanizmu cachowania plików przez jądro.

Dla testów RAMDisku został stworzony 9GB RAMDisk typu tmpfs zamontowany w /mnt. Następnie wywołana została komenda gobonnie z następującymi parametrami:

./gobonniego -dir /mnt -size 8.0 -runs 3

Uśrednione wyniki przedstawiają się następująco:

Host 1 (SpaceShip)Host 2 (SpaceStation)
Średnia prędkość zapisu (Dysk)382 MB/s163 MB/s
Średnia prędkość odczytu (Dysk)1570 MB/s1036 MB/s
Średnia ilość IOPS (Dysk)9824546478
Średnia prędkość zapisu (RAMDisk - tmpfs)16268 MB/s15328 MB/s
Średnia prędkość odczytu (RAMDisk - tmpfs )22094 MB/s22018 MB/s
Średnia lość IOPS (RAMDisk - tmpfs )20238031929915

Jak widać:

  • zapis może być nawet 100 razy szybszy
  • prędkość odczytu jest średnio około 10-15 razy większa
  • ilość IOPS w tym wypadku jest od 20 do 40 razy większa.

Zakończenie

Jak zwykle chciałbym podziękować za czas poświęcony na przeczytanie tego artykułu. Mam nadzieję, że poznaliście wiele nowych informacji dotyczących RAMDisków. Tym bardziej że naprawdę potrafią one przyspieszyć pracę wybranych zdań, a sama ich idea jest po prostu ciekawa.

Bibliografia

man 8 mount
https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt
https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt
https://lwn.net/Articles/330985/
http://www.linuxfromscratch.org/lfs/view/development/chapter07/udev.html

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *