2009-05-17

Processos, parte II

Se ainda se lembram, na primeira parte as ocorrências não eram impressas por ordem. Na altura eu toquei muito ao de leve neste assunto. Desta vez vou falar um pouco mais sobre isso, visto esta segunda parte ter como objectivo resolver esse problema.

Até aqui, o código que nós escrevíamos usava sempre apenas um processo. Com isto tinhamos algumas garantias, especialmente o facto de sabermos que o código executa pela ordem que está escrito. Este facto é bastante óbvio e básico. Mas a partir do momento em que criamos um processo novo, esta garantia desaparece. Não desaparece por completo! Dentro de cada processo individual, o código continua a executar numa ordem bem definida. Só perdemos essa garantia se olharmos para o conjunto de processos que compõe o nosso programa. Não podemos afirmar que a linha X no processo A vai executar antes da linha Y no processo B.

Porquê?

Porque é que não temos garantias de ordem entre processos? Porque não somos nós que distribuimos o tempo do processador. É o sistema operativo.

Mas eu quero ordem!

E se nós quisermos ordem nos processos? Seguindo as “hints” do enunciado vamos tentar com as syscalls wait e waitpid. Mais uma vez, o manual é nosso amigo:

man 2 wait

wait bloqueia um processo pai até um processo filho qualquer terminar. Bah, afinal não podemos forçar a ordem com wait… Vamos ver o waitpid, que por acaso fica na mesma página…

Bloqueia um processo pai até o filho com o PID especificado terminar. Óptimo! Assim já podemos ordenar os resultados dos processos-filho. Basta guardar todos os seus PIDs e depois esperar por eles por ordem.

Se, por exemplo, o segundo filho terminar antes do primeiro, também não há problema. Quando um filho termina, a sua informação (o seu código de saída, por exemplo) continua a existir até ser colectada pelo pai. Isto significa que a informação do segundo processo ficará “em espera” enquanto o primeiro termina. Depois o pai colecta o primeiro (com waitpid) e depois, por fim, colecta o segundo.

E o total?

Para podermos imprimir o total de ocorrências necessitamos de uma forma de passar dados dos processos filhos para o pai. Como fazemos isso? Há várias formas de IPC (Inter-Process Communication) com os mais variados níveis de complexidade e flexibilidade. Mais tarde vamos ver algumas dessas formas de IPC, nomeadamente sinais, pipes e named pipes. Sockets serão abordados mais tarde em CC (Comunicações por Computador) e algumas não serão abordadas em nenhuma cadeira do curso (shared memory, por exemplo).

Para o nosso problema vamos usar uma solução muito simples, embora bastante fraca e limitada: códigos de saída. Basta que os processos filho devolvam o número de ocorrências como o seu código de saída, assim:

    exit(ocorrencias);

Assim, com o segundo parâmetro de waitpid, podemos receber o código de saída (porque esse parâmetro é passado por referência!). É aqui que encontramos mais uma daquelas coisas irritantes características de trabalhar neste baixo nível. O status que a função waitpid “devolve” não é simplesmente o código de saída. Também não é uma estrutura com vários detalhes, incluindo o código de saída. Não. É uma amálgama de várias flags e do código de saída. Isto tudo num inteiro. É por isso que temos de escrever este código:

    int exitcode;
int status;
waitpid(pid_filho, &status, 0);
if(WIFEXITED(status))
{
exitcode = WEXITSTATUS(status);
// Outras operações
}
else
{
// O filho terminou de forma anormal.
}

… para podermos obter o código de saída. WIFEXITED verifica se o filho terminou por meios normais (ou chegou ao fim da main ou chamou exit). WEXITSTATUS extrai o código de saída.

Tenham atenção a este pormenor, e não usem directamente status para ver o código de saída. Se o vosso programa estiver a mostrar valores completamente malucos, provavelmente esqueceram-se de usar WIFEXITED e WEXITSTATUS. É um erro muito comum.

Devido ao facto do código de saída ser devolvido nesta amálgama de flags, estamos limitados a códigos de saída entre 0 e 127. É por isso que esta estratégia é bastante limitada. Mas para o nosso problema vamos favorecer a simplicidade. Uma alternativa razoável (e dentro dos conhecimentos adquiridos até agora) seria usar ficheiros para comunicar. Ao terminar cada filho escreve o seu resultado num ficheiro com o seu PID como nome. O pai espera por cada um e depois vai a todos ficheiros ler os resultados individuais. Recomendo que tentem resolver este exercício desta forma quando estudarem para o exame porque exercitam ficheiros e processos ao mesmo tempo.

Mas voltando ao problema em mãos. Agora que já analisámos as questões que surgiram, podemos escrever o seguinte esqueleto do nosso programa:

    pid_t children[LINES];

// Iniciar matriz aleatoriamente (igual ao exercício anterior) // Obter o número a procurar
int goal = atoi(argv[1]);

// Lançar processos
for(i = 0; i < LINES; i++)
{
pid_t pid = fork();
if(pid == 0)
{
int count = 0;
for(j = 0; j < COLUMNS; j++)
{
if(matrix[i][j] == goal)
count++;
}
exit(count);
}
else
{
children[i] = pid;
}
}
// Colectar resultados
int total = 0;
for(i = 0; i < LINES; i++)
{
int exitcode;
int status;
waitpid(children[i], &status, 0);
if(WIFEXITED(status))
{
exitcode = WEXITSTATUS(status);
printf("Line %d: %d\n", i, exitcode);
total += exitcode;
}
else
{
printf("Line %d: failed\n", i);
}
}
printf("Total: %d\n", total);

E é tudo por hoje. Podem fazer download do código completo no sítio do costume.

9 commentários:

TMF disse...

Boas

Olha, uma duvida: não devia na linha waitpid(pid_filho, &status, 0) estar children em vez de pid_filho?

Um abraço e mais uma vez obrigado por te dares a todo este trabaho;)

Anónimo disse...

onde e que colocas o codigo completo.
o apontador para o site que das esta vazio

Martinho Fernandes disse...

@TMF: Fixed. thanks.

Martinho Fernandes disse...

@Anónimo: Enganei-me no link. Já está direito.

Anónimo disse...

Olha pela tua grande experiência em SO sabes-me informar se no teste sai exercicios dos guiões?

Martinho Fernandes disse...

Eu fui ao primeiro teste no ano passado e tinha dois exercícios teóricos e um exercício prática que envolvia processos e ficheiros. No segundo teste não sei porque não apareci.
Mas este ano a avaliação está diferente. Não sei como vai ser isso. O melhor é perguntares ao prof.

MJ disse...

Boas!

Estou a estudar para o exame de recurso, e mais uma vez obrigado por este blog. Os profs bem que podiam criar uma wiki do genero para ajudar os alunos mas pronto, adiante...

Olha, se me pudesses tirar umas duvidas acerca do funcionamento do fork, agradecia imenso:

Neste caso concreto, o processo pai, esta "apenas" a guardar o numero do processo filho, certo? Assim, e imaginando que os computadores sao muitooooo lentos, cada linha vai ter um processo a procurar o numero que queremos, enquanto o pai, vai criando novos processos para as restantes linhas. No fim, ele vai esperar que cada uma termine, para poder receber os resultados.

É isto?

Se puderes confirmar, agradeço, pq perceber isto acho que é importante!

Obrigado e bom trabalho! ;)

Martinho Fernandes disse...

É mesmo isso. Só não percebi o que querias dizer com "computadores muitooooo lentos". Os processos vão ser sempre lançados, independentemente de o sistema ter uma certa performance. O que pode acontecer é não estarem todos a correr ao mesmo tempo, porque, dependendo de como o SO atribui o tempo de processador, os primeiros podem terminar antes dos últimos ser lançados.

MJ disse...

Sim tens razão, é uma expressão infeliz, lol.
Eu só usei essa expressão, pq é mais "fácil" imaginar dessa forma.

Obrigado

;)

Enviar um comentário