Linux 2.6 vs 2.4 Made with Notepad!

Wojciech Rygielski
Piotr Stańczyk
Marek Żylak

Przedstawione tutaj materiały obrazują istotne zmiany powstałe w kodzie jądra Linuxa w wersji 2.6 w stosunku do poprzedniej stabilnej serii 2.4.

Kod źródłowy jądra 2.6 obejmuje już ok. 6,5 mln linii kodu (200MB), co stanowi o ok. 1 mln linii kodu więcej w porównaniu z wersją 2.4. To właśnie ten malutki milion linii jest przedmiotem dalszych naszych rozważań - prosimy jednak nie dziwić się bardzo, jeśli opisu którejś z linii brakuje.

Spis treści

Zmiany w procesie rozwoju

Koncepcja tworzenia oprogramowania typu Open Source niesie ze sobą wiele pozytywnych akcentów - każdy użytkownik takiego oprogramowania ma znacznie większe możliwości dostosowania aplikacji do własnych potrzeb, wszelkiego rodzaju błędy i usterki są wykrywane bardzo szybko. Tego typu koncepcja jest szczególnie pożyteczna dla ośrodków naukowych i uczelni wyższych, gdyż umożliwia efektywny sposób nauczania i rozpowszechnianie wiedzy.

Jednak każdy medal ma dwie strony. W przypadku Linux'a nadzorowanie pracy tysięcy osób modyfikujących źródła systemu operacyjnego i nadsyłających swoje poprawki w celu umieszczenia ich w oficjalnej dystrybucji systemu jest rzeczą niezmiernie trudną. Zdarzać się może przecież, że nie tylko modyfikacje dokonane przez dwóch niezależnych programistów są ze sobą niezgodne, ale również dwie osoby mogą dokonywać niezależnych modyfikacji tych samych fragmentów kodu. Efektywny rozwój oprogramowania, którego kod źródłowy jest wielkości rzędu 200 MB, na dłuższą metę nie jest możliwy w ten sposób.

W celu uproszczenia i zautomatyzowania tych procesów, twórcy Linux'a na czele z Linusem Torvaldsem dokonali zmian w procesie rozwoju.

BitKeeper

Jedną z modyfikacji jest wykorzystywanie specjalnych programów służących do nadzorowania pracy wielu programistów nad wspólnym kodem źródłowym. Takim programem jest właśnie BitKeeper wykorzystywany m.in. przez Linusa Torvaldsa:

BitKeeper has made me more than twice as productive, and its fundamentally distributed nature allows me to work the way I prefer to work - with many different groups working independently, yet allowing for easy merging between them.

-- Linus Torvalds, February 2004

Udogodnienia, jakie niesie ze sobą korzystanie z BitKeepera są ogromne, m.in.:

Zobacz też

Możesz też obejrzeć interfejs niektórych narzędzi (screenshoty):
Check In Tool, Diff Tool, File Merge Tool.

Więcej informacji na temat BitKeeper'a można znaleźć na stronie http://www.bitkeeper.com/.

Linux Test Project (LTP)

Linux™ Test Project jest projektem rozwijanym przez SGI™, IBM®, OSDL™, Bull® oraz Wipro Technologies, którego celem jest stworzenie zestawu testów dostępnych na zasadach Open Source, oceniających niezawodność solidność i stabilność Linuxa.

W skład LTP wchodzą zestawy narzędzi do testowania jądra Linuxa i elementów z nim związanych. Dzięki LTP nie trzeba już tworzyć własnego zestawu testów mających na celu weryfikację poprawności działania oraz efektywności własnych modyfikacji wprowadzonych w jądro. Wystarczy przeprowadzić odpowiednie testy LTP, których zadaniami są m.in.:

Więcej informacji dostępnych pod adresem http://www.linuxtestproject.org/

Skalowalność, nowe architektury sprzętowe

Wsparcie dla NUMA

Architektura NUMA (Non-Uniform Memory Access) jest następcą architektury SMP. Różnice między nimi pokazuje poniższy schemat.

Schematyczne porównanie architektury SMP (na górze) i NUMA (niżej)

Każdy z procesorów w architekturze SMP korzysta z jednej, wspólnej pamięci RAM. Mamy dostęp do dowolnej komórki pamięci i taka operacja jest szybka, ale ogranicza przy dzisiejszej technologii skalowalność systemu do conajwyżej kilkunastu procesorów (16). W architekturze NUMA każdy procesor ma własny kawałek RAMu do którego dostęp jest w stanie uzyskać szybciej niż do pozostałych "kawałków".

Pamięć widziana przez dany procesor dzieli się na obszary (nodes) ze względu na odległość od tego procesora (w sensie czasu dostępu). Najszybszy dostęp jest do pamięci lokalnej. Jądro 2.6 rozumie już pojęcia związane z NUMA (takie jak odległości między węzłami) i umie wykorzystać je do zarządzania pracą systemu, np. optymalizacji szeregowania procesów.

Istnieją systemy NUMA zawierające nawet 64 procesory.

Hyperthreading vs SMP

(HT na górze, SMP na dole)

Wsparcie dla Hyperthreading

Technologia Hyper-Threading została wprowadzona poraz pierwszy przez firmę Intel w procesorach Pentium 4. Umożliwia ona przechowywanie w procesorze zestawów rejestrów odpowiadających dwóm wątkom i bezzwłoczne przełączenie pomiędzy nimi. Procesor z HT jest widziany przez system jako dwa oddzielne procesory w architekturze SMP, ale w rzeczywistości nie może wykonywać obliczeń współbieżnie (tylko jedna jednostka arytmetyczna), ani reagować jednocześnie na kilka przerwań.

Skąd w takim razie bierze się zysk, wynoszący ok 20-30% (w porywach 50%)? Procesor traci sporo czasu na drobne (kilka cykli pracy) opóźnienia spowodowane takimi operacjami jak odwołanie do pamięci nie ściągniętej do w cache'a. W takim przypadku tracone są cenne cykle pracy skomplikowanej jednoski arytmetycznej. Opłaca się zatem trzymać w procesorze jednocześnie dwa wątki i, gdy jeden musi poczekać, drugi wątek od razu dostaje do dyspozycji jednostę obliczeniową i może kontynuować swoją pracę.

Ten sposób zwiększa średnią ilość obliczeń na sekundę wykonywanych przez procesor (ale nie daje to nic w przypadku obliczeń jednowątkowych).

Inne wsparcia

Opisana wyżej obsługa architektur NUMA i Hyperthreading, zwiększa użyteczność Linuksa w pracy z dużymi serwerami. Poniżej wymieniamy jeszcze parę udogodnień jakie wprowadzono w jądrze 2.6, a na które warto zwrócić uwagę w tym kontekście. Pokrótce omawiamy też projekt uClinux, który - w odróżnieniu od pozostałych - nie dotyczy dużych serwerów, a nawet wręcz przeciwnie...

PAE, APIC

Przede wszystkim zwiększono wsparcie dla nowych technologii Intela, m.in. PAE (Physical Address Extension). Dzięki PAE większość nowszych systemów 32-bitowych (na x86) będzie mogła uzyskać dostęp nawet do 64GB pamięci RAM - w trybie stronicowania.

Poprawiono wsparcie dla APIC (Advanced Programmable Interrupt Controller), który odpowiada za adresowanie indywidualnych procesorów w systemach wieloprocesorowych.

Limity wewnętrzne

Gdyby komuś bardzo nie chciało się czytać...

...powyższy wykres przedstawia wzrost UIDów, w przypadku PIDów wykres wygląda bardzo podobnie...

Prócz zmian związanych z obsługą nowego sprzętu, zwiększono (gdzie się dało) wewnętrzne limity systemowe. Na przykład:

Kolejną ważną zmianą jest obsługa większej ilości typów urządzeń oraz możliwość podłączenia dużo większej ilości urządzeń jednego typu.

Ograniczenie 255 na liczbę urządzeń było oczywiście dużo za małe - w szczególności dawało się we znaki administratorom sieci np. przechowujących duże ilości danych na wielu dyskach, taśmach (storage arrays) itp. Co ważniejsze kończyły się już powoli numery wolnych typów urządzeń, co w niedługim czasie mogło doprowadzić do zablokowania dodawania ubsługi nowych urządzeń.

Obsługa innych nowych architektur

Pojawiło się wspomaganie nowych architektur, takich jak:

Projekt uClinux

Właściwie wszystkie omówione do tej pory zmiany skalowalności dotyczyły skalowania w górę, tzn. dostosowania Linuksa do obsługi architektur stosowanych w dużych serwerach. "Wchłonięcie" przez jądro 2.6 wielu elementów projektu uClinux jest zmianą w drugą stronę.

Projekt uClinux (wymawiany you-see-Linux) zajmuje się tworzeniem Linuksa dla mikrokontrolerów. Projekt cieszy się dużą popularnością w kręgach związanych z rynkiem urządzeń typu embedded (zakorzenionych). Włączenie go do oficjalnej dystrybucji Linuksa, wpłynie na pewno na dalszy rozwój projektu.

W przeciwieństwie do normalnych architektur, systemy zakorzenione nie posiadają wielu funkcji do których programiści są przyzwyczajeni, np. brak jednostki MMU (memory management unit), która jest podstawą bezpieczeństwa każdego systemu operacyjnego (działającego w trybie chronionym).

Szybkość

Wstęp

W kolejnych podrozdziałach opiszemy zmiany wpływające na ogólną interaktywność i szybkość reakcji systemu.

Prócz rozszerzenia obsługi nowych, dużych architektur, kolejnym priorytetem jądra 2.6 było ogólne poprawienie czasów reakcji systemu, przydatne nie tylko dla typowych użytkowników desktopów (którzy zawsze cieszą się widząc np. szybkie otwarcie okienka), ale również dla aplikacji typu time-critical, w przypadku których - bez zachowania absolutnej dokładności i synchronizacji - nie uzyskamy zadowalającego wyniku. Wprowadzone zmiany nie oznaczają oczywiście że Linux staje się systemem czasu rzeczywistego (choć łatki RTOS są dostępne).

Pingwin w wersji 2.6 jest gotowy żeby wypłynąć na szerokie wody

Poniższe aspekty (wpływające na poprawienie szybkości jądra) będą dokładniej rozwinięte w kolejnych rozdziałach:

Nowy scheduler O(1)

Niewątpliwie najistotniejszą zmianą w jądrze 2.6, mającą drastyczny wpływ na efektywność działania Linuxa na komputerach wieloprocesorowych jest nowy Scheduler. Jest on lepszy nie tylko z punktu widzenia algorytmicznego od swojego starego odpowiednika, ale również w istotny sposób koncentruje się nad efektywnym wykorzystaniem wielu procesorów w komputerze, jak i redukuje do absolutnego minimum konieczność "skakania" procesów po procesorach, co z kolei pozwala w sposób bardziej efektywny wykorzystywać pamięć podręczną procesorów (proces wykonując się za każdym razem na tym samym procesorze ma w jego pamięci podręcznej część swoich danych, co redukuje potrzebę ściągania ich za każdym razem po wykonaniu przełączenia kontekstu).

Autorem tej modyfikacji jest Ingo Molnar. Implementację schedulera można podzielić na dwie niezależne fazy - faza przełączania kontekstu podczas której należy wyznaczyć kolejny proces do wykonania na procesorze, oraz faza wyliczania nowej ery. W nowym algorytmie obie te fazy są realizowane w czasie stałym.

Przełączanie kontekstu

Stara implementacja. Podczas wyznaczania nowego procesu do wykonania na procesorze, stary scheduler musiał przeszukać listę wszystkich procesów znajdujących się w stanie Running, a następnie wybrać spośród nich proces o najwyższym priorytecie. Takie wyszukiwanie było do tej pory realizowane sekwencyjnie w czasie liniowym. Co więcej, podczas wykonywania należało blokować struktury danych, co uniemożliwiało wykonywanie algorytmu równocześnie na wielu procesorach, to z kolei miało drastyczny wpływ na efektywność działania systemu, w szczególności w przypadku pracy z aplikacjami interaktywnymi dla których ruszenie myszką lub naciśnięcie klawisza oznaczało wykonanie reschedule w czasie liniowym.

Nowa implementacja reschedule jest realizowana w czasie stałym. Każdy procesor ma własną tablicę list uporządkowaną po priorytecie procesów znajdujących się na tych listach. Każdy wykonujący się proces jest przypisany do procesora na stałe i umieszczony jest na odpowiedniej liście w zależności od swojego priorytetu (w linux'ie 2.6 procesy mogą mieć priorytety od 0 do 139 - czym niższy priorytet tym ważniejszy proces, i właśnie takiej wielkości jest tablica list procesów).

Schemat struktury danych Schedulera 2.6

W celu wyznaczenia procesu, który powinien zostać wykonany jako następny, należy wyznaczyć pierwszą niepustą listę procesów (realizuje się to poprzez zastosowanie mapy bitowej z oznaczonymi niepustymi kolejkami procesów), a następnie wybraniu pierwszego procesu z listy:

idx = sched_find_first_bit(array->bitmap);
queue = array->queue + idx;
next = list_entry(queue->next, task_t, run_list);

W momencie, gdy proces zużyje swój kwant czasu przydzielony na aktualną erę, wstawiany jest do odpowiedniej kolejki procesów zużytych.

Zauważmy, że takie rozwiązanie problemu uniezależnia przełączanie kontekstu na różnych procesorach, dzięki czemu algorytmy te mogą wykonywać się równolegle.

W przypadku, gdy obciążenie któregoś procesora zmienia się istotnie w stosunku do obciążenia innych procesorów, następuje przesunięcie procesów między procesorami, co wymaga jedynie zmodyfikowania zawartości dwóch odpowiednich list z procesami.

Wyliczanie nowej ery

Proces wyliczania nowej ery również zmienił swoje oblicze w sposób istotny.

Stara implementacja. W starej wersji scheduler musiał przejrzeć listę wszystkich procesów w systemie i wyliczyć dla nich nowe kwanty czasu. Ta operacja wymagała w systemach wieloprocesorowych wstrzymania wykonywania obliczeń na wszystkich procesorach w oczekiwaniu zakończenia wyliczania danych dla nowej ery. W przypadku systemów o dużej liczbie procesorów oraz procesów oznaczało to dość istotne koszty.

Dodatkowo, w przypadku gdy jeden z procesorów kontynuował wykonanie ostatniego procesu w danej erze, inne procesory czekały bezczynnie na zakończenie wykonania ostatniego zadania i przeliczenie priorytetów dla wszystkich procesorów pomimo faktu, iż pracy jest pod dostatkiem.

Nowa implementacja. W przypadku nowego schedulera sprawa ma się zupełnie inaczej. Gdy nie ma więcej procesów do wykonania w aktualnej erze (wszystkie listy procesów dla poszczególnych priorytetów są puste), wykonywane jest bardzo proste zadanie (niezależne od sytuacji dla innych procesorów), polegające na zamianie kolejek procesów gotowych z kolejkami procesów zużytych. Ponieważ kwanty czasu dla procesów są wyliczane w czasie wstawiania ich do kolejki procesów zużytych, zatem na tym kończy się cała procedura:

array = rq->active;
if (unlikely(!array->nr_active)) {
	rq->active = rq->expired;
	rq->expired = array;
	array = rq->active;
	rq->expired_timestamp = 0;
	rq->best_expired_prio = MAX_PRIO;
}

Więcej szczegółów na temat nowego schedulera można znaleźć w źródłach: kernel/sched.c.

Wywłaszczanie w trybie jądra

Kolejną bardzo istotną zmianą wprowadzoną w jądrze 2.6 a mającą na celu przybliżenie Linuxa do systemów czasu rzeczywistego jest wprowadzenie możliwości wywłaszczania procesów w czasie wykonywanie się w trybie jądra. Do tej pory każdy program wywołujący funkcję systemową miał pewność, że jeżeli kod tej funkcji systemowej nie zrzeknie się dobrowolnie procesora lub nie zostanie przerwany przez przerwanie sprzętowe, to żaden inny proces nie jest w stanie przerwać wykonania takiego procesu. Podejście takie jednak w znacznym stopniu zmniejszało interaktywność systemu operacyjnego.

Wywłaszczenie w trybie jądra zostało zaimplementowane przez Roberta Love. Dzięki nowemu podejściu, dowolny program wykonywany w trybie jądra może zostać wywłaszczony podczas wykonywania dowolnego zadania, nawet niskopoziomowego, na rzecz np. procesu o wyższym priorytecie (np. obsługującego interakcję z użytkownikiem, czy też będącego procesem czasu rzeczywistego).

Oczywiście w wielu przypadkach może się okazać, że przerwanie wykonania procesu może być zjawiskiem niepożądanym - takie sytuacje zdarzają się podczas wykonywania sekcji krytycznych. Dlatego też istnieje możliwość blokowania wywłaszczania przy użyciu następujących funkcji (ich deklaracje można znaleźć w pliku include/linux/preempt.h):

Wprowadzenie wywłaszczania jądra w istotny sposób komplikuje i tak nie do końca przejrzysty kod jądra. Pisząc funkcje systemowe należy przewidywać co się stanie, gdy wykonanie zostanie przerwane przez inne zadanie i wznowione po pewnym bliżej nieokreślonym czasie. Wywłaszczenie procesu, który posiada spinlocki lub zamknięte mutexy na strukturach danych w wielu przypadkach może doprowadzić do zakleszczenia. Dlatego też w przypadku, gdy proces wchodzi do sekcji krytycznych, wywłaszczanie procesu w trybie jądra jest automatycznie wyłączane, taka sama sytuacja ma miejsce w przypadku gdy proces wykonuje się z wyłączonymi przerwaniami.

Jednak nie są to jedyne zagrożenia. Bardzo niebezpieczną sytuacją o której można zapomnieć jest fakt, iż stan FPU nie jest zapisywany przy zmianie kontekstu, zatem podczas wykonywania operacji zmiennopozycyjnych należy wykonywać blokowanie, inaczej bowiem może się okazać że proces odczyta błędne informacje z rejestrów stanu.

Program przedstawiony poniżej jest nieprawidłowy, może się okazać bowiem, że proces po wywłaszczeniu trafi na nie ten procesor, na którym był wykonywany wcześniej (takie zdarzenie jest mało prawdopodobne przy nowej implementacji scheduler'a, lecz jednak możliwe):

struct this_needs_locking tux[NR_CPUS]; 
tux[smp_processor_id()] = some_value; /* proces zostaje wywłaszczony... */ 
something = tux[smp_processor_id()];

Implementacja funkcji vmalloc_to_page w jądrze 2.4

struct page * vmalloc_to_page(void * vmalloc_addr) {
  unsigned long addr = (unsigned long) vmalloc_addr;
  struct page *page = NULL;
  pgd_t *pgd = pgd_offset_k(addr);
  pmd_t *pmd;
  pte_t *pte;
  if (!pgd_none(*pgd)) {
      pmd = pmd_offset(pgd, addr);
      if (!pmd_none(*pmd)) {
          pte = pte_offset(pmd, addr);
          if (pte_present(*pte))
              page = pte_page(*pte);
      }
  }
  return page;
}

Implementacja funkcji vmalloc_to_page w jądrze 2.6

struct page * vmalloc_to_page(void * vmalloc_addr) {
  unsigned long addr = (unsigned long) vmalloc_addr;
  struct page *page = NULL;
  pgd_t *pgd = pgd_offset_k(addr);
  pmd_t *pmd;
  pte_t *ptep, pte;
  if (!pgd_none(*pgd)) {
    pmd = pmd_offset(pgd, addr);
    if (!pmd_none(*pmd)) {
      preempt_disable();
      ptep = pte_offset_map(pmd, addr);
      pte = *ptep;
      if (pte_present(pte))
        page = pte_page(pte);
      pte_unmap(ptep);
      preempt_enable();
    }
  }
  return page;
}

Distribution of Preemption / Task Response Latency for IBM PowerPC 440GP
Źródło: The Preemptible Kernel Project

NPTL - Obsługa wątków

Wcześniejsza implementacja wątków w Linuksie (zarówno w kontekście przestrzeni użytkownika jak i jądra) była słaba: LinuxThreads nie dość że nie był zgodny z POSIX, to jeszcze do tego powolny.

Szybka obsługa mechanizmu wątków jest ważną częścią systemu operacyjnego - weźmy dla przykładu serwer WWW - ładnym i prostym rozwiązaniem jest zaimplementowanie obsługi każdego nowego połączenia poprzez utworzenia nowego wątku. Jednak przy dużym obciążeniu serwera połączeń może być nawet dziesiątki tysięcy praktycznie w jednej chwili, zaś każde połączenie odpowiada jednemu wątkowi - wymagamy zatem od systemu operacyjnego: szybkiego tworzenia nowych wątków, szybkiego przełączania kontekstu pomiędzy wątkami oraz wydajnej synchronizacji przy dostępie do wspólnych struktur danych i komunikacji międzywątkowej.

W jądrze 2.6 zastąpiono LinuxThreads nową infrastrukturą - NPTL (Native Posix Threading Library). Nowa implementacja jest dużo szybsza i (prawie) zgodna ze standardem POSIX (np. wprowadzono funkcję exit_group(), zamykającą proces i wszystkie jego wątki).

W celu poprawienia wydajności w /proc nie są juz tworzone oddzielne katalogi dla każdego wątku obecnego w systemie. Obecnie pokazywane są tylko sumaryczne statystyki dla całych grup wątków związanych z jednym procesem (np. suma zużycia czasu procesora).

W poprzedniej implementacji wątków, gdy tworzyliśmy n wątków związanych z jednym procesem, system potrzebował jeszcze n+1-ego, ukrytego wątku pomocniczego, synchronizującyego działania pozostałych. W NPTL zrezygnowano z wątku pomocniczego - jest dokładnie tyle wątków ile użytkownik sam stworzył.

NTPL nie zapewnia jeszcze pełnej zgodności z stantardem POSIX. Niekompatybilnie działają jeszcze np. funkcje setuid, które powinny dotyczyć wszystkich wątków w procesie, a nie jednego.

NPTL pozwala uruchomić i zatrzymać 100,000 wątków na raz w ciągu 2 sekund (15 minut w starym modelu)

Fast Userspace muTex

Futex jest nowym, niskopoziomowym mechanizmem dostępnym dla programów przestrzeni użytkownika. Umożliwia wydajne zaimplementowanie wszystkich wysokopoziomowych mechanizmów synchronizacji wątków, czyli mutex'a, conditional'a, semafora (semafora wątkowego, a nie IPC).

Szczegóły

Uzyskiwanie futex'a w NPTL:

Oddawanie futex'a:

Poprzednia implementacja wątków (LinuxThreads) wymagała często pomocy systemu operacyjnego do synchronizacji wątków. Pomoc taka polegała oczywiście na - z natury kosztownych - wywołaniach systemowych. Teraz w przypadku operacji niewspółzawodniczące (non-contended), czyli takich, które nie wymagają uśpienia lub obudzenia jakiegoś wątku, unikamy całkowicie odwoływania się do systemu, co znacznie zwiększa wydajność. Operacje współzawodniczące (contended) nie zostały zlekceważone przez projektantów nowej biblioteki i także są wydajniejsze niż wcześniej.

Z punktu widzenia procesu użytkownika futex jest zmienną całkowitą umieszczoną we wspólnej pamięci wątków. Można o niej muśleć jako o semaforze. Z taką zmienną jest związana (poprzez adres) kolejka procesów oczekujących, która znajduje się już w przestrzeni jądra. Wątek może odczytać i zapisać wartość futex'a bezpośrednio, nie odwołując się do funkcji systemowej. W przypadku gdy futex jest zajęty, nic to nie daje (bo i tak musi wywołać funkcję systemową i zasnąć). Ale w przypadku gdy futex jest wolny - wątek może od razu przejść do sekcji krytycznej (aktualizując zmienną atomowo), bez potrzeby przerywania swojego wykonania.

Więcej informacji:

Usunięcie Big Kernel Lock

Pierwsze wersje jądra Linuksa były czysto jednoprocesorowe. Dodatkowo istniało założenie, że procesor w trybie jądra nie może być wywłaszczony. Upraszczało to programistom jądra zadanie, gdyż nie musieli się przejmować ochroną struktur danych (przed jednoczesną modyfikacją).

Począwszy od wersji 2.0 jądra, Linux obsługuje architekturę SMP - jedna pamięć, wiele procesorów. Jednak jednoczesne działanie systemu na wielu procesorach wiąże się z utratą wspomnianego wcześniej założenia - dwa procesory mogą wykonywać kod w trybie jądra zmieniając przy tym wspólne struktury danych!

Nie możliwe było przebudowanie całego jądra "od razu", dlatego zdecydowano się na "prowizorkę" - powstał Big Kernel Lock (BKL) - wielka blokada na wszystkie struktury danych jądra (które mogą być modyfikowane jednocześnie). Gdy jeden procesor założy taką blokadę, wszystkie pozostałe procesory stają się bezczynne - czekają na jej zdjęcie.

Odwołania do BKL były systematycznie usuwane, w jądrze 2.6 praktycznie już ich nie ma, a jeśli już są to tylko w bardzo rzadko wykonywanych funkcjach (np. sys_reboot), nie obniżają wydajności systemu. Prawdopodobnie w jądrze 2.8 odwołania do BKL zostaną usunięte całkowicie.

Inne poprawki

Prócz wymienionych już kwestii, zmieniono i dodano jeszcze parę mniejszych udogodnień, których nie będziemy dokładnie omawiać, m.in. optymalizacje w buforowaniu odczytu i zapisu w systemach plików, manipulacje małymi plikami i inne podobne zmiany.

Pozostałe zmiany

System modułów

W nowej wersji jądra linux'a dokonano istotnych zmian dotyczących obsługi modułów.

Jedną z mniej istotnych, kosmetycznych zmian jest zamiana nazw modułów z *.o, na *.ko - Kernel Objects. Oprócz tego zmieniono nazwy funkcji inicjujących i usuwających moduł z pamięci - module_init() i module_exit(), zamiast starych init_module() i cleanup_module().

W serii 2.4 do dynamicznego ładowania modułów używany był pakiet modutils. Przed zainstalowaniem jądra 2.6 należy pamiętać o zmianie - należy zainstalować pakiet module-init-tools (jeśli tego nie zrobimy, nowostworzone jądro nie będzie w stanie ładować modułów).

Funkcje systemowe

Tablica sys_call jest wektorem adresów funkcji systemowych, które są wykonywane w trybie jądra w imieniu procesu je wywołującego. Do tej pory moduły miały możliwość nieograniczonego przeglądania tablicy sys_call, włącznie ze zmienianiem jej zawartości. To mogło powodować, że dowolny moduł bez szczególnych uprawnień - niejednokrotnie niepożądanie mógł zmodyfikować działania systemu operacyjnego.

Przykładowo, w celu zastąpienia standardowej funkcji systemowej read(), wystarczyło wykonać następujący kod:

extern int sys_call_table[];

read_save = sys_call_table[NR_read];
sys_call_table[NR_read] = read_sub;

read_sub() jest wskaźnikiem do funkcji systemowej, którą chcemy zastąpić aktualną funkcję.

Z praktycznego punktu widzenia takie rozwiązanie jest niebezpieczne z uwagi na występowanie współzawodniczenia między procesami w systemach wieloprocesorowych, jednak nie jest to główną przyczyną, dla której zrezygnowano z tego typu rozwiązania.

Niektóre funkcje systemowe mają istotny wpływ na działanie jądra Linux'a. Dostarczane moduły w wersji binarnej, których kody źródłowe nie są dostępne na zasadach GPL, miały możliwość wykorzystywania techniki podmieniania funkcji systemowych w celu głębokiej integracji w jądro. Co więcej eksportowane symbole były widziane dla wszystkich takich modułów.

Ciekawostka

Istnieje możliwość obejścia "zabezpieczeń" związanych ze zmianą sposobu podmieniania funkcji systemowych. Wystarczy przypisać adres sys_call_table z System.map do tablicy wskaźników utworzonej we własnym module.

Dzięki takiemu rozwiązaniu nie dość, że możemy używać tablicy sys_call_table[] tak jak to miało miejsce w jądrach z serii 2.4.*, ale dodatkowo możemy wyeksportować tę tablicę do /proc/kallsyms, co spowoduje, że będzie ona dostępna dla każdego załadowanego modułu.

Poniżej znajduje się przykładowa implementacja modułu dla jądra 2.6, który stosując w.w. technikę dokonuje zamiany funkcji systemowej setuid().

[kliknij tutaj]

W celu umożliwienia nakładania ograniczeń na moduły nie GPL, w modutils (który obsługuje ładowanie i usuwanie modułów z jądra) w wersji 2.4.10, wprowadzono makro EXPORT_SYMBOL_GPL pozwalające na definiowanie typu licencji dla modułów.

W jądrze 2.4.11 wprowadzono dwa różne rodzaje symboli eksportowanych. Stary rodzaj symboli pozostał bez zmian (wszystkie dotychczas istniejące symbole należą do tego zbioru) - są to symbole widziane przez wszystkie moduły załadowane do jądra. Nowy rodzaj symboli jest widziany tylko i wyłącznie przez moduły, które są wydawane na bazie odpowiedniej licencji open-source.

Dostęp do konfiguracji jądra

Proces kompilacji jądra wymaga od nas wcześniejszego stworzenia pliku konfiguracyjnego (tworzymy go poprzez wykonanie np. make menuconfig). Tworzony jest plik o nazwie .config w katalogu ze źródłami jądra - z niego korzysta kompilator w kolejnych etapach kompilacji jądra.

Do tej pory nie było możliwości obejrzenia konfiguracji z jaką zostało skompilowane jądro na którym aktualnie "chodzi" nasz system - jeśli usunęliśmy źródła jądra, usunęliśmy również jego konfigurację (plik .config), a zatem pozbawieni zostaliśmy możliwości np. późniejszego skompilowania jądra z tymi samymi opcjami.

W jądrze 2.6 informacje o kompilacji umieszczane są w binarnym obrazie jądra, a obejrzeć je można przez plik /proc/config.gz, czyli poznać konfigurację dokładnie tego jądra, które jest aktualnie w pamięci i zarządza komputerem.

Sysfs (katalog /sys)

Sysfs (/sys) jest to wirtualny system plików (tzn. generowany dynamicznie, tak jak np. /proc) w którym znajdują się zunifikowane informacje o wszystkich podłączonych do komputera urządzeniach. W odróżnieniu od używanego dotychczas katalogu /dev, urządzenia rozmieszczone są w logicznej drzewiastej postaci, a dostęp do ich parametrów i procedur obsługi staje się łatwiejszy i bardziej przejrzysty.

Rozwinięcie przykładowej gałęzi sysfs

Technologie związane z obsługą urządzeń

Najwięcej uaktualnień wprowadzono w obsłudze urządzeń zewnętrznych - jest to związane z tym, że urządzenia zewnętrzne w ciągu ostatnich lat stały się dużo bardziej "modne", zaś zmian w architekturach urządzeń wewnętrznych było względnie mało. I tak:

Dodano opcję software-suspend-to-disk, lecz prace nad tą funkcją wciąż trwają. Funkcja ta umożliwia przejście systemu w stan hibernacji (nazwa zaporzyczona z Windowsów), operacja polega na zrzuceniu aktualnego stanu pamięci na dysk i wyłączeniu komputera. Po ponownym włączeniu komputera stan pamięci jest odtwarzany i możemy kontynuować pracę. O ile idea wydaje się prosta, wiąże się z nią wiele problemów, jak np. zapewnienie poprawnej inicjalizacji urządzeń które były używane w momencie przejścia w stan hibernacji.

Nowe jądro obsługuje też (choć jeszcze słabo) technologię skalowania zegara procesora - tzn. odpowiedniego zmniejszania częstotliwości taktowania zegara gdy system jest mało zajęty (co wiąże się z oszczędnością energii - ważne dla użytkowników laptopów).

Zmiany w instalacji jądra

Graficzny konfigurator

Poprawiono wygląd standardowego konfiguratora graficznego (make gconfig) poprzez użycie zestawu komponentów graficznych Qt, czyli konfigurator wygląda podobnie do innych aplikacji KDE. Dodano też opcję dla miłośników GTK (make gconfig).

Sekwencja kompilacji

Wcześniej do kompilacji jądra, już po skonfigurowaniu trzeba było użyć trzech poleceń:

make dep 
make bzImage
make module

Teraz wystarczy wpisać po prostu:

make

Wsparcie dla nowych systemów plików

Użycie urządzeń blokowych w Linux'ie skoncentrowane jest w głównej mierze na dyskach. Szeroka gama wspieranych systemów plików bazowała do tej pory na dość przestarzałym modelu. Na szczęście jądro 2.6 niesie ze sobą nowe rozwiązania, na czele których stoją wspomaganie dodatkowych atrybutów dla plików oraz modularyzacja systemu uprawnień.

Metadane

W jądrze 2.6 rozszerzono w stosunku do 2.4 możliwości standardowych, Linuxowych systemów plików: ext2, ext3, reiserfs. Teraz można z każdym plikiem związać metadane (Extended Attributes). Metadane to zbiory par przyporządkowujące kluczom pewne wartości.

Access Control List

Na bazie metadanych został zrealizowany ACL. System ACL pozwala na dokładniejszą kontrolę dostępu do plików.

Przykład:

Dla każdego pliku można określić listę użytkowników którzy mogą go czytać. Wcześniej zwykły użytkownik mógł określić uprawnienia do czytania pliku tylko dla siebie, swojej grupy lub wszystkich.

XFS

Dodano pełną obsługę systemu plików XFS. Jest on teraz nowym standardowym systemem plików (jak ext2, ext3 i reiserfs). Przewyższa on wydajnością pozostałe systemy plików w niektórych rodzajach operacji, ale prawdopodobnie nie jest dość dobry, żeby znacząco się upowszechnić.

Reiser4 - prawie dodany

W jednej z głównych łatek (-mm, czyli Andrew Morton'a) do oficjalnego jądra, pojawiła się już obsługa systemu plików reiser4, co sugeruje, że lada moment zostanie on włączony do oficjalnego kernela. Jest wydajniejszy od wcześniejszych systemów plików w praktycznie wszystkich rodzajach operacji, ale przede wszystkim oferuje zaawansowane cechy:

FAT12

Poprawiono obsługę systemu plików FAT12, używanego m.in. na dyskietkach DOS'owych, w celu obsługi niektórych niestandardowych odtwarzaczy mp3.

Quota

Przepisano obsługę quoty, która umożliwia wprowadzenie ograniczeń na przydział pamięci dyskowej dla poszczególnych użytkowników. Teraz obsługuje jeszcze więcej użytkowników i umożliwia oznaczenie niektórych katalogów jako synchroniczne. Wszelkie zmiany w katalogach oznaczonych jako atomowe są atomowe.

Przeźroczysta kompresja w ISO9660

Dodano obsługę Linuxowego rozszerzenia systemu plików dla CD-ROMów. Rozszerzenie ma odtwarzać płyty skompresowane na poziomie systemu plików.

Urządzenia interfejsu użytkownika

Ustandaryzowano wreszcie dostęp do takich urządzeń jak klawiatura i myszka. Wcześniej do komunikacji z myszką używanych było kilka urządzeń: /dev/psaux dla myszek PS/2, /dev/ttySn dla myszek podłączonych do COMn itd. Co więcej, dla każdego rodzaju myszki używany był oddzielny protokół.

Teraz dostępne jest urządzenie /dev/input/mouse0, pod którym jest zawsze podłączona myszka. Do korzystania z /dev/input/mouse0 używa się także ujednoliconego protokułu, niezależnie od rodzaju myszki.

Obsługa systemów headless

Headless - tzn. bez urządzeń interfejsu użytkownika. Linuxa 2.6 można wreszcie skompilować całkowicie bez obsługi klawiatury, myszki i monitora. Tego typu instalacje przydają się na różnego rodzaju serwerach (np. komputer, który ma tylko pełnić rolę routera).

Ponadto wsześniej nie było możliwe debugowanie tego typu stacji przy pomocy mechanizmu sysrq, ponieważ wymagał on dostępu do lokalnej konsoli. Teraz jest możliwe korzystanie z sysrq także zdalnie.

ALSA - nowa architektura dźwięku

Jedną z bardziej oczekiwanych przez użytkowników nowości w jądrze 2.6 jest zastąpienie stare OSS (Open Sound System) nowym system ALSA (Advanced Linux Sound Architecture).

Poprzednia architektura (OSS) istniała w Linuksie od samych jego początków, jej ograniczona budowa uniemożliwiała dalszy rozwój.

ALSA od samego początku robiona była z myślą o bezpiecznym współgraniu z architekturą SMP (OSS działał dobrze tylko na jednym procesorze). Co ważniejsze - wszystkie sterowniki zmodularyzowano (OSS doznał "na siłę" wszczepionej modularności w jądrze 2.2). Zaowocowało to lepszą obsługą wielu kart dźwiękowych na jednym systemie, obsługą nowych urządzeń USB i MIDI itd.

Multimedia

Ulepszono podsystem Video4Linux odpowiadający za multimedia. Jądro obsługuje takie urządzenia jak tunery telewizyjne, kamery wideo, Digital Video Broadcasting itp.

W celu obsługi urządzeń takich jak Tablet PC oraz różne terminale publiczne dodano obsługę tzw. ekranów dotykowych.

Yeah...

Kierownice ze sprzężeniem zwrotnym (force-feedback)

Kierownice i joysticki ze sprzężeniem zwrotnym są używane przez graczy chcących pogłębić wrażenia związane z graniem w gry symulacyjne, głównie symulatory lotu (joystick) i wyścigi samochodowe (kierownica). Urządzenia tego typu w połączeniu z odpowiednim oprogramowaniem potrafią naśladować szarpnięcia pojawiające się na urządzeniach sterowniczych w prawdziwych pojazdach. Linux 2.6 obsługuje już takie gadżety.

Sieć i sieciowe systemy plików

Multicasting

Poprawiono obsługę multicastingu, czyli rozgłaszania pakietów do wybranych komputerów w sieci, poprzez dodanie nowych protokołów SSM (Source Specific Multicast).

IPsec

Ten moduł umożliwia zapewnienie szyfrowania i weryfikacji autentyczności pakietów na poziomie protokołu sieciowego.

NFS

Poprawiono skalowalność sieciowego systemu plików NFS. Teraz możlewe jest jedoczesne korzystanie udostępnionych katalogów nawet do 64 razy więcej użytkowników.

NFSv4

Wprowadzono obsługę z poziomu jądra serwera czwartej wersji systemu plików NFS. Obsługa nie jest jeszcze pełna.

CIFS

Przepisano obsługę systemu plików używanego przez nowsze Windows'y do udostępniania zasobów w sieci lokalnej. CIFS jest rozszerzeniem protokołu SMB używanego w starszych Windowsach. Nowa wersja CIFS z jądra 2.6 została rozszerzona o możliwość udostępniania typowo UNIX'owych plików takich jak np. urządzenia.

AFS i InterMezzo

Dodano ograniczoną obsługę rozproszonego systemu plików Andrew Filesystem oraz pełną obsługę InterMezzo, który umożliwia np. pracę na lokalnym cache'u bo odłączeniu od serwera.

Wirtualizacja

UML (User-Mode Linux) - prawdopodobnie dobrze znany wszystkim którzy czytają ten artykuł ;) - jest dołączony do oficjalnego jądra. Nie trzeba zatem nakładać patcha.


Get Firefox

Aktulizacja: 20 stycznia 2005
© Wojciech Rygielski, Piotr Stańczyk, Marek Żylak
Wydział Matematyki Informatyki i Mechaniki Uniwersystemu Warszawskiego