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:

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

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.

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

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.

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:

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.

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.

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.

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:

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.

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

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:

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é

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:

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.

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.

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.

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.