O documento discute os conceitos fundamentais de processos no Unix, incluindo: (1) O que é um processo e como é identificado através de um PID; (2) Como processos filhos são criados através do método fork e herdam recursos do processo pai; (3) As diferentes formas como processos podem se comunicar e interagir através de sinais, pipes e sockets.
4. Um pouco de história
- A programação unix existe desde 1970
- Foi inventado nos laboratórios Bell (Bell labs)
- Os conceitos e técnicas de programação unix
não são novidades. Essas técnicas vão além das
linguagens de programação.
- Essas técnicas não são modificadas há décadas
6. Processos
Kernel
- É a camada que está em cima do hardware. Ou
seja, é o homem do meio entre toda interação que
acontece entre a userland e o hardware
- Essas interações incluem coisas como:
. Ler/Escrever no diretório de arquivos
. Enviar arquivos pela rede
. Alocar memória
. Tocar música através dos auto-falantes
Devido ao seu poder, nenhum programa possui
acesso direto ao Kernel
7. Processos
Userland
- É onde todos os seus programas são executados
- Seu programa pode fazer muita coisa sem
precisar do kernel:
. Realizar operações matemáticas
. Realizar operações com strings
. Controle de fluxo através de operações lógicas
Mas se você quiser fazer várias coisas legais terá
que utilizar o kernel
8. Processos
System call
- Qualquer comunicação entre o kernel e userland
é feita através de system calls
- É a interface que conecta o kernel e a userland
- Define as interações que são permitidas entre o
seu programa e o hardware
9. Processos
Process identifier (pid)
- Todo processo possui um identificador único
- Nada mais é que um número sequencial. É assim
que o kernel vê o seu processo: como um número
>> puts Process.pid || $$ #system call: getpid
- Não está ligado a nenhum aspecto do conteúdo
do processo.
- Pode ser entendido por qualquer linguagem de
programação
10. Processos
Parents process (ppid)
- Todo processo rodando no seu sistema possui
um pai
- O processo pai é o processo que invocou outro
determinado processo.
>> puts Process.ppid #system call: getppid
- O ppid pode ser importante quando estivermos
detectando processos deamons
11. Processos
File descriptors
- Representa arquivos abertos
- Em unix tudo é arquivo!
. Isso significa que dispositivos são tratados como
arquivo, sockets e pipes são tratados como
arquivos e arquivos são tratados como arquivos!
- Qualquer momento que você abrir um recurso, é
atribuido um número de file descriptor a esse
recurso
12. Processos
File descriptors
- Não são compartilhados entre processos que não
estão relacionados
- Eles vivem e morrem com o processo que eles
estão ligados
>> file = File.open(“path/to/the/file”)
>> file.fileno
=> 3
- É através do fileno que o kernel consegue manter
o controle dos recursos que seu processo está
utilizando
13. Processos
File descriptors
- Apenas recursos abertos possuem file
descriptors
- Todo processo unix vem com três recursos
abertos
. STDIN
. STDOUT
. STDERR
- Existe uma limitação de 1024 file descriptors
abertos por processo
14. Processos
Environment variables
- São pares chave-valor que possuem dados para
os processos
- Todo processo herda variáveis de ambiente de
seus pais
- São definidas por processo e são globais para
cada processo
$ MESS=‘teste’ ruby -e “puts ENV[‘MESS’]”
15. Processos
Argumentos
- ARGV: argument vector
- É uma forma de passar argumentos para os
processos
p ARGV
$ ruby argv.rb foo bar -ba
[“foo”, “bar”, “-ba”]
16. Processos
Possuem nome
- Todo processo possue um nome e esse nome
pode ser mudado durante seu runtime
>> puts $PROGRAM_NAME
- É uma forma eficiente de comunicar a tarefa que
está sendo desenvolvida
=> irb
17. Processos
Possuem um código de finalização
- Todo processo, ao finalizar, possui um código
- É um número entre 0-255 que denota se o
processo finalizou com sucesso ou não
- A finalização com um código 0 indica que tudo
ocorreu como esperado
- Mas... Os outros códigos são utilizados como
forma de comunicação
19. Processos
Fork
- É um dos conceitos mais poderosos da
programação unix
- Permite que um processo em execução crie outro
processo, utilizando recursos de programação
para isso
- O novo processo criado é uma cópia exata do
processo original
20. Processos
Fork
- O processo filho herda uma cópia de toda
memória usada pelo processo pai bem como todos
os file descriptors
Recapitulando...
- O ppid do processo filho é o pid do processo pai.
- O processo filho possui o mesmo mapa de file
descriptors que o processo pai
21. Processos
Fork
if fork
puts “entrando no if”
else
puts “entrando no else”
end
=> entrando no if
=> entrando no else
WTF???
- O método fork retorna duas vezes (ele cria outro
processo, lembra??)
- Retorna uma vez no processo pai e uma vez no
processo filho
22. Processos
Fork
puts “processo pai #{Process.pid}”
if fork
puts “entrando no if atraves do processo #{Process.pid}”
else
puts “entrando no else atraves do processo #{Process.pid}”
end
=> processo pai 2000
=> entrando no if atraves do processo 2000
=> entrando no else atraves do processo 2010
- O processo filho é finalizado após executar o
código que está no else
- O retorno do fork no processo pai é o pid do filho.
Já no processo filho o fork retorna nil
23. Processos
Fork
Multicore Programming
- Não é garantido de ser distribuido através das
CPUs disponíveis. Depende do SO
- É preciso ser feito com cuidado!
# system call => fork
24. Processos
Processos orfãos
fork do
5.times do
sleep 1
puts “Eu sou orfão!”
end
end
abort “Parent process died ...”
- O que acontece com um processo filho quando
seu pai morre?
. Nada!
25. Processos
CoW Friendly
- Copiar toda memória na hora do fork pode ser
considerado um overhead
- Sistemas Unix modernos empregam uma técnica
chamada Copy on Write para evitar o overhead
- A cópia da memória é evitada até que alguma
escrita seja necessária
- Dessa forma o processo pai e o processo filho
compartilham a memória até uma escrita ser
realizada pelo processo filho
26. Processos
CoW Friendly
arr = [1,2,4]
fork do
# nesse momento não há copia de memória
# a leitura é feita de forma compartilhada
puts arr
end
fork do
# Ao modificar o array uma cópia do array precisa ser feita
# para o processo filho
arr << 4
end
- Isso mostra que realizar um fork é rápido
- Os processos filhos possuem uma cópia dos
dados modificados. O resto pode ser
compartilhado
27. Processos
CoW Friendly
- MRI não é CoW friendly
. O garbage collector utiliza um algoritmo ‘mark-
and-sweep’ que não é compatível com cow
. O algoritmo realiza uma iteração por todo objeto
existente marcando se o mesmo deve ser coletado
ou não
. Dessa forma, quando o garbage collector rodar
toda memória sera copiada para o processo filho
28. Processos
Podem esperar
- Realizar um fork e não esperar o resultado é
interessante para tarefas assincronas
message = “Opa, bão?”
recipient = “rodrigo.diley@dito.com.br”
fork do
StatsCollector.record message, recipient
end
#continua o envio da mensagem
- Mas em alguns outros casos você quer manter o
controle sobre seus processos filhos
29. Processos
Podem esperar
fork do
5.times do
sleep 1
puts “Eu sou orfão!”
end
end
- Basta utilizar em ruby: Process.wait
Process.wait
abort “Processo pai morreu ...”
=> “Eu sou orfão!”
=> “Eu sou orfão!”
=> “Eu sou orfão!”
=> “Eu sou orfão!”
=> “Eu sou orfão!”
=> “Processo pai morreu ...”
30. Podem esperar
- Process.wait
Processos
. É uma chamada bloqueante que faz o processo
pai esperar por um de seus filhos terminar a
execução
. É você que precisa saber qual processo está
sendo terminado. Process.wait retorna o pid do
processo filho
31. Processos
Podem esperar
- Condições de corrida
. O que acontece quando um processo pai demora
para realizar a chamada ao metodo wait?
. O Kernel coloca todas as informações de exit dos
processos filhos em uma fila!
. Se Process.wait for chamado e não existir
processo filho será lançada uma exception
32. Processos
Zombieee
- É sempre bom limpar os processos filhos
- O Kernel guarda informações de todos os
processos filhos em um fila, estão lembrados?
- Se nós não retirarmos ela de lá, quem irá?
NINGUEM
- Estamos gastando mal os recursos do Kernel
33. Processos
Zombieee
message = “Opa, bão?”
recipient = “rodrigo.diley@dito.com.br”
pid = fork do
StatsCollector.record message, recipient
end
Process.detach(pid)
#continua o envio da mensagem
- Arrumando nosso exemplo
- Basicamente, Process.detach dispara uma nova
thread que terá como terefa esperar o processo
filho terminar
34. Processos
Zombieee
- Todo processo filho que termina e não possui
seus status coletado é considerado zombie
pid = fork { sleep(1) }
puts pid
sleep
$ ps -ho pid,state -p [pid do processo]
- O status impresso será z ou Z+
35. Processos
Recebem sinais
- Process.wait é uma chamada bloqueante
- Como esperar por processos filhos em processos
pais que estão sempre ocupados?
. Basta trabalhar com sinais!
. CHLD é o sinal que o kernel envia para o
processo pai indicando que um processo filho foi
finalizado
36. Processos
Recebem sinais
child_process = 3
dead_process = 0
child_process.times do
fork do
sleep 3
end
end
trap(:CHLD) do
while pid = Process.wait(-1, Process::WNOHANG)
puts pid
dead_process += 1
exit if dead_process == child_process
end
end
loop do
(Math.sqrt(rand(44)) ** 8).floor
sleep 1
end
37. Processos
Recebem sinais
- É uma forma de comunicação assincrona
- Quando um processo recebe um sinal do Kernel
as seguintes ações podem ser tomadas:
. Ignorar
. Realizar algum comportamento específico
. Realizar o comportamento padrão
38. Processos
Recebem sinais
- Os sinais são enviados através do Kernel
- Basicamente, o Kernel é o middleman utilizado
pelos processos para enviarem sinais uns aos
outros
40. Processos
Recebem sinais
- Se sua tarefa já estiver rodando em um console
basta enviar o sinal de stop para que a tarefa seja
interrompida
- Após sua interrupção basta colocá-la para rodar
em background através do comando bg
- Basta remover o vínculo com o terminal através
do comando disown
42. Processos
Podem se comunicar
- Pipe
. É um caminho de mão única de fluxo de dados
reader, writer = IO.pipe => [#<IO:fd 5>, #<IO:fd 6>]
writer.write(“Galo doido não perde em casa”)
writer.close
puts reader.read
=> “Galo doido não perde em casa”
. É um stream de dados!
43. Processos
Podem se comunicar
- Pipe
. É um recurso compartilhado como qualquer
outro
reader, writer = IO.pipe => [#<IO:fd 5>, #<IO:fd 6>]
fork do
reader.close
10.times do
#trabalhando...
writer.puts “Quase acabando”
end
end
writer.close
while message = reader.gets
$stdout.puts message
end
44. Processos
Podem se comunicar
- Sockets
. Pode ser um socket unix, para comunicação
local
. Pode ser um socket tcp, para comunicação
remota
- Outra possível solução seria RPC
45. Processos
Deamon
- São processos que rodam em background e que
não estão ligados a nenhum terminal controlado
pelo usuário
- Tem um processo deamon que é muito
importante para o sistema operacional
. É o pai de todos. É o processo chamado init, seu
ppid é 0 e o seu pid é 1
46. Processos
Deamon
- Process group e session group
$ git log | grep shipped | less
- Todo sinal enviado para o pai de um process
group é encaminhado para os filhos
- Todo sinal encaminhado para um session group
é encaminhado para os processos que fazem parte
desse grupo