Sistemas Operativos
Processos e threads no Windows
Jorge Martins
(jmartins@isel.pt)
baseado parcialmente em slides do Eng. João Patriarca
Centro de Cálculo
Handles de objectos kernel
•
Os objectos kernel (processos, sections, files, mutexes, eventos, semáforos, etc.) partilham
uma série de características:
• A maior parte pode ter um nome associado. No caso de processos um número (process id), nos outros casos um valor alfanúmerico.
• As permissões de acesso dependem do processo em que são usados.
• Contador de referências.
– O objecto só é destruído quando o seu contador de referências atingir o valor 0. Logo podem permanecer activos para além do processo que os criou.
• Os objectos kernel são acedidos pelos processos que para eles têm referências. Um processo pode partilhar o acesso aos objectos com os processos criados por si (herança).
•
O acesso aos objectos e efectuado através de handles. Em geral, um handle é obtido como
resultado de uma operação de criação (
Create...) ou abertura (
Open...) de objectos
kernel. O que designamos por handle não é mais que um ponteiro (índice) para uma tabela
(tabela de handles), incluída no objecto que representa o processo, que contém os handles
que referem os objectos usados pelo processo. Cada entrada da tabela de handles contém:
Access Mask
Flags
Reference
define as permissões
de acesso ao objecto
flags adicionais, por
exemplo se o handle é
herdável
ponteiro para o objecto
referido
Criação/Abertura de objectos kernel
HANDLE CreateThread( ... ); HANDLE CreateFile( ... ); HANDLE CreateFileMapping( ... ); HANDLE CreateEvent( ... ); HANDLE CreateSemaphore( ... );• Nas funções de criação, se o objecto ainda não existe, o kernel aloca e inicia um bloco de
memória para alojar o objecto a criar. Se o objecto existe, é retornando um handle para o objecto, mas a chamada à função GetLastError() retorna ERROR_ALREADY_EXISTS.
• O kernel procura por uma entrada livre na tabela de handles do processo onde teve origem a criação (ou abertura) do objecto.
• Preenchimento de uma entrada livre na tabela de handles:
– O campo Reference é afectado com o endereço do bloco alocado em kernel space para alojar o objecto criado.
– Os campos Access mask e Flags são afectados de acordo com o valor do parâmetro psa do tipo PSECURITY_ATTRIBUTES.
• Em caso de erro durante a criação do objecto, a maioria das funções retornam NULL, algumas retornam INVALID_HANDLE_VALUE (-1).
• Em caso de sucesso retornam o índice (HANDLE) da entrada preenchida.
– O valor retornado é válido apenas no âmbito do processo que efectuou a criação/abertura
11/05/2016 Sistemas Operativos 4 HANDLE OpenThread( ... ); HANDLE OpenFile( ... ); HANDLE OpenFileMapping( ... ); HANDLE OpenEvent( ... ); HANDLE OPenSemaphore( ... );
Partilha de objectos entre processos
•
Cenários onde há necessidade de partilha:
–
Memória partilhada entre processos (através da função
CreateFileMapping)
–
Sincronização de threads pertencentes a diferentes processos.
•
Formas de partilha (detalhadas nos slides seguintes):
–
Por herança entre processo pai e processos filhos
–
Por nome do objecto (parâmetro pszName do tipo PCTSTR da função
de criação ou abertura).
–
Por duplicação de handles (função DuplicateHandle ).
Processos e partilha de objectos – partilha por nome
11/05/2016 Sistemas Operativos 6 Kernel memory User memory Processo A tabela de handles Processo B tabela de handlescódigo user de Processo A
código user de Processo B
...
handle1=CreateFileMapping(..., _T(“map1”)) ...
...
handle2 = CreateFileMapping(..., _T(“map1”)) ... 0x30 0x40 Name:“map1” Ref Count: 2 Section object outras informações outras informações
Processos e partilha de objectos – partilha por herança
11/05/2016 Sistemas Operativos 7 Kernel memory User memory Processo A tabela de handles Processo B (filho) tabela de handlescódigo user de Processo A código user de Processo B
(execução de “Program B”)
...
handle1 = CreateFileMapping(&sa,..., NULL); ...
handle2 = CreateProcess(“Program B”, ..., TRUE,...);
... ... 0x30 0x30 Name: NULL Ref Count: 2 Section object outras informações outras informações outras informações 0x20 sa.bInherithandle = TRUE
Indica que os handles (herdáveis) são herdados. Note-se que o handle para a section tem o mesmo valor no filho (0x30). Tem de arranjar forma de passar essa informação ao filho. Por exemplo através da linha de comandos...
não necessita de nome
Processos e partilha de objectos –
partilha por duplicação de handles
11/05/2016 Sistemas Operativos 8 Kernel memory User memory Processo A tabela de handles Processo B (pid = 125) tabela de handlescódigo user de Processo A
...
handle1 = CreateFileMapping(NULL,..., NULL) hprocB = OpenProcess(..., 125) DuplicateHandle(GetCurrentProcess(),handle1,hprocB,&hObj,0,TRUE, DUPLICATE_SAME_ACCESS); ... 0x30 0x40 Name: NULL Ref Count: 2 Section object outras informações outras informações outras informações 0x20
hObj vale 0x40. É preciso agora informar o processo B de que o handle para a section vale 0x40. Por exemplo, através de
memória partilhada...
A função
CloseHandle
•
Deverá ser chamada quando o objecto não for mais necessário
•
Decrementa o contador de utilizadores do objecto referenciado pelo
handle
–
Se o contador atingir o valor 0, o objecto é destruído
•
Limpa a entrada da tabela de handles correspondente ao handle
•
Na terminação de um processo, o sistema percorre a sua tabela de
handles fechando todos os handles ainda presentes na tabela
Função
CreateProcess
BOOL CreateProcess(
PCTSTR pszApplicationName,
PTSTR pszCommandLine,
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES psaThread,
BOOL bInheritHandles,
DWORD fdwCreate,
PVOID pvEnvironment,
PCTSTR pszCurDir,
PSTARTUPINFO psiStartInfo,
PPROCESS_INFORMATION ppiProcInfo);
Pode ter o valor NULL (o primeiro token do próximo parâmetro deverá indicar o nome da aplicação)
Pode ser NULL; usado em alternativa com pszApplicationName
Indica se o processo filho herdará os
handles herdáveis do processo pai
Flags que controlam a classe de prioridade
e a criação do processo
Pode ter o valor NULL, herdando as variáveis de ambiente do pai
A NULL, a directoria é a mesma do processo pai; caso contrário, deverá incluir a drive e directoria
Inicialização mínima (consultar bib):
STARTUPINFO si = { sizeof(si) }; CreateProcess( ..., &si, ...);
typedef struct _PROCESS_INFORMATION { HANDLE hProcess;
HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION;
Parâmetro
fdwCreateFlags
•
Controlo da criação do processo
–
CREATE_DEBUG, CREATE_SUSPENDED, CREATE_NEW_CONSOLE,
CREATE_NO_WINDOW, DETACHED_PROCESS
•
Classes de prioridade (a ver mais tarde).
–
REALTIME_PRIORITY_CLASS, HIGH_PRIORITY_CLASS,
ABOVE_NORMAL_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS,
BELOW_NORMAL_PRIORITY_CLASS, IDLE_NORMAL_PRIORITY_CLASS
–
Normalmente não é especificado: processos criados com a prioridade
STARTUPINFO
11/05/2016 Sistemas Operativos 12
typedef struct _STARTUPINFO {
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX; DWORD dwY;
DWORD dwXSize; DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
Criação de processo – passos envolvidos
Convert and validateparameters and flags
Perform Windows-subsystem specific process initialization
Open EXE and create section object
Create Windows process object
Create Windows thread object
Start execution of the initial thread
Return to caller
Set up for new process and thread Final process/image initialization Start execution at entry point to image
Stage 1
Stage 2
Stage 3
Stage 4
Stage 5
Stage 6
Stage 7
Creating processWindows subsystem (process)
Terminação de um processo
•
Diferentes formas de terminar um processo:
–
A função de entrada da thread primária (
main
) retorna. É a forma
adequada de terminar o processo, supondo que todas as outras threads
já terminaram. Se outras threads ainda não terminaram, a sua
terminação é forçada pelo Windows (via
TerminateThread).
–
Uma thread do processo chama a função ExitProcess (evitar este
método). As outras threads do processo são forçadas a terminar sem
libertação controlada de recursos.
–
Uma thread noutro processo chama a função TerminateProcess (evitar
este método). As outras threads do processo são forçadas a terminar
sem libertação controlada de recursos.
Terminação de um processo -
Função TerminateProcess
BOOL TerminateProcess(HANDLE hProcess, UINT fuExitCode);
•
Distingue-se da função ExitProcess por poder ser chamada por
qualquer thread dentro ou fora do processo (desde que tenha um
handle válido para o processo, é claro!),
•
Usar apenas na condição de não se conseguir terminar o processo
por qualquer outro método.
•
O sistema garante que os recursos associados ao processo são
completamente eliminados, mas o estado aplicacional (persistente)
pode perder informação ou ficar inconsistente.
•
Acção de terminação assíncrona o que justifica a chamada da função
WaitForSingleObject para determinar a conclusão do processo.
Terminação de um processo -
Acções realizadas
•
Termina as threads ainda existentes no processo.
•
Todos os objectos kernel são fechados (
CloseHandle
) e eliminados se
os contadores de referências dos objectos atingiram o valor 0.
•
O código de saída do processo altera de STILL_ACTIVE (0x103) para
o valor passado na função
ExitProcess ou TerminateProcess,
obtido por:
•
O objecto kernel processo é sinalizado (para efeitos de
sincronização).
•
O contador de referências do objecto kernel processo é
decrementado
PROCESS_INFORMATION pi;
DWORD dwExitCode;
// Waiting for child process termination
BOOL fsuccess = CreateProcess(..., &pi);
if (fsuccess) {
// Close the thread handle as soon as it is no longer needed!
CloseHandle(pi.hThread);
// Suspend our execution until the child has terminated.
WaitForSingleObject(pi.hProcess, INFINITE);
// The child process terminated; get its exit code.
GetExitCodeProcess(pi.hProcess, &dwExitCode);
// Close the process ahndle as soon as it is no longer needed.
CloseHandle(pi.hProcess);
}
Criação de processo filho com e sem espera
// Without wait for child ending
PROCESS_INFORMATION pi;
BOOL fsuccess =
CreateProcess(..., &pi);
if (fsuccess) {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
Função
CreateThread
HANDLE CreateThread(
PSECURITY_ATTRIBUTES psa,
DWORD cbStackSize,
PTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam,
DWORD dwCreateFlags,
PDWORD pdwThreadID);
•
Na escrita de programas em C/C++ dever-se-á usar a função
_beginthreadex
em vez de
CreateThread
Pode ter o valor NULL (atributos de segurança
por omissão). Pode ser usado para determinar
se um objecto processo filho tem acesso ao
objecto kernel thread criado por esta função
Memória que ficará committed
inicialmente. O valor 0 usa os valores
presentes na imagem do executável.
DWORD WINAPI <threadFuncName>(
PVOID param);
O retorno corresponde ao valor de saída
(idêntico ao valor de retorno da função main)
Pode ter o valor
NULL
Valor 0: a thread pode ser colocada de
imediato em execução
Valor CREATE_SUSPENDED: a thread sai do
estado Suspended quando for
Terminação de uma thread
•
Cenários em que uma thread pode terminar
–
A função correspondente ao ponto de entrada da thread retorna (método
adequado).
–
A thread mata-se a si própria chamando a função ExitThread (evitar
este método).
–
Uma qualquer thread deste processo chama a função TerminateThread
(evitar este método)
Terminação de uma thread
- Acções realizadas
•
O código de saída da thread, obtido por:
BOOL GetExitCodeThread(HANDLE hThread, PDWORD pdwExitCode);
altera de
STILL_ACTIVE (0x103) para o valor passado na função
ExitThread ou TerminateThread
•
O objecto kernel thread é sinalizado (para efeitos de sincronização)
•
Se a thread for a última thread activa do processo, o sistema considera o
processo terminado também
Thread Local Storage
• Por vezes há necessidade de manter estado global por cada thread e não estado global ao nível do processo. O Windows suporta esta necessidade através da funcionalidade designada de Thread Local Storage.
• A TLS é simplesmente um array de ponteiros que pode ser acedido unicamente pela thread a que está associado. A TLS é usada tipicamente por bibliotecas ou serviços que necessitam de manter estado global por thread e não por processo . Na sua iniciação, tipicamente em DllMain, a biblioteca reserva uma entrada nas TLS de cada thread via TlsAlloc e armazena o índice devolvido numa variável global ao processo. O código executado em cada thread usa TlsSetValue e TlsGetValue para escrever e ler informação na posição reservada previamente (a posição é a mesma em todas as threads, a informação é naturalmente distinta). No fecho da biblioteca a entrada reservada é libertada via TlsFree.
• Como é que o Windows sabe o endereço do bloco de memória que contém a TLS da thread corrente?
11/05/2016 Sistemas Operativos 21 TLS thread 1 thread 1 ... TLS thread 2 thread 2 ... TLS thread 3 thread 3 ...
Biblioteca runtime C/C++ - uso de Thread Local Storage (TLS)
•
Preocupações com a biblioteca standard do C num cenário de programação
concorrente:
–
A biblioteca na sua génese não previa execução concorrente
–
Muitas funções usam variáveis com alocação estática: errno, strtok, strerror,
...
•
Solução:
–
Cada thread tem as variáveis globais da biblioteca reunidas num bloco
–
O bloco fica alojado num espaço visível apenas pela thread (TLS – Thread Local
Storage)
• É por esta razão que se deve utilizar _beginthreadex em vez da função CreateThread. A função, antes de criar a thread, cria o bloco com as variáveis globais da biblioteca, sendo o bloco
associado à nova thread no início da sua execução, via TlsSetValue. Quando uma função de biblioteca necessita de aceder a estado global, obtém o bloco da thread em que está a executar (via TlsGetvalue) e acede ao campo do bloco que representa o estado global. Como é que
beginthreadex garante a iniciação TLS da nova thread antes de começar a execução do códifo especificado pelo chamador? Mais detalhes sobre a implementação de beginthreadex nos
c/c++ run time library data block in TLS
11/05/2016 Sistemas Operativos 23
struct _tiddata {
unsigned long _tid; /* thread ID */ uintptr_t _thandle; /* thread handle */ int _terrno; /* errno value */
unsigned long _holdrand; /* rand() seed value */ char * _token; /* ptr to strtok() token */ wchar_t * _wtoken; /* ptr to wcstok() token */ unsigned char * _mtoken; /* ptr to _mbstok() token */
char * _errmsg; /* ptr to strerror()/_strerror() buff */ wchar_t * _werrmsg; /* ptr to _wcserror()/__wcserror() buff */ char * _namebuf0; /* ptr to tmpnam() buffer */
wchar_t * _wnamebuf0; /* ptr to _wtmpnam() buffer */ char * _namebuf1; /* ptr to tmpfile() buffer */ wchar_t * _wnamebuf1; /* ptr to _wtmpfile() buffer */ char * _asctimebuf; /* ptr to asctime() buffer */ wchar_t * _wasctimebuf; /* ptr to _wasctime() buffer */ void * _gmtimebuf; /* ptr to gmtime() structure */ char * _cvtbuf; /* ptr to ecvt()/fcvt buffer */
/* following fields are needed by _beginthread code */
void * _initaddr; /* initial user thread address */ void * _initarg; /* initial user thread argument */
// ...
};
• Para já um excerto do estrutura que define o bloco de memória a armazenar por thread.
Implementação de
_beginthreadex
11/05/2016 Sistemas Operativos 24
uintptr_t _beginthreadex( void* psa, unsigned cbStackSize,
unsigned (__stdcall * pfnStartAddr) (void*), void* pvParam, unsigned dwCreateFlags, unsigned * pdwThreadID) {
_ptiddata ptd; // Pointer to thread’s data block
uintptr_t thdl; // Thread handles // Allocate data block for the new thread
if ((ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) ==NULL) goto error_return;
initptd(ptd); // Initialize data block
// Save desired thread function and parameter in threads’s data block
ptd->_initaddr = (void*)pfnStartAddr; ptd->_initarg = pvParam;
ptd->handle = (uintptr_t)(-1);
// Create the new thread
thdl = (uintptr_t) CreateThread((LPSECURITY_ATTRIBUTES)psa, cbStackSize,
_threadstartex, (PVOID)ptd, dwCreateFlags, pdwThreadID);
if (thdl == 0) goto error_return; return (thdl); error_return: _free_crt(ptd); return ((uintptr_t)0L); }
Função que executa o código da thread criada via
_beginthreadex
11/05/2016 Sistemas Operativos 25
static unsigned long WINAPI _threadstartex( void* _ptd) { _ptiddata ptd = (_ptiddata ) _ptd;
// Associate the tiddata block with this thread
TlsSetValue(__tlsindex, // global telling the TLS index for c/c++ run time library
ptd);
ptd->_tid = GetCurrentThreadId(); // save this thread ID
// Using Windows exceptions to catch any fatal error on thread execution….
__try {
// Call desired thread function, passing it he desired parameter // Pass thread’s exit code value to _endthreadex
_endthreadex(
(unsigned (WINAPI*)(void*))(ptd->_initaddr))(ptd->_initarg) );
}
__except(_XcptFilter(GetExceptionCode(), GetExceptionInformation())) {
// The execution code should never reach here
_exit(GetExceptionCode()); }
// The execution code never reach here (the thread dies in _callthreadstartex)
return (0L); }
Bloco PEB (Process Environment Block)
Image base address Module list
Thread-local storage management data Code page data
Number of heaps Heap size information
Operating system version number information Image version information
Image process affinity mask
Process heap
Bloco de informação associada a cada processo, disponível em user mode. A figura apresenta alguns dos campos mais importantes.
Bloco TEB (Thread environment block)
Exception list Stack base Stack limit Thread ID Active RPC handle LastError valueCount of owned critical sections
Subsystem thread information block (TIB) Fiber information
PEB
Current locale
User32 client information GDI32 information
OpenGL information TLS array
Winstock data
bib: Windows internals, Russinovich, Solomon, fig. 5-9 Bloco de informação associada a cada thread do processo, disponível em user mode. A figura apresenta alguns dos campos mais importantes.
A instrução mov eax, fs[0] coloca em eax o endereço da estrutura TEB para a thread corrente
Contabilização de tempos associados à execução de threads
ULONGLONG GetTickCount64();
// Não considera a preempçãoDWORD GetTickCount();
// variante de 32 bitsBOOL GetThreadTimes(HANDLE hThread,
PFILETIME pftCreationTime,
// valor absoluto desde 1 de Janeiro de 1601PFILETIME pftExitTime,
// valor absoluto desde 1 de Janeiro de 1601PFILETIME pftKernelTime,
// valor relativo gasto em kernel modePFILETIME pftUserTime);
// valor relativo gasto em user mode• PFILETIME expresso no número de intervalos de 100 nano-segundos
• Funções adequadas para medir intervalos de tempo longos (fraca precisão)
– O tempo de CPU de uma thread é contabilizado com base num temporizador que gera ciclos entre os 10 e 15 ms
BOOL GetProcessTimes(<same as GetThreadTimes>); // Tempo gasto por todas as threads
BOOL QueryPerformanceFrequency(LARGE_INTEGER* pliFrequency);
BOOL QueryPerformanceCounter(LARGE_INTEGER* pliCount);
•
Funções medidoras de tempo de alta resolução
– Não consideram a preempção