Windows

Ogni risorsa è un kernel object con un puntatore di tipo HANDLE.

8-bit/Unicode

Utilizzo:

#define UNICODE #define _UNICODE #include <Windows.h> #include <tchar.h> _tprintf(...); _stprintf(...); int _tmain(int argc, LPTSTR argv[]); // Main generico int wmain(int argc, LPWSTR argv[]); // Unicode

File IO

HANDLE CreateFile(LPTSTR *name, DWORD access, DWORD shareMode, NULL, DWORD create, FILE_ATTRIBUTE_NORMAL, NULL);

Ritorna INVALID_HANDLE_VALUE in case of failure.

access: GENERIC_READ o GENERIC_WRITE, eventualmente in OR

shareMode: 0, FILE_SHARE_READ o FILE_SHARE_WRITE

create: CREATE_NEW (fails if file exists), CREATE_ALWAYS (overwrite if file exists), OPEN_EXISTING (fails if file does not exist), OPEN_ALWAYS (creates file if it doesn't exist)

BOOL ReadFile(HANDLE file, void *buf, DWORD nBytesToRead, DWORD *nBytesRead, LPOVERLAPPED ol); BOOL WriteFile(HANDLE file, void *buf, DWORD nBytesToWrite, DWORD *nBytesWritten, LPOVERLAPPED ol);

Ritornano true se la lettura/scrittura avviene con successo (anche se è EOF!)

BOOL CloseHandle(HANDLE handle);
DWORD SetFilePointer(HANDLE hFile, long distance, long distanceHigh, DWORD moveMethod);

Imposta il file pointer dell'handle (non del file!). distance è lungo 32 bit, distanceHigh sono i 32 MSB.

moveMethod: FILE_BEGIN, FILE_CURRENT, FILE_END.

DWORD SetFilePointerEx(HANDLE file, LARGE_INTEGER distance, LARGE_INTEGER *pointer, DWORD moveMethod);

LARGE_INTEGER: è una struct dove posso accedere a QuadPart come un int64, o a LowPart e HighPart come degli int32.

Overlapped: descrivo l'offset all'interno di un file. È una struct con Offset e OffsetHigh -> li posso settare con un LARGE_INTEGER:

OVERLAPPED ov = {0, 0, 0, 0, NULL}; LARGE_INTEGER filePos; filePos.QuadPart = 1234; ov.Offset = filePos.LowPart; ov.OffsetHigh = filePos.HighPart;

Le file API di Windows sono thread-synchronous: il thread aspetta che termini l'input/output.

Posso acquisire dei lock che appartengono al processo, e lockare in parte o del tutto il file (ovviamente senza overlap). Inoltre può essere uno shared o exclusive lock.

Se due thread vogliono lockare parti diverse del file devono usare handle diversi.

BOOL LockFileEx(HANDLE file, DWORD flags, 0, DWORD nBytesLow, DWORD nBytesHigh, OVERLAPPED *ol); BOOL UnlockFileEx(HANDLE file, 0, DWORD nBytesLow, DWORD nBytesHigh, OVERLAPPED *ol);

flags: LOCKFILE_EXCLUSIVE_LOCK se è un lock esclusivo (di default è shared), LOCKFILE_FAIL_IMMEDIATELY se dev'essere un trywait

ol indica l'offset da cui iniziare a lockare.

BOOL CopyFile(LPTSTR *oldFile, LPTSTR *newFile, BOOL failIfExists);

Se failIfExists == FALSE, sostituisce il nuovo file se già esistente.

BOOL MoveFile(LPTSTR existing, LPTSTR new);
BOOL DeleteFile(LPTSTR filename);

Directory IO

BOOL CreateDirectory(LPTSTR path, NULL); BOOL RemoveDirectory(LPTSTR path); // Must be empty DWORD GetCurrentDirectory(DWORD dirNameLen, LPTSTR path); // Returns pathname length BOOL SetCurrentDirectory(LPTSTR path); // One per process!

Per leggere i contenuti di una cartella devo aprirla (FindFirstFile), leggere i file uno alla volta (FindNextFile) e poi chiuderla (FindClose).

typedef struct { // Flag FILE_ATTRIBUTE_DIRECTORY = è una cartella DWORD dwFileAttributes; DWORD nFileSizeHigh; DWORD nFileSizeLow; TCHAR cFileName[MAX_PATH]; } WIN32_FIND_DATA; HANDLE FindFirstFile(LPTSTR directory, WIN32_FIND_DATA *find_data); BOOL FindNextFile(HANDLE findFile, WIN32_FIND_DATA *find_data); // Ritorna true se rimangono altri file BOOL FindClose(HANDLE findFile); // NON CloseHandle!
HANDLE searchHandle; WIN32_FIND_DATA findData; searchHandle = FindFirstFile(_T("C:\\Users\\User\\Desktop"), &findData); do { // ... } while (FindNextFile(searchHandle, findData)); FindClose(searchHandle);

Threads

HANDLE CreateThread(NULL, 0, (void* -> DWORD) startAddr, void *arg, DWORD flags, DWORD *tid);

Crea un thread che esegue la funzione startAddr passando l'argomento arg. Se flags == CREATE_SUSPENDED il thread viene creato ma non avviato, serve ResumeThread.

void ExitThread(DWORD exitCode);

In alternativa posso fare return dell'exit code dalla thread function.

Un thread rimane nel sistema fino a che faccio CloseHandle sull'ultimo handle a esso.

HANDLE GetCurrentThread(); DWORD GetCurrentThreadId(); DWORD GetThreadId(HANDLE thread);
DWORD ResumeThread(HANDLE thread); // suspend++ DWORD SuspendThread(HANDLE thread); // suspend--

Ogni thread ha un "suspend count"; viene eseguito solo se il suspend count è zero.

Resume/SuspendThread ritornano il suspend count precedente.

DWORD SetThreadPriority(HANDLE thread, DWORD priority); DWORD GetThreadPriority(HANDLE thread);
DWORD WaitForSingleObject(HANDLE object, DWORD timeout); DWORD WaitForMultipleObjects(DWORD count, HANDLE *objects, BOOL waitAll, DWORD timeout);

timeout: un valore in millisecondi. 0 = trywait, INFINITE = no timeout

Il return value è WAIT_OBJECT_0 + n dove n è l'handle che ha concluso il wait. Se WFSO o waitAll, allora ritorna WAIT_OBJECT_0.

WFMO prende un array di handle di lunghezza count. Massimo count = 64 handle! Se voglio waitare su più thread, waito su 64 thread alla volta e quando ho finito passo avanti.

Interlocked functions/atomic variables

LONG InterlockedIncrement(int32 volatile *a); LONG InterlockedIncrement64(int64 volatile *a); LONG InterlockedDecrement(int32 volatile *a); LONG InterlockedDecrement64(int64 volatile *a);

È una memory fence: ogni modifica viene sincronizzata su tutti i core. Non usa il kernel.

Critical sections

"Fast mutex": non usano il kernel. Permettono l'accesso a un thread alla volta.

VOID InitializeCriticalSection(CRITICAL_SECTION *cs); VOID DeleteCriticalSection(CRITICAL_SECTION *cs);
VOID EnterCriticalSection(CRITICAL_SECTION *cs); VOID TryEnterCriticalSection(CRITICAL_SECTION *cs); VOID LeaveCriticalSection(CRITICAL_SECTION *cs);

Come assicurarsi di chiamare sempre LeaveCriticalSection? _try { ... } _finally { LeaveCriticalSection(&cs); }.

Mutex

Kernel objects con un HANDLE. Sono ricorsivi: lo stesso thread può acquisire più volte un mutex senza bloccare, ma deve unlockarlo lo stesso numero di volte.

HANDLE CreateMutex(NULL, bool initialOwner, LPTSTR mutexPath);

initialOwner = true dà l'ownership del mutex al thread chiamante.

mutexPath è il pathname del mutex; può essere NULL per un mutex anonimo. Serve un path per condividere un mutex tra più processi. Stesso namespace per mutex, semafori, eventi, mmap!

HANDLE OpenMutex(DWORD access, BOOL inheritHandle, LPTSTR mutexPath);

Li waito con WFSO/WFMO.

BOOL ReleaseMutex(HANDLE mutex);

Semafori

Combinano eventi e mutex; sono signaled quando il contatore è > 0.

HANDLE CreateSemaphore(NULL, LONG initialValue, LONG maxValue, LPTSTR semPath);

Li waito con WFSO/WFMO, decrementando il contatore di 1.

BOOL ReleaseSemaphore(HANDLE sem, LONG releaseCount, LONG *prevCount);

Notare che il release può incrementare il contatore di releaseCount, ma wait è solo di 1 -> se mi serve decrementare di N volte devo trattare il blocco di N WFSO come una critical section.

Eventi

Kernel sync object. Usati per segnalare a un thread che un evento è avvenuto (come i segnali UNIX o le cv pthread). A differenza delle altre strategie posso segnalare uno o più thread!

Gli eventi sono signaled con SetEvent/PulseEvent:

Sono resettati con Automatic o Manual reset:

HANDLE CreateEvent(NULL, BOOL manualReset, BOOL initialState, LPTSTR name); BOOL SetEvent(HANDLE event); BOOL ResetEvent(HANDLE event); BOOL PulseEvent(HANDLE event);

Uso WFSO/WFMO. Attenzione: con WFMO il thread viene rilasciato se tutti gli eventi sono segnalati contemporaneamente!

Problema: gli eventi non hanno memoria -> settare più volte un evento o resettarlo più volte non fa nulla

Problema: PulseEvent è unreliable!

In sintesi:

Thread pool

Simili a un semaforo che regola il numero di thread che possono eseguire una certa operazione: serve non tanto a sincronizzare dei dati quanto a evitare high thread contention.

Creo una "thread pool queue", al cui interno ci sono dei "work objects"/"tasks" (callback functions) anzichè dei thread. Windows gestisce automaticamente un piccolo numero di worker threads che eseguono tutti i task uno dietro l'altro.

Procedura:

  1. Crea un "callback environment" TP_CALLBACK_ENVIRON
  2. Crea la thread pool
  3. Crea work objects e inviali alla thread pool
  4. Wait for thread pool
  5. Close thread pool
void InitializeThreadpoolEnvironment(TP_CALLBACK_ENVIRON *env);
// Crea il work object, ma non lo inserisce ancora nella pool PTP_WORK CreateThreadpoolWork( TP_WORK_CALLBACK *callback, VOID *arg, TP_CALLBACK_ENVIRON *env ); // Invia il work object alla pool VOID SubmitThreadpoolWork(PTP_WORK pwk); VOID CloseThreadpoolWork(PTP_WORK pwk);
// Aspetta che tutti i work objects siano completati // ed eventualmente annulla quelli in coda VOID WaitForThreadpoolWorkCallbacks (PTP_WORK pwk, BOOL cancelPendingCallbacks);

Memory-mapped files

Associo parte dell'address space con un file -> manipolo data structures senza file IO functions -> applicazioni molto più veloci: posso usare algoritmi in-memory

Inoltre processi diversi possono condividere la memoria in modo coerente. Però devono usare entrambi i memory-mapped files: se uno dei due usa la file IO le modifiche non sono sincronizzate!

Le dimensioni dei mapping sono fisse, se cambio la dimensione del file devo rimappare.

Procedura:

HANDLE file = CreateFile(); HANDLE mapping = CreateFileMapping(file); void *mem = MapViewOfFile(mapping); // ... UnmapViewOfFile(mem); CloseHandle(mapping);
HANDLE CreateFileMapping(HANDLE file, NULL, DWORD protect, DWORD maxSizeHigh, DWORD maxSizeLow, LPTSTR name);

Protect: PAGE_READONLY/PAGE_READWRITE/PAGE_WRITECOPY. Con PAGE_WRITECOPY le modifiche sono scritte non sul file ma solo in memoria.

maxSize = 0 per mappare tutto il file.

Se voglio condividere un mapping tra processi devo crearlo con un nome e poi usare OpenFileMapping:

HANDLE OpenFileMapping(DWORD access, BOOL inheritHandle, LPTSTR name);

Una volta creato il mapping posso mappare il file:

VOID *MapViewOfFile(HANDLE mapping, DWORD access, DWORD offsetHigh, DWORD offsetLow, SIZE_T numberOfBytes);

access: FILE_MAP_READ/FILE_MAP_WRITE/FILE_MAP_ALL_ACCESS

L'offset dev'essere un multiplo di 64K!

size = 0 per mappare tutto il file.

BOOL UnmapViewOfFile (LPVOID lpBaseAdress);

Async IO: overlapped

In Windows l'async IO si può fare con:

Serve FILE_FLAG_OVERLAPPED negli attributi di CreateFile, e una struttura OVERLAPPED:

typedef struct { DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; HANDLE Event; } OVERLAPPED;

Faccio wait sull'handle se mi serve aspettare una singola IO call, sull'evento se ne ho fatte diverse. Una volta fatto il wait uso GetOverlappedResult per ottenere il dato:

BOOL GetOverlappedResult (HANDLE file, OVERLAPPED *overlapped, WORD *nBytesTransfered, BOOL wait);

Indico l'IO call a cui mi riferisco con la combinazione di handle e overlapped.

Con wait attendo che l'operazione termini.