Wydawnictwo Helion
ul. Kociuszki 1c
44-100 Gliwice
tel. 032 230 98 63
e-mail: helion@helion.pl
Linux. Programowanie
systemowe
Autor: Robert Love
T³umaczenie: Jacek Janusz
ISBN: 978-83-246-1497-4
Tytu³ orygina³u:
Linux System
Programming: Talking Directly
to the Kernel and C Library
Format: 168x237, stron: 400
Wykorzystaj moc Linuksa i twórz funkcjonalne oprogramowanie systemowe!
Jak zarz¹dzaæ plikowymi operacjami wejcia i wyjcia?
Jak zablokowaæ fragmenty przestrzeni adresowej?
Jak sterowaæ dzia³aniem interfejsu odpytywania zdarzeñ?
Dzisiaj systemu Linux nie musimy ju¿ nikomu przedstawiaæ, dziêki swojej
funkcjonalnoci i uniwersalnoci sta³ siê niezwykle popularny i szeroko
wykorzystywany. Dzia³a wszêdzie ? poczynaj¹c od najmniejszych telefonów
komórkowych, a na potê¿nych superkomputerach koñcz¹c. Z Linuksa korzystaj¹
agencje wywiadowcze i wojsko, jego niezawodnoæ doceni³y równie¿ banki i instytucje
finansowe. Oprogramowanie z przestrzeni u¿ytkownika w systemie Linux mo¿e byæ
uruchamiane na wszystkich platformach, na których poprawnie dzia³a kod j¹dra.
Czytaj¹c ksi¹¿kê Linux. Programowanie systemowe, dowiesz siê, jak utworzyæ
oprogramowanie, które jest niskopoziomowym kodem, komunikuj¹cym siê
bezporednio z j¹drem oraz g³ównymi bibliotekami systemowymi. Opisany zosta³ tu
sposób dzia³ania standardowych i zaawansowanych interfejsów zdefiniowanych
w Linuksie. Po lekturze napiszesz inteligentniejszy i szybszy kod, który dzia³a
we wszystkich dystrybucjach Linuksa oraz na wszystkich rodzajach sprzêtu.
Nauczysz siê budowaæ poprawne oprogramowanie i maksymalnie je wykorzystywaæ.
Programowanie systemowe
Biblioteka jêzyka C
Kompilator jêzyka C
Interfejs odpytywania zdarzeñ
Zarz¹dzanie procesami i pamiêci¹
U¿ytkownicy i grupy
Ograniczenia zasobów systemowych
Zarz¹dzanie plikami i katalogami
Identyfikatory sygna³ów
Struktury danych reprezentuj¹ce czas
Konwersje czasu
Poznaj i ujarzmij potêgê Linuksa!
3
Spis tre
ļci
Przedmowa ............................................................................................................................... 7
Wst
ýp ........................................................................................................................................9
1. Wprowadzenie — podstawowe poj
ýcia .................................................................... 15
Programowanie systemowe
15
API i ABI
18
Standardy
20
Pojöcia dotyczñce programowania w Linuksie
23
Poczñtek programowania systemowego
36
2. Plikowe operacje wej
ļcia i wyjļcia .............................................................................37
Otwieranie plików
38
Czytanie z pliku przy uĔyciu funkcji read()
43
Pisanie za pomocñ funkcji write()
47
Zsynchronizowane operacje wejĈcia i wyjĈcia
51
BezpoĈrednie operacje wejĈcia i wyjĈcia
55
Zamykanie plików
56
Szukanie za pomocñ funkcji lseek()
57
Odczyty i zapisy pozycyjne
59
Obcinanie plików
60
Zwielokrotnione operacje wejĈcia i wyjĈcia
61
Organizacja wewnötrzna jñdra
72
Zakoþczenie
76
3. Buforowane operacje wej
ļcia i wyjļcia ...................................................................... 77
Operacje wejĈcia i wyjĈcia, buforowane w przestrzeni uĔytkownika
77
Typowe operacje wejĈcia i wyjĈcia
79
Otwieranie plików
80
4
_
Spis tre
ļci
Otwieranie strumienia poprzez deskryptor pliku
81
Zamykanie strumieni
82
Czytanie ze strumienia
83
Pisanie do strumienia
86
Przykäadowy program uĔywajñcy buforowanych operacji wejĈcia i wyjĈcia
88
Szukanie w strumieniu
89
OpróĔnianie strumienia
91
Bäödy i koniec pliku
92
Otrzymywanie skojarzonego deskryptora pliku
93
Parametry buforowania
93
Bezpieczeþstwo wñtków
95
Krytyczna analiza biblioteki typowych operacji wejĈcia i wyjĈcia
97
Zakoþczenie
98
4. Zaawansowane operacje plikowe wej
ļcia i wyjļcia ..................................................99
Rozproszone operacje wejĈcia i wyjĈcia
100
Interfejs odpytywania zdarzeþ
105
Odwzorowywanie plików w pamiöci
110
Porady dla standardowych operacji plikowych wejĈcia i wyjĈcia
123
Operacje zsynchronizowane, synchroniczne i asynchroniczne
126
Zarzñdcy operacji wejĈcia i wyjĈcia oraz wydajnoĈè operacji wejĈcia i wyjĈcia
129
Zakoþczenie
141
5. Zarz
édzanie procesami ............................................................................................. 143
Identyfikator procesu
143
Uruchamianie nowego procesu
146
Zakoþczenie procesu
153
Oczekiwanie na zakoþczone procesy potomka
156
UĔytkownicy i grupy
166
Grupy sesji i procesów
171
Demony
176
Zakoþczenie
178
6. Zaawansowane zarz
édzanie procesami .................................................................. 179
Szeregowanie procesów
179
Udostöpnianie czasu procesora
183
Priorytety procesu
186
Wiñzanie procesów do konkretnego procesora
189
Systemy czasu rzeczywistego
192
Ograniczenia zasobów systemowych
206
Spis tre
ļci
_
5
7. Zarz
édzanie plikami i katalogami ............................................................................ 213
Pliki i ich metadane
213
Katalogi
228
Dowiñzania
240
Kopiowanie i przenoszenie plików
245
Wözäy urzñdzeþ
248
Komunikacja poza kolejkñ
249
ćledzenie zdarzeþ zwiñzanych z plikami
251
8. Zarz
édzanie pamiýcié ............................................................................................... 261
Przestrzeþ adresowa procesu
261
Przydzielanie pamiöci dynamicznej
263
Zarzñdzanie segmentem danych
273
Anonimowe odwzorowania w pamiöci
274
Zaawansowane operacje przydziaäu pamiöci
278
Uruchamianie programów, uĔywajñcych systemu przydzielania pamiöci
281
Przydziaäy pamiöci wykorzystujñce stos
282
Wybór mechanizmu przydzielania pamiöci
286
Operacje na pamiöci
287
Blokowanie pamiöci
291
Przydziaä oportunistyczny
295
9. Sygna
ĥy .......................................................................................................................297
Koncepcja sygnaäów
298
Podstawowe zarzñdzanie sygnaäami
304
Wysyäanie sygnaäu
309
WspóäuĔywalnoĈè
311
Zbiory sygnaäów
314
Blokowanie sygnaäów
315
Zaawansowane zarzñdzanie sygnaäami
316
Wysyäanie sygnaäu z wykorzystaniem pola uĔytkowego
324
Zakoþczenie
325
10. Czas ............................................................................................................................ 327
Struktury danych reprezentujñce czas
329
Zegary POSIX
332
Pobieranie aktualnego czasu
334
Ustawianie aktualnego czasu
337
Konwersje czasu
338
6
_
Spis tre
ļci
Dostrajanie zegara systemowego
340
Stan uĈpienia i oczekiwania
343
Liczniki
349
A Rozszerzenia kompilatora GCC dla j
ýzyka C ............................................................357
B Bibliografia ................................................................................................................369
Skorowidz .................................................................................................................. 373
261
ROZDZIA
Ĥ 8.
Zarz
édzanie pamiýcié
Pamiöè naleĔy do najbardziej podstawowych, a jednoczeĈnie najwaĔniejszych zasobów dostöp-
nych dla procesu. W rozdziale tym omówione zostanñ tematy zwiñzane z zarzñdzaniem niñ:
przydzielanie, modyfikowanie i w koþcu zwalnianie pamiöci.
Säowo przydzielanie — powszechnie uĔywany termin, okreĈlajñcy czynnoĈè udostöpniania obszaru
pamiöci — wprowadza w bäñd, poniewaĔ wywoäuje obraz wydzielania deficytowego zasobu,
dla którego wielkoĈè Ĕñdaþ przewyĔsza wielkoĈè zapasów. Na pewno wielu uĔytkowników
wolaäoby mieè wiöcej dostöpnej pamiöci. Dla nowoczesnych systemów problem nie polega
jednak na rozdzielaniu zbyt maäych zapasów dla zbyt wielu uĔytkowników, lecz wäaĈciwym
uĔywaniu i monitorowaniu danego zasobu.
W tym rozdziale przeanalizowane zostanñ wszystkie metody przydzielania pamiöci dla róĔnych
obszarów programu, jednoczeĈnie z ukazaniem ich zalet i wad. Przedstawimy równieĔ pewne
sposoby, pozwalajñce na ustawianie i modyfikacjö zawartoĈci dowolnych obszarów pamiöci,
a takĔe wyjaĈnimy, w jaki sposób naleĔy zablokowaè dane w pamiöci, aby w programach nie
trzeba byäo oczekiwaè na operacje jñdra, które zajmowaäoby siö przerzucaniem danych z obszaru
wymiany.
Przestrze
ħ adresowa procesu
Linux, podobnie jak inne nowoczesne systemy operacyjne, wirtualizuje swój fizyczny zasób
pamiöci. Procesy nie adresujñ bezpoĈrednio pamiöci fizycznej. Zamiast tego jñdro wiñĔe kaĔdy
proces z unikalnñ wirtualnñ przestrzeniñ adresowñ. Jest ona liniowa, a jej adresacja rozpoczyna siö
od zera i wzrasta do pewnej granicznej wartoĈci maksymalnej.
Strony i stronicowanie
Wirtualna przestrzeþ adresowa skäada siö ze stron. Architektura systemu oraz rodzaj maszyny
determinujñ rozmiar strony, który jest staäy: typowymi wartoĈciami sñ na przykäad 4 kB (dla
systemów 32-bitowych) oraz 8 kB (dla systemów 64-bitowych)
1
. Strony sñ albo prawidäowe,
1
Czasami systemy wspierajñ rozmiary stron, które mieszczñ siö w pewnym zakresie. Z tego powodu rozmiar
strony nie jest czöĈciñ interfejsu binarnego aplikacji (ABI). Aplikacje muszñ w sposób programowy uzyskaè
rozmiar strony w czasie wykonania. Zostaäo to opisane w rozdziale 4. i bödzie jednym z tematów poruszonych
w tym rozdziale.
262 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
albo nieprawidäowe. Strona prawidäowa (ang. valid page) zwiñzana jest ze stronñ w pamiöci fizycz-
nej lub jakñĈ dodatkowñ pamiöciñ pomocniczñ, np. partycjñ wymiany lub plikiem na dysku.
Strona nieprawid
äowa (ang. invalid page) nie jest z niczym zwiñzana i reprezentuje nieuĔywany
i nieprzydzielony obszar przestrzeni adresowej. Dostöp do takiej strony spowoduje bäñd seg-
mentacji. Przestrzeþ adresowa nie musi byè koniecznie ciñgäa. Mimo Ĕe jest ona adresowana
w sposób liniowy, zawiera jednak mnóstwo przerw, nieposiadajñcych adresacji.
Program nie moĔe uĔyè strony, która znajduje siö w dodatkowej pamiöci pomocniczej zamiast
w fizycznej. Bödzie to moĔliwe dopiero wtedy, gdy zostanie ona poäñczona ze stronñ w pamiöci
fizycznej. Gdy proces próbuje uzyskaè dostöp do adresu z takiej strony, ukäad zarzñdzania
pamiöciñ (MMU) generuje bäñd strony (ang. page fault). Wówczas wkracza do akcji jñdro, w spo-
sób niewidoczny przerzucajñc Ĕñdanñ stronö z pamiöci pomocniczej do pamiöci fizycznej. Ponie-
waĔ istnieje duĔo wiöcej pamiöci wirtualnej niĔ rzeczywistej (nawet w przypadku systemów
z pojedynczñ wirtualnñ przestrzeniñ adresowñ!), jñdro równieĔ przez caäy czas wyrzuca strony
z pamiöci fizycznej do dodatkowej pamiöci pomocniczej, aby zrobiè miejsce na nowe strony,
przerzucane w drugim kierunku. Jñdro przystöpuje do wyrzucania danych, dla których ist-
nieje najmniejsze prawdopodobieþstwo, iĔ bödñ uĔyte w najbliĔszej przyszäoĈci. Dziöki temu
nastöpuje poprawa wydajnoĈci.
Wspó
ĥdzielenie i kopiowanie podczas zapisu
Wiele stron w pamiöci wirtualnej, a nawet w róĔnych wirtualnych przestrzeniach adresowych
naleĔñcych do oddzielnych procesów, moĔe byè odwzorowanych na pojedynczñ stronö fizycznñ.
Pozwala to róĔnym wirtualnym przestrzeniom adresowym na wspóädzielenie danych w pamiöci
fizycznej. Wspóädzielone dane mogñ posiadaè uprawnienia tylko do odczytu lub zarówno do
odczytu, jak i zapisu.
Gdy proces przeprowadza operacjö zapisu do wspóädzielonej strony, posiadajñcej uprawnienia
do wykonania tej czynnoĈci, mogñ zaistnieè jedna lub dwie sytuacje. Najprostsza wersja polega
na tym, Ĕe jñdro zezwoli na wykonanie zapisu i wówczas wszystkie procesy wspóädzielñce
danñ stronö, bödñ mogäy „zobaczyè” wyniki tej operacji. Zezwolenie wielu procesom na czy-
tanie lub zapis do wspóädzielonej strony wymaga zazwyczaj zapewnienia pewnego poziomu
wspóäpracy i synchronizacji miödzy nimi.
Inaczej jest jednak, gdy ukäad zarzñdzania pamiöciñ przechwyci operacjö zapisu i wygeneruje
wyjñtek; w odpowiedzi, jñdro w sposób niewidoczny stworzy nowñ kopiö strony dla procesu
zapisujñcego i zezwoli dla niej na kontynuowanie zapisu. To rozwiñzanie zwane jest kopiowaniem
podczas zapisu (ang. copy-on-write, w skrócie COW)
2
. Proces faktycznie posiada uprawnienia
do odczytu dla wspóädzielonych danych, co przyczynia siö do oszczödzania pamiöci. Gdy
proces chce zapisaè do wspóädzielonej strony, otrzymuje wówczas na bieĔñco unikalnñ jej kopiö.
Dziöki temu jñdro moĔe dziaäaè w taki sposób, jak gdyby proces zawsze posiadaä swojñ wäasnñ
kopiö strony. PoniewaĔ kopiowanie podczas zapisu jest zaimplementowane dla kaĔdej strony
z osobna, dlatego teĔ duĔy plik moĔe zostaè efektywnie udostöpniony wielu procesom,
którym zostanñ przydzielone unikalne strony fizyczne tylko wówczas, gdy bödñ chciaäy
coĈ w nich zapisywaè.
2
W rozdziale 5. napisano, Ĕe funkcja
fork()
uĔywa metody kopiowania podczas zapisu, aby powieliè i udo-
stöpniè przestrzeþ adresowñ rodzica tworzonemu procesowi potomnemu.
Przydzielanie pami
ýci dynamicznej
_ 263
Regiony pami
ýci
Jñdro rozmieszcza strony w blokach, które posiadajñ pewne wspólne cechy charakterystyczne,
takie jak uprawnienia dostöpu. Bloki te zwane sñ regionami pamiöci (ang. memory regions), segmen-
tami (ang. segments) lub odwzorowaniami (ang. mappings). Pewne rodzaje regionów pamiöci mogñ
istnieè w kaĔdym procesie:
x
Segment tekstu zawiera kod programu dla danego procesu, literaäy äaþcuchowe, staäe oraz
inne dane tylko do odczytu. W systemie Linux segment ten posiada uprawnienia tylko do
odczytu i jest odwzorowany bezpoĈrednio na plik obiektowy (program wykonywalny
lub bibliotekö).
x
Segment stosu, jak sama nazwa wskazuje, zawiera stos wykonania procesu. Segment stosu
rozrasta siö i maleje w sposób dynamiczny, zgodnie ze zmianami struktury stosu. Stos
wykonania zawiera lokalne zmienne oraz dane zwracane z funkcji.
x
Segment danych (lub sterta) zawiera dynamicznñ pamiöè procesu. Do segmentu tego moĔna
zapisaywaè, a jego rozmiar siö zmienia. Sterta jest zwracana przy uĔyciu funkcji
malloc()
(omówionej w nastöpnym podrozdziale).
x
Segment bss
3
zawiera niezainicjalizowane zmienne globalne. Zmienne te majñ specjalne war-
toĈci (w zasadzie same zera), zgodnie ze standardem jözyka C. Linux optymalizuje je przy
uĔyciu dwóch metod. Po pierwsze, poniewaĔ segment bss przeznaczony jest dla prze-
chowywania niezainicjalizowanych danych, wiöc linker (ld) w rzeczywistoĈci nie zapisuje
specjalnych wartoĈci do pliku obiektowego. Powoduje to zmniejszenie rozmiaru pliku binar-
nego. Po drugie, gdy segment ten zostaje zaäadowany do pamiöci, jñdro po prostu odwzo-
rowuje go w trybie kopiowania podczas zapisu na stronö zawierajñcñ same zera, co efek-
tywnie ustawia domyĈlne wartoĈci w zmiennych.
x
WiökszoĈè przestrzeni adresowej zajmuje grupa plików odwzorowanych, takich jak sam
program wykonywalny, róĔne biblioteki — miödzy innymi dla jözyka C, a takĔe pliki
z danymi. ćcieĔka /proc/self/maps lub wynik dziaäania programu pmap sñ bardzo dobrymi
przykäadami plików odwzorowanych w procesie.
Rozdziaä ten omawia interfejsy, które sñ udostöpnione przez system Linux, aby otrzymywaè
i zwalniaè obszary pamiöci, tworzyè i usuwaè nowe odwzorowania oraz wykonywaè inne
czynnoĈci zwiñzane z pamiöciñ.
Przydzielanie pami
ýci dynamicznej
Pamiöè zawiera takĔe automatyczne i statyczne zmienne, lecz podstawñ dziaäania kaĔdego
systemu, który niñ zarzñdza, jest przydzielanie, uĔywanie, a w koþcu zwalnianie pamiöci dyna-
micznej. Pamiöè dynamiczna przydzielana jest w czasie dziaäania programu, a nie kompilacji,
a jej rozmiary mogñ byè nieznane do momentu rozpoczöcia samego procesu przydzielania. Dla
projektanta jest ona uĔyteczna w momencie, gdy zmienia siö iloĈè pamiöci, którñ potrzebuje
tworzony program lub teĔ zmienny jest czas, w ciñgu którego bödzie ona uĔywana, a dodat-
kowo wielkoĈci te nie sñ znane przed uruchomieniem aplikacji. Na przykäad, moĔna zaimple-
mentowaè przechowywanie w pamiöci zawartoĈci jakiegoĈ pliku lub danych wczytywanych
3
Nazwa to relikt historii — jest to skrót od säów: blok rozpoczöty od symbolu (ang. block started by symbol).
264 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
z klawiatury. PoniewaĔ wielkoĈè takiego pliku jest nieznana, a uĔytkownik moĔe wprowadziè
dowolnñ liczbö znaków z klawiatury, rozmiar bufora musi byè zmienny, by programista dyna-
micznie go zwiökszaä, gdy danych zacznie przybywaè.
ēadne zmienne jözyka C nie sñ zapisywane w pamiöci dynamicznej. Na przykäad, jözyk C nie
udostöpnia mechanizmu, który pozwala na odczytanie struktury
pirate_ship
znajdujñcej siö
w takiej pamiöci. Zamiast tego istnieje metoda pozwalajñca na przydzielenie takiej iloĈci pamiöci
dynamicznej, która wystarczy, aby przechowaè w niej strukturö
pirate_ship
. Programista
nastöpnie uĔywa tej pamiöci poprzez posäugiwanie siö wskaĒnikiem do niej — w tym przy-
padku, stosujñc wskaĒnik
struct pirate_ship *
.
Klasycznym interfejsem jözyka C, pozwalajñcym na otrzymanie pamiöci dynamicznej, jest funkcja
malloc()
:
#include <stdlib.h>
void * malloc (size_t size);
Poprawne jej wywoäanie przydziela obszar pamiöci, którego wielkoĈè (w bajtach) okreĈlona
jest w parametrze
size
. Funkcja zwraca wskaĒnik do poczñtku nowo przydzielonego regionu.
ZawartoĈè pamiöci jest niezdefiniowana i nie naleĔy oczekiwaè, Ĕe bödzie zawieraè same zera.
W przypadku bäödu, funkcja
malloc()
zwraca
NULL
oraz ustawia zmiennñ
errno
na
ENOMEM
.
UĔycie funkcji
malloc()
jest raczej proste, tak jak w przypadku poniĔszego przykäadu przy-
dzielajñcego okreĈlonñ liczbö bajtów:
char *p;
/* przydziel 2 kB! */
p = malloc (2048);
if (!p)
perror ("malloc");
Nieskomplikowany jest równieĔ kolejny przykäad, przydzielajñcy pamiöè dla struktury:
struct treasure_map *map;
/*
* przydziel wystarczaj
ąco duĪo pamiĊci, aby przechowaü strukturĊ treasure_map,
* a nast
Ċpnie przypisz adres tego obszaru do wskaĨnika 'map'
*/
map = malloc (sizeof (struct treasure_map));
if (!map)
perror ("malloc");
Jözyk C automatycznie rzutuje wskaĒniki typu
void
na dowolny typ, wystöpujñcy podczas
operacji przypisania. Dlatego teĔ w przypadku powyĔszych przykäadów, nie jest konieczne
rzutowanie typu zwracanej wartoĈci funkcji
malloc()
na typ l-wartoĈci, uĔywanej podczas
operacji przypisania. Jözyk programowania C++ nie wykonuje jednak automatycznego rzu-
towania wskaĒnika
void
. Zgodnie z tym uĔytkownicy jözyka C++ muszñ rzutowaè wyniki
wywoäania funkcji
malloc()
, tak jak pokazano to w poniĔszym przykäadzie:
char *name;
/* przydziel 512 bajtów */
name = (char *) malloc (512);
if (!name)
perror ("malloc");
Przydzielanie pami
ýci dynamicznej
_ 265
Niektórzy programiĈci jözyka C preferujñ wykonywanie rzutowania wyników dowolnej funkcji,
która zwraca wskaĒnik
void
. Dotyczy to równieĔ funkcji
malloc()
. Ten styl programowania
jest jednak niepewny z dwóch powodów. Po pierwsze, moĔe on spowodowaè pominiöcie bäödu
w przypadku, gdy wartoĈè zwracana z funkcji kiedykolwiek ulegnie zmianie i nie bödzie równa
wskaĒnikowi
void
. Po drugie, takie rzutowanie ukrywa bäödy równieĔ w przypadku, gdy
funkcja jest niewäaĈciwie zadeklarowana
4
. Pierwszy z tych powodów nie jest przyczynñ powsta-
wania problemów podczas uĔycia funkcji
malloc()
, natomiast drugi moĔe juĔ ich przysparzaè.
PoniewaĔ funkcja
malloc()
moĔe zwróciè wartoĈè
NULL
, dlatego teĔ jest szczególnie waĔne,
aby projektanci oprogramowania zawsze sprawdzali i obsäugiwali przypadki bäödów. W wielu
programach funkcja
malloc()
nie jest uĔywana bezpoĈrednio, lecz istnieje dla niej stworzony
interfejs programowy (wrapper), który wyprowadza komunikat bäödu i przerywa dziaäanie
programu, gdy zwraca ona wartoĈè
NULL
. Zgodnie z konwencja nazewniczñ, ten ogólny interfejs
programowy zwany jest przez projektantów
xmalloc()
:
/* dzia
áa jak malloc(), lecz koĔczy wykonywanie programu w przypadku niepowodzenia */
void * xmalloc (size_t size)
{
void *p;
p = malloc (size);
if (!p)
{
perror ("xmalloc");
exit (EXIT_FAILURE);
}
return p;
}
Przydzielanie pami
ýci dla tablic
Dynamiczne przydzielanie pamiöci moĔe byè skomplikowane, jeĈli rozmiar danych, przeka-
zany w parametrze
size
, jest równieĔ zmienny. Jednym z tego typu przykäadów jest dynamiczne
przydzielanie pamiöci dla tablic, których rozmiar jednego elementu moĔe byè staäy, lecz liczba
alokowanych elementów jest zmienna.
Aby uproĈciè wykonywanie tej czynnoĈci, jözyk C udostöpnia funkcjö
calloc()
:
#include <stdlib.h>
void * calloc (size_t nr, size_t size);
Poprawne wywoäanie funkcji
calloc()
zwraca wskaĒnik do bloku pamiöci o wielkoĈci wy-
starczajñcej do przechowania tablicy o liczbie elementów okreĈlonej w parametrze
nr
. KaĔdy
z elementów posiada rozmiar
size
. Zgodnie z tym iloĈè pamiöci, przydzielona w przypadku
uĔycia zarówno funkcji
malloc()
, jak i
calloc()
, jest taka sama (obie te funkcje mogñ zwróciè
wiöcej pamiöci, niĔ jest to wymagane, lecz nigdy mniej):
int *x, *y;
x = malloc (50 * sizeof (int));
4
Funkcje niezadeklarowane zwracajñ domyĈlnie wartoĈci o typie
int
. Rzutowanie liczby caäkowitej na wskaĒnik
nie jest wykonywane automatycznie i powoduje powstanie ostrzeĔenia podczas kompilacji programu. UĔycie
rzutowania typów nie pozwala na generowanie takiego ostrzeĔenia.
266 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
if (!x)
{
perror ("malloc");
return -1;
}
y = calloc (50, sizeof (int));
if (!y)
{
perror ("calloc");
return -1;
}
Zachowanie powyĔszych dwóch funkcji nie jest jednak identyczne. W przeciwieþstwie do
funkcji
malloc()
, która nie zapewnia, jaka bödzie zawartoĈè przydzielonej pamiöci, funkcja
calloc()
zeruje wszystkie bajty w zwróconym obszarze pamiöci. Dlatego teĔ kaĔdy z 50 ele-
mentów w tablicy liczb caäkowitych
y
posiada wartoĈè
0
, natomiast wartoĈci elementów tablicy
x
sñ niezdefiniowane. Dopóki w programie nie ma potrzeby natychmiastowego zainicjalizo-
wania wszystkich 50 wartoĈci, programiĈci powinni uĔywaè funkcji
calloc()
, aby zapewniè,
Ĕe elementy tablicy nie sñ wypeänione przypadkowymi danymi. NaleĔy zauwaĔyè, Ĕe zero
binarne moĔe byè róĔne od zera wystöpujñcego w liczbie zmiennoprzecinkowej!
UĔytkownicy czösto chcñ „wyzerowaè” pamiöè dynamicznñ, nawet wówczas, gdy nie uĔywajñ
tablic. W dalszej czöĈci tego rozdziaäu poddana analizie zostanie funkcja
memset()
, która dostar-
cza interfejsu pozwalajñcego na ustawienie wartoĈci dla dowolnego bajta w obszarze pamiöci.
Funkcja
calloc()
wykonuje jednak tö operacjö szybciej, gdyĔ jñdro moĔe od razu udostöpniè
obszar pamiöci, który wypeäniony jest juĔ zerami.
W przypadku bäödu funkcja
calloc()
, podobnie jak
malloc()
, zwraca
–1
oraz ustawia zmiennñ
errno
na wartoĈè
ENOMEM
.
Dlaczego w standardach nie zdefiniowano nigdy funkcji „przydziel i wyzeruj”, róĔnej od
calloc()
, pozostaje tajemnicñ. Projektanci mogñ jednak w prosty sposób zdefiniowaè swój wäa-
sny interfejs:
/* dzia
áa tak samo jak funkcja malloc(), lecz przydzielona pamiĊü zostaje wypeániona zerami */
void * malloc0 (size_t size)
{
return calloc (1, size);
}
MoĔna bez käopotu poäñczyè funkcjö
malloc0()
z poprzednio przedstawionñ funkcjñ
xmalloc()
:
/* dzia
áa podobnie jak malloc(), lecz wypeánia pamiĊü zerami i przerywa dziaáanie programu w przypadku báĊdu */
void * xmalloc0 (size_t size)
{
void *p;
p = calloc (1, size);
if (!p)
{
perror ("xmalloc0");
exit (EXIT_FAILURE);
}
return p;
}
Przydzielanie pami
ýci dynamicznej
_ 267
Zmiana wielko
ļci obszaru przydzielonej pamiýci
Jözyk C dostarcza interfejsu pozwalajñcego na zmianö wielkoĈci (zmniejszenie lub powiöksze-
nie) istniejñcego obszaru przydzielonej pamiöci:
#include <stdlib.h>
void * realloc (void *ptr, size_t size);
Poprawne wywoäanie funkcji
realloc()
zmienia rozmiar regionu pamiöci, wskazywanego
przez
ptr
, na nowñ wartoĈè, której wielkoĈè podana jest w parametrze
size
i wyraĔona w baj-
tach. Funkcja zwraca wskaĒnik do obszaru pamiöci posiadajñcego nowy rozmiar. WskaĒnik ten
nie musi byè równy wartoĈci parametru
ptr
, który byä uĔywany w funkcji podczas wykony-
wania operacji powiökszenia rozmiaru obszaru. JeĈli funkcja
realloc()
nie potrafi powiök-
szyè istniejñcego obszaru pamiöci poprzez zmianö rozmiaru dla wczeĈniej przydzielonego
miejsca, wówczas moĔe ona zarezerwowaè pamiöè dla nowego regionu pamiöci o rozmiarze
size
, wyraĔonym w bajtach, skopiowaè zawartoĈè poprzedniego regionu w nowe miejsce,
a nastöpnie zwolniè niepotrzebny juĔ obszar Ēródäowy. W przypadku kaĔdej operacji zacho-
wana zostaje zawartoĈè dla takiej wielkoĈci obszaru pamiöci, która równa jest mniejszej war-
toĈci z dwóch rozmiarów: poprzedniego i aktualnego. Z powodu ewentualnego istnienia ope-
racji kopiowania, wywoäanie funkcji
realloc()
, które wykonuje powiökszenie obszaru pamiöci,
moĔe byè stosunkowo kosztowne.
JeĈli
size
wynosi zero, rezultat jest taki sam jak w przypadku wywoäania funkcji
free()
z para-
metrem
ptr
.
JeĈli parametr
ptr
jest równy
NULL
, wówczas rezultat wykonania operacji jest taki sam jak dla
oryginalnej funkcji
malloc()
. JeĈli wskaĒnik
ptr
jest róĔny od
NULL
, powinien zostaè zwró-
cony przez wczeĈniejsze wykonanie jednej z funkcji
malloc()
,
calloc()
lub
realloc()
.
W przypadku bäödu, funkcja
realloc()
zwraca
NULL
oraz ustawia zmiennñ
errno
na wartoĈè
ENOMEM
. Stan obszaru pamiöci, wskazywanego przez parametr
ptr
, pozostaje niezmieniony.
RozwaĔmy przykäad programu, który zmniejsza obszar pamiöci. Najpierw naleĔy uĔyè funkcji
calloc()
, która przydzieli wystarczajñcñ iloĈè pamiöci, aby zapamiötaè w niej dwuelementowñ
tablicö struktur
map
:
struct map *p;
/* przydziel pami
Ċü na dwie struktury 'map' */
p = calloc (2, sizeof (struct map));
if (!p)
{
perror ("calloc");
return -1;
}
/* w tym momencie mo
Īna uĪywaü p[0] i p[1]… */
ZaäóĔmy, Ĕe jeden ze skarbów zostaä juĔ znaleziony, dlatego teĔ nie ma potrzeby uĔycia drugiej
mapy. Podjöto decyzjö, Ĕe rozmiar obszaru pamiöci zostanie zmieniony, a poäowa przydzielo-
nego wczeĈniej regionu zostanie zwrócona do systemu (operacja ta nie byäaby wäaĈciwie zbyt
potrzebna, chyba Ĕe rozmiar struktury
map
byäby bardzo duĔy, a program rezerwowaäby dla niej
pamiöè przez däuĔszy czas):
268 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
struct map *r;
/* obecnie wymagana jest tylko pami
Ċü dla jednej mapy */
r = realloc (p, sizeof (struct map));
if (!r)
{
/* nale
Īy zauwaĪyü, Īe 'p' jest wciąĪ poprawnym wskaĨnikiem! */
perror ("realloc");
return -1;
}
/* tu mo
Īna juĪ uĪywaü wskaĨnika 'r'… */
free (r);
W powyĔszym przykäadzie, po wywoäaniu funkcji
realloc()
zostaje zachowany element
p[0]
. Jakiekolwiek dane, które przedtem znajdowaäy siö w tym elemencie, bödñ obecne równieĔ
teraz. JeĈli wywoäanie funkcji siö nie powiedzie, naleĔy zwróciè uwagö na to, Ĕe wskaĒnik
p
nie
zostanie zmieniony i stñd teĔ bödzie wciñĔ poprawny. MoĔna go ciñgle uĔywaè i w koþcu
naleĔy go zwolniè. JeĈli wywoäanie funkcji siö powiedzie, naleĔy zignorowaè wskaĒnik
p
i zamiast
niego uĔyè
r
(który jest przypuszczalnie równy
p
, gdyĔ najprawdopodobniej nastñpiäa zmiana
rozmiaru aktualnie przydzielonego obszaru). Obecnie programista odpowiedzialny bödzie za
zwolnienie pamiöci dla wskaĒnika
r
, gdy tylko przestanie on byè potrzebny.
Zwalnianie pami
ýci dynamicznej
W przeciwieþstwie do obszarów pamiöci przydzielonych automatycznie, które same zostajñ
zwolnione, gdy nastöpuje przesuniöcie wskaĒnika stosu, dynamicznie przydzielone regiony
pamiöci pozostajñ trwaäñ czöĈciñ przestrzeni adresowej procesu, dopóki nie zostanñ röcznie
zwolnione. Dlatego teĔ programista odpowiedzialny jest za zwolnienie do systemu dynamicz-
nie przydzielonej pamiöci (oba rodzaje przydzielonej pamiöci — statyczna i dynamiczna —
zostajñ zwolnione, gdy caäy proces koþczy swoje dziaäanie).
Pamiöè, przydzielona za pomocñ funkcji
malloc()
,
calloc()
lub
realloc()
, musi zostaè zwol-
niona do systemu, jeĈli nie jest juĔ wiöcej uĔywana. W tym celu stosuje siö funkcjö
free()
:
#include <stdlib.h>
void free (void *ptr);
Wywoäanie funkcji
free()
zwalnia pamiöè, wskazywanñ przez wskaĒnik
ptr
. Parametr
ptr
powinien byè zainicjalizowany przez wartoĈè zwróconñ wczeĈniej przez funkcjö
malloc()
,
calloc()
lub
realloc()
. Oznacza to, Ĕe nie moĔna uĔyè funkcji
free()
, aby zwolniè fragment
obszaru pamiöci — na przykäad poäowö — poprzez przekazanie do niej parametru wskazujñcego
na Ĉrodek wczeĈniej przydzielonego obszaru.
WskaĒnik
ptr
moĔe byè równy
NULL
, co powoduje, Ĕe funkcja
free()
od razu wraca do procesu
wywoäujñcego. Dlatego teĔ niepotrzebne jest sprawdzanie wskaĒnika
ptr
przed wywoäaniem
funkcji
free()
.
Oto przykäad uĔycia funkcji
free()
:
void print_chars (int n, char c)
{
int i;
for (i = 0; i < n; i++)
Przydzielanie pami
ýci dynamicznej
_ 269
{
char *s;
int j;
/*
* Przydziel i wyzeruj tablic
Ċ znaków o liczbie elementów równej i+2.
* Nale
Īy zauwaĪyü, Īe wywoáanie 'sizeof (char)' zwraca zawsze wartoĞü 1.
*/
s = calloc (i + 2, 1);
if (!s)
{
perror ("calloc");
break;
}
for (j = 0; j < i + 1; j++)
s[j] = c;
printf ("%s\n", s);
/* Wszystko zrobione, obecnie nale
Īy zwolniü pamiĊü. */
free (s);
}
}
PowyĔszy przykäad przydziela pamiöè dla
n
tablic typu
char
, zawierajñcych coraz wiökszñ
liczbö elementów, poczynajñc od dwóch (2 bajty), a koþczñc na
n + 1
elementach (n + 1 baj-
tów). Wówczas dla kaĔdej tablicy nastöpuje w pötli zapisanie znaku
c
do poszczególnych jej
elementów, za wyjñtkiem ostatniego (pozostawiajñc tam bajt o wartoĈci
0
, który jednoczeĈnie
jest ostatnim w danej tablicy), wyprowadzenie zawartoĈci tablicy w postaci äaþcucha znaków,
a nastöpnie zwolnienie przydzielonej dynamicznie pamiöci.
Wywoäanie funkcji
print_chars()
z parametrami
n
równym
5
, a
c
równym
X
, wymusi uzy-
skanie nastöpujñcego wyniku:
X
XX
XXX
XXXX
XXXXX
Istniejñ oczywiĈcie duĔo efektywniejsze metody pozwalajñce na zaimplementowanie takiej
funkcji. WaĔne jest jednak, Ĕe pamiöè moĔna dynamicznie przydzielaè i zwalniaè, nawet wów-
czas, gdy rozmiar i liczba przydzielonych obszarów znana jest tylko w momencie dziaäania
programu.
Systemy uniksowe, takie jak SunOS i SCO, udostöpniajñ wäasny wariant funkcji
free()
,
zwany
cfree()
, który w zaleĔnoĈci od systemu dziaäa tak samo jak
free()
lub posiada
trzy parametry i wówczas zachowuje siö jak funkcja
calloc()
. Funkcja
free()
w sys-
temie Linux moĔe obsäuĔyè pamiöè uzyskanñ dziöki uĔyciu dowolnego mechanizmu,
säuĔñcego do jej przydzielania i juĔ omówionego. Funkcja
cfree()
nie powinna byè
uĔywana, za wyjñtkiem zapewnienia wstecznej kompatybilnoĈci. Wersja tej funkcji dla
Linuksa jest identyczna z
free()
.
NaleĔy zauwaĔyè, Ĕe gdyby w powyĔszym przykäadzie nie uĔyto funkcji
free()
, pojawiäyby
siö pewne nastöpstwa tego. Program mógäby nigdy nie zwolniè zajötego obszaru do systemu
i co gorsze, straciè swoje jedyne odwoäanie do pamiöci — wskaĒnik
s
— i przez to spowodo-
waè, Ĕe dostöp do niej staäby siö w ogóle niemoĔliwy. Ten rodzaj bäödu programistycznego
270 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
zwany jest wyciekaniem pamiöci (ang. memory leak). Wyciekanie pamiöci i tym podobne pomyäki,
zwiñzane z pamiöciñ dynamicznñ, sñ najczöstszymi i niestety najbardziej szkodliwymi bäödami
wystöpujñcymi podczas programowania w jözyku C. PoniewaĔ jözyk C zrzuca caäñ odpowie-
dzialnoĈè za zarzñdzanie pamiöciñ na programistów, muszñ oni zwracaè szczególnñ uwagö na
wszystkie przydzielone obszary.
Równie czösto spotykanñ puäapkñ jözyka C jest uĔywanie zasobów po ich zwolnieniu. Problem
ten wystöpuje w momencie, gdy blok pamiöci zostaje zwolniony, a nastöpnie ponownie uĔyty.
Gdy tylko funkcja
free()
zwolni dany obszar pamiöci, program nie moĔe juĔ ponownie uĔy-
waè jego zawartoĈci. ProgramiĈci powinni zwracaè szczególnñ uwagö na zawieszone wskaĒ-
niki lub wskaĒniki róĔne od
NULL
, które pomimo tego wskazujñ na niepoprawne obszary
pamiöci. Istniejñ dwa powszechnie uĔywane narzödzia pomagajñce w tych sytuacjach; sñ to Elec-
tric Fence i valgrind
5
.
Wyrównanie
Wyrównanie danych dotyczy relacji pomiödzy ich adresem oraz obszarami pamiöci udostöp-
nianymi przez sprzöt. Zmienna posiadajñca adres w pamiöci, który jest wielokrotnoĈciñ jej
rozmiaru, zwana jest zmiennñ naturalnie wyrównanñ. Na przykäad, zmienna 32-bitowa jest natu-
ralnie wyrównana, jeĈli posiada adres w pamiöci, który jest wielokrotnoĈciñ 4 — oznacza to, Ĕe
najniĔsze dwa bity adresu sñ równe zeru. Dlatego teĔ typ danych, którego rozmiar wynosi 2
n
bajtów, musi posiadaè adres, którego n najmniej znaczñcych bitów jest ustawionych na zero.
Reguäy, które dotyczñ wyrównania, pochodzñ od sprzötu. Niektóre architektury maszynowe
posiadajñ bardzo rygorystyczne wymagania dotyczñce wyrównania danych. W przypadku
pewnych systemów, zaäadowanie danych, które nie sñ wyrównane, powoduje wygenerowanie
puäapki procesora. Dla innych systemów dostöp do niewyrównanych danych jest bezpieczny,
lecz zwiñzany z pogorszeniem sprawnoĈci dziaäania. Podczas tworzenia kodu przenoĈnego
naleĔy unikaè problemów zwiñzanych z wyrównaniem. TakĔe wszystkie uĔywane typy danych
powinny byè naturalnie wyrównane.
Przydzielanie pami
ýci wyrównanej
W wiökszoĈci przypadków kompilator oraz biblioteka jözyka C w sposób przezroczysty obsäu-
gujñ zagadnienia, zwiñzane z wyrównaniem. POSIX definiuje, Ĕe obszar pamiöci, zwracany
w wyniku wykonania funkcji
malloc()
,
calloc()
oraz
realloc()
, musi byè prawidäowo
wyrównany dla kaĔdego standardowego typu danych jözyka C. W przypadku Linuksa funkcje
te zawsze zwracajñ obszar pamiöci, która wyrównana jest do adresu bödñcego wielokrotnoĈciñ
oĈmiu bajtów w przypadku systemów 32-bitowych oraz do adresu, bödñcego wielokrotnoĈciñ
szesnastu bajtów dla systemów 64-bitowych.
Czasami programiĈci Ĕñdajñ przydzielenia takiego obszaru pamiöci dynamicznej, który wyrów-
nany jest do wiökszego rozmiaru, posiadajñcego na przykäad wielkoĈè strony. Mimo istnienia
róĔnych argumentacji, najbardziej podstawowym wymaganiem jest zdefiniowanie prawidäowo
wyrównanych buforów, uĔywanych podczas bezpoĈrednich operacji blokowych wejĈcia i wyj-
Ĉcia lub innej komunikacji miödzy oprogramowaniem a sprzötem. W tym celu POSIX 1003.1d
udostöpnia funkcjö zwanñ
posix_memalign()
:
5
Znajdujñ siö one odpowiednio w nastöpujñcych miejscach: http://perens.com/FreeSoftware/ElectricFence/ oraz
http://valgrind.org.
Przydzielanie pami
ýci dynamicznej
_ 271
/* nale
Īy uĪyü jednej z dwóch poniĪszych definicji - kaĪda z nich jest odpowiednia */
#define _XOPEN_SOURCE 600
#define _GNU_SOURCE
#include <stdlib.h>
int posix_memalign (void **memptr, size_t alignment, size_t size);
Poprawne wywoäanie funkcji
posix_memalign()
przydziela pamiöè dynamicznñ o rozmiarze
przekazanym w parametrze
size
i wyraĔonym w bajtach, zapewniajñc jednoczeĈnie, Ĕe obszar
ten zostanie wyrównany do adresu pamiöci, bödñcego wielokrotnoĈciñ parametru
alignment
.
Parametr
alignment
musi byè potögñ liczby 2 oraz wielokrotnoĈciñ rozmiaru wskaĒnika
void
.
Adres przydzielonej pamiöci zostaje umieszczony w parametrze
memptr
, a funkcja zwraca zero.
W przypadku bäödu nie nastöpuje przydzielenie pamiöci, parametr
memptr
ma wartoĈè nieokre-
Ĉlonñ, a funkcja zwraca jednñ z poniĔszych wartoĈci kodów bäödu:
EINVAL
Parametr
alignment
nie jest potögñ liczby 2 lub wielokrotnoĈciñ rozmiaru wskaĒnika
void
.
ENOMEM
Nie ma wystarczajñcej iloĈci pamiöci, aby dokoþczyè rozpoczötñ operacjö przydzielania
pamiöci.
NaleĔy zauwaĔyè, Ĕe zmienna
errno
nie zostaje ustawiona — funkcja bezpoĈrednio zwraca
kod bäödu.
Obszar pamiöci, uzyskany za pomocñ funkcji
posix_memalign()
, moĔe zostaè zwolniony przy
uĔyciu
free()
. Sposób uĔycia funkcji jest prosty:
char *buf;
int ret;
/* przydziel 1 kB pami
Ċci wyrównanej do adresu równego wielokrotnoĞci 256 bajtów */
ret = posix_memalign (&buf, 256, 1024);
if (ret)
{
fprintf (stderr, "posix_memalign: %s\n", strerror (ret));
return -1;
}
/* tu mo
Īna uĪywaü pamiĊci, wskazywanej przez 'buf'… */
free (buf);
Starsze interfejsy. Zanim w standardzie POSIX zostaäa zdefiniowana funkcja
posix_memalign()
,
systemy BSD oraz SunOS udostöpniaäy odpowiednio nastöpujñce interfejsy:
#include <malloc.h>
void * valloc (size_t size);
void * memalign (size_t boundary, size_t size);
Funkcja
valloc()
dziaäa identycznie jak
malloc()
, za wyjñtkiem tego, Ĕe przydzielona pamiöè
jest wyrównana do rozmiaru strony. Jak napisano w rozdziale 4., rozmiar systemowej strony
moĔna äatwo uzyskaè po wywoäaniu funkcji
getpagesize()
.
Funkcja
memalign()
jest podobna, lecz wyrównuje przydzielonñ pamiöè do rozmiaru przeka-
zanego w parametrze
boundary
i wyraĔonego w bajtach. Rozmiar ten musi byè potögñ liczby 2.
W poniĔszym przykäadzie obie wspomniane funkcje alokacyjne zwracajñ blok pamiöci o wiel-
koĈci wystarczajñcej do przechowania struktury
ship
. Jest on wyrównany do rozmiaru strony:
272
_
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
struct ship *pirate, *hms;
pirate = valloc (sizeof (struct ship));
if (!pirate)
{
perror ("valloc");
return -1;
}
hms = memalign (getpagesize ( ), sizeof (struct ship));
if (!hms)
{
perror ("memalign");
free (pirate);
return -1;
}
/* tu mo
Īna uĪywaü obszaru pamiĊci wskazywanego przez 'pirate' i 'hms'… */
free (hms);
free (pirate);
W przypadku systemu Linux obszar pamiöci, otrzymany za pomocñ tych dwóch funkcji, moĔe
zostaè zwolniony po wywoäaniu funkcji
free()
. Nie musi tak byè jednak w przypadku innych
systemów uniksowych, gdyĔ niektóre z nich nie dostarczajñ Ĕadnego mechanizmu pozwala-
jñcego na bezpieczne zwolnienie pamiöci przydzielonej za pomocñ wyĔej wspomnianych funkcji.
Dla programów, które powinny byè przenoĈne, moĔe nie istnieè inny wybór poza niezwalnia-
niem pamiöci przydzielonej za pomocñ tych interfejsów!
ProgramiĈci Linuksa powinni uĔywaè powyĔszych funkcji tylko wtedy, gdy naleĔy zachowaè
kompatybilnoĈè ze starszymi systemami; funkcja
posix_memalign()
jest lepsza. UĔycie trzech
wspomnianych funkcji jest niezbödne jedynie wtedy, gdy wymagany jest inny rodzaj wyrów-
nania, niĔ dostarczony razem z funkcjñ
malloc()
.
Inne zagadnienia zwi
ézane z wyrównaniem
Problemy zwiñzane z wyrównaniem obejmujñ wiökszy obszar zagadnieþ niĔ tylko wyrównanie
naturalne dla standardowych typów danych oraz dynamiczny przydziaä pamiöci. Na przykäad,
typy niestandardowe oraz zäoĔone posiadajñ bardziej skomplikowane wymagania niĔ typy
standardowe. Ponadto, zagadnienia zwiñzane z wyrównaniem sñ szczególnie waĔne w przy-
padku przypisywania wartoĈci miödzy wskaĒnikami róĔnych typów oraz uĔycia rzutowania.
Typy niestandardowe. Niestandardowe i zäoĔone typy danych posiadajñ wiöksze wymagania
dotyczñce wyrównania przydzielonego obszaru pamiöci. Zachowanie zwykäego wyrównania
naturalnego nie jest wystarczajñce. W tych przypadkach stosuje siö cztery poniĔsze reguäy:
x
Wyrównanie dla struktury jest równe wyrównaniu dla najwiökszego pod wzglödem roz-
miaru typu danych, z których zbudowane sñ jej pola. Na przykäad, jeĈli najwiökszy typ
danych w strukturze jest 32-bitowñ liczbñ caäkowitñ, która jest wyrównana do adresu bödñ-
cego wielokrotnoĈciñ czterech bajtów, wówczas sama struktura musi byè takĔe wyrównana
do adresu bödñcego wielokrotnoĈciñ co najmniej czterech bajtów.
x
UĔycie struktur wprowadza takĔe koniecznoĈè stosowania wypeänienia, które jest wyko-
rzystywane w celu zapewnienia, Ĕe kaĔdy typ skäadowy bödzie poprawnie wyrównany,
zgodnie z jego wymaganiami. Dlatego teĔ, jeĈli po polu posiadajñcym typ
char
(o wyrówna-
niu prawdopodobnie równym jednemu bajtowi) pojawi siö pole z typem
int
(posiadajñce
Zarz
édzanie segmentem danych
_ 273
wyrównanie prawdopodobnie równe czterem bajtom), wówczas kompilator wstawi dodat-
kowe trzy bajty wypeänienia pomiödzy tymi dwoma polami o róĔnych typach danych, aby
zapewniè, Ĕe
int
znajdzie siö w obszarze wyrównanym do wielokrotnoĈci czterech
bajtów. ProgramiĈci czasami porzñdkujñ pola w strukturze — na przykäad, wedäug male-
jñcego rozmiaru typów skäadowych — aby zminimalizowaè obszar pamiöci „tracony” na
wypeänienie. Opcja kompilatora GCC, zwana
-Wpadded
, moĔe pomóc w tym przypadku,
poniewaĔ generuje ostrzeĔenie w momencie, gdy kompilator wstawia domyĈlne wypeänienia.
x
Wyrównanie dla unii jest równe wyrównaniu dla najwiökszego pod wzglödem rozmiaru
typu danych, z których zbudowane sñ jej pola.
x
Wyrównanie dla tablicy jest równe wyrównaniu dla jej podstawowego typu danych. Dla-
tego teĔ wymagania dla tablic sñ równe wymaganiu dotyczñcemu pojedynczego elementu,
z których siö skäadajñ tablice. Zachowanie to powoduje, Ĕe wszystkie elementy tablicy
posiadajñ wyrównanie naturalne.
Dzia
ĥania na wskaŚnikach. PoniewaĔ kompilator w sposób przezroczysty obsäuguje wiökszoĈè
Ĕñdaþ zwiñzanych z wyrównaniem, dlatego teĔ, aby doĈwiadczyè ewentualnych problemów,
wymagany jest wiökszy wysiäek. Mimo to jest nieprawdñ, Ĕe nie istniejñ komplikacje zwiñzane
z wyrównaniem, gdy uĔywa siö wskaĒników i rzutowania.
Dostöp do danych poprzez rzutowanie wskaĒnika z bloku pamiöci o mniejszej wartoĈci wyrów-
nania na blok, posiadajñcy wiökszñ wartoĈè wyrównania, moĔe spowodowaè, Ĕe dane te nie bödñ
wäaĈciwie wyrównane dla typu o wiökszym rozmiarze. Na przykäad, przypisanie zmiennej
c
do
badnews
w poniĔszym fragmencie kodu powoduje, Ĕe zmienna ta bödzie zrzutowana na
typ
unsigned long
:
char greeting[] = "Ahoj Matey";
char *c = greeting[1];
unsigned long badnews = *(unsigned long *) c;
Typ
unsigned long
jest najprawdopodobniej wyrównany do adresu bödñcego wielokrotnoĈciñ
oĈmiu bajtów; zmienna
c
prawie na pewno przesuniöta jest o 1 bajt poza tö granicö. Odczytanie
zmiennej
c
podczas wykonywania rzutowania spowoduje powstanie bäödu wyrównania.
W zaleĔnoĈci od architektury moĔe byè to przyczynñ róĔnych zachowaþ, poczynajñc od mniej
waĔnych, np. pogorszenie sprawnoĈci dziaäania, a koþczñc na powaĔnych, jak zaäamanie pro-
gramu. W architekturach maszynowych, które potrafiñ wykryè, lecz nie mogñ poprawnie obsäu-
Ĕyè bäödów wyrównania, jñdro wysyäa do takich niepoprawnych procesów sygnaä
SIGBUS
, który
przerywa ich dziaäanie. Sygnaäy zostanñ omówione w rozdziale 9.
Przykäady podobne do powyĔszego sñ czöĈciej spotykane, niĔ sñdzimy. Niepoprawne konstruk-
cje programowe, spotykane w Ĉwiecie realnym, nie bödñ wyglñdaè tak bezmyĈlnie, lecz bödñ
najprawdopodobniej trudniejsze do wykrycia.
Zarz
édzanie segmentem danych
Od zawsze system Unix udostöpniaä interfejsy pozwalajñce na bezpoĈrednie zarzñdzanie seg-
mentem danych. Jednak wiökszoĈè programów nie posiada bezpoĈredniego dostöpu do tych
interfejsów, poniewaĔ funkcja
malloc()
i inne sposoby przydzielania pamiöci sñ äatwiejsze
w uĔyciu, a jednoczeĈnie posiadajñ wiöksze moĔliwoĈci. Interfejsy te zostanñ jednak omówione,
274 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
aby zaspokoiè ciekawoĈè czytelników i udostöpniè dociekliwym programistom metodö pozwa-
lajñcñ na zaimplementowanie swojego wäasnego mechanizmu przydzielania pamiöci, opartego
na stercie:
#include <unistd.h>
int brk (void *end);
void * sbrk (intptr_t increment);
Funkcje te dziedziczñ swoje nazwy z dawnych systemów uniksowych, dla których sterta i stos
znajdowaäy siö w tym samym segmencie. Przydzielanie obszarów pamiöci dynamicznej na
stercie powoduje jej narastanie od dolnej czöĈci segmentu, w kierunku adresów wyĔszych;
stos roĈnie w kierunku przeciwnym — od szczytu segmentu do niĔszych adresów. Linia gra-
niczna pomiödzy tymi dwoma strukturami danych zwana jest podziaäem lub punktem podziaäu
(ang. break lub break point). W nowoczesnych systemach operacyjnych, w których segment danych
posiada swoje wäasne odwzorowanie pamiöci, koþcowy adres tego odwzorowania w dalszym
ciñgu zwany jest punktem podziaäu.
Wywoäanie funkcji
brk()
ustawia punkt podziaäu (koniec segmentu danych) na adres przeka-
zany w parametrze
end
. W przypadku sukcesu, funkcja zwraca wartoĈè
0
. W przypadku bäödu,
zwraca
–1
oraz ustawia zmiennñ
errno
na
ENOMEM
.
Wywoäanie funkcji
sbrk()
zwiöksza adres koþca segmentu o wartoĈè przekazanñ w parame-
trze
increment
, który moĔe byè przyrostem dodatnim lub ujemnym. Funkcja
sbrk()
zwraca
uaktualnionñ wartoĈè poäoĔenia punktu podziaäu. Dlatego teĔ uĔycie parametru
increment
równego zeru powoduje wyprowadzenie aktualnej wartoĈci poäoĔenia punktu podziaäu:
printf ("Aktualny punkt podzia
Īu posiada adres %p\n", sbrk (0));
Oba standardy — POSIX i C — celowo nie definiujñ Ĕadnej z powyĔszych funkcji. Prawie
wszystkie systemy uniksowe wspierajñ jednak jednñ lub obie te funkcje. Programy przenoĈne
powinny uĔywaè interfejsów zdefiniowanych w standardach.
Anonimowe odwzorowania w pami
ýci
W celu wykonania operacji przydzielania pamiöci, zaimplementowanej w bibliotece glibc, uĔy-
wany jest segment danych oraz odwzorowania pamiöci. Klasycznñ metodñ, zastosowanñ
w celu implementacji funkcji
malloc()
, jest podziaä segmentu danych na ciñg partycji o roz-
miarach potögi liczby 2 oraz zwracanie tego obszaru, który najlepiej pasuje do Ĕñdanej wiel-
koĈci. Zwalnianie pamiöci jest prostym oznaczaniem, Ĕe dana partycja jest „wolna”. Kiedy
graniczñce ze sobñ partycje sñ nieuĔywane, mogñ zostaè poäñczone w jeden wiökszy obszar
pamiöci. JeĈli szczyt sterty jest zupeänie nieprzydzielony, system moĔe uĔyè funkcji
brk()
, aby
obniĔyè adres poäoĔenia punktu podziaäu, a przez to zmniejszyè rozmiar tej struktury danych
i zwróciè pamiöè do jñdra.
Algorytm ten zwany jest schematem przydziaäu wspieranej pamiöci (ang. buddy memory allocation
scheme). Posiada takie zalety jak prödkoĈè i prostota, ale równieĔ wady w postaci dwóch rodza-
jów fragmentacji. Fragmentacja wewnötrzna (ang. internal fragmentation) wystöpuje wówczas, gdy
wiöcej pamiöci, niĔ zaĔñdano, zostanie uĔyte w celu wykonania operacji przydziaäu. Wynikiem
tego jest nieefektywne uĔycie dostöpnej pamiöci. Fragmentacja zewnötrzna (ang. external fragmen-
tation) wystöpuje wówczas, gdy istnieje wystarczajñca iloĈè pamiöci, aby zapewniè wykonanie
operacji przydziaäu, lecz jest ona podzielona na dwa lub wiöcej niesñsiadujñcych ze sobñ frag-
Anonimowe odwzorowania w pami
ýci
_ 275
mentów. Fragmentacja ta moĔe powodowaè nieefektywne uĔycie pamiöci (poniewaĔ moĔe zo-
staè uĔyty wiökszy, mniej pasujñcy blok) lub niepoprawne wykonanie operacji jej przydziaäu
(jeĈli nie ma innych bloków).
Ponadto, schemat ten pozwala, aby pewien przydzielony obszar mógä „unieruchomiè” inny, co
moĔe spowodowaè, Ĕe biblioteka glibc nie bödzie mogäa zwróciè zwolnionej pamiöci do jñdra.
ZaäóĔmy, Ĕe istniejñ dwa przydzielone obszary pamiöci: blok A i blok B. Blok A znajduje siö
dokäadnie w punkcie podziaäu, a blok B zaraz pod nim. Nawet jeĈli program zwolni blok B,
biblioteka glibc nie bödzie mogäa uaktualniè poäoĔenia punktu podziaäu, dopóki blok A równieĔ
nie zostanie zwolniony. W ten sposób aplikacje, których czas Ĕycia w systemie jest däugi, mogñ
unieruchomiè wszystkie inne przydzielone obszary pamiöci.
Nie zawsze jest to problemem, gdyĔ biblioteka glibc nie zwraca w sposób rutynowy pamiöci
do systemu
6
. Sterta zazwyczaj nie zostaje zmniejszona po kaĔdej operacji zwolnienia pamiöci.
Zamiast tego biblioteka glibc zachowuje zwolnionñ pamiöè, aby uĔyè jej w nastöpnej operacji
przydzielania. Tylko wówczas, gdy rozmiar sterty jest znaczñco wiökszy od iloĈci przydzielonej
pamiöci, biblioteka glibc faktycznie zmniejsza wielkoĈè segmentu danych. Przydziaä duĔej iloĈci
pamiöci moĔe jednak przeszkodziè temu zmniejszeniu.
Zgodnie z tym, w przypadku przydziaäów duĔej iloĈci pamiöci, w bibliotece glibc nie jest uĔy-
wana sterta. Biblioteka glibc tworzy anonimowe odwzorowanie w pamiöci, aby zapewniè poprawne
wykonanie Ĕñdania przydziaäu. Anonimowe odwzorowania w pamiöci sñ podobne do odwzo-
rowaþ dotyczñcych plików i omówionych w rozdziale 4., za wyjñtkiem tego, Ĕe nie sñ zwiñ-
zane z Ĕadnym plikiem — stñd teĔ przydomek „anonimowy”. Takie anonimowe odwzorowanie
jest po prostu duĔym blokiem pamiöci, wypeänionym zerami i gotowym do uĔycia. NaleĔy
traktowaè go jako nowñ stertö uĔywanñ wyäñcznie w jednej operacji przydzielania pamiöci.
PoniewaĔ takie odwzorowania sñ umieszczane poza stertñ, nie przyczyniajñ siö do fragmentacji
segmentu danych.
Przydzielanie pamiöci za pomocñ anonimowych odwzorowaþ ma kilka zalet:
x
Nie wystöpuje fragmentacja. Gdy program nie potrzebuje juĔ anonimowego odwzorowania
w pamiöci, jest ono usuwane, a pamiöè zostaje natychmiast zwrócona do systemu.
x
MoĔna zmieniaè rozmiar anonimowych odwzorowaþ w pamiöci, posiadajñ one modyfi-
kowane uprawnienia, a takĔe mogñ otrzymywaè poradö — podobnie, jak ma to miejsce
w przypadku zwykäych odwzorowaþ (szczegóäy w rozdziale 4.).
x
KaĔdy przydziaä pamiöci realizowany jest w oddzielnym odwzorowaniu. Nie ma potrzeby
uĔycia globalnej sterty.
Istniejñ równieĔ wady uĔywania anonimowych odwzorowaþ w pamiöci, w porównaniu z uĔy-
ciem sterty:
x
Rozmiar kaĔdego odwzorowania w pamiöci jest caäkowitñ wielokrotnoĈciñ rozmiaru strony
systemowej. Zatem takie operacje przydziaäów, dla których rozmiary nie sñ caäkowitñ wie-
lokrotnoĈciñ rozmiaru strony, generujñ powstawanie nieuĔywanych obszarów „wolnych”.
Problem przestrzeni wolnej dotyczy gäównie maäych obszarów przydziaäu, dla których
pamiöè nieuĔywana jest stosunkowo duĔa w porównaniu z rozmiarem przydzielonego
bloku.
6
W celu przydzielania pamiöci, biblioteka glibc uĔywa równieĔ duĔo bardziej zaawansowanego algorytmu niĔ
zwykäego schematu przydziaäu wspieranej pamiöci. Algorytm ten zwany jest algorytmem areny (ang. arena algorithm).
276
_
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
x
Tworzenie nowego odwzorowania w pamiöci wymaga wiökszego nakäadu pracy niĔ zwra-
canie pamiöci ze sterty, które moĔe w ogóle nie obciñĔaè jñdra. Im obszar przydziaäu jest
mniejszy, tym to zjawisko jest bardziej widoczne.
Porównujñc zalety i wady, moĔna stwierdziè, Ĕe funkcja
malloc()
w bibliotece glibc uĔywa
segmentu danych, aby zapewniè poprawne wykonanie operacji przydziaäu niewielkich
obszarów, natomiast anonimowych odwzorowaþ w pamiöci, aby zapewniè przydzielenie
duĔych obszarów. Próg dziaäania jest konfigurowalny (szczegóäy w podrozdziale Zaawan-
sowane operacje przydziaäu pamiöci, znajdujñcym siö w dalszej czöĈci tego rozdziaäu) i moĔe
byè inny dla kaĔdej wersji biblioteki glibc. Obecnie próg wynosi 128 kB: operacje przydziaäu
o obszarach mniejszych lub równych 128 kB uĔywajñ sterty, natomiast wiöksze przydziaäy
korzystajñ z anonimowych odwzorowaþ w pamiöci.
Tworzenie anonimowych odwzorowa
ħ w pamiýci
Wymuszenie uĔycia mechanizmu odwzorowania w pamiöci zamiast wykorzystania sterty
w celu wykonania okreĈlonego przydziaäu, kreowanie wäasnego systemu zarzñdzajñcego przy-
dziaäem pamiöci, röczne tworzenie anonimowego odwzorowania w pamiöci — te wszystkie
operacje sñ äatwe do zrealizowania w systemie Linux. W rozdziale 4. napisano, Ĕe odwzoro-
wanie w pamiöci moĔe zostaè utworzone przez funkcjö systemowñ
mmap()
, natomiast usuniöte
przez funkcjö systemowñ
munmap()
:
#include <sys/mman.h>
void * mmap (void *start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap (void *start, size_t length);
Kreowanie anonimowego odwzorowania w pamiöci jest nawet prostsze niĔ tworzenie odwzo-
rowania opartego na pliku, poniewaĔ nie trzeba tego pliku otwieraè i nim zarzñdzaè. Podsta-
wowñ róĔnicñ miödzy tymi dwoma rodzajami odwzorowania jest specjalny znacznik, wska-
zujñcy, Ĕe dane odwzorowanie jest anonimowe.
Oto przykäad:
void *p;
p = mmap (NULL, /* niewa
Īne, w jakim miejscu pamiĊci */
512 * 1024, /* 512 kB */
PROT_READ | PROT_WRITE, /* zapis/odczyt */
MAP_ANONYMOUS | MAP_PRIVATE, /* odwzorowanie anonimowe i prywatne */
-1, /* deskryptor pliku (ignorowany) */
0); /* przesuni
Ċcie (ignorowane) */
if (p == MAP_FAILED)
perror ("mmap");
else
/* 'p' wskazuje na obszar 512 kB anonimowej pami
Ċci… */
W wiökszoĈci anonimowych odwzorowaþ parametry funkcji
mmap()
sñ takie same jak w powyĔ-
szym przykäadzie, oczywiĈcie za wyjñtkiem rozmiaru, przekazanego w parametrze
length
i wyraĔonego w bajtach, który jest okreĈlany przez programistö. Pozostaäe parametry sñ nastö-
pujñce:
x
Pierwszy parametr,
start
, ustawiony jest na wartoĈè
NULL
, co oznacza, Ĕe anonimowe
odwzorowanie moĔe rozpoczñè siö w dowolnym miejscu w pamiöci — decyzja w tym
przypadku naleĔy do jñdra. Podawanie wartoĈci róĔnej od
NULL
jest dopuszczalne, dopóki
Anonimowe odwzorowania w pami
ýci
_ 277
jest ona wyrównana do wielkoĈci strony, lecz ogranicza to przenoĈnoĈè. PoäoĔenie odwzo-
rowania jest rzadko wykorzystywane przez programy.
x
Parametr
prot
zwykle ustawia oba bity
PROT_READ
oraz
PROT_WRITE
, co powoduje, Ĕe
odwzorowanie posiada uprawienia do odczytu i zapisu. Odwzorowanie bez uprawnieþ nie
ma sensu, gdyĔ nie moĔna z niego czytaè ani do niego zapisywaè. Z drugiej strony, zezwo-
lenie na wykonywanie kodu z anonimowego odwzorowania jest rzadko potrzebne, a jed-
noczeĈnie tworzy potencjalnñ lukö bezpieczeþstwa.
x
Parametr
flags
ustawia bit
MAP_ANONYMOUS
, który oznacza, Ĕe odwzorowanie jest anoni-
mowe, oraz bit
MAP_PRIVATE
, który nadaje odwzorowaniu status prywatnoĈci.
x
Parametry
fd
i
offset
sñ ignorowane, gdy ustawiony jest znacznik
MAP_ANONYMOUS
. Nie-
które starsze systemy oczekujñ jednak, Ĕe w parametrze
fd
zostanie przekazana wartoĈè
–1
,
dlatego teĔ warto to uczyniè, gdy waĔnym czynnikiem jest przenoĈnoĈè.
Pamiöè, otrzymana za pomocñ mechanizmu anonimowego odwzorowania, wyglñda tak samo
jak pamiöè ze sterty. Jednñ korzyĈciñ z uĔycia anonimowego odwzorowania jest to, Ĕe strony
sñ juĔ wypeänione zerami. Jest to wykonywane bez jakichkolwiek kosztów, poniewaĔ jñdro
odwzorowuje anonimowe strony aplikacji na stronö wypeänionñ zerami, uĔywajñc do tego
celu mechanizmu kopiowania podczas zapisu. Dlatego teĔ nie jest wymagane uĔycie funkcji
memset()
dla zwróconego obszaru pamiöci. Faktycznie istnieje jedna korzyĈè z uĔycia funkcji
calloc()
zamiast zestawu
malloc()
oraz
memset()
: biblioteka glibc jest poinformowana, Ĕe
obszar anonimowego odwzorowania jest juĔ wypeäniony zerami, a funkcja
calloc()
, po popraw-
nym przydzieleniu pamiöci, nie wymaga jawnego jej zerowania.
Funkcja systemowa
munmap()
zwalnia anonimowe odwzorowanie, zwracajñc przydzielonñ
pamiöè do jñdra:
int ret;
/* wykonano wszystkie dzia
áania, związane z uĪyciem wskaĨnika 'p', dlatego naleĪy zwróciü 512 kB pamiĊci */
ret = munmap (p, 512 * 1024);
if (ret)
perror ("munmap");
Szczegóäy uĔycia funkcji
mmap()
,
munmap()
oraz ogólny opis mechanizmu odwzorowania
znajdujñ siö w rozdziale 4.
Odwzorowanie pliku /dev/zero
Inne systemy operacyjne, takie jak BSD, nie posiadajñ znacznika
MAP_ANONYMOUS
. Zamiast tego
zaimplementowane jest dla nich podobne rozwiñzanie, przy uĔyciu odwzorowania specjalnego
pliku urzñdzenia /dev/zero. Ten plik urzñdzenia dostarcza takiej samej semantyki jak anonimowa
pamiöè. Odwzorowanie zawiera strony uzyskane za pomocñ mechanizmu kopiowania podczas
zapisu, wypeänione zerami; dlatego teĔ zachowanie to jest takie samo jak w przypadku anoni-
mowej pamiöci.
Linux zawsze posiadaä urzñdzenie /dev/zero oraz udostöpniaä moĔliwoĈè odwzorowania tego
pliku i uzyskania obszaru pamiöci wypeänionego zerami. RzeczywiĈcie, zanim wprowadzono
znacznik
MAP_ANONYMOUS,
programiĈci w Linuksie uĔywali powyĔszego rozwiñzania. Aby
278 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
zapewniè wstecznñ kompatybilnoĈè ze starszymi wersjami Linuksa lub przenoĈnoĈè do innych
systemów Uniksa, projektanci w dalszym ciñgu mogñ uĔywaè pliku urzñdzenia /dev/zero, aby
stworzyè anonimowe odwzorowanie. Operacja ta nie róĔni siö od tworzenia odwzorowania
dla innych plików:
void *p;
int fd;
/* otwórz plik /dev/zero do odczytu i zapisu */
fd = open ("/dev/zero", O_RDWR);
if (fd < 0)
{
perror ("open");
return -1;
}
/* odwzoruj obszar [0, rozmiar strony) dla urz
ądzenia /dev/zero */
p = mmap (NULL, /* niewa
Īne, w jakim miejscu pamiĊci */
getpagesize ( ), /* odwzoruj jedn
ą stronĊ */
PROT_READ | PROT_WRITE, /* uprawnienia odczytu i zapisu */
MAP_PRIVATE, /* odwzorowanie prywatne */
fd, /* odwzoruj plik /dev/zero */
0); /* bez przesuni
Ċcia */
if (p == MAP_FAILED)
{
perror ("mmap");
if (close (fd))
perror ("close");
return -1;
}
/* zamknij plik /dev/zero, je
Ğli nie jest juĪ potrzebny */
if (close (fd))
perror ("close");
/* wska
Ĩnik 'p' wskazuje na jedną stronĊ w pamiĊci, moĪna go uĪywaü… */
Pamiöè, otrzymana za pomocñ powyĔej przedstawionego sposobu, moĔe oczywiĈcie zostaè
zwolniona przy uĔyciu funkcji
munmap()
.
Ta metoda generuje dodatkowe obciñĔenie przez uĔycie funkcji systemowej, otwierajñcej i zamy-
kajñcej plik urzñdzenia. Dlatego teĔ wykorzystanie pamiöci anonimowej jest rozwiñzaniem
szybszym.
Zaawansowane operacje przydzia
ĥu pamiýci
Wiele operacji przydziaäu pamiöci, omówionych w tym rozdziale, jest ograniczanych i stero-
wanych przez parametry jñdra, które mogñ zostaè modyfikowane przez programistö. Aby to
wykonaè, naleĔy uĔyè funkcji
mallopt()
:
#include <malloc.h>
int mallopt (int param, int value);
Wywoäanie funkcji
mallopt()
ustawia parametr zwiñzany z zarzñdzaniem pamiöciñ, którego
nazwa przekazana jest w argumencie
param
. Parametr ten zostaje ustawiony na wartoĈè równñ
argumentowi
value
. W przypadku sukcesu funkcja zwraca wartoĈè niezerowñ; w przypadku
bäödu zwraca
0
. NaleĔy zauwaĔyè, Ĕe funkcja
mallopt()
nie ustawia zmiennej
errno
. NajczöĈciej
Zaawansowane operacje przydzia
ĥu pamiýci
_ 279
jej wywoäanie równieĔ koþczy siö sukcesem, dlatego teĔ nie naleĔy optymistycznie podchodziè
do zagadnienia uzyskiwania uĔytecznej informacji z jej kodu powrotu.
Linux wspiera obecnie szeĈè wartoĈci dla parametru
param
, które zdefiniowane sñ w pliku
nagäówkowym
<malloc.h>
:
M_CHECK_ACTION
WartoĈè zmiennej Ĉrodowiskowej
MALLOC_CHECK_
(omówiona w nastöpnym podrozdziale).
M_MMAP_MAX
Maksymalna liczba odwzorowaþ, które mogñ zostaè udostöpnione przez system, aby
poprawnie zrealizowaè Ĕñdania przydzielania pamiöci dynamicznej. Gdy to ograniczenie
zostanie osiñgniöte, wówczas dla kolejnych przydziaäów pamiöci zostanie uĔyty segment
danych, dopóki jedno z odwzorowaþ nie zostanie zwolnione. WartoĈè
0
caäkowicie unie-
moĔliwia uĔycie mechanizmu anonimowych odwzorowaþ jako podstawy do wykonywania
operacji przydziaäu pamiöci dynamicznej.
M_MMAP_THRESHOLD
WielkoĈè progu (wyraĔona w bajtach), powyĔej którego Ĕñdanie przydziaäu pamiöci zosta-
nie zrealizowane za pomocñ anonimowego odwzorowania zamiast udostöpnienia seg-
mentu danych. NaleĔy zauwaĔyè, Ĕe przydziaäy mniejsze od tego progu mogñ równieĔ
zostaè zrealizowane za pomocñ anonimowych odwzorowaþ, ze wzglödu na swobodö postö-
powania pozostawionñ systemowi. WartoĈè
0
umoĔliwia uĔycie anonimowych odwzoro-
waþ dla wszystkich operacji przydziaäu, stñd teĔ w rzeczywistoĈci nie zezwala na wyko-
rzystanie dla nich segmentu danych.
M_MXFAST
Maksymalny rozmiar (wyraĔony w bajtach) podajnika szybkiego. Podajniki szybkie (ang.
fast bins) sñ specjalnymi fragmentami pamiöci na stercie, które nigdy nie zostajñ poäñczone
z sñsiednimi obszarami i nie sñ zwrócone do systemu. Pozwala to na wykonywanie bardzo
szybkich operacji przydziaäu, kosztem zwiökszonej fragmentacji. WartoĈè
0
caäkowicie
uniemoĔliwia uĔycie podajników szybkich.
M_TOP_PAD
WartoĈè uzupeänienia (w bajtach) uĔytego podczas zmiany rozmiaru segmentu danych.
Gdy biblioteka glibc wykonuje funkcjö
brk()
, aby zwiökszyè rozmiar segmentu danych,
moĔe zaĔyczyè sobie wiöcej pamiöci, niĔ w rzeczywistoĈci potrzebuje, w nadziei na to, Ĕe
dziöki temu w najbliĔszej przyszäoĈci nie bödzie konieczne wykonanie kolejnego wywoäania
tejĔe funkcji. Podobnie dzieje siö w przypadku, gdy biblioteka glibc zmniejsza rozmiar
segmentu danych — zachowuje ona dla siebie pewnñ iloĈè pamiöci, zwracajñc do systemu
mniej, niĔ mogäaby naprawdö oddaè. Ten dodatkowy obszar pamiöci jest omawianym
uzupe
änieniem. WartoĈè
0
uniemoĔliwia caäkowicie uĔycie wypeänienia.
M_TRIM_THRESHOLD
Minimalna iloĈè wolnej pamiöci (w bajtach), która moĔe istnieè na szczycie segmentu danych.
JeĈli liczba ta bödzie mniejsza od podanego progu, biblioteka glibc wywoäa funkcjö
brk()
,
aby zwróciè pamiöè do jñdra.
Standard XPG, który w luĒny sposób definiuje funkcjö
mallopt()
, okreĈla trzy inne parametry:
M_GRAIN
,
M_KEEP
oraz
M_NLBLKS
. Linux równieĔ je definiuje, lecz ustawianie dla nich wartoĈci
nie powoduje Ĕadnych zmian. W tabeli 8.1. znajduje siö peäny opis wszystkich poprawnych
parametrów oraz odpowiednich dla nich domyĈlnych wartoĈci. Podane sñ równieĔ zakresy
akceptowalnych wartoĈci.
280 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
Tabela 8.1. Parametry funkcji
mallopt()
Parametr
řródĥo pochodzenia
Warto
ļë domyļlna
Poprawne warto
ļci
Warto
ļci specjalne
M_CHECK_ACTION
Specyficzny dla Linuksa
0
0 – 2
M_GRAIN
Standard XPG
Brak wsparcia w Linuksie
>= 0
M_KEEP
Standard XPG
Brak wsparcia w Linuksie
>= 0
M_MMAP_MAX
Specyficzny dla Linuksa
64 * 1024
>= 0
0
uniemo
żliwia użycie
mmap()
M_MMAP_THRESHOLD
Specyficzny dla Linuksa
128 * 1024
>= 0
0
uniemo
żliwia użycie
sterty
M_MXFAST
Standard XPG
64
0 – 80
0
uniemo
żliwia użycie
podajników szybkich
M_NLBLKS
Standard XPG
Brak wsparcia w Linuksie
>= 0
M_TOP_PAD
Specyficzny dla Linuksa
0
>= 0
0
uniemo
żliwia użycie
uzupe
ĥnienia
Dowolne wywoäanie funkcji
mallopt()
w programach musi wystñpiè przed pierwszym uĔy-
ciem funkcji
malloc()
lub innych interfejsów, säuĔñcych do przydzielania pamiöci. UĔycie
jest proste:
int ret;
/* u
Īyj funkcji mmap( ) dla wszystkich przydziaáów pamiĊci wiĊkszych od 64 kB */
ret = mallopt (M_MMAP_THRESHOLD, 64 * 1024);
if (!ret)
fprintf (stderr, "Wywo
Īanie funkcji mallopt() nie powiodĪo siĂ!\n");
Dok
ĥadne dostrajanie przy użyciu funkcji malloc_usable_size()
oraz malloc_trim()
Linux dostarcza kilku funkcji, które pozwalajñ na niskopoziomowñ kontrolö dziaäania systemu
przydzielania pamiöci dla biblioteki glibc. Pierwsza z tych funkcji pozwala na uzyskanie infor-
macji, ile faktycznie dostöpnych bajtów zawiera dany obszar przydzielonej pamiöci:
#include <malloc.h>
size_t malloc_usable_size (void *ptr);
Poprawne wywoäanie funkcji
malloc_usable_size()
zwraca rzeczywisty rozmiar przydziaäu
dla obszaru pamiöci wskazywanego przez
ptr
. PoniewaĔ biblioteka glibc moĔe zaokrñglaè
wielkoĈci przydziaäów, aby dopasowaè siö do istniejñcego fragmentu pamiöci, przydzielonego
do anonimowego odwzorowania, dlatego teĔ wielkoĈè przestrzeni dla danego przydziaäu,
nadajñcej siö do uĔytku, moĔe byè wiöksza od tej, jakñ zaĔñdano. OczywiĈcie obszary przydzia-
äów pamiöci nie bödñ nigdy mniejsze od tych, jakie sñ wymagane. Oto przykäad uĔycia funkcji:
size_t len = 21;
size_t size;
char *buf;
buf = malloc (len);
if (!buf)
{
perror ("malloc");
Uruchamianie programów, u
żywajécych systemu przydzielania pamiýci
_ 281
return -1;
}
size = malloc_usable_size (buf);
/* w rzeczywisto
Ğci moĪna uĪyü 'size' bajtów z obszaru pamiĊci 'buf'... */
Wywoäanie drugiej funkcji nakazuje bibliotece glibc, aby natychmiast zwróciäa caäñ zwolnionñ
pamiöè do jñdra:
#include <malloc.h>
int malloc_trim (size_t padding);
Poprawne wywoäanie funkcji
malloc_trim()
powoduje maksymalne zmniejszenie rozmiaru
segmentu danych, za wyjñtkiem obszarów uzupeänieþ, które sñ zarezerwowane. Nastöpnie
funkcja zwraca
1
. W przypadku bäödu zwraca
0
. Zazwyczaj biblioteka glibc samodzielnie prze-
prowadza takie operacje zmniejszania rozmiaru segmentu danych, gdy tylko wielkoĈè pamiöci
zwolnionej osiñga wartoĈè
M_TRIM_THRESHOLD
. Biblioteka uĔywa uzupeänienia okreĈlonego
w parametrze
M_TOP_PAD
.
Programista nie bödzie potrzebowaä nigdy uĔyè obu wspomnianych funkcji do niczego innego
niĔ tylko celów edukacyjnych i wspomagajñcych uruchamianie programów. Nie sñ one prze-
noĈne i udostöpniajñ programowi uĔytkownika niskopoziomowe szczegóäy systemu przydzie-
lania pamiöci, zaimplementowanego w bibliotece glibc.
Uruchamianie programów,
u
żywajécych systemu przydzielania pamiýci
Programy mogñ ustawiaè zmiennñ Ĉrodowiskowñ
MALLOC_CHECK_
, aby umoĔliwiè poszerzone
wspomaganie podczas uruchamiania programów wykorzystujñcych podsystem pamiöci. Opcja
poszerzonego wspomagania uruchamiania dziaäa kosztem zmniejszenia efektywnoĈci operacji
przydzielania pamiöci, lecz obciñĔenie to jest czösto tego warte podczas tworzenia aplikacji i
w trakcie jej uruchamiania.
PoniewaĔ zmienna Ĉrodowiskowa steruje procesem wspomagania uruchamiania, dlatego teĔ nie
istnieje potrzeba, aby ponownie kompilowaè program. Na przykäad, moĔna wykonaè proste
polecenie, podobne do poniĔej przedstawionego:
$ MALLOC_CHECK_=1 ./rudder
JeĈli zmienna
MALLOC_CHECK_
zostanie ustawiona na
0
, podsystem pamiöci w sposób automa-
tyczny zignoruje wszystkie bäödy. W przypadku, gdy bödzie ona równa
1
, na standardowe
wyjĈcie bäödów
stderr
zostanie wysäany komunikat informacyjny. JeĈli zmienna ta bödzie
równa
2
, wykonanie programu zostanie natychmiast przerwane przy uĔyciu funkcji
abort()
.
PoniewaĔ zmienna
MALLOC_CHECK_
modyfikuje zachowanie dziaäajñcego programu, jest igno-
rowana przez aplikacje posiadajñce ustawiony bit SUID.
Otrzymywanie danych statystycznych
Linux dostarcza funkcji
mallinfo()
, która moĔe zostaè uĔyta w celu uzyskania danych staty-
stycznych dotyczñcych dziaäania systemu przydzielania pamiöci:
282 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
#include <malloc.h>
struct mallinfo mallinfo (void);
Wywoäanie funkcji
mallinfo()
zwraca dane statystyczne zapisane w strukturze
mallinfo
.
Struktura zwracana jest przez wartoĈè, a nie przez wskaĒnik. Jej zawartoĈè jest równieĔ zdefi-
niowana w pliku nagäówkowym
<malloc.h>
:
/* wszystkie rozmiary w bajtach */
struct mallinfo
{
int arena; /* rozmiar segmentu danych, u
Īywanego przez funkcjĊ malloc */
int ordblks; /* liczba wolnych fragmentów pami
Ċci */
int smblks; /* liczba podajników szybkich */
int hblks; /* liczba anonimowych odwzorowa
Ĕ */
int hblkhd; /* rozmiar anonimowych odwzorowa
Ĕ */
int usmblks; /* maksymalny rozmiar ca
ákowitego przydzielonego obszaru */
int fsmblks; /* rozmiar dost
Ċpnych podajników szybkich */
int uordblks; /* rozmiar ca
ákowitego przydzielonego obszaru */
int fordblks; /* rozmiar dost
Ċpnych fragmentów pamiĊci */
int keepcost; /* rozmiar obszaru, który mo
Īe zostaü zwrócony do systemu przy uĪyciu funkcji malloc_trim() */
};
UĔycie funkcji jest proste:
struct mallinfo m;
m = mallinfo ( );
printf ("Liczba wolnych fragmentów pami
Ăci: %d\n", m.ordblks);
Linux dostarcza równieĔ funkcji
malloc_stats()
, która wyprowadza na standardowe wyjĈcie
bäödów dane statystyczne zwiñzane z podsystemem pamiöci:
#include <malloc.h>
void malloc_stats (void);
Wywoäanie funkcji
malloc_stats()
dla programu, który intensywnie uĔywa pamiöci, powo-
duje wyprowadzenie kilku wiökszych liczb:
Arena 0:
system bytes = 865939456
in use bytes = 851988200
Total (incl. mmap):
system bytes = 3216519168
in use bytes = 3202567912
max mmap regions = 65536
max mmap bytes = 2350579712
Przydzia
ĥy pamiýci wykorzystujéce stos
Wszystkie mechanizmy omówione do tej pory, dotyczñce wykonywania operacji przydziaäu
pamiöci dynamicznej, uĔywaäy sterty lub odwzorowaþ w pamiöci, aby zrealizowaè przydziele-
nie obszaru tejĔe pamiöci. NaleĔaäo tego oczekiwaè, gdyĔ sterta i odwzorowania w pamiöci sñ
z definicji bardzo dynamicznymi strukturami. Innñ, powszechnie uĔywanñ strukturñ w prze-
strzeni adresowej programu jest stos, w którym zapamiötane sñ automatyczne zmienne dla
aplikacji.
Przydzia
ĥy pamiýci wykorzystujéce stos
_ 283
Nie istnieje jednak przeciwwskazanie, aby programista nie mógä uĔywaè stosu dla realizowania
operacji przydzielania pamiöci dynamicznej. Dopóki taka metoda przydziaäu pamiöci nie prze-
peäni stosu, moĔe byè prosta w realizacji i powinna dziaäaè zupeänie dobrze. Aby dynamicznie
przydzieliè pamiöè na stosie, naleĔy uĔyè funkcji systemowej
alloca()
:
#include <alloca.h>
void * alloca (size_t size);
W przypadku sukcesu, wywoäanie funkcji
alloca()
zwraca wskaĒnik do obszaru pamiöci
posiadajñcego rozmiar przekazany w parametrze
size
i wyraĔony w bajtach. Pamiöè ta znajduje
siö na stosie i zostaje automatycznie zwolniona, gdy wywoäujñca funkcja koþczy swoje dzia-
äanie. Niektóre implementacje zwracajñ wartoĈè
NULL
w przypadku bäödu, lecz dla wiökszoĈci
z nich wywoäanie funkcji
alloca()
nie moĔe siö nie udaè lub nie jest moĔliwe informowanie
o niepoprawnym jej wykonaniu. Na bäñd wskazuje przepeäniony stos.
UĔycie jest identyczne jak w przypadku funkcji
malloc()
, lecz nie trzeba (w rzeczywistoĈci
nie wolno) zwalniaè przydzielonej pamiöci. PoniĔej przedstawiony zostanie przykäad funkcji,
która otwiera dany plik z systemowego katalogu konfiguracyjnego (równego prawdopodobnie
/etc), lecz dla zwiökszenia przenoĈnoĈci jego nazwa okreĈlana jest w czasie wykonania programu.
Funkcja musi przydzieliè pamiöè dla nowego bufora, skopiowaè do niego nazwö systemowego
katalogu konfiguracyjnego, a nastöpnie poäñczyè ten bufor z dostarczonñ nazwñ pliku:
int open_sysconf (const char *file, int flags, int mode)
{
const char *etc = SYSCONF_DIR; /* "/etc/" */
char *name;
name = alloca (strlen (etc) + strlen (file) + 1);
strcpy (name, etc);
strcat (name, file);
return open (name, flags, mode);
}
Po powrocie z funkcji, pamiöè przydzielona za pomocñ funkcji
alloca()
zostaje automatycz-
nie zwolniona, poniewaĔ wskaĒnik stosu przesuwa siö do pozycji funkcji wywoäujñcej. Ozna-
cza to, Ĕe nie moĔna uĔyè przydzielonego obszaru pamiöci po tym, gdy zakoþczy siö funkcja
uĔywajñca wywoäania
alloca()
! PoniewaĔ nie naleĔy wykonywaè Ĕadnego porzñdkowania
pamiöci za pomocñ funkcji
free()
, ostateczny kod programu staje siö trochö bardziej przej-
rzysty. Oto ta sama funkcja, lecz zaimplementowana przy uĔyciu wywoäania
malloc()
:
int open_sysconf (const char *file, int flags, int mode)
{
const char *etc = SYSCONF_DIR; /* "/etc/" */
char *name;
int fd;
name = malloc (strlen (etc) + strlen (file) + 1);
if (!name)
{
perror ("malloc");
return -1;
}
strcpy (name, etc);
strcat (name, file);
fd = open (name, flags, mode);
free (name);
284 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
return fd;
}
NaleĔy zauwaĔyè, Ĕe w parametrach wywoäania funkcji nie powinno uĔywaè siö bezpoĈred-
niego wywoäania
alloca()
. Powodem takiego zachowania jest to, Ĕe przydzielona pamiöè
bödzie istnieè na stosie poĈrodku obszaru zarezerwowanego do przechowywania parametrów
funkcji. Na przykäad, poniĔszy kod jest niepoprawny:
/* TAK NIE NALE
ĩY ROBIû! */
ret = foo (x, alloca (10));
Interfejs
alloca()
posiada ciekawñ historiö. W przypadku wielu systemów jego dziaäanie byäo
nieprawidäowe lub w pewnym sensie niezdefiniowane. W systemach posiadajñcych nieduĔy
stos o staäym rozmiarze, uĔycie funkcji
alloca()
byäo äatwym sposobem, aby go przepeäniè
i w rezultacie zaäamaè wykonanie programu. W niektórych systemach funkcja
alloca()
nie
jest do tej pory zaimplementowana. Bäödne i niespójne implementacje funkcji
alloca()
spowo-
dowaäy, Ĕe cieszy siö ona zäñ reputacjñ.
JeĈli program powinien byè przenoĈny, nie naleĔy uĔywaè w nim funkcji
alloca()
. W przy-
padku systemu Linux funkcja ta jest jednak bardzo uĔytecznym i niedocenionym narzödziem.
Dziaäa wyjñtkowo dobrze — w przypadku wielu architektur realizowanie przydzielania pamiöci
za pomocñ tej funkcji nie powoduje niczego ponad zwiökszenie wskaĒnika stosu, dlatego teĔ
äatwo przewyĔsza ona pod wzglödem wydajnoĈci funkcjö
malloc()
. W przypadku niewielkich
obszarów przydzielonej pamiöci i kodu, specyficznego dla Linuksa, uĔycie funkcji
alloca()
moĔe spowodowaè bardzo dobrñ poprawö wydajnoĈci.
Powielanie
ĥaħcuchów znakowych na stosie
Powszechnym przykäadem uĔycia funkcji
alloca()
jest tymczasowe powielanie äaþcucha zna-
kowego. Na przykäad:
/* nale
Īy powieliü áaĔcuch 'song' */
char *dup;
dup = alloca (strlen (song) + 1);
strcpy (dup, song);
/* tutaj mo
Īna juĪ uĪywaü wskaĨnika 'dup'… */
return; /* 'dup' zostaje automatycznie zwolniony */
Z powodu czöstego uĔycia tego rozwiñzania, a równieĔ korzyĈci zwiñzanych z prödkoĈciñ dzia-
äania, jakñ oferuje funkcja
alloca()
, systemy linuksowe udostöpniajñ wersjö funkcji
strdup()
,
która pozwala na powielenie danego äaþcucha znakowego na stosie:
#define _GNU_SOURCE
#include <string.h>
char * strdupa (const char *s);
char * strndupa (const char *s, size_t n);
Wywoäanie funkcji
strdupa()
zwraca kopiö äaþcucha
s
. Wywoäanie funkcji
strndupa()
powiela
n
znaków äaþcucha
s
. JeĈli äaþcuch
s
jest däuĔszy od
n
, proces powielania koþczy siö w pozy-
cji
n
, a funkcja doäñcza na koniec skopiowanego äaþcucha znak pusty. Funkcje te oferujñ te same
korzyĈci co funkcja
alloca()
. Powielony äaþcuch zostaje automatycznie zwolniony, gdy
wywoäujñca funkcja koþczy swoje dziaäanie.
Przydzia
ĥy pamiýci wykorzystujéce stos
_ 285
POSIX nie definiuje funkcji
alloca()
,
strdupa()
i
strndupa()
, a w innych systemach opera-
cyjnych wystöpujñ one sporadycznie. JeĈli naleĔy zapewniè przenoĈnoĈè programu, wówczas
uĔycie tych funkcji jest odradzane. W Linuksie wspomniane funkcje dziaäajñ jednak caäkiem
dobrze i mogñ zapewniè znakomitñ poprawö wydajnoĈci, zamieniajñc skomplikowane czynnoĈci,
zwiñzane z przydziaäem pamiöci dynamicznej, na zaledwie przesuniöcie wskaĒnika stosu.
Tablice o zmiennej d
ĥugoļci
Standard C99 wprowadziä tablice o zmiennej däugoĈci (ang. variable-length arrays, w skrócie VLA),
których rozmiar ustalany jest podczas dziaäania programu, a nie w czasie jego kompilacji. Kom-
pilator GNU dla jözyka C wspieraä takie tablice juĔ od jakiegoĈ czasu, lecz odkñd standard C99
formalnie je zdefiniowaä, pojawiä siö istotny bodziec, aby ich uĔywaè. Podczas uĔycia tablic
o zmiennej däugoĈci unika siö przydzielania pamiöci dynamicznej w taki sam sposób, jak pod-
czas stosowania funkcji
alloca()
.
Sposób uĔycia äaþcuchów o zmiennej däugoĈci jest dokäadnie taki, jak siö oczekuje:
for (i = 0; i < n; ++i)
{
char foo[i + 1];
/* tu mo
Īna uĪyü 'foo'… */
}
W powyĔszym fragmencie kodu zmienna
foo
jest äaþcuchem znaków o róĔnej däugoĈci,
równej
i + 1
. Podczas kaĔdej iteracji w pötli zostaje dynamicznie utworzona zmienna
foo
,
a nastöpnie automatycznie zwolniona, gdy znajdzie siö poza zakresem widocznoĈci. Gdyby
zamiast äaþcuchów o zmiennej däugoĈci uĔyto funkcji
alloca()
, pamiöè nie zostaäaby zwolniona,
dopóki funkcja nie zakoþczyäaby swojego dziaäania. UĔycie äaþcuchów o zmiennej däugoĈci
zapewnia, Ĕe pamiöè zostanie zwolniona podczas kaĔdej iteracji w pötli. Dlatego teĔ uĔycie
takich äaþcuchów zuĔywa w najgorszym razie
n
bajtów pamiöci, podczas gdy uĔycie funkcji
alloca()
wykorzystywaäoby
n*(n+1)/2
bajtów.
Funkcja
open_sysconf()
moĔe zostaè obecnie ponownie napisana, wykorzystujñc do jej imple-
mentacji äaþcuch znaków o zmiennej däugoĈci:
int open_sysconf (const char *file, int flags, int mode)
{
const char *etc = SYSCONF_DIR; /* "/etc/" */
char name[strlen (etc) + strlen (file) + 1];
strcpy (name, etc);
strcat (name, file);
return open (name, flags, mode);
}
Podstawowñ róĔnicñ miödzy uĔyciem funkcji
alloca()
, a uĔyciem tablic o zmiennej däugoĈci
jest to, iĔ pamiöè otrzymana przy uĔyciu tej pierwszej metody istnieje w czasie wykonywania
funkcji, natomiast pamiöè uzyskana przy uĔyciu drugiej metody istnieje do momentu, gdy
zmienna, która jñ reprezentuje, znajdzie siö poza zakresem widocznoĈci. MoĔe siö to zdarzyè,
zanim funkcja zakoþczy swoje dziaäanie — bödñc cechñ pozytywnñ lub negatywnñ. W przy-
padku pötli
for
, która zostaäa zastosowana w powyĔszym przykäadzie, odzyskiwanie pamiöci
przy kaĔdej iteracji zmniejsza realne zuĔycie pamiöci bez Ĕadnych efektów ubocznych (do
286 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
wykonania programu nie byäa potrzebna dodatkowa pamiöè). JeĈli jednakĔe z pewnych powo-
dów wymagane jest, aby przydzielona pamiöè byäa dostöpna däuĔej niĔ tylko przez pojedynczñ
iteracjö pötli, wówczas bardziej sensowne jest uĔycie funkcji
alloca()
.
ãñczenie wywoäania funkcji
alloca()
oraz uĔycia tablic o zmiennej däugoĈci w jednym
miejscu programu moĔe powodowaè zaskakujñce efekty. NaleĔy postöpowaè rozsñd-
nie i uĔywaè tylko jednej z tych dwóch opcji w tworzonych funkcjach.
Wybór mechanizmu przydzielania pami
ýci
Wiele opcji przydzielania pamiöci, omówionych w tym rozdziale, moĔe byè powodem powsta-
nia pytania o to, jakie rozwiñzanie jest najbardziej odpowiednie dla danej czynnoĈci. W wiök-
szoĈci sytuacji uĔycie funkcji
malloc()
zaspokaja wszystkie potrzeby programisty. Czasami
jednak inny sposób dziaäania pozwala na uzyskanie lepszych wyników. Tabela 8.2. przedsta-
wia informacje pomagajñce wybraè mechanizm przydzielania pamiöci.
Tabela 8.2. Sposoby przydzielania pami
öci w Linuksie
Sposób przydzielania pami
ýci
Zalety
Wady
Funkcja
malloc()
Prosta,
ĥatwa, powszechnie używana.
Pami
ýë zwracana nie musi byë wypeĥniona
zerami.
Funkcja
calloc()
Prosta metoda przydzielania pami
ýci
dla tablic, pami
ýë zwracana wypeĥniona
jest zerami.
Dziwny interfejs w przypadku, gdy pami
ýë
musi zosta
ë przydzielona dla innych struktur
danych ni
ż tablice.
Funkcja
realloc()
Zmienia wielko
ļë istniejécych obszarów
przydzielonej pami
ýci.
U
żyteczna wyĥécznie dla operacji zmiany wielkoļci
istniej
écych obszarów przydzielonej pamiýci.
Funkcje
brk()
i
sbrk()
Pozwala na szczegó
ĥowé kontrolý
dzia
ĥania sterty.
Zbyt niskopoziomowa dla wi
ýkszoļci
u
żytkowników.
Anonimowe odwzorowania
w pami
ýci
Ĥatwe w obsĥudze, wspóĥdzielone,
pozwalaj
é projektantowi na ustalanie
poziomu zabezpiecze
ħ oraz dostarczania
porady; optymalne rozwi
ézanie dla dużych
przydzia
ĥów pamiýci.
Niezbyt pasuj
éce do niewielkich przydziaĥów
pami
ýci; funkcja
malloc()
w razie potrzeby
automatycznie u
żywa anonimowych
odwzorowa
ħ w pamiýci.
Funkcja
posix_memalign()
Przydziela pami
ýë wyrównané do dowolnej,
rozs
édnej wartoļci.
Stosunkowo nowa, dlatego te
ż jej przenoļnoļë
jest dyskusyjna; u
życie ma sens dopiero
wówczas, gdy wyrównanie ma du
że znaczenie.
Funkcje
memalign()
i
valloc()
Bardziej popularna w innych systemach
uniksowych ni
ż funkcja
posix_memalign()
.
Nie jest zdefiniowana przez POSIX, oferuje
mniejsze mo
żliwoļci kontroli wyrównania niż
posix_memalign()
.
Funkcja
alloca()
Bardzo szybki przydzia
ĥ pamiýci, nie ma
potrzeby, aby po u
życiu jawnie jé zwalniaë;
bardzo dobra w przypadku niewielkich
przydzia
ĥów pamiýci.
Brak mo
żliwoļci informowania o bĥýdach,
niezbyt dobra w przypadku du
żych przydziaĥów
pami
ýci, bĥýdne dziaĥanie w niektórych
systemach uniksowych.
Tablice o zmiennej d
ĥugoļci
Podobnie jak
alloca()
, lecz pami
ýë
zostanie zwolniona, gdy tablica znajdzie
si
ý poza zasiýgiem widocznoļci, a nie
podczas powrotu z funkcji.
Metoda u
żyteczna jedynie dla tablic;
w niektórych sytuacjach mo
że byë preferowany
sposób zwalniania pami
ýci, charakterystyczny
dla funkcji
alloca()
; metoda mniej popularna
w innych systemach uniksowych ni
ż użycie
funkcji
alloca()
.
Operacje na pami
ýci
_ 287
Wreszcie, nie naleĔy zapominaè o alternatywie dla wszystkich powyĔszych opcji, czyli o auto-
matycznym i statycznym przydziale pamiöci. Przydzielanie obszarów dla zmiennych automa-
tycznych na stosie lub dla zmiennych globalnych na stercie jest czösto äatwiejsze i nie wymaga
obsäugi wskaĒników oraz troski o prawidäowe zwolnienie pamiöci.
Operacje na pami
ýci
Jözyk C dostarcza zbioru funkcji pozwalajñcych bezpoĈrednio operowaè na obszarach pamiöci.
Funkcje te dziaäajñ w wielu przypadkach w sposób podobny do interfejsów säuĔñcych do obsäugi
äaþcuchów znakowych, takich jak
strcmp()
i
strcpy()
, lecz uĔywana jest w nich wartoĈè roz-
miaru bufora dostarczonego przez uĔytkownika, zamiast zakäadania, Ĕe äaþcuchy sñ zakoþ-
czone znakiem zerowym. NaleĔy zauwaĔyè, Ĕe Ĕadna z tych funkcji nie moĔe zwróciè bäödu.
Zabezpieczenie przed powstaniem bäödu jest zadaniem dla programisty — jeĈli do funkcji
przekazany zostanie wskaĒnik do niepoprawnego obszaru pamiöci, rezultatem jej wykonania
nie bödzie nic innego, jak tylko bäñd segmentacji!
Ustawianie warto
ļci bajtów
WĈród zbioru funkcji modyfikujñcych zawartoĈè pamiöci, najczöĈciej uĔywana jest prosta
funkcja
memset()
:
#include <string.h>
void * memset (void *s, int c, size_t n);
Wywoäanie funkcji
memset()
ustawia
n
bajtów na wartoĈè
c
, poczynajñc od adresu przekazanego
w parametrze
s
, a nastöpnie zwraca wskaĒnik do zmienionego obszaru
s
. Funkcji uĔywa siö
czösto, aby wypeäniè dany obszar pamiöci zerami:
/* wype
ánij zerami obszar [s,s+256) */
memset (s, '\0', 256);
Funkcja
bzero()
jest starszym i niezalecanym interfejsem, wprowadzonym w systemie BSD
w celu wykonania tej samej czynnoĈci. W nowym kodzie powinna byè uĔywana funkcja
mem-
set()
, lecz Linux udostöpnia
bzero()
w celu zapewnienia przenoĈnoĈci oraz wstecznej kom-
patybilnoĈci z innymi systemami:
#include <strings.h>
void bzero (void *s, size_t n);
PoniĔsze wywoäanie jest identyczne z poprzednim uĔyciem funkcji
memset()
:
bzero(s, 256);
NaleĔy zwróciè uwagö na to, Ĕe funkcja
bzero()
, podobnie jak inne interfejsy, których nazwy
zaczynajñ siö od litery
b
, wymaga doäñczenia pliku nagäówkowego
<strings.h>
, a nie
<string.h>
.
Porównywanie bajtów
Podobnie jak ma to miejsce w przypadku uĔycia funkcji
strcmp()
, funkcja
memcmp()
porównuje
dwa obszary pamiöci, aby sprawdziè, czy sñ one identyczne:
288 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
Nie naleĔy uĔywaè funkcji
memset()
, jeĈli moĔna uĔyè funkcji
calloc()
! NaleĔy unikaè
przydzielania pamiöci za pomocñ funkcji
malloc()
, a nastöpnie bezpoĈredniego wypeä-
niania jej zerami przy uĔyciu funkcji
memset()
. Mimo Ĕe uzyska siö takie same wyniki,
duĔo lepsze bödzie uĔycie pojedynczego wywoäania funkcji
calloc()
, która zwraca
pamiöè wypeänionñ zerami. Nie tylko zaoszczödzi siö na jednym wywoäaniu funkcji,
ale dodatkowo wywoäanie
calloc()
bödzie mogäo otrzymaè od jñdra odpowiednio
przygotowany obszar pamiöci. W tym przypadku nastöpuje unikniöcie röcznego wypeä-
niania bajtów zerami i poprawa wydajnoĈci.
#include <string.h>
int memcmp (const void *s1, const void *s2, size_t n);
Wywoäanie tej funkcji powoduje porównanie pierwszych
n
bajtów dla obszarów pamiöci
s1
i
s2
oraz zwraca
0
, jeĈli bloki pamiöci sñ sobie równe, wartoĈè mniejszñ od zera, jeĈli
s1
jest mniej-
szy od
s2
oraz wartoĈè wiökszñ od zera, jeĈli
s1
jest wiökszy od
s2
.
System BSD ponownie udostöpnia niezalecany juĔ interfejs, który realizuje w duĔym stopniu
to samo zadanie:
#include <strings.h>
int bcmp (const void *s1, const void *s2, size_t n);
Wywoäanie funkcji
bmcp()
powoduje porównanie pierwszych
n
bajtów dla obszarów pamiöci
s1
i
s2
, zwracajñc
0
, jeĈli bloki sñ sobie równe lub wartoĈè niezerowñ, jeĈli sñ róĔne.
Z powodu istnienia wypeänienia struktur (opisanego wczeĈniej w podrozdziale Inne zagad-
nienia zwiñzane z wyrównaniem), porównywanie ich przy uĔyciu funkcji
memcmp()
lub
bcmp()
jest niepewne. W obszarze wypeänienia moĔe istnieè niezainicjalizowany fragment nieuĔytecz-
nych danych powodujñcy powstanie róĔnic podczas porównywania dwóch egzemplarzy danej
struktury, które poza tym sñ sobie równe. Zgodnie z tym, poniĔszy kod nie jest bezpieczny:
/* czy dwa egzemplarze struktury dinghy s
ą sobie równe? (BàĉDNY KOD) */
int compare_dinghies (struct dinghy *a, struct dinghy *b)
{
return memcmp (a, b, sizeof (struct dinghy));
}
Zamiast stosowaè powyĔsze, bäödne rozwiñzanie, programiĈci, którzy muszñ porównywaè ze
sobñ struktury, powinni czyniè to dla kaĔdego elementu struktury osobno. Ten sposób pozwala
na uzyskanie pewnej optymalizacji, lecz wymaga wiökszego wysiäku niĔ niepewne uĔycie pro-
stej funkcji
memcmp()
. Oto poprawny kod:
/* czy dwa egzemplarze struktury dinghy s
ą sobie równe? */
int compare_dinghies (struct dinghy *a, struct dinghy *b)
{
int ret;
if (a->nr_oars < b->nr_oars)
return -1;
if (a->nr_oars > b->nr_oars)
return 1;
ret = strcmp (a->boat_name, b->boat_name);
if (ret)
return ret;
/* i tak dalej, dla ka
Īdego pola struktury… */
}
Operacje na pami
ýci
_ 289
Przenoszenie bajtów
Funkcja
memmove()
kopiuje pierwszych
n
bajtów z obszaru pamiöci
src
do
dst
, a nastöpnie
zwraca wskaĒnik do
dst
:
#include <string.h>
void * memmove (void *dst, const void *src, size_t n);
System BSD ponownie udostöpnia niezalecany juĔ interfejs, który wykonuje tö samñ czynnoĈè:
#include <strings.h>
void bcopy (const void *src, void *dst, size_t n);
NaleĔy zwróciè uwagö na to, Ĕe mimo iĔ obie funkcje uĔywajñ takich samych parametrów,
kolejnoĈè dwóch pierwszych jest zmieniona w
bcopy()
.
Obie funkcje
bcopy()
oraz
memmove()
mogñ bezpiecznie obsäugiwaè nakäadajñce siö obszary
pamiöci (na przykäad, gdy czöĈè obszaru
dst
znajduje siö wewnñtrz
src
). Dziöki temu bajty
w pamiöci mogñ przykäadowo zostaè przesuniöte w stronö wyĔszych lub niĔszych adresów
wewnñtrz danego regionu. PoniewaĔ taka sytuacja jest rzadkoĈciñ, a programista wiedziaäby,
jeĈliby miaäa ona miejsce, dlatego teĔ standard jözyka C definiuje wariant funkcji
memmove()
, który
nie wspiera nakäadajñcych siö rejonów pamiöci. Ta wersja moĔe dziaäaè potencjalnie szybciej:
#include <string.h>
void * memcpy (void *dst, const void *src, size_t n);
PowyĔsza funkcja dziaäa identycznie jak
memmove()
, za wyjñtkiem tego, Ĕe obszary
dst
i
src
nie
mogñ posiadaè wspólnej czöĈci. JeĈli tak jest, rezultat wykonania funkcji jest niezdefiniowany.
Innñ funkcjñ, wykonujñcñ bezpieczne kopiowanie pamiöci, jest
memccpy()
:
#include <string.h>
void * memccpy (void *dst, const void *src, int c, size_t n);
Funkcja
memccpy()
dziaäa tak samo jak
memcpy()
, za wyjñtkiem tego, Ĕe zatrzymuje proces
kopiowania, jeĈli wĈród pierwszych
n
bajtów obszaru
src
zostanie odnaleziony bajt o warto-
Ĉci
c
. Funkcja zwraca wskaĒnik do nastöpnego bajta, wystöpujñcego po
c
w obszarze
dst
lub
NULL
, jeĈli
c
nie odnaleziono.
Ostatecznie funkcja
mempcpy()
pozwala poruszaè siö po pamiöci:
#define _GNU_SOURCE
#include <string.h>
void * mempcpy (void *dst, const void *src, size_t n);
Funkcja
mempcpy()
dziaäa tak samo jak
memcpy()
, za wyjñtkiem tego, Ĕe zwraca wskaĒnik do
miejsca znajdujñcego siö w pamiöci za ostatnim skopiowanym bajtem. Jest to przydatne, gdy
zbiór danych naleĔy skopiowaè do nastöpujñcych po sobie obszarów pamiöci — nie stanowi
to jednak zbyt duĔego usprawnienia, poniewaĔ wartoĈè zwracana jest zaledwie równa
dst + n
.
Funkcja ta jest specyficzna dla GNU.
Wyszukiwanie bajtów
Funkcje
memchr()
oraz
memrchr()
wyszukujñ dany bajt w bloku pamiöci:
#include <string.h>
void * memchr (const void *s, int c, size_t n);
290 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
Funkcja
memchr()
przeszukuje obszar pamiöci o wielkoĈci
n
bajtów, wskazywany przez para-
metr
s
, aby odnaleĒè w nim znak
c
, który jest interpretowany jako typ
unsigned char
. Funkcja
zwraca wskaĒnik do miejsca w pamiöci, w którym znajduje siö bajt pasujñcy do parametru
c
.
JeĈli wartoĈè
c
nie zostanie odnaleziona, funkcja zwróci
NULL
.
Funkcja
memrchr()
dziaäa tak samo jak funkcja
memchr()
, za wyjñtkiem tego, Ĕe przeszukuje
obszar pamiöci o wielkoĈci
n
bajtów, wskazywany przez parametr
s
, rozpoczynajñc od jego koþca
zamiast od poczñtku:
#define _GNU_SOURCE
#include <string.h>
void * memrchr (const void *s, int c, size_t n);
W przeciwieþstwie do
memchr()
, funkcja
memrchr()
jest rozszerzeniem GNU i nie naleĔy do
standardu jözyka C.
Aby przeprowadzaè bardziej skomplikowane operacje wyszukiwania, moĔna uĔyè funkcji
o dziwnej nazwie
memmem()
, przeszukujñcej blok pamiöci w celu odnalezienia dowolnego äaþ-
cucha bajtów:
#define _GNU_SOURCE
#include <string.h>
void * memmem (const void *haystack, size_t haystacklen, const void *needle,
size_t needlelen);
Funkcja
memmem()
zwraca wskaĒnik do pierwszego miejsca wystñpienia äaþcucha bajtów
needle
o däugoĈci
needlelen
, wyraĔonej w bajtach. Przeszukiwany obszar pamiöci wskazywany jest
przez parametr
haystack
i posiada däugoĈè
haystacklen
bajtów. JeĈli funkcja nie odnajdzie äaþ-
cucha
needle
w
haystack
, zwraca
NULL
. Jest równieĔ rozszerzeniem GNU.
Manipulowanie bajtami
Biblioteka jözyka C dla Linuksa dostarcza interfejsu, który pozwala na wykonywanie trywial-
nej operacji kodowania bajtów:
#define _GNU_SOURCE
#include <string.h>
void * memfrob (void *s, size_t n);
Wywoäanie funkcji
memfrob()
koduje pierwszych
n
bajtów z obszaru pamiöci wskazywanego
przez
s
. Polega to na przeprowadzeniu dla kaĔdego bajta operacji binarnej róĔnicy symetrycz-
nej (XOR) z liczbñ
42
. Funkcja zwraca wskaĒnik do zmodyfikowanego obszaru
s
.
Aby przywróciè pierwotnñ zawartoĈè zmodyfikowanego obszaru pamiöci, naleĔy dla niego
ponownie wywoäaè funkcjö
memfrob()
. Dlatego teĔ wykonanie poniĔszego fragmentu kodu nie
powoduje Ĕadnych zmian w obszarze
secret
:
memfrob (memfrob (secret, len), len);
Funkcja ta nie jest jednak Ĕadnñ prawdziwñ (ani nawet okrojonñ) namiastkñ operacji szyfrowa-
nia; ograniczona jest jedynie do wykonania trywialnego zaciemnienia bajtów. Jest specyficzna
dla GNU.
Blokowanie pami
ýci
_ 291
Blokowanie pami
ýci
W Linuksie zaimplementowano operacjö stronicowania na Ĕñdanie, która polega na tym, Ĕe
strony pobierane sñ z dysku w razie potrzeby, natomiast zapisywane na dysku, gdy nie sñ juĔ
uĔywane. Dziöki temu nie istnieje bezpoĈrednie powiñzanie wirtualnych przestrzeni adreso-
wych dla procesów w systemie z caäkowitñ iloĈciñ pamiöci fizycznej, gdyĔ istnienie obszaru
wymiany na dysku dostarcza wraĔenia posiadania prawie nieskoþczonej iloĈci tejĔe pamiöci.
Wymiana stron wykonywana jest w sposób przezroczysty, a aplikacje w zasadzie nie muszñ
„interesowaè siö” (ani nawet znaè) sposobem dziaäania stronicowania, przeprowadzanym przez
jñdro Linuksa. Istniejñ jednak dwie sytuacje, podczas których aplikacje mogñ wpäywaè na spo-
sób dziaäania stronicowania systemowego:
Determinizm
Aplikacje, posiadajñce ograniczenia czasowe, wymagajñ deterministycznego zachowania.
JeĈli pewne operacje dostöpu do pamiöci koþczñ siö bäödami stron (co wywoäuje powsta-
wanie kosztownych operacji wejĈcia i wyjĈcia), wówczas aplikacje te mogñ przekraczaè
swoje parametry ograniczeþ czasowych. Aby zapewniè, Ĕe wymagane strony bödñ zawsze
znajdowaè siö w pamiöci fizycznej i nigdy nie zostanñ wyrzucone na dysk, moĔna dla danej
aplikacji zagwarantowaè, Ĕe dostöp do pamiöci nie zakoþczy siö bäödem, co pozwoli na
speänienie warunków spójnoĈci i determinizmu, a równieĔ na poprawö jej wydajnoĈci.
Bezpiecze
þstwo
JeĈli w pamiöci przechowywane sñ tajne dane prywatne, wówczas poziom bezpieczeþstwa
moĔe zostaè naruszony po wykonaniu operacji stronicowania i zapisaniu tych danych
w postaci niezaszyfrowanej na dysku. Na przykäad, jeĈli prywatny klucz uĔytkownika
jest zwykle przechowywany na dysku w postaci zaszyfrowanej, wówczas jego odszyfro-
wana kopia, znajdujñca siö w pamiöci, moĔe zostaè wyrzucona do pliku wymiany. W przy-
padku Ĉrodowiska o wysokim poziomie bezpieczeþstwa, zachowanie to moĔe byè niedo-
puszczalne. Dla aplikacji wymagajñcych zapewnienia du
Ĕego poziomu bezpieczeþstwa,
moĔna zdefiniowaè, Ĕe obszar, w którym znajduje siö odszyfrowany klucz, bödzie istniaä
wyäñcznie w pamiöci fizycznej.
OczywiĈcie zmiana zachowania jñdra moĔe spowodowaè pogorszenie ogólnej sprawnoĈci sys-
temu. Dla danej aplikacji nastñpi poprawa determinizmu oraz bezpieczeþstwa, natomiast gdy
jej strony bödñ zablokowane w pamiöci, strony innej aplikacji bödñ wyrzucane na dysk. Jñdro
(jeĈli moĔna ufaè metodzie jego zaprojektowania) zawsze optymalnie wybiera takñ stronö,
która powinna zostaè wyrzucona na dysk (to znaczy stronö, która najprawdopodobniej nie
bödzie uĔywana w przyszäoĈci), dlatego teĔ po zmianie jego zachowania wybór ten nie bödzie
juĔ optymalny.
Blokowanie fragmentu przestrzeni adresowej
POSIX 1003.1b-1993 definiuje dwa interfejsy pozwalajñce na „zamkniöcie” jednej lub wiöcej
stron w pamiöci fizycznej, dziöki czemu moĔna zapewniè, Ĕe nie zostanñ one nigdy wyrzucone
na dysk. Pierwsza funkcja blokuje pamiöè dla danego przedziaäu adresów:
#include <sys/mman.h>
int mlock (const void *addr, size_t len);
292 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
Wywoäanie funkcji
mlock()
blokuje w pamiöci fizycznej obszar pamiöci wirtualnej, rozpoczy-
najñcy siö od adresu
addr
i posiadajñcy wielkoĈè
len
bajtów. W przypadku sukcesu, funkcja
zwraca wartoĈè
0
. W przypadku bäödu zwraca
–1
oraz odpowiednio ustawia zmiennñ
errno
.
Poprawne wywoäanie funkcji blokuje w pamiöci wszystkie strony fizyczne, których adresy
zawierajñ siö w zakresie
[addr, addr + len)
. Na przykäad, jeĈli funkcja chce zablokowaè tylko
jeden bajt, wówczas w pamiöci zostanie zablokowana caäa strona, w której on siö znajduje.
Standard POSIX definiuje, Ĕe adres
addr
powinien byè wyrównany do wielkoĈci strony. Linux
nie wymusza tego zachowania i w razie potrzeby niejawnie zaokrñgla adres
addr
w dóä do
najbliĔszej strony. W przypadku programów, dla których wymagane jest zachowanie warunku
przenoĈnoĈci do innych systemów, naleĔy jednak upewniè siö, Ĕe
addr
jest wyrównany do
granicy strony.
Poprawne wartoĈci zmiennej
errno
obejmujñ poniĔsze kody bäödów:
EINVAL
Parametr
len
ma wartoĈè ujemnñ.
ENOMEM
Proces wywoäujñcy zamierzaä zablokowaè wiöcej stron, niĔ wynosi ograniczenie zasobów
RLIMIT_MEMLOCK
(szczegóäy w podrozdziale Ograniczenia blokowania).
EPERM
WartoĈè ograniczenia zasobów
RLIMIT_MEMLOCK
byäa równa zeru, lecz proces nie posiadaä
uprawnienia
CAP_IPC_LOCK
(podobnie, szczegóäy w podrozdziale Ograniczenia blokowania).
Podczas wykonywania funkcji
fork()
, proces potomny nie dziedziczy pamiöci zablo-
kowanej. Dziöki istnieniu mechanizmu kopiowania podczas zapisu, uĔywanego dla
przestrzeni adresowych w Linuksie, strony procesu potomnego sñ skutecznie zablo-
kowane w pamiöci, dopóki potomek nie wykona dla nich operacji zapisu.
ZaäóĔmy przykäadowo, Ĕe pewien program przechowuje w pamiöci odszyfrowany äaþcuch
znaków. Proces moĔe za pomocñ kodu, podobnego do poniĔej przedstawionego, zablokowaè
stronö zawierajñcñ dany äaþcuch:
int ret;
/* zablokuj
áaĔcuch znaków 'secret' w pamiĊci */
ret = mlock (secret, strlen (secret));
if (ret)
perror ("mlock");
Blokowanie ca
ĥej przestrzeni adresowej
JeĈli proces wymaga zablokowania caäej przestrzeni adresowej w pamiöci fizycznej, wówczas
uĔycie funkcji
mlock()
staje siö niewygodne. Aby zrealizowaè to zadanie — powszechnie
wykonywane w przypadku aplikacji czasu rzeczywistego — standard POSIX definiuje funkcjö
systemowñ, która blokuje caäñ przestrzeþ adresowñ:
#include <sys/mman.h>
int mlockall (int flags);
Blokowanie pami
ýci
_ 293
Wywoäanie funkcji
mlockall()
blokuje w pamiöci fizycznej wszystkie strony przestrzeni adre-
sowej dla aktualnego procesu. Parametr
flags
steruje zachowaniem funkcji i jest równy sumie
bitowej poniĔszych znaczników:
MCL_CURRENT
JeĈli znacznik jest ustawiony, powoduje to, Ĕe funkcja
mlockall()
blokuje wszystkie aktu-
alnie odwzorowane strony w przestrzeni adresowej procesu. Stronami takimi moĔe byè stos,
segment danych, pliki odwzorowane itd.
MCL_FUTURE
JeĈli znacznik jest ustawiony, wówczas wykonanie funkcji
mlockall()
zapewnia, iĔ wszyst-
kie strony, które w przyszäoĈci zostanñ odwzorowane w przestrzeni adresowej, bödñ rów-
nieĔ zablokowane w pamiöci.
WiökszoĈè aplikacji uĔywa obu tych znaczników jednoczeĈnie.
W przypadku sukcesu funkcja zwraca wartoĈè
0
. W przypadku bäödu zwraca
–1
oraz odpowied-
nio ustawia zmiennñ
errno
na jednñ z poniĔszych wartoĈci:
EINVAL
Parametr
flags
ma wartoĈè ujemnñ.
ENOMEM
Proces wywoäujñcy zamierzaä zablokowaè wiöcej stron, niĔ wynosi ograniczenie zasobów
RLIMIT_MEMLOCK
(szczegóäy w podrozdziale Ograniczenia blokowania).
EPERM
WartoĈè ograniczenia zasobów
RLIMIT_MEMLOCK
byäa równa zeru, lecz proces nie posiadaä
uprawnienia
CAP_IPC_LOCK
(podobnie, szczegóäy w podrozdziale Ograniczenia blokowania).
Odblokowywanie pami
ýci
Aby umoĔliwiè odblokowanie stron z pamiöci fizycznej, pozwalajñc jñdru w razie potrzeby
ponownie wyrzucaè je na dysk, POSIX definiuje dwa dodatkowe interfejsy:
#include <sys/mman.h>
int munlock (const void *addr, size_t len);
int munlockall (void);
Funkcja systemowa
munlock()
odblokowuje strony, które rozpoczynajñ siö od adresu
addr
i zajmujñ obszar
len
bajtów. Jest ona przeciwieþstwem funkcji
mlock()
. Funkcja systemowa
munlockall()
jest przeciwieþstwem
mlockall()
. Obie funkcje zwracajñ zero w przypadku
sukcesu, natomiast w przypadku niepowodzenia zwracajñ
–1
oraz ustawiajñ zmiennñ
errno
na
jednñ z poniĔszych wartoĈci:
EINVAL
Parametr
len
jest nieprawidäowy (tylko dla
munlock()
).
ENOMEM
Niektóre z podanych stron sñ nieprawidäowe.
EPERM
WartoĈè ograniczenia zasobów
RLIMIT_MEMLOCK
byäa równa zeru, lecz proces nie posiadaä
uprawnienia
CAP_IPC_LOCK
(szczegóäy w nastöpnym podrozdziale Ograniczenia blokowania).
294 _
Rozdzia
ĥ 8. Zarzédzanie pamiýcié
Blokady pamiöci nie zagnieĔdĔajñ siö. Dlatego teĔ, bez wzglödu na to, ile razy dana strona
zostaäa zablokowana za pomocñ funkcji
mlock()
lub
mlockall()
, pojedyncze wywoäanie funkcji
munlock()
lub
munlockall()
spowoduje jej odblokowanie.
Ograniczenia blokowania
PoniewaĔ blokowanie pamiöci moĔe spowodowaè spadek wydajnoĈci systemu (faktycznie,
jeĈli zbyt wiele stron zostanie zablokowanych, operacje przydziaäu pamiöci mogñ siö nie powieĈè),
dlatego teĔ w systemie Linux zdefiniowano ograniczenia, które okreĈlajñ, ile stron moĔe zostaè
zablokowanych przez jeden proces.
Proces, który posiada uprawnienie
CAP_IPC_LOCK
, moĔe zablokowaè dowolnñ liczbö stron
w pamiöci. Procesy nieposiadajñce takiego uprawnienia, mogñ zablokowaè wyäñcznie tyle bajtów
pamiöci, ile wynosi ograniczenie
RLIMIT_MEMLOCK
. DomyĈlnie, ograniczenie to wynosi 32 kB —
jest ono wystarczajñce, aby zablokowaè jeden lub dwa tajne klucze w pamiöci, lecz nie tak duĔe,
aby skutecznie wpäynñè na wydajnoĈè systemu (w rozdziale 6. omówiono ograniczenia zasobów
oraz metody pozwalajñce na pobieranie i ustawianie tych parametrów).
Czy strona znajduje si
ý w pamiýci fizycznej?
Aby uäatwiè uruchamianie programów oraz usprawniè diagnostykö, Linux udostöpnia funkcjö
mincore()
, która moĔe zostaè uĔyta, by ustaliè, czy obszar danych znajduje siö w pamiöci fizycz-
nej lub w pliku wymiany na dysku:
#include <unistd.h>
#include <sys/mman.h>
int mincore (void *start, size_t length, unsigned char *vec);
Wywoäanie funkcji
mincore()
zwraca wektor bajtów, który opisuje, jakie strony odwzoro-
wania znajdujñ siö w pamiöci fizycznej w czasie jej uĔycia. Funkcja zwraca wektor poprzez
parametr
vec
oraz opisuje strony rozpoczynajñce siö od adresu
start
(który musi byè wyrów-
nany do granicy strony) i obejmujñce obszar o wielkoĈci
length
bajtów (który nie musi byè
wyrównany do granicy strony). KaĔdy element w wektorze
vec
odpowiada jednej stronie
z dostarczonego zakresu adresów, poczynajñc od pierwszego bajta opisujñcego pierwszñ stronö
i nastöpnie przechodzñc w sposób liniowy do kolejnych stron. Zgodnie z tym, wektor
vec
musi
byè na tyle duĔy, aby przechowaè odpowiedniñ liczbö bajtów, równñ wyraĔeniu
(length
- 1 + rozmiar strony)/rozmiar strony
. Najmniej znaczñcy bit w kaĔdym bajcie wektora
równy jest 1, gdy strona znajduje siö w pamiöci fizycznej lub 0, gdy jej tam nie ma. Inne bity
sñ obecnie niezdefiniowane i zarezerwowane do przyszäego wykorzystania.
W przypadku sukcesu funkcja zwraca
0
. W przypadku bäödu zwraca
–1
oraz odpowiednio
ustawia zmiennñ
errno
na jednñ z poniĔszych wartoĈci:
EAGAIN
Brak wystarczajñcych zasobów jñdra, aby zakoþczyè tö operacjö.
EFAULT
Parametr
vec
wskazuje na bäödny adres.
EINVAL
Parametr
start
nie jest wyrównany do granicy strony.