| | | Sincronização em Multiprocessadores |
Sincronização em Multiprocessadores
Multiprocessadores oferecem várias vantagens:
- Expansibilidade: adicionar mais CPUs.
- Aumentar CPU sem aumentar outros recursos.
- MTBF: importante para "fault tolerant systems".
Paralelização de Unix exige muitas alterações:
- Modelo de sincronização não funciona.
- Colocação e granularidade de locks.
- Escalonamento
Reentrante e Nonpreemtive:
- Reentrante significa que vários processos podem estar no kernel;
- "Non-preemtive" significa que um processo não pode ser
retirado do kernel.
- Interrupts controlados por ipl:
- Sistema só aceita interrupts com ipl superior;
- Linux apenas irqsave e irqrestore (vd.
include/asm-i386/system.h:
- em x86 cli e sti são usados para
desabilitar interrupções;
- pushfl e popfl são usados para guardar o
contexto corrente.
Recursos partilhados são controlados por flags locked e
wanted:
- Quando uma thread precisa de um recuso partilhado (buffer de
bloco), se locked limpo, coloca a 1 e entra;
- Se locked a 1, coloca wanted a 1 e
bloqueia;
- quando o thread termina, limpa locked e verifica
wanted: se a 1 percorre a sleep queue e acorda
todos os threads;
- acordar é remover da fila, mudar estado para
runnable, e colocar processo na fila do escalonador;
- processo depois recomeça do princípio
Recursos são mapeados numa sleep queue:
- recursos são associados ao sleep channel, habitualmente
o endereço do recurso;
- função de hash mapeia o recurso para entrada na fila;
- Acorda-se todos os threads bloqueados no mesmo canal.
- Colisões.
Soluções: fila por recurso e turnstiles
A baixo nível
- Atomic test-and-set: atómicamente retorna o valor antigo do bit e
coloca o novo valor a um.
- Extensão: fazer isso com uma palavra. LDSTUB e SWAP
no SPARC e MC88100.
- LL e SC no MIPS e ALPHA.
- x86 tem o prefixo lock, xchgb que faz swap
atómico;
- Ultra-sparc tem swap condicional casa;
- Arquitecturas modernas precisam de sync ou
membar.
Três variantes:
- Master-Slave: mestre pode ser único a realizar I/0 e a receber
interrupts.
- Assimétricos funcionalmente: processadores especializados.
- Exemplo: servidor de ficheiros Auspex NS5000.
- Totalmente Simétricos:
- Memória Partilhada;
- DSM;
- Clusters.
Mecanismo de Unix não funciona:
- Vários threads podem aceder a locked simultaneamente.
- Bloqueamento de Interrupts não funciona.
- Wakeup perdido um processo está a adormecer enquanto
outro processo está a devolver o recurso. O primeiro processo pode
bloquear para sempre.
- Thundering Herd: vários processos bloqueados no mesmo
recurso podem acordar ao mesmo tempo, e ser escalonados para
diferentes CPUs, competindo pelo mesmo recurso.
- Starvation: um processo pode nunca conseguir chegar ao
recurso.
Usados nas primeiras implementações de Unix SMP (IBM/370 e AT&T
3B20A):
- Semáforos não têm spinning;
- Bloquear em semáforos pode ser lento porque exige manipulação de
filas e mudança de contexto;
- Semáforos não dão garantia sobre o que estão a proteger:
- getblk() encontra um bloco na cache;
- faz P() no buffer e bloqueia;
- não sabemos porque getblk() fez P() no
semáforo;
- mas temos que garantir que quando processo acordar está lá o
mesmo bloco!
Comboios: problema típico de semáforos.
- Acontecem quando há contenção frequente:
- O thread que recebe o semáforo pode não estar executando:
- O thread que roda pode suspender no semáforo.
- Threads podem bloquear desnecessáriamente.
Mutexes com busy-wait. Para operações rápidas:
void spin_lock(spinlock_t *s) {
while (test_and_set(s) != 0) ;
}
Evitar tráfego desnecessário (máquinas antigas):
void spin_lock(spinlock_t *s) {
while (test_and_set(s) != 0)
while (*s != 0) ;
}
- Bloqueiam CPU: usados por tempo curto.
- Uniprocessadores podem bloquear se spin-lock tem disable de
interrupts.
- Usados para implementar semáforos.
- Linux: include/asm-i386/spinlock.c.
Associadas a um predicado baseado em dados partilhados.
- wait(): espera pelo recurso
void wait(condition_t *c, spinlock_t *s) {
spin_lock(&c->listLock);
add self to linked list;
spin_unlock(&c->listLock);
spin_unlock(s);
schedule();
/* event has occurred */
spin_lock(s);
}
- signal() e broadcast(): acorda um ou todos os
processos.
void do_signal(condition_t *c) {
spin_lock(&c->listLock);
remove thread from linked list;
spin_unlock(&c->listLock);
if thread was removed, make it runnable.
}
- Usado em UTS.
Usar rwlocks:
- lockShared():
void lockShared(rwlock_p r) {
spin_lock(&r->sl);
r->nPendingReads++;
/* don't starve writers */
if (r->nPendingWrites > 0)
wait(&r->canRead,&r->sl);
/* exclusive at work ? */
while(r->nActive < 0)
wait(&r->canRead,&r->sl);
r->nActive++; r->nPendingReads--;
spin_unlock(&r->sl);
}
- unlockShared():
void unlockShared(rwlock_p r) {
spin_lock(&r->sl);
r->nActive--;
if (r->nActive == 0) {
spin_unlock(&r->sl);
do_signal(&r->canWrite);
} else spin_unlock(&r->sl);
}
- lockExclusive():
void lockExclusive(rwlock_p r) {
spin_lock(&r->sl);
r->nPendingWrites++;
while(r->nActive)
wait(&r->canWrite,&r->sl);
r->nPendingReads--; r->nActive = -1;
spin_unlock(&r->sl);
}
- unlockExclusive():
void unlockExclusive(rwlock_p r) {
boolean_t wakeReaders;
spin_lock(&r->sl);
r->nActive = 0;
wakeReaders = (r->nPendingReads != 0);
spin_unlock(&r->sl);
if (wakeReaders)
do_broadcast(&r->canRead);
else
do_signal(&r->canWrite);
}
- Outras: tryLock(), upgrade() e
downgrade().
- O que fazer quando se liberta um lock?
- Último leitor deve acordar um escritor.
- Um escritor pode acordar leitores (um ou todos) ou outro
escritor.
- Muitos leitores podem bloquear escritor: bloquear se há
escritor.
- upgrade() corre o risco de deadlock.
- Linux: ver include/asm-i386/rwlock.h.
- Usados em código de rede, file-system, ...
- Contador de Referências: necessários para quando se
partilham objectos.
- Prevenção de Deadlock: locking hierárquico e estocástico,
- Geralmente, primeiro buffer e depois lista de blocos em disco;
- E se quisermos libertar um bloco da lista?
- Solução: try_lock().
- Locks Recursivos: processo que já tem um lock pode voltar
apedi-lo (UFS): ter um campo dono.
- Bloquear ou rodar? Depende da duração e de quem tem o recurso. Hints.
Solaris tem locks adaptativos.
- Granularidade e Duração.
- SVR4.2 MP suporta mutexes (com IPL), RW locks, sleep locks e
variáveis de sincronização.
- Digital Unix Locks Simples (spin-locks) e Complexos
(abstracções):
- uso partilhado ou exclusivo; bloqueamento; recursão.
- Também suporta sleep() e wakeup() com
variáveis de condição.
- NCR introduziu Advisory Processor Locks, com hints
(dormir ou spin) que podem ser voluntários ou mandatórios.
- Solaris usa locks adaptativos e turnstiles e fornece
semáforos, RW locks e variáveis de condição.
vitor@cos.ufrj.br
| | | Sincronização em Multiprocessadores |