🎧 New: AI-Generated Podcasts Turn your study notes into engaging audio conversations. Learn more

SO2_egzamin+wyjasnienia.pdf

Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...

Full Transcript

1. Określ, które informacje odnośnie zarządzania procesami w Linuksie 3.0 i nowszych są prawdziwe: NIE-deskryptor procesu jest umieszczony na dnie jego stosu w jądrze systemu. TAK-makrodefinicja "current" zwraca adres deskryptora bieżącego procesu. NIE-proces w stanie TASK_UNINTERRUPTIBLE m...

1. Określ, które informacje odnośnie zarządzania procesami w Linuksie 3.0 i nowszych są prawdziwe: NIE-deskryptor procesu jest umieszczony na dnie jego stosu w jądrze systemu. TAK-makrodefinicja "current" zwraca adres deskryptora bieżącego procesu. NIE-proces w stanie TASK_UNINTERRUPTIBLE może być ustawiony w stanie gotowości przez dowolny sygnał, który otrzyma. NIE-proces zakończony ma stan TASK_STOPPED. TAK-proces, który jest wykonywany ma stan TASK_RUNNING. Wyjaśnienia: Deskryptor procesu był na dnie stosu jądra w Linuxie przed wersją 2.6. Od 2.6 w górę jest on w pliku nagłówkowym linux/sched.h Makro current zwraca adres deskryptora bieżącego procesu, czyli tego, który uaktywnił kod jądra Proces w stanie TASK_UNINTERRUPTIBLE oczekuje na zdarzenie (śpi) i może być wybudzony tylko przez sygnał z nim związany. Przez dowolny sygnał może być wybudzony proces w stanie TASK_INTERRUPTIBLE Stan TASK_STOPPED ma proces wstrzymany przez sygnał. Zakończony proces może mieć stany EXIT_ZOMBIE lub EXIT_DEAD, ale po całkowitym poprawnym zakończeniu znika po nim wszelki ślad. Stan TASK_RUNNING oznacza, że proces jest aktywny lub gotów do wykonania. Jądro nie rozróżnia tych dwóch przypadków, więc widząc ten stan nie możemy określić czy jest wykonywany. Ale jeśli wiemy, że proces jest wykonywany to znaczy, że ma stan TASK_RUNNING. 2. Określ, które informacje odnośnie zarządzania procesami w Linuksie 3.0 i nowszych są prawdziwe: TAK-proces, który jest gotów do wykonania znajduje się w stanie TASK_RUNNING. NIE-wątki w Linuksie są tworzone za pomocą innego wywołania systemowego niż procesy. TAK-proces "init" może zostać nowym rodzicem procesu, którego proces macierzysty już się zakończył. NIE-w trakcie tworzenia nowy proces otrzymuje osobny obszar tekstu i danych. TAK-deskryptory procesów są powiązane w dwukierunkową listę nazywaną listą procesów. Wyjaśnienia: Stan TASK_RUNNING oznacza, że proces jest aktywny lub gotów do wykonania. Jądro nie rozróżnia tych dwóch przypadków. Ale jeśli wiemy, że proces jest gotowy do wykonania to znaczy, że ma stan TASK_RUNNING. Linux nie posiada żadnych podsystemów, które przeznaczone byłyby wyłącznie do obsługi wątków. Wątek jest po prostu procesem użytkownika, który współdzieli większość swoich zasobów, włącznie z przestrzenią adresową, z innymi procesami-wątkami (jego rodzeństwem). Proces, którego proces macierzysty się zakończył może utknąć w stanie zombie. Wówczas jest adoptowany przez proces init (lub jego nowszy odpowiednik) lub przez proces, który należy do tej samej grupy co rodzic. Linux używa także techniki copy-on-write (COW), która pozwala rodzicowi i potomkowi współdzielić przestrzeń adresową do momentu, aż któryś z nich nie zmodyfikuje wartości dowolnej zmiennej. W takim przypadku oba procesy otrzymują osobne segmenty danych, ale dalej współdzielą segment tekstu (kodu), który jest tylko do odczytu. Deskryptory wszystkich procesów użytkownika są połączone w cykliczną listę dwukierunkową. Jej pierwszym elementem jest deskryptor procesu init (lub jeden z jego nowszych zamienników). 3. Określ, które informacje o planiście O(1) są prawdziwe: NIE-nowy kwant czasu dla procesu jest ustalany dopiero wtedy, gdy wszystkie procesy w systemie wyczerpią swoje kwanty. TAK-kolejka procesów gotowych zawiera wskaźniki na dwie tablice priorytetów. TAK-każdy procesor ma swoją kolejkę procesów gotowych. NIE-priorytet zwykłych procesów jest ustalany wyłącznie na podstawie stopnia ich interaktywności. NIE-im wyższy jest priorytet procesu, tym krótszy kwant czasu on otrzymuje. Wyjaśnienia: W Uniksie priorytety dla procesów były wyliczane na nowo dopiero po tym, kiedy wszystkie uległy przeterminowaniu, czyli nowe kwanty były wyliczane jak się skończyły dla wszystkich procesów. Linux przydziela nowy priorytet (a tym samym nowy kwant czasu) jeśli zarówno proces macierzysty, jak i potomny uległy przeterminowaniu. Poza tym Linux wylicza nowy priorytet dla procesu typu SCHED_OTHER, zaraz po tym, jak staje się on przeterminowany. Pytanie podchwytliwe, ale kluczem jest wiedza, gdzie występował planista O(1). Był używany w jądrze Linuksa od wersji 2.6.0 do 2.6.22, sam w sobie bazuje na oryginalnym planiście systemu Unix, ale nigdy nie występował w tamtym systemie. Kolejka procesów czekających na przydział procesora (gotowych do wykonania) ma wskaźniki na dwie tablice priorytetów: tablicę priorytetów aktywnych i tablicę priorytetów przeterminowanych. Planista O(1) dla każdego CPU w systemie tworzy kolejkę procesów czekających na przydział procesora (gotowych do wykonania). Zwykłe procesy otrzymują priorytety od 100 (najwyższy) do 139 (najniższy). Interaktywność wpływa na priorytet. Priorytety mało interaktywnych procesów są zmniejszane o +5, a priorytety bardziej interaktywnych podnoszone o −5. Im więcej proces oczekuje (względem faktycznego korzystania z CPU), tym bardziej jest interaktywny. Im wyższy priorytet statyczny (czyli im niższa liczba - poziom uprzejmości od -20 do 19, domyślnie 0) tym dłuższy kwant czasu otrzymuje procesor. Oprócz tego jest też priorytet dynamiczny (dodawany do statycznego, ustalany po każdej rundzie szeregowania). Tu również procesy interaktywne otrzymują dłuższe kwanty czasu. 4. Określ, które informacje o planiście O(1) są prawdziwe: NIE-każdy nowo utworzony proces otrzymuje taki sam kwant czasu, jaki ma jego rodzic. TAK-zadanie o dużym stopniu interaktywności może, po wyczerpaniu swojego kwantu czasu, ponownie trafić do tablicy procesów aktywnych. TAK-proces użytkownika może być wywłaszczony w ramach powrotu z wywołania systemowego lub procedury obsługi przerwania. TAK-implementacja funkcji przełączającej kontekst ("context_switch()") zależy od architektury platformy sprzętowej, na której działa Linux. NIE-równoważenie obciążenia procesorów następuje wyłącznie wtedy, gdy kolejka procesów gotowych któregoś z nich jest pusta. Wyjaśnienia: Potomek otrzymuje połowę kwantu czasu, który pozostał rodzicowi. Rodzicowi ustawiana jest druga połówka (zmniejszany jest jego czas), czyli mają tyle samo, ale w momencie tworzenia procesu dostaje tylko połowę. Jeśli proces ma wysoki poziom interaktywności, to po wykorzystaniu kwantu czasu nie staje się automatycznie przeterminowany, ale otrzymuje ten sam kwant czasu i trafia z powrotem do tablicy procesów aktywnych. Jednakże wykonywane jest też sprawdzanie, czy nie występuje głodzenie procesów, więc nie zawsze zostanie ponownie aktywowany. Proces może być wywłaszczony kiedy sterowanie wraca do przestrzeni użytkownika, po wykonaniu wywołania systemowego lub obsługi przerwania. Kod funkcji context_switch() i jego efektywność są zależne od architektury procesora. Zazwyczaj przełączanie kontekstu zawiera więcej czasu w przypadku procesorów RISC niż CISC, ze względu na liczbę rejestrów, których stan należy zapamiętać lub przywrócić. Równoważenie obciążenia procesorów następuje, gdy jedna z kolejek procesów gotowych (runqueues) jest pusta lub jeśli jedna z kolejek runqueues jest o 25% dłuższa niż pozostałe. 5. Określ, które informacje o planiście CFS są prawdziwe: TAK-priorytety procesów są prawie niezmienne. NIE-klasa szeregowania SCHED_BATCH jest obsługiwana za pomocą algorytmu rotacyjnego. NIE-waga procesu jest odwrotnością wartości jego priorytetu. NIE-zegar wirtualny jest wprost implementowany w jądrze Linuksa. TAK-kolejka procesów gotowych jest zrealizowana w postaci drzewa czerwono-czarnego. Wyjaśnienia: Priorytety procesów od czasu wprowadzenia mechanizmu CFS stały się prawie niezmienne. Służą one do określenia tak zwanej wagi procesu. SCHED_BATCH jest to klasa procesów niskopriorytetowych ograniczonych przez procesor, które obsługiwane są przez planistę CFS. Waga dla domyślnego priorytetu (poziom uprzejmości 0) wynosi 1024. Wagi procesów o wyższych priorytetach są wyliczone poprzez mnożenie tej wartości przez kolejne potęgi liczby 1,25. Wagi procesów o niższych priorytetach są wyliczone poprzez dzielenie tej wartości przez kolejne potęgi liczby 1,25. Zegar wirtualny, który jest jednym z elementów wyznaczających czas, przez który proces bieżący korzystał z procesora nie został wprost zaimplementowany w jądrze Linuksa. Kolejka procesów gotowych, w przypadku planisty CFS, jest zaimplementowana w postaci drzewa czerwono-czarnego. Jest to rodzaj wyważonego drzewa BST, w którym każdy węzeł ma dodatkową cechę nazywaną kolorem. 6. Określ, które informacje o planiście CFS są prawdziwe: TAK-czas fizyczny jest mierzony z dokładnością nanosekundową. TAK-dla procesów z priorytetem równym 0 czas wirtualny jest taki sam jak czas fizyczny. NIE-czas wybierania procesów przez planistę CFS jest zawsze krótszy w porównaniu z analogicznym czasem dla planisty O(1). TAK-czas wirtualny procesu zależy od jego wagi. NIE-planista przydziela procesor temu procesowi, który najdłużej z niego korzystał. Wyjaśnienia: Czas fizyczny jest mierzony z rozdzielczością nanosekundową. Dla procesów z priorytetem 0 (domyślnym) upływ czasu wirtualnego, bez uwzględnienia liczby procesów aktywnych, jest taki sam jak upływ czasu rzeczywistego. Planista CFS w porównaniu do O(1) zazwyczaj dłużej wykonuje operacje szeregowania z uwagi na konstrukcję jego kolejki procesów gotowych. Dla pojedynczego procesu wartość czasu wirtualnego jest wyliczana na podstawie faktycznego czasu jego działania, liczby procesów gotowych do działania i wagi związanej z jego priorytetem. Tak więc czas wirtualny procesu zależy od jego wagi, ale jest to tylko jeden z trzech czynników. Procesor jest przydzielany dla procesu z najkrótszym wirtualnym czasem wykonania. 7. Określ, które informacje dotyczące wywołań systemowych w Linuksie 3.0 i nowszych są prawdziwe: NIE-wszystkie funkcje z biblioteki "libc" korzystają z wywołań systemowych. TAK-wywołania systemowe mogą nie przyjmować żadnych argumentów. TAK-każde wywołanie systemowe zwraca wartość stanowiącą kod jego wykonania. NIE-zazwyczaj wywołania systemowe implementowane są w postaci funkcji napisanych w assemblerze. NIE-dodanie do jądra nowego wywołania systemowego wymaga modyfikacji biblioteki "libc", aby umożliwić procesom użytkownika korzystanie z niego. Wyjaśnienia: API Linuksa jest w większości zaimplementowane w standardowej bibliotece języka C, nazywanej w Linuksie glibc (libc w Uniksie). Niektóre z funkcji w niej dostępnych nie używają żadnych wywołań systemowych, np. strcpy(). Wywołania systemowe podobnie jak zwykłe funkcje mogą przyjmować pewną liczbę argumentów, lub nie przyjmować ich w ogóle. Każde wywołanie systemowe zwraca wartość typu long, która stanowi kod wykonania. Wywołanie systemowe implementowane jest za pomocą funkcji napisanej w języku C. W wersjach jądra od 2.6.18 wywołania systemowe uruchamia się funkcją syscall(). 8. Określ, które informacje dotyczące wywołań systemowych w Linuksie 3.0 i nowszych są prawdziwe: TAK-adresy wszystkich zarejestrowanych wywołań systemowych są przechowywane w specjalnej tablicy. NIE-kod funkcji implementującej wywołanie systemowe może być umieszczony w module. NIE-dodawanie nowych wywołań systemowych jest zalecaną przez programistów jądra Linuksa praktyką. TAK-część procesorów wymaga, aby argumenty do funkcji implementujących wywołania systemowe były przekazywane wyłącznie przez stos. TAK-funkcja implementująca wywołanie systemowe musi sprawdzać poprawność przekazanych jej argumentów. Wyjaśnienia: Adresy wszystkich wywołań systemowych przechowywane są w tablicy sys_call_table. Kod funkcji realizującej wywołanie systemowe nie może być umieszczony w module. Należy unikać dodawania nowych wywołań systemowych. Z niektórymi z problemów, których rozwiązaniem początkowo może się wydawać dodanie nowego wywołania systemowego, można sobie poradzić z pomocą podsystemu obsługi urządzeń lub systemu plików. Nazwy funkcji implementujących wywołania systemowe są poprzedzone modyfikatorem asmlinkage, celem poinformowania kompilatora, że argumenty dla tych funkcji są przekazywane wyłącznie za pomocą stosu. Nie każda architektura procesora tego wymaga, więc dla niektórych z nich ten modyfikator jest pusty. Wartości wszystkich argumentów muszą zostać zweryfikowane celem sprawdzenia, czy nie są one błędne i czy nie spowodują naruszenia ochrony. 9. Określ, które informacje dotyczące obsługi przerwań w Linuksie 3.0 i nowszych są prawdziwe: TAK-procedury obsługi przerwań w Linuksie są funkcjami napisanymi w języku C. TAK-procedury obsługi przerwań są wykonywane w kontekście przerwania. TAK-procedury obsługi przerwań są wywoływane w sposób asynchroniczny. TAK-linie zgłaszania przerwań (IRQ) mogą być współdzielone. TAK-niektóre przerwania mogą zasilać pulę entropii jądra. Wyjaśnienia: Kiedy procesor otrzymuje sygnał przerwania, to automatycznie przełącza się w tryb systemowy (o ile już w nim nie był) i wykonuje zależny od jego architektury kod jądra, napisany w assemblerze, który odkłada na stos jądra procesu wartości rejestrów i przygotowuje środowisko do wykonania funkcji napisanych w języku C. Tymi funkcjami są procedury obsługi przerwań. Procedury obsługi przerwań w Linuksie wykonywane są w kontekście przerwania. Oznacza to, że ich działanie podlega kilku ograniczeniom. Przede wszystkim muszą one działać szybko, ponieważ obsługa przerwania może wstrzymywać inne istotne operacje w jądrze lub przestrzeni użytkownika. Procedura obsługi przerwania nie jest związana z żadnym procesem, w związku z tym nie może wywołać żadnych funkcji powodujących przejście procesu w stan oczekiwania. Źródłem zdarzenia obsługiwanego przez jądro może być także urządzenia peryferyjne. Takie zdarzenia nazywane są przerwaniami i są asynchroniczne w stosunku do operacji wykonywanych przez jądro. Oznacza to, że mogą one przerwać wykonywanie istotnych czynności, zatem muszą być szybko obsłużone. Z uwagi na to, kod jądra zajmujący się tym działaniem (tak zwana procedura obsługi przerwania) jest wykonywany w kontekście przerwania, który nie jest związany z żadnym procesem użytkownika. Współczesne urządzenia, używające takich magistral jak USB, PCI, PCI–Express potrzebują dużej liczby przerwań, które są przydzielane im dynamicznie (niektóre przerwania są przydzielane statycznie z powodów historycznych). To oznacza, że wiele linii IRQ musi być współdzielone przez te urządzenia, co prowadzi do wielu problemów. Współcześnie wszystkie przerwania wnoszą wkład do puli entropii, ale nie bezpośrednio. W ramach obsługi przerwania jądro odczytuje kilka wartości z różnych zasobów, które są dobrymi źródłami losowości, jak np. niektóre rejestry CPU. 10. Określ, które informacje dotyczące obsługi przerwań w Linuksie 3.0 i nowszych są prawdziwe: NIE-większość procedur obsługi przerwań korzysta z makrodefinicji "current". NIE-stos z którego korzystają procedury obsługi przerwań ma nieograniczoną wielkość. NIE-wszystkie procedury obsługi przerwań wymagają wyłączenia wszystkich linii zgłaszania przerwań na czas ich wykonywania. NIE-w komputerach klasy PC wszystkie numery przerwań są przydzielane statycznie. NIE-czas wykonania procedur obsługi przerwań (górnych połówek) może być dowolnie długi. Wyjaśnienia: Wartość makra current nie jest przydatna dla procedur obsługi przerwań, które nie są związane z żadnym procesem, więc nie potrzebują deskryptora bieżącego procesu. Procedury obsługi przerwań używają stosu jądra procesu, tak jak inne funkcje jądra. Jego wielkość jest ograniczona do dwóch stron, więc w przypadku procesorów x86 ma on rozmiar 8KiB, a dla procesorów Alpha, 16KiB. Tylko procedury obsługi przerwań związane z górną połówką wymagają wyłączenia na czas ich wykonywania linii przerwań (IRQ) z nimi związanej, a niekiedy całego systemu przerwań. Współczesne urządzenia, używające takich magistral jak USB, PCI, PCI–Express potrzebują dużej liczby przerwań, które są przydzielane im dynamicznie (niektóre przerwania są przydzielane statycznie z powodów historycznych). Górną połówkę stanowią procedury obsługi przerwań, które muszą działać szybko, ponieważ linia IRQ z nimi związana, a niekiedy nawet cały system przerwań są wyłączone w trakcie ich wykonywania. Zazwyczaj wykonują one tylko niezbędne prace związane z obsługą przerwania, a pozostałe czynności są odraczane i wykonywane w dolnej połówce. 11. Określ, które informacje dotyczące dolnych połówek w Linuksie 3.0 i nowszych są prawdziwe: TAK-całkowita liczba przerwań programowych jest ograniczona. NIE-przerwania programowe są wykonywane w kontekście procesu. NIE-tasklety są wykonywane w kontekście procesu. NIE-czynności z kolejek prac są wykonywane w kontekście przerwania. NIE-można precyzyjne określić czas, po którym przerwanie programowe powinno być wykonane. Wyjaśnienia: Przerwania programowe są statycznie deklarowane, co oznacza, że nie mogą być używane w modułach jądra. Ich liczba jest ograniczona do 32. Wszystkie przerwania programowe są wykonywane w kontekście przerwania. Tasklety są dolnymi połówkami wykonywanymi w kontekście przerwania i dostępnymi z poziomu modułu jądra. Kolejki prac są implementacją dolnych połówek działającą w kontekście procesu. Niektóre mechanizmy dolnych połówek pozwalają określić po jakim czasie powinny być wykonane zlecone im czynności, ale nie gwarantują dokładności. 12. Określ, które informacje dotyczące dolnych połówek w Linuksie 3.0 i nowszych są prawdziwe: TAK-przerwania programowe są alokowane statycznie podczas kompilacji jądra. TAK-można określić minimalny czas o jaki zostanie opóźnione wykonanie czynności w kolejkach prac. NIE-te same tasklety mogą być wykonywane współbieżnie na platformach wieloprocesorowych. NIE-wątek "ksoftirqd" odpowiedzialny jest zarówno za obsługę przerwań programowych, jak i kolejek prac. TAK-istnieje możliwość stworzenia kolejki prac, która będzie obsługiwana na platformach wieloprocesorowych przez pojedynczy wątek roboczy. Wyjaśnienia: Przerwania programowe są alokowane statycznie podczas kompilacji jądra i wykorzystywane dosyć rzadko. Kolejki prac pozwalają określić po jakim czasie powinno rozpocząć się ich wykonanie, ale gwarantują jedynie, że opóźnione prace nie rozpoczną się przed upływem tego czasu, a nie że zostaną wykonane natychmiast po jego upływie. W systemie wieloprocesorowymi tylko jedna instancja danego taskletu może być wykonywana w tym samym czasie, ale tyle różnych taskletów, ile jest procesorów może być wykonywanych równolegle. Przerwania programowe i tasklety, które są powtarzane z dużą częstotliwością lub same się reaktywują stanową dla systemu operacyjnego problem. Mogą generować zbyt duże obciążenie dla procesorów. Aby złagodzić ten problem są one oddawane pod kontrolę wątkowi jądra ksoftirqd. Każdy procesor wykonuje jedną instancję takiego wątku, który wykonuje się z najniższym możliwym priorytetem. Kiedy jest on wybudzany, to sprawdza, czy są jakieś przerwania programowe i tasklety oczekujące na wykonanie. Dla każdej utworzonej kolejki tworzony jest również osobny wątek roboczy na każdym procesorze. Ponieważ takie rozwiązanie jest kosztowne, to stworzono makro create_singlethread_workqueue tworzące kolejkę prac obsługiwaną przez pojedynczy wątek roboczy, działający na pierwszym procesorze w komputerze. 13. Określ, które informacje dotyczące środków synchronizacji w Linuksie 3.0 i nowszych są prawdziwe: NIE-funkcje realizujące operacje niepodzielne na liczbach całkowitych działają na zmiennych typu "int". NIE-niepodzielne operacje na bitach przeprowadzane są za pomocą tych samych funkcji co niepodzielne operacje na liczbach całkowitych. TAK-rygle pętlowe wprowadzają wątki oczekujące na ich zwolnienie w stan aktywnego oczekiwania. NIE-zwykły rygiel pętlowy może być przetrzymywany przez kilka wątków jednocześnie. TAK-rygle pętlowe nie są używane w systemach jednoprocesorowych. Wyjaśnienia: Aby ujednolicić i zapewnić atomowe operacje dla wszystkich obsługiwanych procesorów, programiści jądra Linuksa stworzyli typ abstrakcyjny atomic_t. Zmienne tego typu przechowują liczby całkowite, zamiast int. Jądro dostarcza osobnych funkcji i makrodefinicji realizujących niepodzielne operacje na pojedynczych bitach. Makra i funkcje, które implementują niepodzielne operacje na bitach nie wymagają specjalnego typu danych. Dany rygiel może przetrzymywać tylko jeden wątek wykonania (zasada wzajemnego wykluczania), natomiast inne wątki chcące skorzystać z chronionego zasobu wykonują aktywne oczekiwanie, czyli w pętli oczekują, aż rygiel zostanie zwolniony i jeden z nich będzie mógł uzyskać dostęp do zasobu. Jeśli wątek wykonania próbuje zająć zwykły rygiel pętlowy, który został wcześniej uzyskany przez inny wątek, to będzie musiał w pętli sprawdzać, czy ten rygiel został już zwolniony. Są również rygle pętlowe R-W (ang. Reader–Writer Spin Locks lub R–W Spin Locks). Służą do rozwiązywania pierwszego problemu czytelników i pisarzy, w którym czytelnicy mają priorytet. Ich API ma osobne funkcje i makra dla wątków-pisarzy i dla wątków-czytelników. Rygiel pętlowy R-W może być zajęty przez więcej niż jednego czytelnika lub nawet kilkukrotnie przez tego samego czytelnika (w tym wypadku rygiel R-W jest rekurencyjny). Mimo, że rygle pętlowe są najczęściej używanym rodzajem blokad w kodzie źródłowym jądra Linuksa, to w jego wersji skompilowanej występują tylko dla systemów wieloprocesorowych. 14. Określ, które informacje dotyczące środków synchronizacji w Linuksie 3.0 i nowszych są prawdziwe: TAK-rygiel pętlowy czytelnika może być jednocześnie przetrzymywany przez więcej niż jeden wątek wykonania. NIE-semafory mogą być stosowane w procedurach obsługi przerwań. TAK-semafor nie może być przetrzymywany przez wątek, który już przetrzymuje rygiel pętlowy. TAK-stosowanie blokady BKL nie jest zalecane. NIE-mechanizm RCU niszczy oryginalną informację w momencie, kiedy wątek-pisarz opublikuje wskaźnik na jej zmodyfikowaną kopię. Wyjaśnienia: Rygiel pętlowy R-W dla czytelników może być przetrzymywany przez większą liczbę wątków wykonania tego typu (a nawet może być rekurencyjnie zakładany przez jeden wątek wykonania tego typu), rygiel dla pisarzy – tylko przez jeden wątek wykonania tego typu. Semafory są używane tylko w kontekście procesu, kiedy czas oczekiwania na ich podniesienie jest dużo dłuższy niż czas przełączania kontekstu. W przeciwieństwie do rygla pętlowego, semafor usypia wątek wykonania, który próbuje go zająć, jeśli jest on już zajęty. Ten wątek jest także dodawany do kolejki oczekiwania związanej z tym semaforem. Aby uniknąć zakleszczenia, wątek, który uzyskał rygiel pętlowy, nie może próbować uzyskać semafora. Blokada BKL (ang. Big Kernel Lock - BKL) została dodana do jadra w serii 2.2, miała być rozwiązaniem przejściowym, które miało być zastąpione blokadami o mniejszej ziarnistości. W wersji 2.6.39 jądra programistom udało się ostatecznie pozbyć BKL i w kolejnych wersjach jądra nie jest już obecna. Jeśli pisarz chce zmodyfikować zasób, to robi jego kopię, zmienia ją i publikuje do niej wskaźnik. Jeśli dowolny czytelnik spróbuje po tym uzyskać dostęp do wspólnego zasobu, to otrzyma wskaźnik do tej jego kopii. Oryginał zasobu jest niszczony, kiedy przestanie z niego korzystać ostatni czytelnik, który miał do niego dostęp. 15. Określ, które informacje dotyczące pomiaru i synchronizacji względem czasu w Linuksie 3.0 i nowszych są prawdziwe: NIE-zegar czasu rzeczywistego (RTC) jest okresowo odczytywany przez jądro systemu. TAK-liczbę taktów zegara od uruchomienia systemu przechowuje zmienna "jiffies". TAK-częstotliwość zegara systemowego jest określona stałą "HZ". NIE-implementacja procedury obsługi przerwania zegarowego jest całkowicie niezależna od sprzętu. NIE-wartość stałej "HZ" jest taka sama dla wszystkich platform sprzętowych. Wyjaśnienia: Zwykle informacja o bieżącym czasie jest dostarczana przez urządzenie sprzętowe, które jest odczytywane przez jądro w trakcie startu systemu, a potem tylko aktualizowana przez procedurę obsługi przerwania zegarowego. Liczba przerwań zegarowych wygenerowanych od uruchomienia systemu komputerowego jest przechowywana w 64-bitowej zmiennej jiffies. Częstotliwość zegara systemowego jest określana przez stałą HZ (hertz). W przypadku platform, które wymagają oszczędności energii, jak systemy wbudowane i laptopy, jądro może zostać skonfigurowane w taki sposób, aby ignorowało stałą HZ i generowało zdarzenia czasowe tylko wtedy, gdy są one potrzebne. Wartość stałej HZ w przypadku większości platform obsługiwanych przez Linuksa wynosi 100. Do wyjątków zaliczają się komputery bazujące na procesorach x86. W ich przypadku ta stała także miała wartość 100 (okres 10ms), ale w serii 2.4 jądra zmieniono ją na 1000 (okres 1ms), aby zaspokoić potrzeby multimedialnego oprogramowania użytkowego. Niestety, spowodowała ona także wzrost obciążenia CPU obsługą większej liczby przerwań zegarowych. Dodatkowo, opisywana zmiana spowodowała problemy z obsługą protokołu NTP (ang. Network Time Protocol). Ostatecznie wartość stałej HZ dla komputerów z procesorami x86 została ustalona na 250 (okres 4ms). Wymogi aplikacji multimedialnych zostały spełnione przy użyciu liczników czasu wysokiej rozdzielczości. 16. Określ, które informacje dotyczące pomiaru i synchronizacji względem czasu w Linuksie 3.0 i nowszych są prawdziwe: NIE-liczniki niskiej rozdzielczości są cykliczne. TAK-mechanizm liczników wysokiej rozdzielczości korzysta z drzewa czerwono-czarnego. TAK -liczniki wysokiej rozdzielczości mogą być cykliczne. TAK-funkcje związane z licznikami niskiej rozdzielczości wykonywane są w kontekście przerwania. NIE-jeśli platforma sprzętowa nie dostarcza zegarów o nanosekundowej precyzji, to mechanizm liczników wysokiej rozdzielczości nie jest dla niej w ogóle dostępny. Wyjaśnienia: Liczniki czasu niskiej rozdzielczości nie są cykliczne, po tym jak skończą odliczać nie są automatycznie odnawiane. Licznik czasu wysokiej rozdzielczości jest reprezentowany przez strukturę typu struct hrtimer. W ramach aktywacji jest on jednocześnie dodawany do drzewa czerwono-czarnego i listy. Lista jest przeglądana sekwencyjnie i posortowana według czasu uruchomienia liczników z pomocą drzewa czerwono-czarnego. W strukturze struct hrtimer jest między innymi pole function, które przechowuje adres funkcji licznika, która implementuje czynności wykonywane po jego uruchomieniu. Prototyp tej funkcji jest następujący: enum hrtimer_restart my_hrtimer(struct hrtimer *); Nazwa funkcji nie musi być taka sama, jak ta użyta w prototypie. Wyliczenie, które definiuje typ wartości zwracanej przez tę funkcję ma dwa elementy: HRTIMER_NORESTART, który oznacza, że licznik nie będzie automatycznie odnowiony i HRTIMER_RESTART, który oznacza, że licznik będzie cykliczny. Liczniki niskiej rozdzielczości są powiązane w listę. Funkcje przez nie wywoływane są uruchamiane w kontekście dolnej połówki, z poziomu przerwania programowego (czyli w kontekście przerwania), po zakończeniu obsługi przerwania zegarowego. Liczniki wysokiej rozdzielczości są dostępne, jeśli ich obsługa została dodana do jądra w trakcie kompilacji, a sprzęt dostarcza co najmniej dwóch zegarów, które one mogą używać. Jeśli ten drugi wymóg nie jest spełniony to API tych liczników jest dostępne, ale działają one tak samo jak liczniki niskiej rozdzielczości. Wymagane zegary to zegar monotoniczny i rzeczywistego czasu. Oba oferują rozdzielczość nanosekundową, ale wartość pierwszego jest zawsze zwiększana w określonych momentach, a wartość drugiego może w pewnych przypadkach być zmniejszana i jądro musi kompensować te zmiany. 17. Określ, które informacje dotyczące zarządzania pamięcią w Linuksie 3.0 i nowszych są prawdziwe: TAK-Linux obsługuje systemy wieloprocesorowe o organizacji NUMA. NIE-domyślnie Linux przydziela pamięć ze strefy DMA (ZONE_DMA). NIE-podstawowym mechanizmem sprzętowym wykorzystywanym przez Linuksa do obsługi pamięci jest segmentacja. TAK-nie we wszystkich platformach sprzętowych musi występować strefa pamięci wysokiej (ZONE_HIGHMEM). NIE-niskopoziomowy mechanizm obsługi pamięci umożliwia przydzielenie obszaru pamięci o wielkości jednego bajta. Wyjaśnienia: Linux obsługuje systemy wieloprocesorowe bazujące na architekturach NUMA i SMP. Jądro, jeśli nie jest to określone w wywołaniu alokatora, przydziela domyślnie pamięć ze strefy ZONE_NORMAL, chyba że nie ma tam już wolnych stron. Wówczas strony są przydzielane z dowolnej z pozostałych stref. Strefa ZONE_DMA zawiera strony używane do transmisji DMA przez urządzenia bazujące na magistrali ISA, które mogą korzystać jedynie z pierwszych 16MiB RAM (ta magistrala jest 24-bitowa). Dodatkowo ten obszar pamięci musi być fizycznie ciągły, bo te urządzenia nie używają pamięci wirtualnej. Strefa ta istnieje tylko z przyczyn historycznych. Główna gałąź jądra używa stronicowania jako podstawowego systemu zarządzania pamięcią, bo jest ono dostępne na większości przez nie obsługiwanych platform sprzętowych. ZONE_HIGHMEM – strefa grupująca strony w wysokiej pamięci (dla 32­bitowych procesorów rodziny x86 jest to pamięć fizyczna powyżej 896MB, na innych może nie występować). Platformy 64-bitowe zazwyczaj używają stref ZONE_DMA, ZONE_NORMAL i ZONE_DMA32. Współcześnie nie ma potrzeby stosowania strefy ZONE_HIGHMEM w takich platformach sprzętowych. Jądro Linuksa ma niskopoziomowy alokator pamięci, nazywany alokatorem strefowym, przydzielający pamięć fizycznie ciągłymi obszarami, których rozmiar stanowi wielokrotność rozmiaru strony wyrażoną potęgą dwójki. Rozmiar strony zazwyczaj ma 4096 bajtów. 18. Określ, które informacje dotyczące zarządzania pamięcią w Linuksie 3.0 i nowszych są prawdziwe: TAK-niskopoziomowy mechanizm obsługi pamięci działa w oparciu o algorytm bliźniaków. TAK-adresowanie stron w Linuksie jest domyślnie czteropoziomowe. NIE-alokator plastrowy wykorzystuje do własnych celów dedykowane pamięci podręczne. TAK-alokator plastrowy jest rozwiązaniem zapożyczonym z systemu operacyjnego firmy Sun Microsystems. TAK-alokator plastrowy przydziela pamięć na struktury często alokowane i zwalniane przez jądro systemu. Wyjaśnienia: Jądro Linuksa ma niskopoziomowy alokator pamięci, nazywany alokatorem sterfowym, przydzielający pamięć fizycznie ciągłymi obszarami, które są potrzebne niektórym urządzeniom w transmisjach DMA i pomagają zredukować częstotliwość aktualizacji rejestrów TLB. Ten alokator używa algorytmu bliźniaków (ang. buddy memory system). Jądro używa wielopoziomowej tablicy stron. Odkąd upowszechniły się 64-bitowe platformy sprzętowe, ta tablica ma 4 poziomy: Katalog Główny, Katalog Górny, Katalog Pośredni, i Tablicę Stron. W przypadku niektórych platform sprzętowych, jak te oparte na 32-bitowych procesorach x86, Katalog Górny i Katalog Pośredni mają tylko jedną pozycję. W Linuksie alokator plastrowy tworzy pamięci podręczne (bufory) dwóch rodzajów: ogólne i dedykowane. Z pamięci ogólnych korzysta on sam, z dedykowanych – pozostałe części jądra. Alokator plastrowy został wynaleziony przez Jeffa Bonwicka, pracownika firmy SUN Microsystem. To rozwiązanie zostało po raz pierwszy zaimplementowane w systemie operacyjnym SunOS 5.4, a jakiś czas później zaadaptowane w jądrze Linuksa. Jądro często przydziela i zwalnia pamięć na różne struktury danych, które potrzebne są do jego funkcjonowania. Takie operacje są czasochłonne. Koszt ten może być zredukowany poprzez użycie buforów takich struktur, tworzonych w czasie uruchamiania systemu. Jeśli określona struktura jest potrzebna, to jądro może ją pobrać z takiego bufora i zwrócić ją, kiedy nie będzie potrzebna. Na tym pomyśle bazuje alokator plastrowy. 19. Określ, które informacje dotyczące wirtualnego systemu plików (VFS) w Linuksie 3.0 i nowszych są prawdziwe: NIE-VFS napisany jest w języku C++. TAK-obiekt superbloku może być stosowany w obsłudze systemów plików, które nie mają fizycznej implementacji. NIE-obiekty i-węzłów związane są wyłącznie z fizycznymi plikami. TAK-niektóre nieuniksowe systemy plików nie posiadają wszystkich informacji, które muszą być umieszczone w obiekcie i-węzła. NIE-obiekty wpisów katalogowych mają swoje odpowiedniki na nośniku danych. Wyjaśnienia: VFS jest w całości zaimplementowany w języku C, ale oparty jest na modelu obiektowym. Wszystkie dane o zamontowanym systemie plików są przechowywane w obiekcie superbloku. Zazwyczaj odpowiadają one informacjom umieszczonym w superbloku systemu plików w urządzeniu pamięci masowej. Istnieją jednak systemy plików utrzymywane całkowicie w RAM, jak sysfs i procfs, które nie mają fizycznego superbloku. W ich przypadku zawartość obiektu superbloku jest całkowicie wygenerowana. Obiekty i-węzłów mogą być związane nie tylko z fizycznymi plikami, ale również z plikami specjalnymi, np. plikami urządzeń lub kolejek FIFO. W systemach plików kompatybilnych z Uniksem obiekty i-węzłów reprezentują bloki i-węzłów, ale w przypadku innych systemów dane dla tych obiektów są pobierane bezpośrednio z plików lub innych miejsc na nośniku. Są również systemy plików, które nie mają wszystkich danych wymaganych przez obiekty i-węzłów. Wtedy używane są wartości domyślne. Obiekty wpisów katalogowych (dentry) są związane z każdą nazwą, która pojawia się w ścieżce. Obiekty dentry reprezentują również nazwy plików znajdujące się na końcach niektórych ścieżek i punkty montowania, które mogą występować w ścieżkach. Te obiekty nie mają swoich odpowiedników na nośniku. Są one tworzone na bieżąco, podczas analizy ścieżek i niezbędne do przeprowadzania operacji specyficznych dla katalogów, takich jak poruszanie się po drzewie katalogów. 20. Określ, które informacje dotyczące wirtualnego systemu plików (VFS) w Linuksie 3.0 i nowszych są prawdziwe: NIE-obiekt wpisu katalogowego, który nie jest w użyciu, ale któremu odpowiada prawidłowy i-węzeł, jest w stanie ujemnym. TAK-obiekty wpisu katalogowego, które są w stanie ujemnym, nie są niszczone, jeśli nie zachodzi taka potrzeba. NIE-obiekty plików związane są ze wszystkimi plikami zapisanymi w systemie plików. TAK-pamięć na obiekty wpisów katalogowych jest przydzielana i zwalniana przez alokator plastrowy. TAK-dla każdego zamontowanego systemu plików tworzona jest zmienna opisująca jego punkt montowania. Wyjaśnienia: Obiekt dentry w stanie „nieużywany” jest związany z prawidłowym obiektem i-węzła, ale przez dłuższy czas nie był używany. Jądro nie usuwa takiego obiektu, chyba, że brakuje wolnej pamięci. Utrzymuje takie obiekty dentry, bo mogą być przydatne w przyszłości. Obiekt wpisu katalogowego w stanie „ujemny” nie jest powiązany z prawidłowym obiektem i-węzła. Oznacza to, że związany jest z plikiem lub katalogiem, który został usunięty lub nigdy nie istniał. Jądro nie usuwa obiektu w stanie „ujemny” bez powodu. Te obiekty mogą być użyteczne, jeśli ścieżka zawierająca pozycje z nimi związane będzie analizowana. Wtedy mogą zapobiec przetwarzaniu przez jądro nieprawidłowych ścieżek, które już były analizowane. Obiekt pliku wskazuje na obiekt dentry, który z kolei wskazuje na obiekt i-węzła związany z plikiem otwartym przez proces użytkownika. Z pojedynczym plikiem może być powiązanych wiele obiektów plików, zależnie od tego ile razy został on otwarty przez procesy użytkownika. Obiekty wpisów katalogowych są tworzone i usuwane przez alokator plastrowy. Kiedy system plików jest montowany, jądro tworzy strukturę typu struct vfsmount, która przechowuje dane o punkcie montowania, w tym flagi określające jakie operacje mogą być przeprowadzane na tym systemie plików. 21. Określ, które informacje dotyczące obsługi urządzeń znakowych i blokowych w Linuksie 3.0 i nowszych są prawdziwe: NIE-urządzenia obsługiwane przez Linuksa są wyłącznie urządzeniami fizycznymi. NIE-w komputerach klasy PC kontroler jest zawsze częścią struktury sprzętowej łączącej szynę wejścia- wyjścia z urządzeniem. TAK-rejestr może pełnić więcej niż jedną funkcję. TAK-numer główny identyfikuje sterownik obsługujący urządzenie lub grupę urządzeń. TAK-urządzenia znakowe adresują dane sekwencyjnie. Wyjaśnienia: Jednym z zadań Wirtualnego Systemu Plików jest obsługa urządzeń wejścia-wyjścia. Słowo „urządzenie” w tym kontekście nie musi koniecznie oznaczać fizyczny sprzęt. Może to być także wirtualne urządzenie nazywane także pseudo-urządzeniem. Każde urządzenie jest połączone z komputerem poprzez szyny wejścia-wyjścia mające trzy składowe się: magistralę danych, sterującą i adresową. Procesory rodziny Pentium wykorzystują 16 lub 32 linie szyny adresowej i 8, 16, 32 lub 64 linie magistrali danych. Szyna nie jest bezpośrednio połączona z urządzeniem, ale poprzez odpowiednią strukturę sprzętową składającą się z maksymalnie trzech komponentów: portów wejścia-wyjścia, interfejsu i/lub kontrolera. Niekiedy pojedynczy rejestr może pełnić dwie funkcje, jak ma to miejsce w przypadku klawiatury, gdzie rejestr stanu jest jednocześnie rejestrem sterującym, a rejestr wejściowy pełni rolę rejestru wyjściowego. Numer główny identyfikuje sterownik odpowiedzialny w jądrze za obsługę rodziny urządzeń (np. drukarek). Numer poboczny określa konkretne urządzenie obsługiwane przez ten sterownik. Urządzenia znakowe adresują dane (zazwyczaj) sekwencyjnie i mogą je przesyłać względnie małymi porcjami, np. o rozmiarze kilku bajtów. Rozmiar ten może być inny dla każdej transmisji. Przykładem takich urządzeń są mysz i klawiatura. 22. Określ, które informacje dotyczące obsługi urządzeń znakowych i blokowych w Linuksie 3.0 i nowszych są prawdziwe: TAK-sterownik urządzenia może być dołączony do systemu w postaci modułu. TAK-sterowniki urządzeń znakowych korzystają z niektórych struktur związanych z wirtualnym systemem plików (VFS). NIE-programista piszący sterownik urządzenia znakowego musi oprogramować metody obiektu i-węzła. TAK-metody obsługujące urządzenia znakowe muszą działać według określonego protokołu. NIE-sterowniki wszystkich urządzeń blokowych muszą tworzyć kolejki żądań. Wyjaśnienia: Sterowniki mogą być umieszczone na stałe w jądrze lub dołączane w postaci modułów. Sterowniki urządzeń znakowych używają trzech struktur danych VFS: obiektu pliku, tablicy metod pliku i obiektu i-węzła. Zazwyczaj programiści piszący sterowniki urządzeń implementują cztery metody: open(), read(), write() i release(), mimo, że zdefiniowanie ich wszystkich nie jest konieczne. Jeśli urządzenie wymaga specyficznych operacji, które nie mogą być zrealizowane przez te metody, to wówczas należy zaimplementować jedną z metod ioctl(). Inne metody nie muszą być definiowane. Metody obsługujące urządzenia powinny działać według określonego protokołu. Jeśli sterownik obsługuje urządzenie, które w przeciwieństwie do dysków twardych oferuje prawdziwie bezpośredni dostęp do danych (np. pendrive), to kolejka żądań jest zbędna. 23. Określ, które informacje dotyczące warstwy operacji blokowych w Linuksie 3.0 i nowszych są prawdziwe: NIE-wielkość bloku w Linuksie jest nieograniczona. TAK-każdy bufor zawsze jest związany z blokiem na nośniku danych. NIE-bufory są wykorzystywane wyłącznie do przechowywania danych odczytanych z nośnika urządzenia blokowego. TAK-każdy z buforów wyposażony jest w nagłówek zawierający dane niezbędne do zarządzania nim. TAK-nagłówek bufora nie przechowuje informacji o operacjach wejścia-wyjścia z jakimi ten bufor jest związany. Wyjaśnienia: Urządzenia blokowe przechowują dane w sektorach, które najczęściej mają wielkość 512 bajtów (choć nie jest to regułą). Sektor jest równocześnie najmniejszą jednostką danych urządzenia blokowego, którą można zaadresować. Pojedyncza operacja wejścia-wyjścia może obejmować jeden lub większą liczbę sektorów. Większość systemów operacyjnych nie posługuje się bezpośrednio sektorami, ale łączy je w zazwyczaj większe jednostki zwane blokami. Rozmiar bloku jest parzystą wielokrotnością rozmiaru sektora. W systemie Linux przyjęto, celem uproszczenia kodu jądra, że bloki będą miały wielkość mniejszą lub równą jednej stronie. Bloki na dane pochodzące z odczytu lub zawierające dane do zapisu na urządzeniu blokowym są umieszczone w pamięci operacyjnej, w buforach. Bloki na dane pochodzące z odczytu lub zawierające dane do zapisu na urządzeniu blokowym są umieszczone w pamięci operacyjnej, w buforach. Każdy z buforów wyposażony jest w nagłówek, określony strukturą typu struct buffer_head, przechowujący dane niezbędne do prawidłowego zarządzania buforem. Nagłówek bufora w wersjach jądra systemu wcześniejszych niż 2.6 przechowywał również informacje dotyczące operacji I/O, w których uczestniczył bufor. Taka sytuacja powodowała niską efektywność tych operacji, gdyż pojedynczy zapis lub odczyt z urządzenia wymagał posłużenia się kilkoma nagłówkami. Dodatkowo rozmiar nagłówka był porównywalny z rozmiarem bufora, który opisywał. W nowszych wersjach jądra postanowiono więc „odchudzić” nagłówek bufora i stworzyć nową strukturę, o nazwie BIO, która osobno przechowuje dane związane z operacjami wejścia-wyjścia. 24. Określ, które informacje dotyczące warstwy operacji blokowych w Linuksie 3.0 i nowszych są prawdziwe: TAK-struktury "bio" reprezentują operacje wejścia-wyjścia podczas ich trwania. NIE-wielkość segmentu jest zawsze równa wielkości bufora. TAK-struktury "bio" ułatwiają realizację operacji wejścia-wyjścia o rozproszonym źródle. NIE-planista terminowy przydziela domyślnie dłuższy termin realizacji operacji odczytu niż operacji zapisu. NIE-planista CFQ nie stosuje przewidywania. Wyjaśnienia: Struktura bio przechowuje dane związane z operacjami wejścia-wyjścia. Reprezentuje ona takie operacje w trakcie ich trwania za pomocą listy segmentów. Segment jest definiowany jako ciągły fragment bufora. Bufory, których segmenty są zgromadzone na liście nie muszą tworzyć ciągłego obszaru w pamięci operacyjnej. Dzięki strukturom bio ułatwiona jest realizacja operacji wejścia-wyjścia, w których dane pochodzą z wielu rozłącznych stron pamięci (tzw. operacje z rozproszonym źródłem). Planista terminowy przydziela każdemu zleceniu termin realizacji. Domyślnie wynosi on 500ms dla operacji odczytu i 5s dla operacji zapisu. Działanie planisty CFQ można krótko scharakteryzować jako połączenie planowania z użyciem kolejek wielopoziomowych, algorytmu rotacyjnego i przewidywania. Ten planista wprowadza również nową cechę procesów użytkownika: priorytet wejścia-wyjścia. 25. Określ, które informacje dotyczące obsługi przestrzeni adresowej procesu w Linuksie 3.0 i nowszych są prawdziwe: NIE-wszystkie wątki jądra korzystają z jednego, wspólnego dla nich deskryptora pamięci. TAK-obiekty zawierające informacje o obszarach pamięci są umieszczone jednocześnie w dwóch różnych strukturach danych. TAK-sekcje tekstu tworzone są nie tylko dla procesów, ale również dla bibliotek współdzielonych. TAK-deskryptor pamięci przechowuje adres startowy i końcowy obszaru argumentów wywołania programu. NIE-deskryptory pamięci nigdy nie są współdzielone przez procesy (wątki) użytkownika. Wyjaśnienia: Informacje na temat przestrzeni adresowej pojedynczego procesu przechowuje jego deskryptor pamięci. Wątki jądra nie mają własnej przestrzeni adresowej i własnego deskryptora pamięci. Jednak również muszą odwoływać się do pamięci operacyjnej, aby móc wykonywać swoje zadania, dlatego też korzystają z deskryptorów pamięci poprzednio zaszeregowanych procesów użytkownika. Dwa pola deskryptora pamięci są związane z osobnymi strukturami danych, które przechowują tę samą informację, ale w różny sposób. Pierwszym z nich jest pole mmap przechowujące adres listy zawierającej dane o wszystkich obszarach pamięci. Drugim jest pole mm_rb przechowujące adres korzenia drzewa czerwono-czarnego, które zawiera te same dane co lista, ale oferuje krótszy czas ich wyszukiwania, niż lista. Za to listę prościej jest przeglądać sekwencyjnie. Sekcje tekstu i sekcje danych tylko do odczytu mogą być współdzielone zarówno przez procesy, jak i biblioteki współdzielone. Deskryptor pamięci zawiera wiele pól, między innymi pola przechowujące początkowe i końcowe adresy sekcji tekstu, danych, stosu, obszaru pamięci przechowującego argumenty wiersza poleceń i obszaru pamięci przechowującego zmienne środowiskowe. Wątki przestrzeni jądra, lub po prostu wątki jądra, nie mają swojej własnej przestrzeni adresowej, współdzielą ją z jądrem. W związku z tym nie mają również deskryptorów pamięci. Wartość pola mm w ich deskryptorach procesów wynosi null. Jednakże, aby się mogły wykonywać, wątki jądra muszą odwoływać się do pamięci. W tym celu korzystają z deskryptora pamięci procesu użytkownika, który korzystał z CPU tuż przed nimi. 26. Określ, które informacje dotyczące obsługi sieci w Linuksie 3.0 i nowszych są prawdziwe: TAK-jądro wykonuje czynności związane z obsługą warstwy łącza, sieci i transportowej modelu ISO/OSI. TAK-NAPI nigdy nie pozwala na sygnalizowanie odbioru pakietu za pomocą przerwania. NIE-przenoszenie bufora pakietu między kolejkami jest czasochłonne. TAK-funkcje związane z filtrem sieciowym mogą "wykradać" niektóre pakiety, aby przetworzyć je w inny sposób niż pozostałe. NIE-w filtrze sieciowym, z pojedynczym uchwytem nie może być skojarzona więcej niż jedna funkcja przetwarzająca. Wyjaśnienia: Podsystem jądra Linuksa odpowiedzialny za komunikację sieciową składa się z trzech części, które odpowiadają trzem warstwom modelu ISO/OSI — warstwie łącza danych, warstwie sieciowej i warstwie transportowej. We wczesnych wersjach sterowników urządzeń sieciowych odebranie każdego pakietu było sygnalizowane przerwaniem. Prowadziło to do dużego obciążenia systemu w przypadku dużego ruchu sieciowego. Dlatego w wersjach 2.5/2.6 jądra wprowadzono nowe API dla sterowników takich urządzeń, które określono mianem NAPI (New API). Umożliwia ono przełączenie urządzenia w tryb przeglądania (ang. polling), co pozwala mu zakumulować większą liczbę pakietów, które w późniejszym terminie zostaną przetworzone przez jądro. Dzięki temu spada liczba generowanych przez nie przerwań i tym samym obciążenie systemu. NAPI nie pozwala na sygnalizowanie odbioru pakietu za pomocą przerwania. Bufor pakietu jest tak zaprojektowany, aby mógł być efektywnie przenoszony między kolejkami. Jeśli zachodzi potrzeba skopiowania go, to wystarczy tylko powielić jego nagłówek, który ma trzy pola wskazujące na prywatne nagłówki przechowujące metadane związane z trzema warstwami modelu ISO/OSI. Funkcja NF_STOLEN „wykrada” pakiet, co oznacza, że będzie on przetwarzany w inny sposób niż pozostałe pakiety. Pole list struktury nf_hook_ops służy do łączenia takich struktur w listę, co umożliwia skojarzenie z jednym uchwytem kilku funkcji przetwarzających. 27. Które z poniższych zdań dotyczących algorytmu szeregowania O(1) są prawdziwe? TAK-Wymiana priorytetów zadań sprowadza się do zamiany wskaźników na tablice aktywną i przeterminowaną. NIE-Priorytet każdego zadania jest ustalany wyłącznie na podstawie jego interaktywności. TAK-Promowane są zadania o wysokim stopniu interaktywności. NIE-Mechanizm szeregowania dokonuje zrównoważenia obciążenia procesorów wyłącznie wtedy, kiedy kolejka zadań jednego z nich jest pusta. NIE -Kwanty czasu dla poszczególnych zadań są przeliczane dopiero wówczas, gdy ostatnie z zadań znajdujących się w tablicy priorytetów aktywnych wyczerpie swój kwant czasu. Wyjaśnienia: Po pewnym czasie tablica priorytetów aktywnych staje się pusta, a wszystkie procesy znajdują się w tablicy priorytetów przeterminowanych. Jądro dokonuje zamiany ich ról w efektywny sposób, zamieniając miejscami adresy we wskaźnikach do tych tablic. Priorytet każdego zwykłego zadania jest ustalany na podstawie priorytetu statycznego, jakim jest poziom uprzejmości oraz na podstawie stopnia interaktywności procesu. Interaktywność wpływa na priorytet. Priorytety mało interaktywnych procesów są zmniejszane o +5, a priorytety bardziej interaktywnych podnoszone o −5. Im więcej proces oczekuje (względem faktycznego korzystania z CPU), tym bardziej jest interaktywny. Zrównoważenie obciążenia procesorów następuje, jeśli jedna z kolejek zadań (runqueues) jest o 25% dłuższa niż pozostałe. W Uniksie priorytety dla procesów były wyliczane na nowo dopiero po tym, kiedy wszystkie uległy przeterminowaniu, czyli nowe kwanty były wyliczane jak się skończyły dla wszystkich procesów. Linux przydziela nowy priorytet (a tym samym nowy kwant czasu) jeśli zarówno proces macierzysty, jak i potomny uległy przeterminowaniu. Poza tym Linux wylicza nowy priorytet dla procesu typu SCHED_OTHER, zaraz po tym, jak staje się on przeterminowany. Pytanie podchwytliwe, ale kluczem jest wiedza, gdzie występował planista O(1). Był używany w jądrze Linuksa od wersji 2.6.0 do 2.6.22, sam w sobie bazuje na oryginalnym planiście systemu Unix, ale nigdy nie występował w tamtym systemie. 28. Które z twierdzeń dotyczących obsługi przerwań w Linuksie 2.6 Są prawdziwe? TAK-Obsługa przerwań podzielona jest na górną połówkę i dolną połówkę. NIE-Procedury obsługi przerwań mogą być wykonywane dowolnie długo. TAK-Do linii obsługi przerwania o określonym numerze może być przypisanych kilka procedur obsługi przerwań. NIE-W procedurach obsługi przerwań można wywołać funkcje, które ulegają blokowaniu. NIE-Procedury obsługi przerwań korzystają ze stosu jądra, który ma nieograniczony rozmiar. Wyjaśnienia: Współczesne systemy operacyjne dzielą obsługę przerwań sprzętowych na dwie części: górną i dolną połówkę. Górną połówkę stanowią procedury obsługi przerwań, które muszą działać szybko, ponieważ linia IRQ z nimi związana, a niekiedy nawet cały system przerwań są wyłączone w trakcie ich wykonywania. Zazwyczaj wykonują one tylko niezbędne prace związane z obsługą przerwania, a pozostałe czynności są odraczane i wykonywane w dolnej połówce. Współczesne urządzenia, używające takich magistral jak USB, PCI, PCI–Express potrzebują dużej liczby przerwań, które są przydzielane im dynamicznie (niektóre przerwania są przydzielane statycznie z powodów historycznych). To oznacza, że wiele linii IRQ musi być współdzielone przez te urządzenia, co prowadzi do wielu problemów. Procedury obsługi przerwań są wywoływane w kontekście przerwania, co oznacza, że nie są dozwolone w nich wywołania funkcji blokujących oraz nie jest przydatna (poza procedurami obsługi wyjątków) wartość zwracana przez makrodefinicję current. Procedury obsługi przerwań używają stosu jądra procesu, tak jak inne funkcje jądra. Wielkość stosu jądra jest ograniczona do dwóch stron, więc w przypadku procesorów x86 ma on rozmiar 8KiB, a dla procesorów Alpha, 16KiB. 29. Określ, które ze zdań dotyczące szeregowania procesów w Linuxie 2.6 są prawdziwe? NIE-Linux realizuje wielozadaniowość w oparciu o kooperację. TAK-Działanie planisty O(1) oparte jest na schemacie kolejek ze sprzężeniem zwrotnym. NIE-Zadania interaktywne otrzymują od planisty O(1) mniejszy kwant czasu niż zadania nieinteraktywne. TAK-Główną struktura planisty CFS jest drzewo czerwono-czarne. TAK-Linux nie jest rygorystycznym systemem czasu rzeczywistego. Wyjaśnienia: Istnieją dwa typy systemów wielozadaniowych: systemy wielozadaniowe z kooperacją (bez wywłaszczania) i systemy wielozadaniowe z wywłaszczaniem. Linux jak większość współczesnych systemów operacyjnych realizuje wielozadaniowość z wywłaszczaniem. Oryginalny planista systemu Unix używał szeregowania opartego na schemacie wielopoziomowych kolejek ze sprzężeniem zwrotnym. Planista O(1) posiada pewne wady, wynikające z tego, że bazuje na schemacie szeregowania opartym na wielopoziomowych kolejkach ze sprzężeniem zwrotnym. Im wyższy priorytet statyczny (czyli im niższa liczba - poziom uprzejmości od -20 do 19, domyślnie 0) tym dłuższy kwant czasu otrzymuje procesor. Oprócz tego jest też priorytet dynamiczny (dodawany do statycznego, ustalany po każdej rundzie szeregowania). Tu również procesy interaktywne otrzymują dłuższe kwanty czasu. Kolejka procesów gotowych, w przypadku planisty CFS, jest zaimplementowana w postaci drzewa czerwono-czarnego. Linux nie jest rygorystycznym systemem czasu rzeczywistego, ale istnieją odmiany jego jądra, które spełniają wymogi nakładane na takie systemy. 30. Które ze zdań dotyczących kolejek prac są prawdziwe? NIE-Czynności odroczone wykonywane w ramach kolejek prac wykonywane są w kontekście przerwania. TAK-Kolejki prac zastąpiły mechanizm dolnych połówek znany, jako ”kolejki zadań”, który był wykorzystywany we wcześniejszych jądra Linuksa. TAK-Mechanizm kolejek prac pozwala na określenie czasu po upływie, którego dana czynność może się rozpocząć. TAK-Jądro systemu Linux zawiera specjalną funkcje, która pozwala na opróżnienie domyślnej kolejki prac. TAK-Jeśli w jądrze tworzona jest nowa kolejka prac, to jest równocześnie dla niej tworzony nowy, odrębny wątek roboczy. Wyjaśnienia: Kolejki prac są implementacją dolnych połówek działającą w kontekście procesu. Kolejki prac zastąpiły inny mechanizm dolnych połówek, który był stosowany w wersjach jądra poprzedzających serię 2.6 i nazywał się kolejkami zadań. Kolejki prac, podobnie jak tasklety są mechanizmem pozwalającym opóźnić wykonanie działań związanych z obsługą przerwań. Kolejki prac pozwalają określić po jakim czasie powinno rozpocząć się ich wykonanie, ale gwarantują jedynie, że opóźnione prace nie rozpoczną się przed upływem tego czasu, a nie że zostaną wykonane natychmiast po jego upływie. Czasem konieczne może się okazać opróżnienie domyślnej kolejki prac. Można tego dokonać za pomocą funkcji flush_scheduled_work(). Do tworzenia nowej kolejki prac służy makro create_workqueue, które w wersjach jądra wcześniejszych niż 3.9 było funkcją. Dla każdej utworzonej przy jego pomocy kolejki tworzony jest również osobny wątek roboczy na każdym procesorze. 31. Pytania INNE: TAK-Algorytm noop realizuje tylko operacje scalania. NIE-Alokator plastrowy przechowuje własne struktury w pamięci dedykowanej. TAK-Alokator plastrowy przydziela pamięć na deskryptor. NIE-Alokator plastrowy przydziela w pierwszej kolejności pamięć z plastrów pustych. NIE-Argumenty wywołań systemowych są przekazywane tylko przez rejestry programowe. Wyjaśnienia: Czwarty mechanizm szeregowania żądań I/O jest bardzo prosty w działaniu – realizuje wyłącznie operację scalania. Ten planista nosi nazwę noop od angielskiego słowa no-operations. W Linuksie alokator plastrowy tworzy pamięci podręczne (bufory) dwóch rodzajów: ogólne i dedykowane. Z pamięci ogólnych korzysta on sam, z dedykowanych – pozostałe części jądra. Systemy operacyjne przechowują wszystkie informacje o każdym pojedynczym procesie w deskryptorze procesu. W przypadku Linuksa jest to struktura typu struct task_struct zdefiniowana w pliku nagłówkowym linux/sched.h. Pamięć na takie struktury jest przydzielana przez alokator plastrowy. Obiekty są przydzielane z plastrów częściowo zajętych. Jeśli ich nie ma, to z plastrów pustych. Argumenty wywołania systemowego przekazywane są do system_call() za pomocą rejestrów ebx, ecx, edx, esi i edi. 32. Pytania INNE: NIE-Deskryptor procesu jest opisywany strukturą struct thread_info. TAK-Deskryptory pamięci są połączone w listę i drzewo czerwono-czarne. NIE-Jądro cyklicznie odczytuje wartości z zegara czasu rzeczywistego (RTC). TAK-Jądro może przydzielić dodatkową przestrzeń adresową dla procesu podczas jego wykonywania. TAK-Każda ramka jest określona strukturą struct page. Wyjaśnienia: Systemy operacyjne przechowują wszystkie informacje o każdym pojedynczymi procesie w deskryptorze procesu. W przypadku Linuksa jest to struktura typu struct task_struct zdefiniowana w pliku nagłówkowym linux/sched.h. Dwa pola deskryptora pamięci są związane z osobnymi strukturami danych, które przechowują tę samą informację, ale w różny sposób. Pierwszym z nich jest pole mmap przechowujące adres listy zawierającej dane o wszystkich obszarach pamięci. Drugim jest pole mm_rb przechowujące adres korzenia drzewa czerwono-czarnego, które zawiera te same dane co lista, ale oferuje krótszy czas ich wyszukiwania, niż lista. Za to listę prościej jest przeglądać sekwencyjnie. Wartość RTC jest odczytywana jednorazowo przy rozruchu systemu. Jądro systemu daje procesom możliwość dynamicznego włączania do swojej przestrzeni adresowej nowych obszarów. Z każdą stroną fizyczną (ramką) w pamięci komputera jest związana struktura typu struct page, zawierająca dane o stronie umieszczonej w tej ramce. 33. Pytania INNE: TAK-Każde urządzenie musi mieć inny numer przerwania. NIE-Kod w mechanizmie RCU może ulec zawieszeniu. TAK-Licznik monotoniczny jest wysokiej rozdzielczości. NIE-Liczniki bazują na taskletach. NIE-Liczniki dynamiczne ze względu na zbyt małą precyzje nie mogą być wykorzystywane w zadaniach czasu rzeczywistego. Wyjaśnienia: Każdemu urządzeniu jest również przyporządkowany numer przerwania, który pozwala określić, które urządzenie zgłosiło przerwanie i jak to przerwanie należy obsłużyć. W mechanizmie RCU, jeśli czytelnik chce odczytać wspólny zasób, to musi uzyskać do niego wcześniej wskaźnik i wykonać tę operację za jego pośrednictwem. Jeśli pisarz chce zmodyfikować zasób, to robi jego kopię, zmienia ją i publikuje do niej wskaźnik. Jeśli dowolny czytelnik spróbuje po tym uzyskać dostęp do wspólnego zasobu, to otrzyma wskaźnik do tej jego kopii. Oryginał zasobu jest niszczony, kiedy przestanie z niego korzystać ostatni czytelnik, który miał do niego dostęp. Mechanizm RCU nie zapewnia ochrony przed współbieżnymi zapisami, więc stosowany jest głównie wtedy, gdy jest tylko jeden pisarz. Ale pisarz robi kopię przed zapisem, więc nie może dojść do zakleszczenia. Dla liczników czasu wysokiej rozdzielczości wymagany jest zegar monotoniczny i zegar rzeczywistego czasu. Oba oferują rozdzielczość nanosekundową, ale wartość pierwszego jest zawsze zwiększana w określonych momentach, a wartość drugiego może w pewnych przypadkach być zmniejszana i jądro musi kompensować te zmiany. Jądro udostępnia liczniki czasu, które pozwalają opóźnić pewne operacje do wykonania w kontekście przerwania, na określony czas. Innymi słowy liczniki czasu są kolejną implementacją dolnych połówek. Tasklety są dolnymi połówkami wykonywanymi w kontekście przerwania i dostępnymi z poziomu modułu jądra. Liczniki, zwane licznikami dynamicznymi lub licznikami jądra, pozwalają opóźnić wykonanie określonych czynności o ustalony przedział czasu, począwszy od chwili bieżącej. Są dwie kategorie takich liczników: liczniki o niskiej i wysokiej rozdzielczości. Te pierwsze nie są mechanizmem precyzyjnym i mogą zdarzać się im niedokładności rzędu długości trwania okresu zegara systemowego. Liczniki czasu wysokiej rozdzielczości (ang. high-resolution timers) oferują rozdzielczość nanosekundową i stosowane są wszędzie tam, gdzie liczniki niskiej rozdzielczości są niewystarczające, jak w przypadku przetwarzania danych multimedialnych. 34. Pytania INNE: TAK-Liczniki wysokiej rozdzielczości pozwalają na ich regulowanie z nanosekundową precyzją. TAK-Listy liczników nie są przez system sortowane. TAK-Maksymalna ilość przerwań programowych wynosi 32. TAK-Mechanizm RCU posługuje się wskaźnikami. TAK-Niektóre pola struktury dotyczącej plików mogą być wypełniane dowolnymi wartościami. Wyjaśnienia: Liczniki czasu wysokiej rozdzielczości (ang. high-resolution timers) oferują rozdzielczość nanosekundową i stosowane są wszędzie tam, gdzie liczniki niskiej rozdzielczości są niewystarczające, jak w przypadku przetwarzania danych multimedialnych. Aby uniknąć sortowania listy liczników i kosztownego przeglądania jest ona podzielona na pięć grup, pod względem czasu, po jakim trzeba będzie wywołać funkcje zgromadzonych na niej liczników. W systemie mogą istnieć maksymalnie 32 przerwania programowe, spośród których obecnie wykorzystywanych jest tylko kilka. Mechanizm RCU wymaga pewnego, najczęściej niewielkiego narzutu pamięci i wymusza spełnienie trzech warunków, aby poprawnie funkcjonował: ❖ kod korzystający z zasobu chronionego RCU nie może ulec uśpieniu, ❖ zapisy chronionego zasobu powinny być sporadyczne, a odczyty częste, ❖ chroniony zasób musi być dostępny dla wątków za pomocą wskaźnika. Obiekty i-węzła przechowują wszystkie informacje niezbędne do przeprowadzenia operacji na plikach i katalogach, z którymi są związane. W przypadku niektórych nieuniksowych systemów plików te informacje są pobierane wprost z pliku lub z innego miejsca na nośniku, gdzie są przechowywane i umieszczane w obiekcie. Część z tych informacji nie jest obecna w niektórych systemach plików. W takich wypadkach pola im odpowiadające wypełniane są dowolnymi wartościami. 35. Pytania INNE: TAK-Obsługa struktury bio jest mniej skomplikowana niż obsługa nagłówków buforów. TAK-Odczyty w mechanizmie RCU powinny być częste, a zapisy sporadyczne. NIE-Pamięć fizyczna nieciągła jest przydzielana za pomocą algorytmu bliźniaków. NIE-PID może być ujemny. TAK-Planista CFS całkowicie zastąpił planistę O(1). Wyjaśnienia: Zastosowanie struktury bio przyniosło następujące korzyści: ❖ blokowe operacje wejścia-wyjścia mogą w prosty sposób korzystać z wysokiej pamięci, gdyż struktura bio posługuje się strukturami typu struct page, ❖ struktura bio może reprezentować zarówno zwykłe operacje i/o, jak i również operacje bezpośrednie, które nie korzystają z buforów jądra, ❖ ułatwiona jest realizacja operacji wejścia-wyjścia, w których dane pochodzą z wielu rozłącznych stron pamięci (tzw. operacje z rozproszonym źródłem), ❖ obsługa takiej struktury jest mniej skomplikowana niż obsługa nagłówków buforów. Mechanizm RCU wymaga pewnego, najczęściej niewielkiego narzutu pamięci i wymusza spełnienie trzech warunków, aby poprawnie funkcjonował: ❖ kod korzystający z zasobu chronionego RCU nie może ulec uśpieniu, ❖ zapisy chronionego zasobu powinny być sporadyczne, a odczyty częste, ❖ chroniony zasób musi być dostępny dla wątków za pomocą wskaźnika. Jądro Linuksa ma niskopoziomowy alokator pamięci, przydzielający pamięć fizycznie ciągłymi obszarami, które są potrzebne niektórym urządzeniom w transmisjach DMA i pomagają zredukować częstotliwość aktualizacji rejestrów TLB. Ten alokator używa algorytmu bliźniaków. Wśród danych przechowywanych w deskryptorze procesu jest jego identyfikator (PID). Jest to liczba naturalna, unikatowa dla każdego procesu i przypisywana mu przez jądro. Planista CFS (ang. Completely Fair Scheduler) zastąpił w Linuksie planistę O(1). 36. Pytania INNE: NIE-Planista CFS korzysta z tablicy odwrotności priorytetów. TAK-Planista CFS przelicza priorytety procesów na wagi. NIE-Plik jest powiązany z wpisem katalogowym. TAK-Procesy które nie korzystają z procesora, są przesuwane w lewą stronę drzewa czerwono-czarnego. TAK-Procesy mogą współdzielić deskryptor pamięci. Wyjaśnienia: Kod planisty CFS używa dwóch tablic, do zamiany priorytetów na wagi i odwrotnie. Pierwsza z nich nazywa się prio_to_weight i przechowuje wagi. Druga tablica nazywa się prio_to_wmul i przechowuje odwrotności tych wag. Waga dla domyślnego priorytetu (poziom uprzejmości 0) wynosi 1024. Wagi procesów o wyższych priorytetach są wyliczane poprzez mnożenie tej wartości przez kolejne potęgi liczby 1,25. Wagi procesów o niższych priorytetach są wyliczone poprzez dzielenie tej wartości przez kolejne potęgi liczby 1,25. Każdy z elementów ścieżki do katalogu lub pliku jest osobnym wpisem katalogowym (ang. dentry = directory entry). Linux traktuje katalogi jak zwykłe pliki, w związku z tym można na nich przeprowadzić część tych samych operacji, co na plikach. Kryterium sortowania jest czas korzystania z procesora. Skrajnie lewy element drzewa czerwono- czarnego jest związany z procesem, który najmniej korzystał z tego procesora Jeśli wywołanie systemowe clone() otrzyma flagę CLONE_VM jako jeden z jego argumentów, to nowy proces będzie dzielił przestrzeń adresową z rodzicem. Innymi słowy, te procesy będą wątkami. W takim przypadku nie jest tworzony deskryptor pamięci dla procesu potomnego, a oba procesy współdzielą ten sam. 37. Pytania INNE: NIE-Procesy UNINTERRUPTIBLE mogą zostać ustawione w stan gotowości przez inne zdarzenie niż to, na które oczekują. NIE-Programista jądra powinien się posługiwać funkcją printf. NIE-Programista jądra powinien używać funkcji rekurencyjnych. TAK-Programista może napisać dedykowaną pamięć podręczną dla alokatora plastrowego. TAK-Programista powinien zwracać uwagę na rozmiar stosu jądra. Wyjaśnienia: Flaga TASK_UNINTERRUPTIBLE powiadamia nas że proces oczekuje na zdarzenie i może być wybudzony tylko przez sygnał z nim związany; ten stan jest rzadko używany, bo uniemożliwia przerwanie procesu. Standardowa biblioteka języka C nie jest dostępna w przestrzeni jądra. Na przykład, zamiast funkcji printf() używana jest printk(). Ze względu na wielowątkowość jądra, osoby początkujące są zwykle zniechęcane do korzystania z algorytmów rekurencyjnych oraz dużych buforów stosu. Programista może utworzyć nowe specjalizowane pamięci podręczne przy pomocy funkcji kmem_cache_create(). Funkcja ta jest powiązana z alokatorem plastrowym. Rozmiar stosu jądra dla procesu wynosi tylko dwie strony. Oznacza to, że miejsce na tym stosie powinno być ostrożnie przydzielane. 38. Pytania INNE: NIE-Przerwania programowe są wykorzystywane przy taskletach. TAK-Przerwanie składa się z dwóch połówek. TAK-Przy wywołaniach systemowych jest używany sys_. NIE-Rozmiar sektora dla urządzeń blokowych wynosi zazwyczaj 1024 bajty. TAK-Rygle pętlowe powinny być stosowane wszędzie tam, gdzie nie można zawiesić wątku i gdzie czas przełączania kontekstu byłby niewspółmiernie dłuższy z czasem aktywnego oczekiwania. Wyjaśnienia: Przerwania programowe podobnie jak tasklety wyparły mechanizm BH. Oba są realizacjami systemu dolnych połówek. Tasklety przypominają przerwania programowe, ale nie skalują się tak dobrze jako one. Są to oddzielne mechanizmy. Kod odpowiedzialny za obsługę przerwań w Linuksie jest podzielony na dwie części. Pierwsza jest nazywana górną połówką (ang. top half) a druga dolną połówką (ang. bottom half), mimo, że nie muszą być tego samego rozmiaru. Wywołania systemowe są zwykłymi funkcjami jądra wykonywanymi w kontekście procesu, ale są one definiowane w szczególny sposób. Nazwy tych funkcji zaczynają się przedrostkiem sys_. Rozmiar bloku jest parzystą wielokrotnością rozmiaru sektora, a w szczególności może być mu równy. Jądro przyjmuje, że rozmiar sektora wynosi 512 bajtów. Rygle pętlowe to semafory z aktywnym oczekiwaniem. Jeśli wątek wykonania próbuje zająć rygiel pętlowy, który został wcześniej uzyskany przez inny wątek, to będzie musiał w pętli sprawdzać, czy ten rygiel został już zwolniony. Ponieważ aktywne oczekiwanie jest marnotrawieniem czasu procesora rygle pętlowe powinny być stosowane wszędzie tam, gdzie nie można zawiesić wątku i gdzie czas przełączania kontekstu byłby wielokrotnie dłuższy od czasu aktywnego oczekiwania. 39. Pytania INNE: NIE-Rygle pętlowe są użyteczne w systemach jednoprocesorowych z wywłaszczaniem jądra. NIE-Stan procesów po zakończeniu jest przechowywany w tym samym polu deskryptora. NIE-Stronicowanie nigdy nie korzysta z segmentacji. NIE-Struktura bio korzysta z listy offsetów. TAK-Struktura bio może być wykorzystywana w macierzach RAID. Wyjaśnienia: Rygle nie są rekurencyjne i nie są stosowane w systemach jednoprocesorowych - kompilator wstawia w ich miejsce puste instrukcje lub, jeśli podczas kompilacji włączona jest opcja wywłaszczania wątków jądra, zastępuje je funkcjami włączającymi i wyłączającymi ich wywłaszczanie. Stan procesów, które zostały zakończone przechowywany jest w polu „exit_state” deskryptora. W przypadku 32-bitowych platform sprzętowych bazujących na procesorach x86 jądro Linuksa oprócz stronicowania używa segmentacji, a dokładniej wbudowanego w nią sprzętowego mechanizmu ochrony, który uzupełnia ten udostępniany przez stronicowanie Struktura BIO, przechowuje dane związane z operacjami wejścia-wyjścia. Reprezentuje ona operacje w trakcie ich trwania za pomocą listy segmentów. Segment w tym przypadku jest definiowany jako ciągły fragment bufora. Drugie pole struktury bio określa ile elementów z opisywanej tablicy bierze udział w operacji. Użycie tego pola pozwala na podział struktury bio, co ma znaczenie w przypadku takich sterowników, jak sterowniki macierzy RAID. 40. Pytania INNE: NIE-VFS jest zapożyczony od Microsoftu. NIE-W 64-bitowych procesorach pamięć wysoka jest oznaczona jako HIGHMEM. NIE-W wersji 2.6 deskryptor pamięci jest przechowywany na stosie. TAK-Wirtualny system plików jest modelem obiektowym. NIE-Wszystkie pliki na dysku są reprezentowane przez VFS. Wyjaśnienia: VFS – Virtual File System – wirtualny system plików został zapożyczony w jądrze Linuxa od pracowników firmy Sun Microsystem. ZONE_HIGHMEM – strefa grupująca strony w wysokiej pamięci (dla 32­bitowych procesorów rodziny x86 jest to pamięć fizyczna powyżej 896MB, na innych może nie występować). Platformy 64-bitowe zazwyczaj używają stref ZONE_DMA, ZONE_NORMAL i ZONE_DMA32. Współcześnie nie ma potrzeby stosowania strefy ZONE_HIGHMEM w takich platformach sprzętowych. Deskryptor był przechowywany na stosie DO wersji 2.6, od wspomnianej wersji w jego miejscu umieszczono inną strukturę, której jedna ze składowych jest wskaźnikiem na deskryptor. Wirtualny system plików został zaimplementowany w całości w C, ale jest oparty na modelu obiektowym. VFS dostarcza jednolity interfejs wspólny dla wszystkich systemów plików obsługiwanych przez jądro systemu operacyjnego. 41. Pytania INNE: NIE-Zawartość zegara czasu rzeczywistego jest odczytywana przez jądro co pewien okres czasu. NIE-Zmienna "jiffies" jest nałożona na starsze 32 bity zmiennej "jiffies_64". NIE-Zmienna jiffies przechowuje informacje o czasie rzeczywistym systemu. NIE-Zwiększenie wartości stałej HZ powoduje zmniejszenie częstotliwości przerwań zegarowych. NIE-Rygle pętlowe są rekurencyjne. Wyjaśnienia: Zawartość zegara czasu rzeczywistego (RTC) odczytywana jest tylko podczas rozruchu systemu. W 32-bitowych komputerach zmienna jiffies jest nałożona na mniej znaczące 4 bajty (czyli 32 bity) zmiennej jiffies_64, a w komputerach 64-bitowych obie nazwy oznaczają tę samą zmienną. Mniej znaczące bity to te liczone od prawej strony, zwane również najmłodszymi bitami. Analogiczne, starsze bity (bardziej znaczące) liczone są od lewej strony. Zmienna jiffies przechowuje liczbę przerwań wygenerowanych od czasu startu systemu. Częstotliwość zegara systemowego jest określana przez stałą HZ. Zwiększenie wartości tej stałej powoduje zwiększenie częstotliwości przerwań zegarowych. Rygle pętlowe są nierekurencyjne – jeśli wątek uzyskał już rygiel i próbuje go uzyskać ponownie to nastąpi autozakleszczenie. 42. Poniżej umieszczono zdania dotyczące obsługi przerwań przez Linuksa 2.6. Które z nich są prawdziwe? NIE-Częstotliwości pojawiania się wszystkich przerwań są wykorzystywane do inicjalizowania generatora liczb losowych. TAK-Numery przerwań mogą być przydzielane niektórym urządzeniom dynamicznie. TAK-Aby procedura obsługi przerwania mogła być wywołana, musi zostać wcześniej zarejestrowana. NIE-Procedury obsługi przerwania mogą korzystać z wartości zwracanej przez makrodefinicję „current”. TAK-Nie jest wymagane, aby funkcje obsługi przerwań były wielobieżne. Wyjaśnienia: Do oznaczania częstotliwości które mogą być źródłem entropii służy flaga IRQF_SAMPLE_RANDOM. Nie wszystkie przerwania nadają się do uzupełniania entropii jądra. Przerwania zegarowe są zbyt regularne, a na częstotliwość pojawiania się przerwań karty sieciowej mogą mieć wpływ potencjalni intruzi. Współczesne urządzenia, używające takich magistral jak USB, PCI, PCI–Express potrzebują dużej liczby przerwań, które są przydzielane im dynamicznie (niektóre przerwania są przydzielane statycznie z powodów historycznych). Po zablokowaniu linii IRQ sprawdzane jest czy dane przerwanie zostało zarejestrowane, jeśli tak to procedura jest uruchamiana. Wartość makra current nie jest przydatna dla procedur obsługi przerwań, ponieważ nie są one związane z żadnym procesem, więc nie potrzebują deskryptora bieżącego procesu. Procedury obsługi przerwań nie muszą być wielobieżne ponieważ jądro Linuxa nie obsługuje zagnieżdżonych przerwać, tzn. takich które pojawiają się gdy ich wcześniejsze instancje są obsługiwane. 43. Które ze zdań dotyczących synchronizacji są prawdziwe? TAK-Niektóre operacje niepodzielne mogą być zrealizowane jako pojedyncze rozkazy procesora. TAK-Przeplot operacji jest przyczyną występowania problemu sekcji krytycznej. TAK-W systemie Linux, w przestrzeni użytkownika wywłaszczenie procesu może zajść tylko w ściśle określonym momencie jego działania. NIE-Zadanie nigdy nie może być wywłaszczone po zakończeniu obsługi przerwania. TAK-Dostęp do zmiennych lokalnych wątków wykonania nie musi podlegać synchronizacji. Wyjaśnienia: Listy rozkazów niektórych procesorów zawierają rozkazy, które pozwalają wprost wykonać niepodzielnie takie instrukcje jak np.: dodawanie, odejmowanie, odczyt, zapis. Problem sekcji krytycznej powstaje kiedy następuje przeplot operacji modyfikacji lub operacji modyfikacji i odczytu stanu współdzielonego zasobu, przy czym operacje te pochodzą od różnych procesów. Jeśli dostęp do zasobu ogranicza się wyłącznie do odczytu, to jest dostępem zawsze bezpiecznym, nawet jeśli dochodzi do przeplotu operacji. Wywłaszczenie procesu użytkownika może zajść w ramach powrotu do przestrzeni użytkownika z wywołania systemowego, bądź z procedury obsługi przerwania. Proces może być wywłaszczony kiedy sterowanie wraca do przestrzeni użytkownika, po wykonaniu wywołania systemowego lub obsługi przerwania. Wątek jądra może być wywłaszczony kiedy sterowanie wraca z obsługi przerwania, kiedy jego licznik preempt_count (jedno z pól struktury task_info) jest ustawiony na 0, kiedy bezpośrednio wywoła funkcję schedule() lub kiedy rozpoczyna oczekiwanie na dowolne zdarzenie. Jeśli wspólne zasoby wymagające ochrony są lokalne dla jednego z CPU w środowisku wieloprocesorowym, czyli niedostępne dla pozostałych procesorów, to wyłączanie i włączanie wywłaszczania wątków jądra dla tego CPU jest wystarczającym środkiem synchronizacji. 44. Które twierdzenie odnośnie wątków i procesów w systemie Linux są prawdziwe? NIE-Za szeregowanie wątków odpowiada inny mechanizm jądra niż za szeregowanie procesów. NIE-Każdy wątek jądra posada swoją odrębną przestrzeń adresową. TAK-Makrodefinicja „current” pozwala na szybki dostęp do deskryptora bieżącego procesu. NIE-Proces macierzysty, którego proces potomny się zakończył przechodzi w stan TASK_ZOMBIE. TAK-Deskryptory procesów powiązane są w listę. Wyjaśnienia: Szeregowanie, w kontekście systemów operacyjnych, to podejmowanie decyzji dotyczących przydzielania zasobów procesom. Zazwyczaj, ale nie zawsze, pojęcie to oznacza szeregowanie procesów, czyli decydowanie o przydziale CPU określonemu procesowi. Linux pozwala procesom użytkownika tworzyć wątki, ale w przeciwieństwie do innych systemów operacyjnych nie posiada żadnych podsystemów, które przeznaczone byłyby wyłącznie do ich obsługi. W przypadku Linuksa wątek użytkownika jest po prostu procesem użytkownika, który zawsze współdzieli większość swoich zasobów, włącznie z przestrzenią adresową, z innymi procesami-wątkami (jego rodzeństwem). Wątki jądra współdzielą przestrzeń adresową z resztą jądra, nie mają własnej. Makro current, zwraca adres deskryptora bieżącego procesu. W tym przypadku słowo „bieżącego” należy rozumieć jako „tego, który uaktywnił kod jądra”. Proces, którego proces macierzysty się zakończył może utknąć w stanie zombie. Wówczas jest adoptowany przez proces init (lub jego nowszy odpowiednik) lub przez proces, który należy do tej samej grupy co rodzic. Czyli w stan TASK_ZOMBIE może przejść proces potomny, a nie proces macierzysty. Deskryptory wszystkich procesów użytkownika są połączone w cykliczną listę dwukierunkową. 45. Pytania INNE: TAK-Zmienna typu „atomic_t” jest 32 bitowa. NIE-Wszystkie architektury, które obsługuje Linux dostarczają rozkazów maszynowych realizujących operacje niepodzielne na wartościach będących liczbami całkowitymi. TAK-Rygle pętlowe mogą być stosowane we fragmentach kodu wykonywujących się w kontekście przerwania. NIE-Rygle R-W stosujemy w zagadnieniach typu problem czytelników i pisarzy, gdzie faworyzowani są pisarze. TAK-Zmienne sygnałowe są uproszczoną wersją semaforów. Wyjaśnienia: Zmienne typu atomic_t przechowują liczby całkowite. Początkowo były to liczby tylko 24-bitowe, mimo że rozmiar zmiennej typu atomic_t zawsze wynosił 32 bity. Najmłodsze 8 bitów (czyli bajt) było używane do implementacji blokady, niezbędnej dla wspomnianych procesorów SPARC. W nowszych wersjach jądra tę blokadę zrealizowano w inny sposób i obecnie wszystkie 32 bity są używane do przechowywania liczby. Operacje niepodzielne (atomowe) na zmiennych prostych typów są zazwyczaj realizowane za pomocą instrukcji maszynowych właściwych dla architektury procesora. Listy rozkazów niektórych procesorów zawierają rozkazy, które pozwalają wprost wykonać niepodzielnie takie instrukcje jak np.: dodawanie, odejmowanie, odczyt, zapis, inne dostarczają rozkazów ułatwiających implementację takich operacji w systemach wieloprocesorowych (np. rozkaz blokowania magistrali), jeszcze inne nie posiadają żadnych rozkazów tego typu (np. SPARC). Rygle pętlowe są użyteczne w środowiskach wieloprocesorowych ze względu na aktywne oczekiwanie. Mogą być one stosowane wewnątrz procedur obsługi przerwań, ale tylko przy wyłączonych przerwaniach lub linii IRQ, celem uniknięcia zakleszczeń. Procedury obsługi przerwań w Linuksie wykonywane są w kontekście przerwania. Wiele problemów związanych ze współdzieleniem zasobów w jądrze sprowadza się do problemu czytelników i pisarzy. Linux posiada specjalną wersję rygli pętlowych do rozwiązywania pierwszego problemu czytelników i pisarzy, w którym czytelnicy mają priorytet. Rygiel pętlowy R-W może być zajęty przez więcej niż jednego czytelnika lub nawet kilkukrotnie przez tego samego czytelnika (w tym wypadku rygiel R-W jest rekurencyjny). Jednakże tylko jeden pisarz może korzystać z takiego rygla w danym momencie i może go zająć tylko wtedy, gdy nie uzyskał go wcześniej żaden czytelnik ani inny pisarz. Zmienne sygnałowe to uproszczone semafory. Stosowane są w sytuacjach, gdzie występuje kilka wątków i jeden z nich musi poinformować pozostałe o zakończeniu wykonywanego przez niego zadania. 46. Które z poniższych stwierdzeń dotyczących środków synchronizacji w jądrze Linuksa 2.6 są prawdziwe? TAK -Wątek wykonania który przetrzymuje semafor nie może równocześnie przetrzymywać rygla pętlowego. TAK-Blokady sekwencyjne pozwalają ustalić czy operacja odczytu nie została przepleciona z operacją zapisu. TAK-Blokada BKL jest blokadą gruboziarnistą. NIE-Blokada BKL nie jest rekurencyjna. TAK-Rygle pętlowe nie są używane w systemach jednoprocesorowych w jądrze, które nie wywłaszcza wątków. Wyjaśnienia: Semafory nie mogą być przetrzymywane przez wątki, które już przetrzymują rygle pętlowe. Blokady sekwencyjne w prosty sposób pozwalają określić, czy operacja odczytu nie została przepleciona z operacją zapisu. Przed odczytem jest zapamiętywana wartość licznika. Po wykonaniu tej operacji odczytywany jest ponownie licznik i jego wartość porównywana jest z wartością poprzednią. Jeśli te wartości są takie same, to można być pewnym, że odczyt nie został przepleciony przez zapis. BKL miała być rozwiązaniem przejściowym, które miało być zastąpione blokadami o mniejszej ziarnistości. Blokada BKL jest rekurencyjna, wyłącza wywłaszczanie jądra i może być używana tylko w kontekście procesu. Rygle pętlowe nie są rekurencyjne i nie są stosowane w systemach jednoprocesorowych - kompilator wstawia w ich miejsce puste instrukcje lub, jeśli podczas kompilacji włączona jest opcja wywłaszczania wątków jądra, zastępuje je funkcjami włączającymi i wyłączającymi ich wywłaszczanie. 47. Które ze stwierdzeń dotyczących wywołań systemowych w Linuksie 2.6 są prawdziwe? TAK-Dodawanie nowych wywołań systemowych nie jest zalecanym przez twórców jądra sposobem dodawania nowej funkcjonalności. TAK-Każde wywołanie systemowe zwraca wartość typu "long". NIE-Każde wywołanie systemowe musi przyjmować co najmniej jeden argument wywołania. NIE-Wszystkie funkcje ze standardowej biblioteki języka C korzystają z wywołań systemowych. NIE-Funkcja realizująca wywołanie systemowe musi być w całości napisana w assemblerze. Wyjaśnienia: Dodanie do jądra nowych wywołań systemowych jest kuszącym pomysłem, jednakże wśród twórców jądra Linuksa panuje silna tendencja, aby tego nie robić. Każde wywołanie systemowe zwraca wartość typu long, która stanowi kod wykonania. Zazwyczaj poprawne zakończenie wywołania sygnalizowane jest liczbą dodatnią lub (najczęściej) zerem, a błędne - ujemną. Wywołania systemowe podobnie jak zwykłe funkcje mogą przyjmować pewną liczbę argumentów, lub nie przyjmować ich w ogóle. Mogą one również, oprócz podstawowej operacji wykonywać czynności dodatkowe, które wpływają na stan systemu. API Linuksa jest zapożyczone z systemu Unix i zgodne ze standardami POSIX oraz SUS. Jest ono w większości zaimplementowane w standardowej bibliotece języka C, nazywanej w Linuksie glibc (libc w Uniksie). Niektóre z funkcji w niej dostępnych nie używają żadnych wywołań systemowych, np. strcpy(). Do komunikacji między procesami użytkownika, a jądrem służą wywołania systemowe. Zazwyczaj aplikacje użytkownika nie wywołują ich bezpośrednio, lecz korzystają z funkcji dostępnych w standardowej bibliotece języka C (libc). Niektóre z tych funkcji zawierają sam kod wywołania funkcji systemowej, czyli są swego rodzaju „opakowaniami” na wywołania systemowe, inne funkcje wykonują przed zainicjowaniem wywołania systemowego dodatkowe czynności, jeszcze inne mogą korzystać z większej liczby wywołań systemowych. Większość kodu Linuksa jest napisana w języku C, a tylko niewielka część, zależna od konkretnej architektury sprzętowej, w assemblerze (w notacji AT&T). 48. Które stwierdzenia są prawidłowe? TAK-Programista nie powinien używać funkcji goto. TAK-Architektura NUMA jest obsługiwana od wersji jądra 2.6. TAK-Stała HZ dla architektury i386 wynosi 1000. TAK-Czy liczniki niskiej rozdzielczości działają z mikrosekundową precyzją? NIE-Użytkownik uprzywilejowany może dowolnie zmieniać wartość stałej HZ. Wyjaśnienia: Ponieważ jądro systemu z definicji ma być wydajne pod względem wykorzystania pamięci i szybkości działania, to przeglądając kod źródłowy Linuksa możemy znaleźć wiele fragmentów, które łamią niektóre przyjęte kanony dobrze napisanego oprogramowania (jak choćby nieużywanie instrukcji goto). Programiści jądra często używają osławionej instrukcji goto, zazwyczaj w celach optymalizacji efektywności lub obsługi wyjątków. Linux obsługuje symetryczne przetwarzanie wieloprocesorowe (SMP) oraz architekturę NUMA, co nie jest cechą wszystkich Uniksów. Wsparcie dla NUMA jest od wersji 2.6 jądra. Częstotliwość zegara systemowego jest określana przez stałą HZ. Okres zegara jest odwrotnością tej stałej. Jej wartość, w przypadku większości platform obsługiwanych przez Linuksa, wynosi 100. Do wyjątków zaliczają się komputery bazujące na procesorach x86. W ich przypadku ta stała także miała wartość 100 (okres 10ms), ale w serii 2.4 jądra zmieniono ją na 1000 (okres 1ms), aby zaspokoić potrzeby multimedialnego oprogramowania użytkowego. Dla architektury i386 stała HZ również wynosi 1000. Rozdzielczość liczników czasu niskiej rozdzielczości jest rzędu okresu zegara systemowego, a więc zgodnie z częstotliwością określaną przez stałą HZ, opiera się na wartościach mikrosekund. Aplikacje użytkowe zakładają, że wartość stałej HZ jest równa 100. Jest to prawdą dla większości platform sprzętowych, z pewnymi wyjątkami, takimi jak komputery bazujące na procesorach x86 lub DEC Alpha. Z powodu takich systemów komputerowych programiści jądra zdefiniowali odrębną stałą o nazwie USER_HZ, której wartość określa częstotliwość zegara systemowego oczekiwaną przez aplikacje użytkownika. Ale prawdziwa stała HZ jest zależy od architektury sprzętowej i niezmienna dla użytkownika. 49. Które stwierdzenia są prawidłowe? TAK-Makrodefinicja current pozwala na dostęp do deskryptora bieżacego procesu. NIE-Deskryptor procesu znajduje się na końcu stosu jądra dla wywołań systemowych procesu. NIE-VFS jest napisany w C++? NIE-Obiekt wpisu katalogowego (dentry) może posiadać wyłącznie dwa stany. NIE-Urządzenie znakowe jest zwykłym plikiem. Wyjaśnienia: Makro current, zwraca adres deskryptora bieżącego procesu. W tym przypadku słowo „bieżącego” należy rozumieć jako „tego, który uaktywnił kod jądra”. W wersjach jądra Linuksa poprzedzających serię 2.6 deskryptor proces był przechowywany na końcu stosu procesu w przestrzeni jądra (nazywanego w skrócie stosem jądra). Począwszy od wspomnianej serii stał się on jednak na tyle dużą strukturą, że zabierał zbyt wiele miejsca na stosie. Dlatego w tym miejscu umieszczono inną strukturę, typu struct thread_info, której jedna ze składowych jest wskaźnikiem na deskryptor. VFS w całości napisany jest w języku C, ale oparty jest na modelu obiektowym. Obiekty dentry są reprezentowane przez struktury typu struct dentry. Każdy taki obiekt może znajdować się w jednym z trzech stanów: używany, nieużywany i ujemny. Obiekt dentry w stanie używany jest związany z prawidłowym obiektem i-węzła i był ostatnio używany przez jądro. Obiekt dentry w stanie nieużywany jest także związany z prawidłowym obiektem i-węzła, ale przez dłuższy czas nie był używany. Obiekt wpisu katalogowego w stanie ujemny nie jest powiązany z prawidłowym obiektem i-węzła. Oznacza to, że związany jest z plikiem lub katalogiem, który został usunięty lub nigdy nie istniał. W systemach kompatybilnych z Uniksem urządzenia są traktowane jak pliki, tzn. są reprezentowane w systemie plików i są obsługiwane przez te same wywołania systemowe co pliki. Pliki reprezentujące urządzenia są nazywane plikami specjalnymi lub po prostu plikami urządzeń, nie są to zwykłe pliki. 50. Które stwierdzenia są prawidłowe? TAK-Struktura bio reprezentuje operacje wejścia-wyjścia w trakcie ich trwania. NIE-Wątki jądra mają własną sekcję tekstu. TAK-Deskryptory procesów są połączone w listę dwukierunkową. NIE-Struktura thread_struct jest deskryptorem procesu. TAK-Urządzenia znakowe mają dostęp sekwencyjny. Wyjaśnienia: W nowszych wersjach jądra postanowiono „odchudzić” nagłówek bufora i stworzyć nową strukturę, o nazwie bio, która przechowuje dane związane z operacjami wejścia-wyjścia. Reprezentuje ona takie operacje w trakcie ich trwania za pomocą listy segmentów. Linux używa także techniki copy-on-write (COW), która pozwala rodzicowi i potomkowi współdzielić przestrzeń adresową do momentu, aż któryś z nich nie zmodyfikuje wartości dowolnej zmiennej. W takim przypadku oba procesy otrzymują osobne segmenty danych, ale dalej współdzielą segment tekstu (kodu), który jest tylko do odczytu. Deskryptory wszystkich procesów użytkownika są połączone w cykliczną listę dwukierunkową. Jej pierwszym elementem jest deskryptor procesu init (lub jeden z jego nowszych zamienników). System

Use Quizgecko on...
Browser
Browser