| | | Comunicação entre Processos |
Comunicação entre Processos
IPC permite:
- transferência de dados;
- partilha de dados;
- notificação de eventos;
- partilha de recursos especializada;
- controle de processos: debugger quer tomar conta dos eventos de
outro processo.
Sinais vêm desde Unix original:
- mecanismo assíncrono de notificação;
- muitos com significado prédefinido (SIGUSR1 e
SIGUSR2);
- kill() envia sinais para outro processo.
- sigpause() espera por sinal.
- sigaction() define handler.
- São caros porque emissor tem que fazer syscall e kernel tem que
mexer na pilha do receptor.
- Banda limitada: apenas 31 em SVR4 e 4.3BSD, 64 em AIX, Linux.
- Úteis para notificação.
FIFO não-estruturado e unidirecional:
- Escrever no fim e ler do princípio;
- Escritores bloqueam se pipe cheio e leitores se vazio.
- pipe() retorna dois file-descriptores, um para ler e
outro para escrever. Descriptores podem ser passados entre
processos.
- Usados pela shell, tem algumas limitações:
- não suportam broadcast;
- não conhecem limites de mensagem;
- não se pode especificar o leitor
Implementação varia:
- Tradicionalmente: inode e entrada na tabela de ficheiros com pipe.
- BSD usa sockets, SVR4 usa streams, Linux usa código
especializado com semáforo e kmalloc() (vd.
fs/pipe.c).
Named pipes: mknod é usado para criar o pipe, que depois é
acessível a processos.
- Vantagens: persistência, acesso para qq processo.
- Desvantagens: têm que ser removidos, não são tão seguros,
consomem mais recursos, mais complicados de criar.
- Linux usa o mesmo código, e pipes pertencem a um
pipefs onde é montado o arquivo.
ptrace(cmd, pid, addr, data).
- Permite a um processo:
- ler ou escrever no espaço de
um filho (incluindo área-u);
- mexer nos registos;
- criar watchpoints no espaço de endereçamento;
- interceptar sinais;
- criar ou alterar watchpoints;
- continuar a execução de um filho parado;
- andar passo a passo;
- matar o filho;
- cmd == 0 é usado pelo filho para indicar que está
controlada por ptrace(), alterando comportamento para
sinais e para fork()
- Parente usa wait() para esperar eventos que mudam o estado
do filho.
- Filho envia SIGCHLD quando acontece alguma coisa.
- exec no filho resulta em SIGTRAP que pode
ser controlada pelo pai.
- Quando SIGCHLD chega pai usa ptrace() para
controlar.
Limitações:
- só pode controlar filhos imediatos;
- não permite apanhar processos em andamento;
- extremamente ineficiente;
- problemas com programs setuid().
Sistemas modernos usam /proc: fs/proc,
kernel/ptrace.c e arch/i386/kernel/ptrace.c.
SYSV suporta semáforos, filas de mensagens, e memória
partilhada
Cada recurso tem os seguintes atributos:
- Chave: inteiro que identifica a instância do recurso.
- Criador: UID e GID do processo que creou
- Dono: pode ser <> do anterior.
- Permissões.
get cria o recurso, cft controla com STAT,
SET, RMID.
Cada recurso tem uma tabela de tamanho fixa.
Ver ipc em Linux.
- semid = semget(key, count, flag) array de
count semáforos.
- status = semop(semid, sops, nsops), onde sops
aponta para um array de operações. Operação pode ser incrementar
(>0), esperar até semáforo estar a 0 (=0), ou esperar que o
valor seja maior ou igual ao valor absoluto (<0) e depois subtrair
esse valor.
- Todas as operações avançam ou bloqueiam. Nenhuma outra operação
pode executar em paralelo.
- IPC_NOWAIT evita bloqueio.
- Kernel mantém UNDO LIST para o caso do processo sair.
Troca de Mensagens é o mecanismo fundamental de comunicação:
- Mensagens podem variar entre alguns bytes e um espaço de
endereçamento.
- Comunicação deve ser segura.
- Comunicação ligada a gestão de memória.
- Comunicação entre user tasks, e com o kernel.
- Suportar o modelo cliente-servidor
- Interface pode ser generalizada para ambiente distribuido.
Bastantes melhoramentos em Mach 3.0.
Tasks tem direitos sobre portas de send e de receive
(apenas a dono): comunicação muitos-para-um.
Mensagens podem ser:
- Simples: dados ordinários que não são interpretados pelo kernel;
- Complexa:
- dados ordinários, +
- memória out-of-line que é passada por referência (COW),
+,
- direitos de envio ou recepção para portas.
Kernel interpreta mensagens complexas.
- Cada porta tem um contador de referências.
- Cada direito ou capacidade, é um nome para a porta. Nomes são
inteiros e locais a tasks.
- Objectos do kernel são representado por uma porta. Acesso a essa
porta permite ao dono fazer operações no objecto. O kernel tem os
direitos de recepção para essas portas.
- Cada porta tem uma fila de mensagens finita. Emissores bloqueiam
quando a fila enche.
- Por Task:
- Cada task tem uma porta task_self para ela própria;
- Pode enviar para bootstrap que fornece acesso ao name
server.
- uma porta de exception.
- Por Thread:
- direitos de envio para self;
- direitos de recepção para reply;
- uma porta de exception.
- Todos as threads numa task partilham direitos.
Mensagens podem ser locais ou por rede (através de
netmsgserver):
- Cabeçalho contém:
- Tipo:
- simples ou complexo;
- Tamanho:
- mensagegem inc. cabeçalho;
- Destino:
- uma porta;
- Resposta:
- uma porta, se necessário;
- ID:
- ao cuidado do usuário.
- Components contêm dados e descriptor:
- nome:
- tipo de dados, eg, memória interna, direitos de envio ou
recepção, escalar (byte, string, int de 16/32
bits, ...).
- Tamanho:
- tamanho de cada item de dados;
- Número:
- de items;
- Flags:
- dados são "in-line" ou "out-of-line" e se a
memória ou os direitos devem ser deadlocados.
Três funções:
- msg_send() envia sem esperar;
- msg_rcv() espera
por mensagens;
- msg_rpc() envia e espera por uma resposta que
pode vir no próprio buffer.
- Optimização de msg_send() + msg_rcv().
- Originalmente o header tem o tamanho máximo da msg que pode
receber.
- No fim o header tem o tamanho da mensagem.
- Todas operações têm TIMEOUT.
Cada porta é uma fila protegida de mensagens no kernel:
- contador de referências para a porta;
- ptr para a task que tem direitos de recepcão;
- nome local no receptor;
- ptr para porta backup;
- lista dupl. ligada de mensagens;
- fila de emissores bloqueados;
- fila de threads receptores bloqueados;
- lista de todas as traduções;
- ptr para um "port set";
- núm. de mensagens na fila;
- núm max. permitido ("backlog").
Tradução é <task,port,local_name,type>:
- sender usa <task,local_name> (TL);
- receiver usa <task,port> (TP);
- tasks têm que encontrar todos os direitos para a porta quando
ela é dealocada;
- direitos são limpos quando a porta é destruída.
Emissão:
- Emissor cria mensagem;
- chama msg_send() do kernel;
- kernel copia mensagem e:
- se thread está à espera é acordado e recebe;
- se lista cheia emissor bloqueia;
- senão mensagem colocada na fila;
Recepção:
- receptor chama msg_rcv();
- kernel chama msg_dequeue();
- kernel copia para receptor.
- Se emissor espera resposta, envia direitos para porta usando
campo reply port na mensagem.
- Quando nova porta chega a task kernel traduz:
- se porta já existe ok;
- aloca novo índice (< int) e cria nova tradução.
- Servidor de nomes passa direitos de acesso a portas de
servidores:
Mach usa copy-on-write:
- msg_send() chama msg_copyin():
- modifica mapeamentos das pág para ser RO e COW;
- cria mapa temp. no kernel.
- msg_rcv() chama msg_copyout():
- aloca espaço no receptor;
- copia entradas do mapa temp;
- remove mapa.
- Quando alguém tenta mexer na página, PF e kernel chama
"fault handler":
- cria uma nova cópia da página;
- muda mapa do processo que falhou;
- se puder, permite ao outro processo escrever na página original.
- Se emissor usar deallocate, msg_copyin() e
msg_copyout() apenas passam as páginas.
- Mensagens podem ser enviadas no caminho lento (colocar na
fila) ou caminho rápido (handoff scheduling).
- backlog é o limite configurável de mensagens numa porta.
- Notificações: mensagens enviadas para informar uma task de
eventos:
- NOTIFY_PORT_DESTROYED: qdo porta destruída msg. é
enviada para porta backup;
- NOTIFY_PORT_DELETED: qdo porta destruída msg. é
enviada para todos os processos com direito de envio.
- NOTIFY_MSG_ACCEPTED: se msg_send() usar
SEND_NOTIFY, msg. é colocada mesmo que fila cheia e qdo
msg. retirada da fila emissor kernel envia-lhe
NOTIFY_MSG_ACCEPTED.
- Destruição de portas:
- mensagens são removidas e
NOTIFY_PORT_DELETED é enviado. Se mensagem contém
direitos sobre a própria porta dá confusão em Mach 2.5.
- Portas backup:
- usadas quando a porta original é destruída.
- Conjuntos de Portas:
- um receptor recebe todas as mensagens para
o conjunto. Permite controle de vários objectos por uma única task.
- Interpolação de portas:
- permite substituir uma capacidade para
uma única porta com uma porta diferente. Usada por debugger para
controlar acesso a um processo.
netmsgserver permite extensão para rede:
- Usa proxy ports para "enganar" clientes, e comunica com outros
netmsgserver para distribuir o sistema.
- Possível porque cliente tem apenas acesso a nome local para a
porta, e porque emissores são anónimos: o emissor pode enviar apenas
o direito de acesso a uma porta de resposta.
- Dificil dealocar send rights, pq não se sabe que threads estão a
usar o direito: send-once rights.
- Apenas envia notificações a processos que as pediram.
- Kernel mantém um contador de referência a direitos por task.
Quando o contador vai a 0, pode dealocar.
vitor@cos.ufrj.br
| | | Comunicação entre Processos |