Tohle jsou me poznamky z uceni na zkousku. Je to jen mensi cast z toho, co je treba ke zkousce umet (neni tu .net atd.). Nejsou a nebudou kompletni.
zdroje:
- MSDN
- Mark E. Russinovich, David A. Solomon: Microsoft Windows Internals, Fourth Edition: Microsoft Windows Server 2003, Windows XP, and Windows 2000
********************************************************************************
* PAMET *
********************************************************************************
ALOKACE PAMETI
Defaultni velikost virtualni pameti procesu jsou 2GB, maximalne 1024GB.
Hlavni ukoly process manageru:
------------------------------
- prekladani a mapovani adres z virtualnich na fyzicke
- strankovani pameti na disk, kdyz je potreba a jeji vraceni zpet do pameti
Rodicovsky proces muze cist a psat do pameti potomka - debuggery...
Existuji 3 druhy API funkci pro praci s pameti:
-----------------------------------------------
1. Virtual...
2. CreateFileMapping, MapViewOfFile
3. Heap... nebo Local... a Global... (starsi verze)
Velikosti stranek
-----------------
dva druhy - velike a male, zavisi na architekture (pro x86 4KB a 4MB)
Vyhodou velikych stranek je rychlejsi preklad na fyzickou adresu, mensi stranky
potrebuji vice TLB zaznamu. nevyhoda je, ze prava muzeme nastavovat jen
strankam, tedy pokud mame stranku s kodem a daty pro cteni a zapis zaroven,
musi byt cela stranka rw, vcetne kodu. Tedy kod muze byt prepsan, aniz by doslo
k poruseni prav.
Druhy stranek
-------------
Stranky muzou byt free, reserved, nebo committed.
Funkce muze stranku nejprve rezervovat, pak ji committed, nebo to muze udelat
v jednom volani zaroven. Toto zarizuji VirtualAlloc funkce.
Rezervovana stranka je rezervovana pro budouci pouziti, neni mozne z ni cist,
to zpusobi chybu.
Committed stranky jsou stranky, ktere maji svou adresu ve fyzicke pameti.
Mohou byt bud soukrome, nebo sdilene (vice metodami, viz dale).
Privatni stranky jsou vytvoreny v dobe prvniho pristupu na ne. Pro cteni
a zapis do stranek cizich procesu se pouzivaji funkce ReadProcessMemory
a WriteProcessMemory. Pokud se na strankach nachazi mapovany soubor, byva
precteny z disku.
Stranky mohou byt uvolneny nebo decommittovany pomoci funkci VirtualFree.
Decommittovana pamet je stale rezervovana, ale neni commited. Uvolnena
pamet neni ani commited, ani rezervovana.
Rezervovani pameti se hodi procesum, ktere potrebuji mit k dispozici velky
souvisly kus pameti. Kdyz nektery kus kustecne potrebuji, committuji si ho
a pak zase decommittuji.
Ochrana pameti
--------------
Copy-on-Write protection:
Pokud si dva procesy nactou z dll stejne stranky, budou je mit nactene na stejne
fyzicke adrese. Az kdyz jeden z nich tam bude chtit zapsat, tak se mu stranka
zkopiruje jinam a budou se pak divat kazdy na jine stranky.
Nacitani aplikaci a dll:
Pokud spustime vice instanci jednoho programu, kazda z nich bude mit svuj
vlastni virtualni prostor. Typicky ale budou stejne hodnoty jejich hInstance,
ktere znazornuji base address instance. Pokud se podari vsechny instance
naloadovat na jejich base address, dokud nebudou chtit zapisovat do pameti,
bude jejich fyzicka pamet sdilena. Pokud nektera instance nebude naloadovana
na sve base address, dostane rovnou samostatnou fyzickou pamet (protoze musi
zmenit stranky obsahujici kod, kvuli posunu adres).
Dllky jsou vytvareny se svou defaltni base addres. Tedy opet vice aplikaci
muze sdilet jedno nacteni dll (ve fyzicke pameti), pokud se je podari nacist
na defaultni adresu ve virtualni pameti.
VirtualAlloc
--------------
- funkce c: malloc, fmalloc, new (oop)
working set: cast virtualni pameti, ktera skutecne odkazuje na fyzickou pamet
Pro alokaci pouzivame funkce HeapAlloc, VirtualAlloc, GlobalAlloc, LocalAlloc.
Timto zpusobem alokujeme pamet pouze pro nas proces, pokud bychom chteli
vytvorit sdilenou pamet, musime pouzit file mapping popsany nize.
VirtualAlloc:
- rezervuje stranky
- commituje stranky
- reservuje a commituje stranky zaroven
Pocatecni adresu pro alokaci bud muzeme sami vybrat, nebo to ponechat na
systemu. Na reserved stranky nesmime pristupovat, commited strankam muzeme
dat prava PAGE_READWRITE, PAGE_READONLY, nebo PAGE_NOACCESS pro pristup.
Kdyz je stranka commited, je vytvorena ve fyzicke pameti, az kdyz je na ni
poprve pristoupeno pro R nebo W.
Uvolnovani pameti
-----------------
Funkce VirtualFree umi:
- decommittovat stranky (na reserved)
- uvolnit stranky (na free)
- oboji najednou
Zamykani pameti a dalsi prace s ni
-----------------------------------
Pro zjisteni velikosti stranky: GetSystemInfo
VirtualQuery:
Vraci informace o pameti od zadane adresy tykajici se naseho procesu.
VirtualQueryEx dovoluje zadat jiny proces (pouziva se k debuggovani).
Dozvime se z ni, jaka prava ma stranka (adresa) nastavena a v jakem stavu je
stranka (free, reserved, committed). Tato funkce ale funguje pouze v dolnich
dvou GB virtualni pameti (pamet nalezici jen nasemu procesu), nefunguje
v hornich dvou GB (pamet nalezici systemu).
Zamceni stranky znamena, ze ji zakazeme swapovat na disk.
Stranky muzeme zamykat dvema zpusoby:
- Zavolanim fuknce VirtualLock pro zamceni stranek v jejich working set.
Pocet takto zamcenych stranek sezmi presahnout urcitou velikost zavislou
na minimalni velikosti working set. Tedy pokud proces potrebuje zamknout
vice stranek, je treba zmenit tuto velikost funkci SetProcessWorkingSetSize.
- Drivery mohou zavolat funkci MmProbeAndLockPages, MmLockPagableCodeSection,
MmLockPagableDataSection, nebo MmLockPagableSectionByHandle. Takto zamcene
stranky zustavaji v pameti, dokud nejsou explicitne odemknuty.
VirtualProtect:
Muzeme menit prava pritupu na stranku. VirtualProtectEx umoznuje totez u ciziho
procesu. Pouzivaji debuggery.
Heap funkce
-----------
Mame moznost si vytvorit vlastni haldu pomoci HeapCreate a pak uz jen z ni
ukrajovat pomoci HeapAlloc. A k cemu je to dobre? Kdyz volame treba
VirtualAlloc, je to pomale, protoze se vola kernel a navic dostaneme hned celou
stranku. Kdyz zavolame treba malloc v C, tak nedostaneme celou stranku. Jak to
ten malloc dela? Malloc ukrajuje z nejakeho heapu jen male kousky a je rychly,
protoze nevola funkce z kernelu. My si muzeme taky vytvori vlastni haldu, kdyz
treba nechceme pracovat s tou, co pouziva malloc... Volanim HeapCreate si
vytvorime haldu, to je asi stejne pomale, jako VirtualAlloc, diky volani
kernelu. Jenze po tom, co to udelame, uz mame haldu. A kdyz ted budeme chtit z
ni kousek pameti, zavolame jen HeapAlloc a dostaneme ho - a to uz je hodne
rychle.
Pri volani HeapCreate zadavame pocatecni i maximalni velikost heapu. Pocatecni
velikost nam rika, kolik pameti ma byt commited, ostatni az do maximalni
velikosti bude reserved. Committed pamet se bude rozsirovat, jak bude potreba,
na ukor reserved pameti, pri volani HeapAlloc. Committed pamet se uz nikdy
nedecommittuje, dokud proces neskonci, nebo nebude halda znicena pomoci
HeapDestroy.
Na starsich Windows (95,98,ME) bychom pomoci HeapCreate nemeli tvorit bloky
vetsi nez 2MB, k tomu je treba pouzit VirtualAlloc.
HeapSize, HeapValidate: zjisti velikost heapu, valizuje, zda je heap v
konzistentnim stavu
GetProcessHeap: pro ziskani heapu vlakna (ten, co pouziva treba ten malloc)
Globalni funkce
---------------
Global a Local funkce jsou zastarale (z 16b Windows), nekde se vsak stale
jeste pouzivaji. Jsou pomale, doporucuje se spise pouzivat Heap funkce. Na
16b Windows byla Local halda a Global halda, na novych tomu jiz tak neni,
proto se Global a Local funkce chovaji stejne.
Stranky alokovane temito funkcemi maji RW pritup a jsou pristupne pouze nasemu
procesu. Pro sdileni pameti se v 16b Windows pouzival parametr GMEM_DDESHARE,
ktery na novych Windows nema na sdileni zadny vliv.
Muze se alokovat vice pameti, nez jsme chteli. Aktualni velikost zjistime pomoci
GlobalSize nebo LocalSize. Nadbytecne stranky muzeme pouzivat.
Na starsich Windows (95,98,ME) bychom pomoci Local a Global funkci nemeli tvorit
bloky vetsi nez 2MB, k tomu je treba pouzit VirtualAlloc.
Souhrn
------
Kazdy proces pri vytvoreni dostane svuj heap. Pri volani funkci HeapAlloc,
LocalAlloc a GlobalAlloc se ukrajuje z tohoto heapu. Pri volani VirtualAlloc
se ukroji z pameti mimo heap. Pokud zavolame HeapCreate, vytvorime tim novy
heap mimo ten puvodni defaultni heap a z nej pak muzeme ukrajovat pomoci
HeapAlloc.
===============================================================================
FILE MAPPING
File mapping: spojeni souboru a pameti procesu
File mapping object: pokud namapujeme soubor do pameti, nazyvame tak cely
vznikly objekt
File view: cast file mapping object, ke ktere pristupujeme
Mapovat je mozne kterykoliv soubor na disku (vcetne swap). Mapovat muzeme cele
soubory, nebo jen jejich casti.
Pokud jsou stranky, na kterych je mapovani souboru, odswapovany na disk, jejich
obsah se zapise do originalniho souboru (na disku). Pri nacitani ze swapu do
pameti se obsah obnovi z originalniho souboru.
Z jednoho mapovani souboru, muzeme vytvorit hned nekolik file view.
Pokud chceme mapovany soubor sdilet, nesmi se nachazet na vzdalenem pocitaci.
Uvod
----
Pamet byva sdilena pomoci section objects nazyvanych file mapping objects.
Section object muze byt spojen se souborem na disku (mapoveni souboru) nebo
s committed pameti (sdileni pameti).
Pro vytvoreni section object pouzivame funkci CreateFileMapping, ktere dame
handle na soubor, ktery chceme mapovat, nebo hodnotu INVALID_HANDLE_VALUE pro
mapovani do pameti, dale jmeno a bezpecnostni popis. Pokud ji pojmenujeme,
ostatni procesy ji mohou otvirat funkci OpenFileMapping. Nebo s ni muzeme
zachazet pomoci handlu. Drivery mohou pozuzivat ZwOpenSection,
ZwMapViewOfSection nebo ZwUnmapViewofSection.
Mapovat muzeme i objekty vetsi, nez je pamet. V jedne chvili budeme pracovat
je s jednou casti, nazyvanou view of file.
K namapovani sdilene pameti k nasemu procesu pouzivame funkci MapViewOfFile.
Fyzicka adresa teto pameti, kde je sdileny objekt ulozen, je pro vsechny
sdilejici procesy stejna, ovsem virtualni adresy se mohou lisit. Pomoci funkce
MapViewOfFileEx vsak muzeme sjednotit i virtualni adresy.
Sdilena pamet je uvolnena a soubor je aktualizovan v okamziku, kdy posledni
sdilejici proces uvolni sdilenou pamet pomoci UnmapViewOfFile. Soubor lze
updatovat i pred tim, pomoci funkce FlushViewOfFile.
Vytvoreni mapovani
------------------
Otevreme soubor pomoci funkce CreateFile s exlizivnim pristupem, abychom
zajistili, ze ostatni nam nebudou do mapovaneho souboru zapisovat. Mapovani
vytvorime funkce CreateFileMapping. Ta rezervuje potrebnou pamet.
Neni mozne mapovat soubory s nulovou delkou a menit velikost mapovani.
Velikost mapovani je omezena mistem na disku, velikost file view velikosti
virtualni pameti.
Vytvoreni file view
-------------------
Pro pristup k datum ze souboru je potreba vytvorit view of file funkci
MapViewOfFile(Ex). File view nemuze byt vietsi, nez mapovany objekt.
Sdileni souboru a pameti
------------------------
Pro sdileni potrebujeme handle nebo jmeno file mapping objectu.
Prvni proces otevre soubor, vytvori file mapping object. Pro sdileni pameti
nezasociovane se souborem misto nazvu souboru dame konstantu
INVALID_HANLE_VALUE.
Ostatni procesy pak jiz jen otevrou mapovani pomoci OpenFileMapping bud pomoci
jmena, nebo duplikovaneho handlu.
Pristup se musi synchronizovat.
Soubor je namapovany, dokud vsechny procesy nezavolaji CloseHandle na jeho
mapovani.
Ukonceni mapovani
-----------------
UnmapViewOfFile, CloseHandle
===============================================================================
TLS
Thread Local Storage
Vlakna procesu sdileji jeho virtualni prostor. Lokalni promenne jsou lokalni
pro kazde volani funkce, globalni jsou globalni mezi vsemi vlakny. Pomoci TLS
zajistime kazdemu vlaknu urcity pocet TLS 'promennych', ke kterym bude mit
jednoduchy pristup.
Na zacatku proces vytvori index (nebo vice indexu - do kazdeho indexu si muze
kazde vlakno dat jednu hodnotu, nebo jeden ukazatel). Pak se vytvori vlakna.
Hned po vytvoreni ma vlakno nastavene tyto indexy na NULL a muze si naalokovat
misto a ukladat do nich.
Napriklad:
1. deklarujeme globalni TLS index \
static DWORD TlsIndex; | hlavni proces
2. alokujeme misto pro index |
TlsIndex = TlsAlloc(); /
3. alokujeme pamet na data, nastavime hodnotu do indexu
int *p=malloc(256);
TlsSetValue(TlsIndex, p);
4. kdyz chceme vratit ulozenou hodnotu
int *q;
q = TlsGetValue(TlsIndex);
5. ukonceni prace s tls
free(p);
TlsFree(TlsIndex);
===============================================================================
VIRTUAL ADDRESS DESCRIPTOR
Windows nacitaji stranky do pameti az tehdy, kdyz je nekdo potrebuje, musime si
o to rict. Podobne je to tvorenim page tables pro popis nove alokovanych
stranek. Pokud si nejaky proces committuje nejake stranky, mohl by k nim
vytvorit page tables hned, ale co kdyz je nikdy proces nepouzije? To by bylo
plytvani casem. Radeji pocka, az nastane vypadek te stranky a az potom k nim
vytvori page tables. Proto i alokovani velkych kusu pameti nezabere moc casu.
Kdyz si vlakno rekne o pamet, spravce pameti mu musi dat nejaky rozsah adres.
Aby to mohl udelat, musi si udrzovat strukturu, ktere stranky virtualniho
adresovehe prostoru vlakna jsou jiz rezervovane a ktere ne. Tato struktura se
nazyva Virtual Address Descriptors (VADs). Pro kazdy proces spravce pameti
udrzuje sadu VADs. VADs jsou usporadany jako samovyvazovaci binarni strom,
ve Windiws 2003 jako AVL strom.
Ve VAD si spravce udrzuje tyto informace:
- rozsah rezervovanych adres
- sdileno-nesdileno
- zda potomek muze dedit obsah
- protection
Kdyz vlaknou poprve pristoupi na adresu, spravce musi vytvorit pro danou stranku
PTE. Potrebne informace cerpa prave z prislusneho VAD. Pokud adresu ve VADs
nenajde vubec, nebo zjisti, ze neni commited, zahlasi chybu.
===============================================================================
PAGE TABLES
Zakladni prehled
----------------
Page tables jsou struktury, diky kterym muze system prelozit virtualni adresu
na fyzickou. Kazda virtualni adresa ma svoji PTE (page table entry), ktera
obsahuje fyzickou adresu, do ktere je ta virtualni mapovana.
fyzicke adresy
: :
+--------------+
| |
+-----------------+ +--------------+
| virtual address |-----> PTE ------+ +->| |
+-----------------+ | | +--------------+
| virtual address |-----> PTE ---------+ | |
+-----------------+ | +--------------+
+---->| |
+--------------+
: :
Defaultne na x86 systemech Windows pouzivaji dvouurovnove page tables. Na 32b
systemech je adresa ve forme tri oddelenyvch casti:
- page directory index
- page table index
- byte index
Adresa se zjistuje takto:
Page directory index nam ukazuje, kde je page table obsahujici PTE virtualni
adresy. Page table index nam ukazuje, kde presne je v te page table nami
pozadovany PTE, cili vime fyzickou adresu stranky. Byte index nalezne
pozadovanou adresu v te fyzicke strance.
page directory index page table index byte index
| | |
| | page tables | fyzicka pamet
| page directory | |
| (of tables) | +-----------+ | +--------------+
| | | | | | |
| +--------------+ | +-----------+| | | |
| | | | | || | | |
| +--------------+ | +-----------+ || | | |
+-->| PDE |-------->| | |: | | |
+--------------+ | | | |: | | |
| | | |...........| :: | | |
: : | | | : | | |
: : +-->| PTE |-----|-> +--------------+...adresa
: : |...........| | | | pozadovane
| | +-----------+| +---->pozadovany | stranky
| | | || | byte |
| | +-----------+|: | |
+--------------+ | ||: +--------------+
| ||| : :
| ||| : :
| |:+
: :
Jak probiha preklad:
1. Hardware pro spravu pameti najde page directory naseho procesu. Pri kazdem
context switchi (prepina procesu na procesoru) se tato adresa typicky ulozi
do nejakeho specialniho registru procesoru.
2. Page directory index je ukazatel do page directory na strukturu PDE, ktera
ukazuje na page table (ma jeji cislo), kde je ulozeno nase PTE.
3. Page table index nam v page table ukaze, kde je nase PTE.
4. Pomoci PTE najdeme fyzickou stranku (pokud tam neni a nepodari se ji nacist
z disku nebo nemame prava, nahlasi se chyba).
5. Byte index nas posune na spravnou adresu na strance.
Page directories na x86
-----------------------
Kazdy proces ma svuj vlastni page directory. Fyzicka adresa page tables je vzdy
na virtualni adrese 0xC0300000. Adresa page direcotory se uklada do registru
CR3.
Page directory se sklada z PDEs - page directory entries. Kazde PDE je 4B
dlouhe. Page directory popisuje vsechny stavy a umisteni page tables.
Pro popis sveho virtualniho adresoveho prostoru ma kazdy proces sve page
tables, ale pro popis virtualniho adresoveho prostoru systemu jsou page tables
spolecne vsem procesum. Kdyz proces vznika, ukazatale z page directory na
systemove stranky se nastavi na jiz existujici page tables.
Ovsem pokud je treba zvetsit pocet systemovych stranek, spravce pameti nechodi
zpetne po procesech a neupravuje jim page directory. Udela to, az kdyz proces
bude chtit na novou adresu pristoupit.
Page tables a PTE na x86
------------------------
Page tables jsou slozeny z policek PTE. Ktere PTE chceme nam ukaze page table
index (10b). V PTE jsou napriklad informace: Dirty, Accessed, Cache Disabled,
Owner, Write, Valid... Odtud tedy vime, zda je stranka dirty, zda je
zapisovatelna, zda ma sve umisteni ve fyzicke pameti...
Byte index
----------
Udava nam, kolik bytu od zacatku stranky je hledana adresa.
Translation Look-Aside Buffer
-----------------------------
Prekladani adres je docela narocne, takze system si casto pouzivana adresy
a jejich preklady uchovava v bufferu nazyvanem TLB. TLB je asociativni pamet,
ze ktere je mozno cist vice hodnot najednou a porovnavat, kterou chceme.
===============================================================================
SYSTEM MEMORY POOLS
Pri inicializaci systemu jsou vytvoreny dva pooly, ze kterych bude system
alokovat pamet.
Nestrankovany pool: sestava z virtualnich adres, u kterych je zaruceno, ne
nebudou vyswapovany na diks, tedy pristup k nim nemuze zpusobit vypadek
stranky. Nutne pro zpracovani preruseni.
Strankovany pool: stranky mohou byt vyswapovany
********************************************************************************
* PROCESY, VLAKNA, PRIORITY *
********************************************************************************
VLAKNA
Zakladni pohled
---------------
Proces vznikne zavolanim jedne z funkci CreateProcess(...). Strucny popois fazi
pri tvorbe procesu:
1. otevreni image file (.exe)
2. vytvoreni objektu procesu
3. vytvoreni prvniho (hlavniho) vlakna (stack, pak kontext, pak objekt vlakna)
4. informujeme windows, ze vytvarime novy proces
5. spustime hlavni vlakno (pokud nebyl nastaven CREATE_SUSPENDED flag)
6. jiz v kontextu noveho procesu dokoncime pripravu adresoveho prostoru
(loadovani dll) a pokracujeme v behu noveho procesu
Ve volani CreateProcess muzeme specifikovat prioritu noveho procesu.
- Pokud neudame zadnou hodnotu, bude mit proces prioritu Normal. Pokud ovsem
bude mit otec tohoto procesu nizsi prioritu (below normal nebo idle), dostane
novy proces tutez prioritu jako jeho otec.
- Kdyz specifikujeme vice priorit, vybere se ta nejnizsi.
- Pokud chceme Real-time prioritu, ale nebude mozne ji procesu nastavit,
CreateProcess jen kvuli tomu nespadne, misto toho mu nastavi Heigh.
- Pokud nespecifikujeme desktop, dostane aktualni desktop.
1. Otevreni image file
Primo lze spoustet pouze aplikace pro Windows primo. Pokud chceme spustit
jinou aplikaci, napr. MS-DOS nebo POSIX aplikaci, musime pred tim naloadovat
pomocny image (Ntvdm.exe, Posix.exe...). Pokud se to nepodari, CreateProcess
zhavaruje.
Windows XP a Windows 2003 pred otevrenim programu kontroluji, zda to neni
zakazane.
2. vytvoreni objektu procesu
adresovy prostor noveho procesu sestava z:
- page directory
- hyperspace page
- working set list
3. Vytvoreni vlakna
NtCreateThread:
- zvysi se pocet vlaken procesu
- vytvori se a inicializuje exclusive thread block (ETHREAD)
- vygenerovani id
- nastaveni TEB
- nastaveni KTHREAD bloku, nastaveni priorit a kvant
- kontrola prav pro tvoreni vlaken
4. Informovani systemu
handly procesu a vlakna, creation flags, id otce, zda se jedna o aplikaci
Windows
Kazdy proces startuje s jednim vlaknem, ale potom muze tvorit nova. Vsechna
vlakna procesu sdileji virtualni adresovy prostor a systemove zdroje.
Multitasking
------------
Multitaskingovy OS deli procesorovy cas mezi vsechny procesy, co ho potrebuji.
Kazde vlakno ma sve casove kvantum, po vyprseni tohoto casu je od procesoru
odebrano, aby ho mohly vyuzit dalsi vlakna. Pri odebirani od procesoru je ulozen
takzvany kontext vlakna, kdyz je vlakno znovu pripusteno k procesoru, jeho stav
se obnovi z tohoto kontextu (kontext se nacte).
Vlakna
------
Nove vlakno tvorime funkci CreateThread, musime zadat adresu, kde zacina kod,
ktery bude vlakno vykonavat (typicky jmeno funkce). Proces muze mit najednou
spusteno mnoho vlaken. Z CreateThread dostaneme handle na vlakno, potom funkci
WaitForMultipleObjects muzeme cekat, dokud vsechna vlakna neskonci.
Argumenty novemu vlaknu muzeme predavat pomoci adres dynamicky alokovanych
promennych, nebo pomoci globalnich promennych (nutna sychronizace).
Vlakno muzeme rovnez tvorit v cizim procesu pomoci CreateRemoteThread, ale
musime na to mit prava (debuggery).
Kazde vlakno ma svuj stack. Stack je uvolnen, kdyz vlakno samo skonci, ale neni
uvolnen, kdyz je ukonceno cizim vlaknem. Velikost stacku muzeme nastavit ve
funkci CreateThread, nebo ji muzeme nechat defaultni.
Po vytvoreni vlakna mame jeho handle se vsemi pravy a id vlakna. Handle muzeme
duplikovat pomoci DuplicateHandle. Handle vlakna muzeme take ziskat z jeho id
pomoci OpenThread.
Sleep, Suspend, Resume
----------------------
Vlakno muze pozdrzet (suspend) a pak opet pokracovat (resume) jine vlakno pomoci
funkci SuspendThread a ResumeThread. Po dobu, co je vlakno pozdrzeno neni
pripousteno k procesoru. Pokud je vlakno rovnou vytvoreno v pozdrzenem stavu,
nezacne se vykonavat, dokud ho nekdo nespusti funkci ResumeThread (pomoci
handle). Vlakno muze take pozdrzet vykonavani sebe saam na urcity casovy usek
pomoci funkce Sleep.
Ukonceni vlakna
---------------
Dusledky ukonceni vlakna:
- uvolni se vsechny prostredky vlastnene vlaknem (okna treba)
- je nastaven exit code vlakna
- je signalizovano, ze objekt vlakna skoncil (coz zaregistruje treba
WaitForSigleObject funkce...)
- pokud se jednalo o posledni vlakno procesu, je ukoncen cely proces
Navratovou hodnotu vlakna muzeme ziskat funkci GetExitCodeThread (bezici
proces ji ma STILL_ACTIVE).
Po ukonceni vlakna neni objekt vlakna uvolnen, dokud se nezavrou vsechny
handly na neho.
Ukoncit vlakno lze temito zpusoby:
- vlakno zavola ExitThread
- kterekoli vlakno procesu zavola ExitProcess
- vlakno samo skonci a vrati hodnotu
- jakekoli vlakno zavola TerminateThread
- jakekoli vlakno zavola TerminateProcess
Pokud ukoncim vlakno funkcemi Exit*, system oznami naloadovanym knihovnam, ze
vlakno uz je nebude pouzivat. Pokud ale pouzijeme Terminate*, knihovny se nic
nedozvi. Terminate* funkce by se nemely moc pouzivat, protoze po vlaknu
neuklidi.
Bezpecnost
----------
Pri vytvareni vlakna muzeme specifikovat security descriptor. Kdyz ho dame NULL,
vlakno dostane defaultni nastaveni. Pro dalsi manipulaci slouzi funkce
GetSecurityInfo a SetSecurityInfo.
================================================================================
PROCESY
Vytvoreni procesu
-----------------
Novy proces muzeme vytvorit funkci CreateProcess. Pokud se stvoreni procesu
povede, dostaneme ukazatel na strukturu PROCESS_INFORMATION obsahuji handly na
proces a jeho primarni vlakno. Pokud je uz nebudeme potrebovat, meli bychom je
zavrit pomoci Closehandle. K novemu procesu ma stvoritelsky proces (rodic)
defaultne plna prava. Pro specialni bezpecnostni nastaveni pouzijme funkce
CreateProcessAsUser nebo CreateProcessWithLogonW.
Ruzne vlastnosti muzeme nastavit ve strukture STARTUPINFO, ktera je argumentem
CreateProcess (ukazatel na ni). Informace ze STARTUPINFO maji prednost pred
vsemi ostatnimi!
Handly vracene CreateProcess maji vsechna prava, mohou se dedit a jsou platne,
i kdyz uz proces skoncil. Ve vracene strukture je rovnez PID procesu.
ID (PID) a handle
-----------------
PID naseho procesu muzeme dostat pomoci funkce GetCurrentProcessId. Handle na
proces ziskame funkci OpenProcess (potrebujeme k tomu PID). Pomoci funkce
GetCurrentProcess ziskame take handle (pseudohandle), ale pouze na nas proces
a handle neni duplikovatelny, neplati v jinych procesech.
Dedicnost
---------
Proces muze a nemusi sdedit nektere veci od sveho rodice. Dedit muze:
- handly z CreateFile
- handly na procesy, vlakna, mutexy, eventy, semafory, pipy a file mapping
objekty
- promenne prostredi
- aktualni adresar
- konzoli
- error mode
- affinity mask
- association with job
Nemuze dedit:
- tridu piority
- handly vracene LocalAlloc, GlobalAlloc, HeapCreate a HeapAlloc
- pseudohandly
- handly vracene LoadLibrary
- GDI a USER handly
Aby mohly byt handly dedicne, musime jim to nastavit pri jejich tvorbe vetsinou
pomoci paramatru bInheritHandle nebo podobne. Pak pri tvoreni procesu musime
nastavit STARTUPINFO.
Dedeni promenych prostredi a aktualniho adresare je dafultni, ale da se zakazat
pri tvorbe procesu.
Ukonceni procesu
----------------
Ukoncenim procesu se stane:
- ukonci se vlakna
- uvolni se zdroje
- kernel objekty se zavrou
- kod procesu je odstranen z pameti
- nastavi se navratova hodnota
- signalizuje se ukonceni procesu
Objekty v kernelu se sice uzavrou, ale dokud je ma otevrene i jiny proces,
nezaniknou.
Funkce GetExitCodeProcess funguje podobne, jako u vlaken.
Ukoncenim procesu nezaniknou jeho potomci.
Jak muze byt proces ukoncen:
- nektere z vlaken zavola ExitProcess
- skonci posledni vlakno procesu
- nektere vlakno zavola TerminateProcess
- console control handler zavola ExitProcess (ctrl+c)
- uzivatel vypne system nebo se odloguje
Problem s dll u Terminate*, jako u vlaken.
Bezpecnost
----------
Pri vytvareni procesu muzeme specifikovat security descriptor (NULL znamena
defualtni).
Funkce
------
WinExec: nepouziva se, zustala pouze pro kompatibilitu s 16b Windows
CreateProcess: vytvori novy proces a jeho primarni vlakno
ShellExecute:
- nejrychlejsi
- slouzi ke spousteni, k editaci, hledani, tisku...
LoadModule: naloaduje zadany modul do adresoveho prostoru nasi aplikace
================================================================================
PRIORITY
Windows poskytuji preemptivni prioritni planovani. Defaultne muze vlakno bezet
na kteremkoli procesoru, ale muzeme to nastavit i jinak.
Funkce:
SuspendThread, ResumeThread: viz vyse
Get/SetPriorityClass
Get/SetThreadPriority: nastaveni priority relativne vuci priorite procesu
SwitchToThread: pro jednou se vzda sveho casoveho kvanta
Sleep: viz vyse
Vlakno bezi na procesu pouze vymezeny cas, nez se vystrida, takzvane kvantum.
Planovac bezi v kernelu. Pro planovac mohou nastat zajimave tyto situace, na
ktere musi reagovat:
- vlakno zacne byt pripraveno ke spusteni (nove vznikle napriklad)
- vlakno odchazi z procesoru (skoncilo, doslo mu kvantum, sleep...)
- zmenila se priorita vlakna
- zmenila se procesorova afinita (vlakno odmita nektere vybrane procesory)
Pri vymene vlaken bezicich na procesoru se ulozi kontext puvodniho vlakna
a nacte se kontext noveho.
Pri planovani se nehledi na procesy, ale na vlakna. Tedy mame-li nekolik vlaken
se stejnou prioritou a ze dvou ruznych procesu, kazde vlakno dostane stejne,
nezavisle na tom, kolik celkove dostane ktery z procesu.
Urovne priorit
--------------
Windows maji celkem 32 (0-31) prioritnich urovni.
31 +-----------+ ]
| | ]
| | ]
| | ] -> 16 real-time stupnu
| | ]
| | ]
16 | | ]
+-----------+
15 | | ]
| | ]
| | ] -> 15 promennych stupnu
| | ]
| | ]
| | ]
1 +-----------+
| | -> 1 systemova uroven
0 +-----------+
Pridelovani priorit se jeste deli do dvou skupin - Windows API a Windows kernel.
Windows API: nejdrive rozdeli procesy podle tridy priorit, kterou dostaly pri
svem stvoreni (Real-time, High, Above Normal, Normal, Below Normal, Idle) a
potom podle realtivnich priorit jejich konkretnich vlaken (Time-critical,
Highest, Above-normal, Normal, Below-normal, Lowest, Idle).
31 +-----+
| | ]
| | ]
| | 26 +----+ ]
| | |real| ]
24 | | |time| ] -> real-time rozmezi
| | | | ]
| | 22 +----+ ]
| | ]
16 | | ]
+-----+ +----+
15 | | 15 | | ]
| | |high| ]
| | | | 12 +----+ ]
| | 11 +----+ |above ]
| | |normal ]
| | 10 +----+ | | ]
| | | | 8 +----+ ]
| | |normal |below ] -> dynamicke rozmezi
| | | | |normal ]
| | 6 +----+ | | ]
| | | | 4 +----+ ]
| | |idle| ]
| | | | ]
| | 2 +----+ ]
| | ]
1 +-----+
| |
0 +-----+
Tedy proces ma pouze jednu zakladni prioritu a kazde vlakno ma dve priority:
aktualni a zakladni. Planovani je zalozeno na aktualni priorite. System
zvysuje prioritu vlaken majicich prioritu v dynamickem rozmezi, ale nemeni
priority vlaken v realtime (ty tedy maji aktualni a zakladni prioritu vzdy
stejnou).
Zakladni priorita vlakna je zdedena z priority procesu (ktery dedi prioritu
po svem otci defaultne). Prioritu procesu lze nastavit pri jeho vytvareni ve
funkci CreateProcess nebo i za jeho existence funkci SetPriorityClass. Zmenou
priority procesu zmenime i priority vlaken (zakladni, relatvni zustava).
Defaultne jsou relativni zakladni priority vlaken nastavena na stred, tedy na
24, 13, 10, 8, 6, nebo 4, ale nektere procesy maji tuto hodnotu rovnou vyssi,
to lze nastavit zavolanim funkce NtSetInformationProcess.
Tedy vlakno ma nejakou defaultni prioritu a my ji muzeme menit o 1 nebo o 2
nahoru i dolu. Navic ho muzeme nastavit na idle nebo time-critical prioritu,
ktera znamena prioritu 1 nebo 15, at je proces v kterekoli tride priorit.
Real-time priority
------------------
Pro zvyseni priority na real-time potrebujeme pravo 'increase scheduling
priority'. V real-time ale bezi mnoho dulezitych sytemovych procesu a proto
kdyz je budeme omezovat, muze to mit neprijemne nasledky.
Stavy vlakna
------------
- ready: vlakno je pripraveno ke spusteni
- standby: je u procesoru, pobezi hned po aktualnim vlaknu
- running: bezi
- waiting: na neco ceka (synchronizace...)
- transition: je ready, ale jeho kernel stack neni v pameti
- terminated: ukonceno
- initialized: pri vytvareni vlakna
********************************************************************************
* SYNCHRONIZACE *
********************************************************************************
Pro synchronizaci muzeme pouzit synchronizacni objekty a wait funkce. Stav
synchronizacniho objektu je bud signaled, nebo nonsignaled. Wait funkce dovoluje
blokovat sve vlakno, dokud urcity synchronizacni objekt neni ve stavu signaled.
================================================================================
OBJEKTY
Synchronizacni objekty
----------------------
Synchronizcani objekt je objekt, jehoz handle ma wait funkce vic jak jednoho
procesu, aby byla synchronizace mozna. Nektere typy objektu:
Event:
upozorni vlakna, ze nastala nejaka udalost
Mutex:
muze byt vlastnen pouze jednim vlaknem v jednom okamziku. umoznuje vykonavat
nejakou cinnost pouze jednomu vlaknu.
Semafor:
obsahuje hodnotu mezi 0 a nejakym maximem limitujici pocet vlaken zaroven
pristupujicich k nejakemu zroji.
Waitable timer:
upozorni vlakno, ze nastal nejaky cas
Mutexy
------
Stav mutexu je signaled, kdyz neni nikym vlastnen a nonsignaled, pokud ho nekdo
vlastni. Mutex muze vlastnit pouze jedno vlakno v kazdem okamziku. Kdyz chce
vlakno udelat neco, co smi je jedno vlakno v jednom okamziku, privlastni si
mutex (pokud ho nikdo nevlastni) a zacne to vykonavat. Po ukonceni teto cinosti
zase mutex uvolni.
Pro vytvoreni objektu mutexu je to funkce CreateMutex(Ex). Vytvarejici vlakno
zada mutexu jmeno (ale nemusi) a muze si zarucit, ze ho okamzite dostane
privlastnen.
Vlakna ostatnich procesu mohou otevrit handle k tomuto mutexu podle jmena funkci
OpenMutex. Pokud je mutex nepojmenovany, potrebuji handle, ktery ziskaji pomoci
DuplicateHandle.
Kazde vlakno vlastnici handle na mutex muze vyuzit nekterou z wait funkci, aby
ziskalo vladu nad mutexem. Wait funkce blokuje provadeni vlakna, dokud nebude
mutex volny (jine vlakno ho uvolni funkce ReleaseMutex).
Pokud na mutex ceka vice vlaken zaroven, nemusi byt dodrzeno poradi FIFO, system
muze poradi zmenit.
Kdyz mutex otevreme vicekrat po sobe jednim vlaknem, musime ho take vicekrat
zavrit. Napr.
capture_mutex(x); capture_mutex(x);
... ;
release_mutex(x); release_mutex(x);
je spravne.
Pokud vlakno skonci bez uvolneni mutexu, stane se mutex abandoned (zanechany).
Cekajici vlakno se ho muze zmocnit a wait funkce mu vrati WAIT_ABANDONED. Vlakno
tim vi, ze mutex byl abandoned, tedy ze doslo k nejake chybe.
Event objekty
-------------
Stav signaled muze byt explicitne specifikovan funkci SetEvent. Existuji dva
typy techto objektu:
Manual-reset event:
Stav zustava signaled, dokud neni funkci ResetEvent nastaven na nonsignaled.
Kdyz je signaled, libovolny pocet cekajich vlaken je uvolneno (najednou).
Auto-reset event:
Stav objektu je signaled, dokud jedno cekajici vlakno neni uvolneno - v tu
chvili system sam nastavi stav na nonsignaled. Pokud zadne vlakno neceka, objekt
zustava signaled. Pokud ceka vice vlaken, jedno je vybrano (neplati FIFO).
Event objekty se hodi pro signalizovani vlaknum, ze nastala nejaka situace.
Vytvarime je funkci CreateEvent(Ex). Pritom zadame pocatecni stav objektu a to,
zda je manual-reset nebo auto-reset, muzeme zadat jeho jmeno. Ostatni vlakna si
otevrou handle na tento objekt funkci OpenEvent.
Funkce:
SetEvent: nastavi specifikovany objekt na signaled. Pro manaul-reset objekty
tak zustane, dokud nebude nastaven fci ResetEvent na nonsignaled. Do
te doby uvolnuje vsechna vlakna. Pro auto-reset objekt zustane
signaled, dokud neuvolni jedno vlakno, pak je automaticky nastaven na
nonsignaled.
PulseEvent: nastavi objektu stav signaled a pak resetuje do nonsignaled stavu
po uvolneni urciteho poctu vlaken (ty, co cekali prave v okamziku
zavolani teto fce). funkce je nespolehliva, nemela by se pouzivat.
(zpetna kompatibilita)
ResetEvent: uvede objekt do stavu nonsignaled.
Priklad na autickach (pro zapamatovani):
manual-reset: kdyz se rozsviti zelena (nastane signaled), projedou sekcy auta
nejednou, dokud nebude cervena
auto-reset: na celnici se rozsviti zelena, je vpusteno jedno auto a okamzite
sviti cervena
:)
Priklad cekani na vice vlaken:
Hlavni vlakno ceka, az si ostatni vlakna neco udelaji, aby pak mohlo neco
udelat. Pro kazde vlakno vytvori eventu, nastavi na nonsignaled. Kdyz vlakno
ukol splni, nastavi svou eventu na signaled. Hlavni vlakno pak funkci
WaitForMultipleObjects pozna, kdyz vsechna vlakna dokoncila, co mela.
Semafory
--------
Obsahuje hodnotu od 0 do nejakeho maxima. Tento pocet je snizen, kdyz nektere
vlakno 'vejde do semaforu' (zacne vykonavat ohranicenou sekci kodu). Kdyz vlakno
'vyleze' ze semaforu, pocet na semaforu se zvysi. Tedy stav je signaled, kdyz
je pocet vetsi nez 0 a nonsignaled, kdyz je nula.
Semafor se hodi pro rizeni pristupu k zarizeni, ktere muze pouzivat pouze
omezeny pocet uzivatelu.
Semafor vytvorime pomoci CreateSemaphore(Ex), zadame pocatecni a maximalni
pocet (pocatecni byva maximalni), muzeme ho pojmenovat. Ostatni vlakna ho pak
mohou otevrit pomoci OpenSemaphore.
Pokud na semafor ceka vice vlaken, jedno je vybrano (ne FIFO).
ReleaseSemaphore: zvysi pocet na semaforu (ne vice, nez max). Napriklad pokud
pri vytvareni dame pocatecni pocet nula (pro inicializaci) a pak chceme semafor
skutecne zprovoznit, pouzijeme k tomu ReleaseSemaphore na maximalni hodnotu.
Zatimco mutex muze uvolnit pouze vlakno, co ho vlastni (ReleaseMutex), semafor
muze uvolnit kterekoliv vlakno (ReleaseSemaphore).
Critical section
----------------
Poskytuje stejne sluby jako mutex, ale mohou ho vyuzivat pouze vlakna jednoho
objektu. Oproti ostatnim objektum je znatelne rychlejsi. Jako u mutexu muze byt
kriticka sekce vlastnena v jednom okamziku pouze jednim vlaknem. Ovsem
neexistuje tu abandoned stav.
Proces sam je odpovedny za alokaci pameti pro pouziti Critical section, byva
to zajistovanu promennou a funkci InitializeCriticalSection.
Funkce EnterCriticalSection se pokusi vstoupit do kriticke sekce, ale pokud je
zrovna vlastnena nekym jinym, vlakno se zablokuje a neni mozne nastavit
time-out. Funkce TryEnterCriticalSection pouze zkontroluje, zda je kriticka
sekce volna, ale nezablokuje vlakno (pokud volna je, vstoupi do ni). Pro
opusteni kriticke sekce pouzivame LeaveCriticalSection funkci.
(Try)EnterCriticalSection lze volat i kdyz uz ji mame, ale je treba ji pak
tolikrat i uvolnit.
Po uplnem ukonceni prace s kritickou sekci uvolnime jeji prostredky pomoci
DeleteCriticalSection.
Na CriticalSection nelze cekat wait funkci.
================================================================================
WAIT FUNKCE
Wait funkce umoznuji vlaknu se zablokovat. Nevrati hodnotu, dokud nejsou splnena
urcita kriteria. Kdyz se takova funkce zavola, zkontroluje danou podminku a
podle toho bud vrati, nebo ji kontroluje opakovane pozdeji, dokud neni splnena.
Typy funkci:
- single object
- multiple object
- alertable
- registered
Single object
-------------
SignalObjectAndWait, WaitForSingleObject, WaitForSingleObjectEx
- potrebuji handle na synchronizacni objekt
Vrati, pokud bud objekt je v signaled stavu, nebo vyprsti time-out (mozno
nastavit na nekonecno).
Multiple objects
----------------
WaitForMultipleObjects, WaitForMultipleObjectsEx, MsgWaitForMultipleObjects,
MsgWaitForMultipleObjectsEx
- potrebuji pole handlu na synchronizacni objekty
Vraci, pokud je nastaven na signaled jeden nebo vsechny objekty (nastavime,
zda jeden nebo vsechny), nebo kdyz vyprsi time-out. Pokud cekame na vsechny
objekty, muze se stat, ze nez se nektery objekt nastavi na signaled, jiny se
po stavu signaled opet uvede do nonsignaled jinym vlaknem.
********************************************************************************
* NTFS *
********************************************************************************
Souborove systemy podporovane Windows maji nasledujici komponenty:
- volumes
- adresare
- soubory
A posledni verze NTFS podporuje take symbolicke linky.
Adresare - velmi strucne
------------------------
Adresare vytvarime a mazeme funkcemi CreateDirectory(Ex) a RemoveDirectory. Pro
smazani je nutne adresar vyprazdnit a smazat pristupova prava pro tento adresar.
Adresar lze presunout (i veskerym obsahem) pomoci MoveFileEx nebo
MoveFileWithProgress. Druha jmenovana funkce ma navic vyhodu, ze vraci informace
o tom, kolik uz je hotovo.
Aktualni adresar aplikaxe zjistime pomoci GetCurrentDirectory, nastavime ho
SetCurrentDirectory.
================================================================================
SOUBORY
Bezna prace se soubory
----------------------
CreateFile: vytvari nebo otevira soubory, vraci handle, ktery pouzivame pro
dalsi praci se souborem. Soubour zavreme CloseHandle.
CopyFile(Ex): kopirovani souboru, Ex poskytuje informace o prubehu
ReplaceFile, MoveFile(Ex)
Smazat muzeme soubor, pouze kdyz na nej neni otevreny zadny handle. Mazeme ho
podle jeho jmena funkci DeleteFile.
Kopmrese
--------
Kompresi uzivatel 'nevidi', zachazi s daty, jako by byla nekomprimovana.
Na NTFS ma kazdy soubor a adresar kompresni atribut, ktery muzeme ziskat pomoci
GetFileAttribute(Ex). Pokud je soubor komprimovany, tento atribut bude mit
hodnotu FILE_ATTRIBUTE_COMPRESSED. U adresare tento atribut rika, zda budou
nove utvorene soubory defaultne komprimovane, nebo ne.
Kazdy soubor a adresar ma taky kompresni stav. Ten mluvi o tom, zda je soubor
komprimovan a jakym zpusobem.
Pro zjisteni velikosti komprimovaneho souboru pouzijme funkci
GetComressedFileSize. Funkce GetFileSize by nam vratila velikost
nekomprimovaneho souboru.
Sifrovani
---------
Zda souborovy system podporuje sifrovani ziskame pomoci GetVolumeInformation.
Nektere soubory ale stale nemohou byt sifrovany:
- zkoprimovane soubory
- systemove soubory
- systemova adresare
- korenova adresare
Uzivatel, ktery ma opravneni soubor desifrovat muze pridat pomoci funkce
AdUserToEncryptedFile dalsiho uzivatele, ktery bude mit toto pravo.
Pro vytvoreni klice pro uzivatele: SetUserFileEncryptionKey.
Adresare nemohou byt jako takove sifrovany, nastaveni sifrovani pro adresare
znamena, ze defaultne bude kazdy novy soubor zasifrovan.
Pro zasifrovani souboru ho vytvorime funkci CreateFile s flagem
FILE_ATTRIBUTE_ENCRYPTED. Pro zasifrovani jiz existujiciho soubrou pouzijme
funkci EncryptFile, zasifruji se vsechny streamy. Desifrovani souboru provedeme
funci DecryptFile.
Pro zjisteni, zda je soubor sifrovan, nebo ne, pouzijem funkci
FileEncryptionStatus nebo GerFileAttributes. Pri kopirovani souboru se funkce
CopyFile(Ex) snazi novy soubor zasifrovat, ale nemusi se jim to povest a tak ho
nechaji nesifrovany.
Zapisovani a cteni souboru
--------------------------
ReadFile(Ex) a WriteFile(Ex): pozaduji otevreny handle na soubor. Zapisi nebo
prectou zadany pocet bytu na misto specifikovane file pointrem. Data jsou
zapsana presne, jak jsou zadane, bez dalsiho formatovani.
Po zavolani fnukce CreateFile je file pointer umisten na zacatek souboru. Pak se
posouva podle toho, kolik bytu jsme precetli nebo zapsali. Muzeme ho posunout i
rucne pomoci SetFilePointer (lze ji pouzit i pro zjisteni aktualni pozice).
Cast souboru si muzeme i zamknout - pokud do souboru treba zapisuje vice vlaken,
pred zapisem si zamkneme, kam chceme zapisovat, abychom nezapisovali najednou s
dalsim vlaknem... Existuji dva druhy zamku: exkluzivni (nikdo nesmi cist ani
psat) a sdileny (ostatni smeji cist). Funkce: LockFile(Ex) a UnlockFile(Ex).
Pri zapisu do souboru se obvykle data ukladaji do bufferu a na disk se skutecne
zapisi jednou za cas. Muzeme si zapis na disk vynutit okamzite funkci
FlushFileBuffers. Nebo CreateFile dame FILE_FLAG_NO_BUFFERING flag, ktery rika,
ze se buffery vubec nebudou vyuzivat a zapise se rovnou na disk.
Soubor muzeme zkratit funkci SetEndOfFile, ta usekne soubor na miste, kde se
prave nachazi file pointer. Tim se zmeni fyzicka velikost souboru. Logickou
velikost souboru nastavime pomoci SetFileValidData.
Overlapped
----------
ReadFile a WriteFile nam dovoluji zapisovat jak synchronne, tak asynchronne,
jejich Ex varianty jen asynchronne.
Synchronni cteni a zapis znamenaji, ze ja zavolam Read/WriteFile a oni neskonci,
dokud cela operace neprobehne, tedy dokud nebude vse potrebne precteno/zapsano.
Asynchronni znamena, ze funkce skonci hned, ale jejich prace bude pokracovat
dale. Zda uz je hotovo, se muzeme prubezne ptat. Lze pouzit pouze, pokud je
soubor otevren s flagem FILE_FLAG_OVERLAPPED.
Poslednim argumentem techto funkci je OVERLAPPED struktura.
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
};
PVOID Pointer;
};
HANDLE hEvent;
} OVERLAPPED,
*LPOVERLAPPED;
Pokud neni soubor otevren s FILE_FLAG_OVERLAPPED, struktura OVERLAPPED neni
potreba a zacne se cist/psat od aktualni pozice v souboru.
Offset: pozice, kde se ma zacit, je to byte offset od zacatku souboru. Offset
musi byt pred volanim funkci Read/WriteFile nastaven. Pokud se nejedna o soubor,
musi byt 0.
OffsetHigh: vyssi slovo offsetu
hEvent: handle na eventu, ktera bude nastavena do stavu signaled, az bude
operace dokoncena (na pocatku bude nastavena na nonsignaled). Musi byt nastaven
na NULL, nebo na skutecny platny handle. Objekt musi byt jiz vytvoren pomoci
CreateEvent.
Funkce GetOverlappedResult nas informuje o vysledku operace: zjistime, zda uz
skoncila (nebo na to muzeme pockat) a take, kolik bytu jiz bylo preneseno. Na
cekani nebo zjisteni ukonceno/neukonceno muzeme rovnez pouzit wait funkci na
eventu zadanou v OVERLAPPED.
Ridke soubory
-------------
Pokud mame soubor, ktery obsahuje velke mnozstvi nul, zabira to zbytecne misto
a nerika nam to nic uzitecneho. Castene lze tento problem vyresit kompresi, ta
se nul zbavi, ale na kompresy a dekopresi je treba cas.
Reseni je zapisovat na disk je nenulova data. Nuly nezapisujeme, jen si
pamatujeme, kde jsou a jejich mnozstvi. Soubor je na disku ve skutecnosti pak
mensi, ale pri cteni jsou na nuly vypisovany, jako by tam skutecne byly.
Aby mohla aplikace vytvorit ridky soubor, musi si toho byt vedoma a sama se pro
to rozhdonout. Pokud tak ucini, da to najevo zavolanim funkce DeviceIoControl
s FSCTL_SET_SPARSE modem. Nulovou oblast nastavi pomoci DeviceIoControl s modem
FSCTL_SET_ZERO_DATA. Pomoci FSCTL_QUERY_ALLOCATED_RANGES najdeme data, ktera
nejsou nulova. Pokud bychom zapsali nuly jinak, nez pomoci FSCTL_SET_ZERO_DATA,
budou na disk skutecne nuly zapsany.
Rovnez muzeme otevrit existujici soubor, preskovat, kde ma nuly a zapsat je
pomoci FSCTL_SET_ZERO_DATA, tim by se mela velikost soubrou na disku zmensit.
Skutecnou velikost souboru na disku potom zjistime pomoci GetCompressedFileSize.
GetFileSize nam vrati velikost i s nulami.
================================================================================
STREAMY
Stream je posloupnos bytu. V NTFS stream obsahuje data, ktera jsou zapsana do
souboru. Streamy nam davaji vetsi moznosti ulozeni nekterych informaci,
napriklad muzeme vytvorit stream, ktery obsahuje nejaka klicova slova pro
vyhledavani nebo identitu uzivatele, ktery ho vytvoril...
Kazdy stream je spojen s nejakym souborem, obsahuje svou velikost, alokovanou
velikost (kolik je pro nej vyhrazeno na disku), delku platnych dat. Dale
obsahuje informace o svem sifrovani nebo kompresi.
Stream ale nema casove znacky. Ty jsou aktualizovany pro vsechny streamy v
souboru, kdyz je zapsano do jednoho z nich.
Pojmenovavani streamu
---------------------
Jmeno streamu ma tvar:
jmeno_souboru:jmeno_streamu:typ_streamu
napriklad:
soubor.dat:muj_stream:$DATA
Pravidla pro pojmenovavani streamu jsou stejna, jako pro pojmenovavani souboru
(vcetne mezer). Nove typy streamu nelze vytvaret, lze pouze pouzit existujici,
vsechny zacinaji znakem dolaru.
Defaultne byva stream nepojmenovan. V takovem pripade se pise:
jmeno_souboru::typ_streamu nebo jen jmeno_souboru.
Pojmenovane streamy se na velikosti souboru zobrazovane v jeho vlastnostech
nepodileji. Tedy uvidime pouze velikost souboru s nepojmenovanych streamem.
********************************************************************************
* DLL *
********************************************************************************
DLL umoznuje misto cele funkce nebo knihovny zapsat do kodu jen adresu funkce
v DLL knihovne, kde se funkce nachazi. Rozdil mezi dynamicky a statickym
linkovanim je ten, ze pri statickem linkovani nakopiruje linker kod pozadovane
funkce primo do kodu, kde se vola.
Druhy DLL
---------
1. load-time dynamic linking
Modul vola funkce dll, jako by byly lokalni funkce. Vyzaduje to nalinkovat
modul s importovaci knihovnou pro dll, co obsahuje pozadovane funkce. Pak
vime, kterou dll naloadovat a kde s v ni funkce nachazi, kdyz je aplikace
spoustena.
2. run-time dynamic linking
Modul pouziva funkce LoadLibrary(Ex) pro naloadovani dll knihovny, kdyz uz
program bezi. Kdyz je knihovna naloadovana, pouzijeme GetProcAddress funkci,
abychom zjistili adresu pozadovane funkce. K tomu potrebujeme handle, ktery
nam vrati LoadLibrary(Ex) nebo ho muzeme ziskat pomoci GetModuleHandle. Tento
postup eliminuje potrebu importovaci knihovny.
Kdyz proces naloaduje dll, mapuje ji vzdy do sveho adresoveho prostoru. System
si udrzuje pro kazdou dll informace, kolik vlaken ji ma naloadovanou. Kdyz
proces skonci nebo kdyz je pocet naloadovani knihovny nulovy, dll je odloadovano
z adresoveho prostoru procesu.
Vlakna procesu, ktera naloadovala dll mohou pouzivat handly pro pristup do dll,
podobne dll muze pouzivat handly techto vlaken. DLL vyuziva stack volajiciho
vlakna a adresovy prostor volajiciho procesu.
Vyhody dynamicke linkovani oproti statickemu
--------------------------------------------
- dva procesy, co maji stejnou dll naloadovanou na stejne base adrese, ji
sdileji ve fyzicke pameti -> uspora pameti, mene swapovani
- kdyz se zmeni funkce v dll (a nemeni se jejich smysl a pocet argumentu),
neni potreba prelinkovavat nebo prekompilovavat volajici modul
- dll funkce mohou volat programy psane v ruznych jazycich, musi jen dodrzovat
konvence volani a argumenty
Nevyhody
--------
Program je zavisly na venkovni knihovne. Pokud jde o load-time a knihovna neni
dostupna, program se nepodari spustit. Pri run-time je pouze oznamena chyba.
================================================================================
TVORBA KNIHOVEN
Musime vytvorit kod, soubor pro linker rikajici o exportovanych funkcich. Pokud
chceme povolit load-time linkovani, musime vytvorit jeste importovaci knihovnu.
Kod musi obsahovat:
- exportovane (a neexportovane) funkce
- entry-point funkci (nepovinne)
Thread-safe: funkce si hlida pristup napriklad ke globalnim promennych,
synchronizuje ho, kdyby dll pouzivalo vice vlaken procesu zaroven.
Linker potrebuje vedet, ktere funkce budou exportovany - zalezi na prostredi,
ve kterem knihovnu tvorime. Nekde staci _declspec u jmena funkce, nekde .def
soubor, nekde je treba oboje.
Importovaci knihovna .lib obsahuje informace, ktere potrebuje linker, aby mohl
funkce loadovat za behu (rika nam, kde je ktera funkce umistena).
Entry-point funkce
------------------
Pokud je tato funkce v knihovne zahrnuta, zavola se pokazde, kdyz je knihovna
naloadovana, nebo odloadovana. Je tedy vhodna pro inicializaci nebo cleanup.
DllMain
-------
Entry-point funkci muze byt DllMain. Ovsem pozor, cinnost entry-point funkce
je omezena, obcas je treba si vytvorit jinou funkci s plnou funkcnosti a
pozadovat po uzivatelich, aby ji sami zavolali a prosli tak inicializaci.
Argumentem DllMain je fdwReason, ve kterem je ulozen duvod, proc je DllMain
volana. Mozne hodnoty:
- DLL_PROCESS_ATTACH
knihovna byla nactena do adresoveho prostoru procesu
funkci LoadLibary. Pri teto prilezitosti muze byt dll inicializovana, zavolan
TlsAlloc...
- DLL_THREAD_ATTACH
Aktualni proces vytvari nove vlakno. Muzeme napriklad inicializovat TLS slot.
- DLL_THREAD_DETACH
Vlakno se ukoncuje. Muzeme uvolnit pamet po TLS slotu atd.
- DLL_PROCESS_DETACH
dll je odloadovavana. Muze to zpusobit ukonceni procesu, FreeLibrary, nebo
chyba pri loadovani knihovny. Muzeme zavolat napr. TlsFree.
Funkce vraci TRUE, poud uspela, jinak FALSE. Pokud vrati FALSE pri naciani
LoadLibrary, vrati LoadLibrary NULL (a zavola DllMain a DLL_PROCESS_DETACH,
pokud DLL_PROCESS_ATACH nezpusobil vyjimku). Pokud vrati DllMain FALSE pri
spousteni procesu, proces se nepodari spustit.
DllMain se nemusi spustit s DLL_THREAD_ATTACH, ale muze s DLL_THREAD_DETACH,
napriklad pokud se dll loadovala az po vzniku vlakna, nebo pokud se jedna o
primarni vlakno procesu.
Pokud konci cely proces nebo je zavolano FreeLibrary, nevola se
DLL_THREAD_DETACH pro kazde vlakno, ale jen DLL_PROCESS_DETACH pro cely proces.
Pokud ukoncime proces nebo vlakno pomoci TerminatePorcess nebo TerminateThread,
zadne volani s DLL_PROCESS/THREAD_DETACH neprobehne.
DllMain nesmi volat LoadLibrary(Ex) nebo funkce tyto funkce volajici. Podobne
FreeLibrary.
Ale muze volat nektere funkce z Kernel32.dll, protoze je zaruceno, ze v okamziku
loadovani nasi dll je jiz Kernel32.dll naloadovana.
********************************************************************************
* DRIVERY *
********************************************************************************
IFS: Installable File System Kit, potreba pouze pokud chceme delat driver
souboroveho systemu
Windows checked version: verze Windows uzpusobena k ladeni
Debugovaci symboly
------------------
Vetsinou *.pdb soubor. Kdyz dostanu debuggovaci verzi neceho s debugovacima
symbolama a bez zdrojaku, muzu to dobre ladit. Napr. misto adres promennych a
funkci vidim jejich jmena. Symboly si musime opatrit pro spravnou verzi a musime
to rict debuggeru, kde jsou.
windbg: windows debugger od microsoftu
setenv.bat: skriptik, co nastavuje promenne prosredi pro sparvnou funkcnost
makefile: vetsnou jen includuje defaultni makefile, ktery hooodne dlouhy a
needitujue se
sources: jeden soubor, nastaveni promennych, jako target name, target path,
target type, flagy ke kompilatoru a hlavne, jake soubory chci
zkompilovat
build: podle makefile builduje
bld: build s barevnym vystupem
Misto main se pouziva DriverEntry funkce. Je volana jako prvni a jejim ukolem je
inicializovat driver. Ma dva argumenty:
- DriveObject: ukazatel na strukturu DRIVER_OBJECT, funkce DriverEntry ho ma
inicializovat
- RegistryPath: cesta v registru na misto, ktere ma driver vyuzivat, pokud chce
zapisovat do registru
Priklad DriverEntry funkce:
- aby mohl kdokoliv s driverem komunikovat, vytvorim device pomoci
IoCreateDevice (tam vyuziji ten DriverObject). Pri vytvareni device zadam i
jmeno toho device, musi byt \Device\jmeno a \DosDevice\jmeno. A kazdy device
ma svuj Device Object, do fce IoCreateDevice dam promennou, do ktere mi
ulozi ukazatel na ten objekt (fce IoCreateDevice ho vytvori)
- pokud chceme, aby s driverem komunikovala user mode aplikace, musime vytvorit
dos device link pomoci IoCreateSymbolicLink
- muzeme nastavit, ze driver bude reagovat na nektere podnety pomoci nastaveni
DriverObject. Napr.:
DriverObject->MajorFunction[IRP_MJ_CREATE]=JmenodriveruDispatchCreate;
Kdyz bude driver otevren pomoci CreateFile nejakou aplikaci, zavola se
JmenodriveruDispatchCreate funkce (nazyva se dispatch routine, jeji konkretni
jmeno muze byt libovolne, ale byva zvykem dodrzet tvar vyse uvedeny). Pripad
IRP_MJ_DEVICE_CONTROL znaci situaci, kdy je driver osloven pomoci funkce
IoDeviceControl (do ktere je zadano komu, buffer tam a zpet a jejich
velikosti, moznost overlapped).
Kdyz user mode aplikace zavola IoDeviceControl, tak se zavola device dispatch
routine. Ta dostane DeviceObject a IRP strukturu, nastane pripad
IRP_MJ_DEVICE_CONTROL. Nejdriv zjistime hodnotu IrpStack.
IrpStack=IoGetCurrentIrpStackLocation(Irp);
Chceme ziskat obsah toho bufferu, co nam ona aplikace posila a chceme ji
odpovedet. Pokud pouzivame metodu Buffered Io tak to znamena, ze budeme pracovat
jen s jednim bufferem pro vstup i vystup:
// ziskani buffer (vstupni a vystupni najednou)
ioBuffer = Irp -> AssociatedIrp AssociatedIrp.SystemBuffer SystemBuffer ;
Pak podobne ziskame velikost vstupniho a vystupniho bufferu a ioControlCode,
na zaklade toho ioControlCodu se rozhodneme, co s tim udelame. Pokud to neni
definovano, nastavime status na STATUS_INVALID_PARAMETER. Jinak nastavime status
na odpovidajici status a information na pocet zapsanych bytu. Pak zavolam
IoComleteRequest pro zakonceni operace.
Ovladani
--------
K driveru se pripojime pres CreateFile, komunikujeme pres IoDeviceControl,
ukocim CloseHandle. ControlCody vytvorim pres makro CTL_CODE.
Instalace
---------
INF:
Soubor vygenerujeme geninf.exe, pak pravym tlacitkem...
Service control manager:
OpenScManager: pripoji se k service control manageru na danem pocitaci a otevre
jeho databazi
CloseServicehandle: zavre hadle k service control manageru
CreateService: vytvori objekt sluzby a prida ji do datazabe manazeru
DeleteService: smaze sluzbu z databaze
OpenService: otevre existujici sluzbu
CloseServicehandle: zavre handle ke sluzbe
StartService: spusti sluzbu
ControlService: ovlada sluzbu (stop, pauza...)