ALTE DOCUMENTE
|
||||||||||
A memória optimális kihasználása az egyik legnehezebb feladata a Windows programok fejlesztőinek. A standard Windows 3.1 API több mint 35 memóriakezelési (foglalás felszabadítás és lokkolási) függvényt kínál. Mivel ezeknek nagy része speciális esetekben használható, ezért egy tipikus Windows alatti C program kb. 8 ilyen függvényt használ.
Ebben a fejezetben áttekintést adunk a memóriakezelés célszerű használatáról valamint a különböző Windows működési módokról. Mivel a későbbiekben az MFC(Microsoft Foundation Class)-vel fogunk dolgozni, ezért számos része a memóriakezelésnek az MFC feladata lesz és csak csekély rész hárul ránk. 12512k1019m Ez a fejezet nem kíván a részletekbe nagyon belemélyedni, hiszen ezek a fogalmak újból elő fognak jönni az MFC tárgyalásánál. Itt annyit említünk, amennyi feltétlenül szükséges egy átlagos C program megírásához.
A korábbi Windows verziók arra voltak tervezve, hogy nagyon korlátozott környezetben fussanak. Az átlag PC egy 8086 processzort és 640 KB RAM-ot tartalmazott.
Azóta az áresés miatt az átlag PC hatékonysága drasztikusan nőtt. Most egy átlagos PC 80486 processzort és 4 MB-RAM-ot tartalmaz. Az új gépeknek megfelelően a Windows memória menedzselése ennek megfelelően fejlődött. A kompatibilitás megőrzése érdekében a Windows 3 működési módot támogat. Minden működési módot egy adott hardver osztályra optimalizáltak.
A "Real" mód a 8086 processzorok szegmentált architektúráján alapszik. A programokat több szegmensre bontja. A Windows mint kvázi operációs rendszer 3 mechanizmust használt a memória menedzselésére. A megosztott (Shared), kidobható (Discardable), mozgatható (Movable) blokkokat. Mindegyikük a modern működési módban is használható.
A megosztottnak az a lényege, hogy egy program több példányban futhat közös kód szegmenssel és külön-külön adatszegmenssel. A DLL-ek is biztosítják azt a lehetőséget, hogy számos program egyszerre használja a DLL függvényeit, és azok csak egy példányban vannak jelen.
A kidobható memória blokkok esetén azok a blokkok, amelyekre éppen nincsen szükség kidobhatók a memóriából, és amikor szükség van rájuk újból betölthetők az EXE fájlból. Tipikus esete ennek az erőforrásokat tartalmazó blokkok (menu, kurzor, ikon,stb..)
Természetesen a fent említett két esetben nem kell foglalkoznunk ennek kezelésével, hiszen ez nem a mi dolgunk, hanem a Windowsé. A harmadik eset a mozgatható blokkok. Ez az eset fejtörést okozhat a programozás során. A mozgatható blokkok nincsenek egy fix helyen a memóriában. A rendszer megmozgathatja őket a memórián belül a jobb memória kihasználás érdekében. Mivel a programunk adatai vándorolnak a memóriában, ezért nem tárolhatunk olyan pointert, amely ezekre az adatokra mutatna. Akkor mit tehetünk vajon mi programozók? A válasz a Handle (leíró) használata. Mi is az a Handle? A Handle indexe egy pointer táblának. Amikor a Windows mozgatja az adatokat módosítja a táblán levő pointereket az új helyeknek megfelelően, de a Handle nem változik. Tehát a mi Hadelünk mindig ugyanarra a tábla rekeszre mutat, amely tartalmazza azt a pointert, ami a mi adatunkra mutat. Az említettek jobb megértése érdekében lássuk a 9. ábrát:
Az új probléma akkor jön elő, amikor használni akarjuk a mozgatható adatokat. A Handlet nem használhatjuk pointerként (nem közvetlenül a memóriában levő adatokra mutat), ilyenkor a Handleből egy pointert kell nyerni egy API függvénnyel. Az API függvény (GlobalLock, LocalLock) visszatérési értéke egy pointer az adatainkra, és az a blokk már rögzítve (lokkolva) van. Miután befejeztük az adatok használatát fel kell oldani a lokkolást, így a Windows újból szabadon mozgathatja az adatainkat az igényeinek megfelelően. A Windows 3.1 már nem támogatja a Real módot csak a következő két módot: Standard és Enhanced.
A második működési mód a Standard. Ez a mód a 80286 és 80386 kevés memóriával rendelkező gépek esetén működik. Ilyenkor a processzor a Protected módban dolgozik, amely hardver szintű memória védelmet biztosít, így az egyik program sem tudja elrontani másik programnak az adatait. Aki ismerte a Windows 3.0-t biztos emlékszik a fatális hibára "Unrecoverable Application Error " vagy UAE dialógus ablak. Ennek a leggyakoribb oka a hardver memória védelem megsértése. Ez az üzenet a 3.1 Windows-ban az ismert fehér ablak üzenete "General Protection Fault". A Windows 3.1-ben az az applikáció, ami megsérti a memória védelmi mechnismust ki lesz dobva a rendszer stabilitása érdekében. A standard mód újabb előnye (a Protected módon kívül) az, hogy támogatja az Extended memória használatát. Mivel az Expanded memória (a 640KB és 1MB közötti rész) kevés bizonyos programok futtatásához, ezért az Extended memória kiterjedhet 16MB-ig a fizikai memóriában.
A harmadik és a legfontosabb, működési mód a 386-Enhanced mód. Ennek a főelőnye, hogy kihasználja a processzor (csak >=80386 processzorok esetén és legalább 2 MB RAM) lapozási mechnizmusát a virtuális memóriakezelést (a lapméret 4 KB). Ez azt jelenti, hogy a Windows egy swap fájlt használ és ebben a fájlban tárolja a memórialapok tartalmát. Nekünk nem kell ezzel foglalkozni a programunk készítése során. A maximálisan címezhető memória 256MB a swap fájllal együtt.
A programok szegmensekre vannak bontva. Legalább egy kód és egy adat szegmens tartozik egy programhoz. 4 memória modell létezik a small, medium, compact és large.
Memória modell |
Kód szegmens |
Adat szegmens |
Small |
egy |
egy |
Medium |
több |
egy |
Compact |
egy |
több |
Large |
több |
több |
A small és a medium modellek esetén egyetlen egy adatszegmense van a programnak. A leggyakrabban használt memória modell a medium modell. Ebben az esetben a program több kódszegmensben lehet, de egyetlen adat szegmenssel rendelkezik.
A small és medium modellek esetén az adat pointerek near (16-bit) típusúak. Az összes pointernek, amely az API függvényekben vagy egy DLL függvényben szerepel farnak kell lennie. Ilyenkor a fordító konvertálja a near pointereket farrá. A compact és Large modell esetén több adat szegmens van és a Windows egyetlen egy példányban engedi meg a futtatást.
A lokális memória a program saját adatszegmensében van. Az adatszegmens általában mozgatható. Amikor a Windows üzenetet továbbít az applikációnak rögzíti az adatszegmenst, amíg a program nem fejezi be az üzenet feldolgozását. A lokális memória tartalmazza a stack-et a heap-et és a lokális változók által foglalt helyet. A heap és a stack mérete a definíciós fájlban megadható. A stack mérete minimum 5K kell, hogy legyen. A lokális heapben foglalhatunk memóriát. Ez max 64 KB lehet, ha nagyobb helyre van szükségünk, akkor ezt a globális heapben kell megvalósítani. A következő függvények szolgálnak a foglalásra, felszabadításra és rögzítésére.
Lokális foglalásra, felszabadításra és rögzítésére a következő függvények használhatók:
LocalAlloc , LocalFree, LocalLock, LocalUnlock
a globális esetben:
GlobalAlloc, GlobalFree, GlobalLock, GlobalUnlock
A foglalást megvalósító függvényeknek két paraméterük van az első a beállítást (mozgatható, fix, megosztott, stb) a második paraméter pedig a foglalandó méret byte-okban. A visszatérési értékük egy leíró(Handle). A beállításokhoz vannak konstansok, amelyek nagybetűvel vannak deklarálva a windows.h -ban. A lokális esetben ezek L-lel kezdődnek, globális esetben pedig G-vel.(LMEM_FIXED, GMEM_FIXED, LMEM_MOVEABLE, GMEM_MOVEABLE, stb...).
A globális memória használatának a legfontosabb előnye, hogy nem korlátozódik 64-Kbyte-ra, hanem a Standard módban 1 MB körül és az Enhanced módban 16 MB körül. Amennyiben 64 KB-nél nagyobb memória területet szeretnénk foglalni, akkor a GlobalLock által visszaadott pointert huge típusúvá kell alakítani. A következő rész egy ilyen példát mutat.
static HGLOBAL hMem; // blokkleíró
long huge *hpMem; // huge mutató
long i;
if ((hMem=GlobalAlloc(GMEM_MOVEABLE, 0x40000L))!=NULL)
else
MessageBox(NULL,"Sikertelen rögzítés", "Figyelmeztetés", MB_OK);
}
else
MessageBox(NULL,"Sikertelen a foglalás", "Figyelmeztetés", MB_OK);
Érdemes itt megemlíteni, hogy a 32 bites változatai a Windowsnak (Win32S Windows 3.1 esetén, Win32C a Windows'95 esetén és a Win32 az NT esetén támogatják a sík memória modellt (flat-memory). Az egész memóriát egyetlen egy pointerrel lehet elérni. Ilyen esetekben újabb 3 módszere van a memóriamenedzselésnek : Heap allokálás, virtuális memória allokálás, és úgynevezett "file-mapped shared memory". Ezeket csak megemlítjük, nem fogunk foglalkozni velük részletesen. Bővebb információt biztosít a VC++ 4.0 online Books Win32 leírása.
A C++ két operátort biztosít az adatok foglalására és felszabadítására a new és a delete. A new helyettesíti a standard malloc függvényeket és a delete a free függvényeket. Amikor objektumokkal dolgozunk kénytelenek leszünk dolgozni ezekkel az operátorokkal. A new operátor létrehoz egy példányt az osztályból, foglal neki helyett a memóriában és inicializálja az osztályt, a konstruktor meghívása révén. A delete pedig a new ellenkezőjét csinálja. Felszabadítja a memóriát, és a szükséges takarítást is megcsinálja a destruktor segítségével.
Minden programozási nyelv rendelkezik saját függvényekkel a fájlkezelésre. Ezek a függvények fájlnyitási, -zárási, -olvasási és -írási funkciókat látnak el. A Windowsban van egy úgynevezett univerzális API függvény OpenFile, amely az egyik paraméterétől függően használható, létrehozásra, megnyitásra és törlésre egyaránt.
HFILE OpenFile(lpszFileName, lpOpenBuff, fuMode)
LPCSTR lpszFileName; /* address of filename */
OFSTRUCT FAR* lpOpenBuff; /*address of buf for file info*/
UINT fuMode; /* action and attributes */
Az OFSTRUCT struktúra tartalmazza a fájl nevét és néhány beállítást. Az fuMode segítségével tudjuk meghatározni, hogy mit csináljon a függvény. Ez lehet, OF_EXIST(csak megnézi, hogy létezik-e a fájl vagy sem - nyitás és zárás), OF_DELETE törli a fájlt, OF_PARSE tölti az OFSTRUCT struktúrát, stb...
A hagyományos fájlkezelő függvények paramétereiben szereplő mutatók típusa függ a memória modelltől és számos problémát vetnek fel. Ennek elkerülésére használjuk a memória modelltől független függvényeket. Ezek _l -lel kezdődnek.
_lclose Closes an open file
_lcreat Creates or opens a file
_llseek Repositions the file pointer
_lopen Opens an existing file
_lread Reads data from a file
_lwrite Writes data to a file
A Windows 3.1 API további lehetőségekkel bővült, amelyek lehetővé teszik 64 Kbyte-nál nagyobb adatterületet egyetlen lépésben történő feldolgozását (olvasását, írását , másolását).
_hread Reads data from a file
_hwrite Writes data to a file
hmemcpy Copies bytes from source to destination buffer
long _lseek( int handle, long offset, int origin );
A visszatérési értéke az új pozíció távolsága bájtokban a fájl kezdetétől
az origin paraméter lehet:
SEEK_SET fájl kezdete
SEEK_CUR aktuális pointer pozíció
SEEK_END fájl vége
UINT _lread(HFILE hFile,LPVOID lpBuffer, UINT uBytes );
A visszatérési értéke az olvasott bájtok száma. Ha ez az érték kisebb mint az uBytes paraméter, akkor a függvény a fájl végét észlelte. Ha a visszatérési érték HFILE_ERROR, akkor az olvasás nem sikerült.
HFILE _lcreat( LPCSTR lpPathName, int iAttribute );
az iAttribute lehet:
Normal(can be read from or written to without estriction).
Read only (cannot be opened for write)
Hidden (not found by directory search)
System (not found by directory search)
HFILE _lopen( LPCSTR lpPathName, int iReadWrite );
Az iReadWrite paraméter lehet:
OF_READ Olvasásra megnyitja a fájlt.
OF_READWRITE Írásra és olvasásra.
OF_WRITE Írásra.
továbbá még más beállításokat is lehet beállítani a fájl megosztással kapcsolatban.
UINT _lwrite(HFILE hFile,LPCSTR lpBuffer, UINT uBytes );
HFILE hfReadFile, hfTempFile;;
int cbRead;
PBYTE pbBuf;
/* Open the input file (read only). */
hfReadFile = _lopen("testfile", OF_READ);
if (hfReadFile == HFILE_ERROR)
hfTempFile = _lopen("tempfile", OF_WRITE);
if (hfTempFile == HFILE_ERROR)
/* Allocate a buffer for file I/O. */
pbBuf = (PBYTE) LocalAlloc(LMEM_FIXED, 2048);
/* Copy the input file to the temporary file. */
do while (cbRead != 0);
/* Free the buffer and close the files. */
LocalFree((HLOCAL) pbBuf);
_lclose(hfReadFile);
_lclose(hfTempFile);
Érdemes megemlíteni, hogy a Windows használja mind az OEM és az ANSI kódolást. Tehát ha egy DOS program által létrehozott fájlt szeretnénk feldolgozni, szükségünk lehet arra, hogy a két kódolási mód között konvertáljunk. Erre külön függvények vannak OemToAnsi és AnsiToOem.
|