Introdução aos Sistemas Operacionais
Oi, Habr! Gostaria de chamar sua atenção para uma série de artigos-traduções de uma literatura interessante na minha opinião - OSTEP. Este material discute profundamente o trabalho de sistemas operacionais do tipo unix, ou seja, trabalhar com processos, vários agendadores, memória e outros componentes semelhantes que compõem um sistema operacional moderno. Você pode ver o original de todos os materiais aqui
O trabalho de laboratório sobre este assunto pode ser encontrado aqui:
Outras partes:
Você também pode conferir meu canal em
Alarme! Existe um laboratório para esta palestra! Olhar
API de processo
Vejamos um exemplo de criação de um processo em um sistema UNIX. Isso acontece através de duas chamadas de sistema garfo() и exec ().
Bifurcação de chamada()
Considere um programa que faz uma chamada fork(). O resultado de sua execução será o seguinte.
Primeiramente, entramos na função main() e imprimimos a string na tela. A linha contém o identificador do processo que no original é chamado PID ou identificador do processo. Este identificador é usado no UNIX para se referir a um processo. O próximo comando chamará fork(). Neste ponto, é criada uma cópia quase exata do processo. Para o sistema operacional, parece que há 2 cópias do mesmo programa em execução no sistema, que por sua vez sairá da função fork(). O processo filho recém-criado (em relação ao processo pai que o criou) não será mais executado, a partir da função main(). Deve ser lembrado que um processo filho não é uma cópia exata do processo pai; em particular, ele possui seu próprio espaço de endereço, seus próprios registradores, seu próprio ponteiro para instruções executáveis e assim por diante. Assim, o valor retornado ao chamador da função fork() será diferente. Em particular, o processo pai receberá como retorno o valor PID do processo filho, e o filho receberá um valor igual a 0. Usando esses códigos de retorno, você pode então separar processos e forçar cada um deles a fazer seu próprio trabalho . No entanto, a execução deste programa não está estritamente definida. Após dividir em 2 processos, o SO passa a monitorá-los, bem como a planejar seu trabalho. Se executado em um processador single-core, um dos processos, neste caso o pai, continuará funcionando e então o processo filho receberá o controle. Ao reiniciar, a situação pode ser diferente.
Chamada em espera()
Considere o seguinte programa. Neste programa, devido à presença de uma chamada esperar() O processo pai sempre aguardará a conclusão do processo filho. Neste caso, obteremos uma saída de texto estritamente definida na tela
chamada exec()
Considere o desafio exec (). Esta chamada de sistema é útil quando queremos executar um programa completamente diferente. Aqui vamos ligar execvp () para executar o programa wc, que é um programa de contagem de palavras. O que acontece quando exec() é chamado? Esta chamada recebe o nome do arquivo executável e alguns parâmetros como argumentos. Depois disso, o código e os dados estáticos deste arquivo executável são carregados e seu próprio segmento com o código é sobrescrito. As áreas de memória restantes, como pilha e heap, são reinicializadas. Depois disso, o sistema operacional simplesmente executa o programa, passando-lhe um conjunto de argumentos. Portanto, não criamos um novo processo, simplesmente transformamos o programa atualmente em execução em outro programa em execução. Depois de executar a chamada exec() no descendente, parece que o programa original não foi executado.
Esta complicação de inicialização é completamente normal para um shell Unix e permite que esse shell execute código após chamar garfo(), mas antes da chamada exec (). Um exemplo desse código seria ajustar o ambiente shell às necessidades do programa que está sendo lançado, antes de lançá-lo.
concha - apenas um programa de usuário. Ela mostra a linha do convite e espera que você escreva algo nela. Na maioria dos casos, se você escrever o nome de um programa lá, o shell encontrará sua localização, chamará o método fork() e, em seguida, chamará algum tipo de exec() para criar um novo processo e esperará que ele seja concluído usando um espera() chamada. Quando o processo filho for encerrado, o shell retornará da chamada wait() e imprimirá o prompt novamente e aguardará a inserção do próximo comando.
A divisão fork() & exec() permite que o shell faça o seguinte, por exemplo:
arquivo wc> novo_arquivo.
Neste exemplo, a saída do programa wc é redirecionada para um arquivo. A maneira como o shell consegue isso é bastante simples - criando um processo filho antes de chamar exec (), o shell fecha a saída padrão e abre o arquivo novo arquivo, portanto, toda a saída do programa em execução adicional wc será redirecionado para um arquivo em vez de uma tela.
tubo Unix são implementados de maneira semelhante, com a diferença de que usam uma chamada pipe(). Neste caso, o fluxo de saída do processo será conectado a uma fila de tubos localizada no kernel, à qual será conectado o fluxo de entrada de outro processo.
Fonte: habr.com