Vamos começar examinando a interface de chamadas do sistema bruta. Isso varia um pouco pela arquitetura, mas no x86-64 é:
long clone(unsigned long flags, void *child_stack,
int *ptid, int *ctid,
unsigned long newtls);
ptid
e ctid
são seus parent_tidptr
e child_tidptr
. Agora vamos ver o que a clone(2)
página de manual tem a dizer:
CLONE_CHILD_CLEARTID (since Linux 2.5.49)
Erase the child thread ID at the location ctid in
child memory when the child exits, and do a wakeup on
the futex at that address.
CLONE_CHILD_SETTID (since Linux 2.5.49)
Store the child thread ID at the location ctid in the
child's memory.
CLONE_PARENT_SETTID (since Linux 2.5.49)
Store the child thread ID at the location ptid in the
parent's memory.
Esses sinalizadores foram projetados principalmente para implementar bibliotecas de encadeamento. Se dermos uma olhada na implementação do NPTL de pthread_create()
dentro da glibc, eventualmente encontrar código em sysdeps/unix/sysv/linux/createthread.c
que faz uma chamada clone()
que inclui CLONE_PARENT_SETTID
e CLONE_CHILD_CLEARTID
em flags
.
Na chamada clone()
, também podemos ver que os argumentos ptid
e ctid
apontam para o mesmo endereço . (Lembre-se de que os encadeamentos POSIX compartilham um espaço de endereço; isso é realizado com o sinalizador clone()
CLONE_VM
.)
Então, o que está acontecendo aqui é o seguinte.
-
CLONE_PARENT_SETTID
está sendo usado para garantir que o ID do encadeamento do kernel esteja sendo armazenado em um determinado local no espaço do usuário. O lado do espaço do usuário da implementação de segmentação precisa saber esse ID de encadeamento.
-
CLONE_CHILD_CLEARTID
está sendo usado para limpar (ou seja, zero) esse mesmo local quando o encadeamento criado por clone()
é finalizado.
Vamos um pouco mais longe ...
O ID do encadeamento retornado por meio de ptid
/ ctid
não é igual ao ID de encadeamento POSIX ( pthread_t
), embora nas implementações de encadeamento 1: 1 como NPTL , existe uma correspondência um-para-um entre IDs de encadeamentos do kernel e IDs de encadeamentos POSIX. O ID do encadeamento do kernel é o mesmo ID que você obtém usando a chamada gettid()
do Linux . Ele também é retornado por clone()
como o valor de retorno da chamada do sistema, que faz a pergunta: por que precisamos de ptid
/ ctid
? O problema é que, do lado do espaço do usuário, as coisas são assim:
tid = clone(...);
Do ponto de vista da implementação da segmentação do espaço do usuário, há uma corrida aqui, porque a designação para tid
ocorre apenas após clone()
retornos. Isso significa que a biblioteca de encadeamentos do espaço do usuário pode ter alguns problemas se desejar obter essas informações antes que o novo encadeamento faça alguma coisa (como terminar, por exemplo). Usar CLONE_PARENT_SETTID
garante que o novo ID de encadeamento seja colocado no local apontado por ptid
antes
clone()
retorna e, assim, permite que uma biblioteca de threads evite essas condições de corrida. ( CLONE_CHILD_SETTID
também pode ser usado para efeito semelhante).
O motivo pelo qual CLONE_CHILD_CLEARTID
é usado para limpar o ptid
/ ctid
é fornecer uma maneira para um pthread_join()
chama em outro thread para descobrir que o segmento foi finalizado. Em essência, a localização ptid
/ ctid
está sendo usada como um futex , e o futex()
chamada do sistema é usada para bloquear, esperando que o inteiro neste local seja alterado. (Os detalhes são um pouco confusos, mas grep
para usos de lll_wait_tid
e lll_futex_wait
no código-fonte glibc. Por fim, há uma operação FUTEX_WAIT
ocorrendo. Lembre-se acima disso CLONE_CHILD_CLEARTID
faz uma ativação de futex no endereço de destino.)