Antes de começar é preciso saber que a microsoft faz mudanças de tempo em tempo no código fonte do seu S.O sobre esse tema,então certas coisas vão ser(ter) abordadas de uma forma mais geral.Como também é um assunto extenso e que ganha mais complexidade a cada nova versão do windows,o que vai ser abordado é o básico,porém com o menos possível de abstração.
Basicamente as estruturas que o windows usa para controlar sincronização de threads são KWAIT_BLOCK _DISPATCHER_HEADER e a própria KTHREAD:
struct _DISPATCHER_HEADER
{
UCHAR Type ;
UCHAR Absolute ;
UCHAR Size ;
UCHAR Inserted ;
int SignalState ;
_LIST_ENTRY WaitListHead ;
};
struct _KWAIT_BLOCK
{
LIST_ENTRY WaitListEntry;
_KTHREAD *Thread;
PVOID Object;
_KWAIT_BLOCK *NextWaitBlock;
USHORT WaitKey;
USHORT WaitType;
};
Veja que KWAIT_BLOCK é usada por ambas,DISPATCHER_HEADER e KTHREAD
1 - O membro "WaitListHead" do objeto despachante(DISPATCHER_HEADER)tem todas as thread que estão esperando por ele,na verdade ele tem entradas do tipo KWAIT_BLOCK,no qual o membro "Thread" é que tem o endereço da thread e "Object" apontando para o próprio objeto.
2 - Tanto o objeto sincronizador quanto a thread(estrutura KTHREAD "vista aqui")precisam de informações sobre que threads estão esperando(fica no objeto)e que objetos estão sendo esperados(fica na thread)
KTHREAD tem um membro "WaitBlockList"(também do tipo KWAIT_BLOCK)onde ficam todos os objetos de sincronização que essa thread está esperando.
3 - O membro "WaitBlock" também de KTHREAD,é um array de até [4]entradas do tipo KWAIT_BLOCK.Essas entradas também são usadas para referenciar objetos de sincronização.A diferença é que WaitBlockList é uma lista usada para quando a thread estiver esperando por mais objetos que o array WaitBlock pode comportar.
A intenção de ter um array de KWAIT_BLOCKs built-in para cada thread como você pode ver é otimização,em vez de ter que ficar alocando KWAIT_BLOCKs para referenciar objetos e suas threads esperando sempre que a thread entrar em modo de espera.
4 - O membro "WaitListEntry" de KWAIT_BLOCK tem as threads esperando pelo objeto.
5 - O membro "WaitListEntry" de KTHREAD contém todas as threads que estão esperando naquela CPU
6 - O membro "NextWaitBlock" é usado para espera por múltiplos objetos.Se tiver limite de tempo,mais um objeto será usado,esse membro irá apontar para uma entrada no "WaitBlock"(um KWAIT_BLOCK)que é usado para um objeto esperando com limite de tempo,ele irá servir como um tipo KTIMER.
7- O membro "WaitKey" servirá como um índice dizendo qual o lugar que esse KWAIT_BLOCK atual(representando o objeto)está.
8 - O membro "WaitType" diz quando esperar por todos os objetos ou só por algum dele.
9 - KiDispatcherReadyListHead,uma lista de threads que estão prontas para rodar.
ANALISANDO KeSetEvent
---------------------------------------------------------------------------------
Qual o trabalho de WaitForSingleObject?Colocar a thread que a chamou para esperar.Quem irá tira-la de seu modo de espera?Será a função SetEvent.
Vamos ver o que KeSetEvent faz :
LONG __stdcall PseudoKeSetEvent ( _KEVENT *Event,
KPRIORITY Increment,
BOOLEAN Wait
)
{
LONG estado_aux = Event->Header.SignalState;
KIRQL Irqeax_aux = KeAcquireQueuedSpinLockRaiseToSynch();
_KWAIT_BLOCK *Kwaitthread = (_KWAIT_BLOCK*)Event->Header.WaitListHead.Flink;
if(Event->Header.WaitListHead.Flink == &Event->Header.WaitListHead)
{
Event->Header.SignalState = 1; //Sinaliza
}
else
{
if(Event->Header.Type == SynchronizationEvent || Kwaitthread->WaitKey == 1)
{
Event->Header.SignalState = 1;
KiUnwaitThread(Kwaitthread->Thread,Increment,0);
}
else //Se FOR NotificationEvent
{
if(Event->Header.SignalState == 0)
{
Event->Header.SignalState = 1;
KiWaitTest();
}
}
}
if(Wait != false)
{
_KTHREAD * thread = (_KTHREAD*)PsGetCurrentThread();
thread->WaitNext = Wait;
thread->WaitIrql = Irqeax_aux;
}
else
{
KiUnlockDispatcherDatabase();
}
return estado_aux;
}
1 - Primeiro ela ergue a IRQL,ou seja,interrupções de threads rodando à uma IRQL menor que DISPATCH_LEVEL(que é SYNCH_LEVEL à partir do windows xp)NÃO vão poder interromper.
Se não tiver nenhuma thread esperando esse objeto,então só sinaliza
2 - Quando o tipo de espera é sincronização,ela irá retirar a thread que estava esperando da lista de espera no objeto e irá colocar a thread em uma lista de threads prontas para rodar chamada "KiDispatcherReadyListHead", para rodar.Quem faz isso é KiReadyThread chamada em KiUnwaitThread.
Quando o tipo de espera é de notificação,então ele deve remover todas threads que estavam esperando pelo objeto da lista de espera (KiWaitTest).
3 - "Wait" é testado,se FALSE,então "KiUnlockDispatcherDatabase" é usada para fazer o contrário que "KeAcquireQueuedSpinLockRaiseToSynch" fez,sendo assim assim as interrupções de threads com IRQL mais baixas podem voltar a acontecer.Se "Wait" for TRUE veja que a thread não vai diminuir a IRQL.
O que a microsoft diz sobre isso é que se Wait for TRUE então quem chamou "KeSetEvent" deve chamar em seguida uma "KeWaitXxx" pois esta função irá diminuir a IRQL e isso evitaria mudanças de contexto. O que isso quer dizer é que continuaria nenhuma outra thread em uma IRQL menor podendo interromper a sua atual.O que também acontece é que "KeWaitXxx" também vai erguer a IRQL se "WaitNext" for FALSE.Passar TRUE para Wait geralmente seria útil em situações que você precisa setar um objeto e esperar por outro.
Veja que os membros "WaitNext" e "WaitIrql" da thread atual serão setados com TRUE e com o valor da IRQL erguida ainda.
ANALISANDO KeWaitForSingleObject
-------------------------------------------------------------------------------------------------
KeWaitForSingleObject é uma função bem grande,cheia de encadeamento de listas e com algumas partes que variam dependendo da versão do S.O,então vamos focar só na parte em que o código se mantém.
Algumas coisas para se entender antes é que as funções 1-KiAdjustQuantumThread é quem reajusta o quantum de tempo pra thread.
2 - KiSwapThread é quem põe a próxima thread que está pronta para rodar como a atual que vai rodar.
3 - KiSwapContext é quem realmente muda o contexto da thread,ou seja,bota uma thread para rodar no lugar da atual,ela é chamada por KiSwapThread
Juntamente com KiUnWaitThread que foi vista em KeSetEvent, essas funções formam o layout mais básico do entra e sai e espera de threads
NTSTATUS
PseudoKeWaitForSingleObject (
__in __deref __drv_notPointer PVOID Object,
__in __drv_strictTypeMatch(__drv_typeCond) KWAIT_REASON WaitReason,
__in __drv_strictType(KPROCESSOR_MODE/enum _MODE,__drv_typeConst)
KPROCESSOR_MODE WaitMode,
__in BOOLEAN Alertable,
__in_opt PLARGE_INTEGER Timeout
)
KIRQL kirql_aux;
LARGE_INTEGER timeout_local = TimeOut
_KMUTANT *objeto = (_KMUTANT *)Object; //Objetos mesmo não sendo mutant são tratados assim
KTHREAD *thread_aux = PsGetCurrentThread();
if(thread_aux->WaitNext == 0) //Testa para ver se a IRQL ja esta erguida,se não,então vai erguer
{
goto KeWaitErgueIRQL;
}
KeWaitInicio:
//Se waitnext for 1,ou seja,se a IRQL já foi erguida
thread_aux->WaitNext = 0;
//Espera só por um objeto,então usa um KWAIT_BLOCK built-in
thread_aux->WaitBlockList = &thread_aux->WaitBlock[0] ;
thread_aux->WaitBlock[0].WaitKey &= 0; //índice
thread_aux->WaitBlock[0].Object = Object; //objeto passado
thread_aux->WaitBlock[0].WaitType = 1; //número de objetos
thread_aux->WaitStatus &= STATUS_SUCCESS;
if(Timeout != NULL)
{
thread_aux->WaitBlock[0].NextWaitBlock = &thread_aux->WaitBlock[2];
thread_aux->WaitBlock[2].NextWaitBlock = &thread_aux->WaitBlock[0]; //EDI
thread_aux->Timer.Header.WaitListHead.Flink = &thread_aux->WaitBlock[2];
thread_aux->Timer.Header.WaitListHead.Blink = &thread_aux->WaitBlock[2];
}
else //Se Timeout FOR NULL então não tem nenhuma contagem de tempo pra fazer
{
thread_aux->WaitBlock[0].NextWaitBlock = &thread_aux->WaitBlock[0];
}
thread_aux->WaitListEntry.Flink = NULL;
thread_aux->Alertable = Alertable;
thread_aux->WaitMode = WaitMode;
thread_aux->WaitReason = WaitReason;
while(true)
{
thread_aux->WaitTime = KeTickCount.LowPart;
if(thread_aux->ApcState.KernelApcInProgress != 0)
{
if(thread_aux->WaitIrql <= 1)
{
KiUnlockDispatcherDatabase();
goto KeWaitErgueIRQL;
}
}
if(objeto->Header0x0.Type == MUTANT)
{
if(objeto->Header0x0.SignalState > 0 &&
objeto->OwnerThread0x18 == thread_aux)
{
if( objeto->Header0x0.SignalState == 0x80000000)
{
KiUnlockDispatcherDatabase();
ExRaiseStatus(STATUS_MUTANT_LIMIT_EXCEEDED);
}
else
{
if(objeto->Header0x0.SignalState -= 1 == 0)
{
thread_aux->KernelApcDisable -objeto->ApcDisable0x1d;
(objeto->OwnerThread0x18 = thread_aux;
if(objeto->Abandoned0x1c == 1)
{
objeto->Abandoned0x1c = 0;
thread_aux->WaitStatus = STATUS_ABANDONED_WAIT_0;
}
objeto->MutantListEntry0x10.Flink = thread_aux->MutantListHead.Flink; objeto->MutantListEntry0x10.Blink = thread_aux->MutantListHead.Flink;
thread_aux->MutantListHead.Blink = &objeto->MutantListEntry0x10;
thread_aux->MutantListHead.Flink = &objeto->MutantListEntry0x10;
}
KiAdjustQuantumThread(thread_aux);
KiUnlockDispatcherDatabase();
return WaitStatus;
}
}
}
else //Se o objeto NÃO é um mutant
{
if( (objeto->Header0x0.SignalState > 0)
{
if( (objeto->Header0x0.Type == SYNCHRONIZATION_EVENT )
{
objeto->Header0x0.SignalState &= 0;
}
else if((objeto->Header0x0.Type == SEMAPHORE)
{
objeto->Header0x0.SignalState -= 1;
}
KiAdjustQuantumThread(thread_aux);
KiUnlockDispatcherDatabase();
return STATUS_SUCCESS;
}
}
LARGER_INTEGER tempoori;
if(Timeout != NULL)
{
if((Timeout->HighPart | Timeout->LowPart) == 0)
{
KiAdjustQuantumThread(thread_aux);
KiUnlockDispatcherDatabase();
return STATUS_TIME_OUT;
}
if(KiInsertTreeTimer(Timeout->HighPart,Timeout->LowPart ) == false)
{
KiAdjustQuantumThread(thread_aux);
KiUnlockDispatcherDatabase();
return STATUS_TIMEOUT;
}
tempoori.LowPart = thread_aux->Timer.DueTime.LowPart;
tempoori.HighPart = thread_aux->Timer.DueTime.HighPart;
}
//Salva lista de espera
thread_aux->WaitBlockList->WaitListEntry.Flink = objeto->Header0x0.WaitListHead;
thread_aux->WaitBlockList->WaitListEntry.Blink = objeto->Header0x0.WaitListHead->Blink;
objeto->Header0x0.WaitListHead->Blink->Flink = thread_aux->WaitBlockList;
objeto->Header0x0.WaitListHead->Blink = thread_aux->WaitBlockList;
if(thread_aux->Queue != NULL)
{
KiActivateWaiterQueue();
}
int re_KiSwapThread;
if((re_KiSwapThread = KiSwapThread() ) != STATUS_KERNEL_APC)
{
return re_KiSwapThread;
}
LARGE_INTEGER duetime_aux;
if(Timeout != NULL)
{
Timeout = KiComputeWaitInterval(&tempoori,&duetime_aux,timeout_local);
}
KeWaitErgueIRQL:
kirql_aux = KeRaiseIrqlToSynchLevel();
thread_aux->WaitIrql = kirql_aux;
goto KeWaitInicio;
.
.
.
COMENTÁRIOS :
Primeiro ela entra no loop vai checar se o objeto já foi sinalizado ou não.Também é feita checagem para ver se alguma apc(asynchronous procedure call) .
Checa para ver o tipo do objeto,se for mutant,o membro signalstate é tratado como um contador em vez de sinalizador,se a quantidade possuidores para o mutant chegou à um nível maximo,uma exceção é lançada.Se não,então um decremento nesse membro é feito.
Se não for um mutant, é testado para ver se o objeto está sinalizado,
depois os tipos são testados e os devidos valores colocados e a thread então retornaria.
A partir daqui é porque o objeto não estava sinalizado.
Checa para ver se o tempo passado foi 0,nesse caso ele já vai ter testado que a thread não foi sinalizada.
Como a descrição de WaitForSingleObject diz "a função retorna imediatamente se 0 foi passado como contador de tempo".
PS:Para não ficar mal entendidos,na verdade todos os códigos até esse ponto devem ser executados já que não há como saber em modo de usuário se a thread foi sinalizada ou não
O que acontece é que se o objeto não estiver sinalizado e o tempo ou não foi passado ou ainda não terminou,a thread entrará em modo de espera(thread->state = waitingThread). A thread então será salva na lista de espera tanto do objeto quanto a da thread nos KWAIT_BLOCKs.
PS:Veja que ambos,objeto e thread apontam para os mesmos KWAIT_BLOCKs.Sendo assim,quando a função KiUnWaitThread
remover
um KWAIT_BLOCK de "WaitBlockList->WaitListEntry" da thread,automaticamente irá remover o
KWAIT_BLOCK que representa a thread,no membro "WaitListHead" do objeto.
KiSwapThread então é chamada,ela irá trocar a thread atual pela próxima esperando.
O que KiSwapThread faz é procurar na lista de threads que estão prontas para rodar(KiDispatcherReadyListHead)e chamar
KiSwapContext que de fato vai salvar os estado da thread atual, carregar os valores da próxima thread pronta para rodar nos registros e então mudando o rumo da execução.
Esses valores vão ficar guardados no membro "ProcessorState" da estrutura KPRCB(única para cada CPU)
Threads normalmente chamam KiSwapThread para colocar uma próxima thread na CPU atual quando terminam seu quantum de tempo.Sendo assim alguma hora então sua thread será chamada para rodar.
Nenhum comentário:
Postar um comentário