informatyka linux programowanie systemowe robert love ebook

background image

Wydawnictwo Helion
ul. Koœciuszki 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 wejœcia i wyjœcia?

•

Jak zablokowaæ fragmenty przestrzeni adresowej?

•

Jak sterowaæ dzia³aniem interfejsu odpytywania zdarzeñ?

Dzisiaj systemu Linux nie musimy ju¿ nikomu przedstawiaæ, dziêki swojej
funkcjonalnoœci i uniwersalnoœci 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ê
bezpoœrednio 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!

background image

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

background image

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

background image

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

background image

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

background image

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.

background image

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.

background image

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).

background image

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");

background image

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.

background image

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;
}

background image

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):

background image

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++)

background image

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

background image

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
.

background image

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:

background image

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

background image

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,

background image

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-

background image

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).

background image

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

background image

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

background image

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

background image

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.

background image

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");

background image

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:

background image

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.

background image

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);

background image

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.

background image

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

background image

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()

.

background image

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:

background image

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… */

}

background image

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);

background image

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.

background image

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);

background image

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);

background image

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).

background image

Czytaj dalej...

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.


Wyszukiwarka

Podobne podstrony:
informatyka pear programowanie w php stephan schmidt ebook
Linux Programowanie systemowe Wydanie II
Linux Programowanie systemowe Wydanie II linps2
Linux Programowanie systemowe Wydanie II 3
informatyka javascript programowanie obiektowe stoyan stefanov ebook
Linux Programowanie systemowe linups
Linux Programowanie systemowe Wydanie II
Linux Programowanie systemowe Wydanie II linps2 2
informatyka skrypty powloki systemu linux receptury sarath lakshman ebook
JAVA 02 programowanie w systemie Linux
Jądro i system plików, Informatyka, Linux, Linux - Podręcznik
R-10-07, ☆☆♠ Nauka dla Wszystkich Prawdziwych ∑ ξ ζ ω ∏ √¼½¾haslo nauka, linuks, programowanie w sys
zagadnienia informatyka Isem, linux operating system ( linux )-поживём — увидим

więcej podobnych podstron