Como um shell executa um programa?

11

Se eu compilar um programa usando o gcc e tentar executá-lo no shell bash, qual é a sequência exata de etapas seguidas pelo bash para executá-lo?

Eu sei fork(), execve(), loader, dynamic linker(e outras coisas) estão envolvidos, mas alguém pode dar uma seqüência exata de passos e alguma referência de leitura adequado?

Editar:

A partir das respostas, parece que a pergunta poderia implicar muitas possibilidades. Eu quero limitar a um caso simples:

(test.c apenas imprime olá mundo)

$ gcc test.c -o test
$ ./test

Quais serão as etapas no caso acima ( ./test), relacionando especificamente o programa de inicialização do bash em algum processo filho, carregando, vinculando etc.?

Jake
fonte
4
Convido você a ler lwn.net/Articles/630727
cuonglm
3
Por que não tentar 'strace bash -c' test '?
Sergiy Kolodyazhnyy 27/08/2015
2
Parece que um livro didático decente sobre sistemas operacionais seria um bom recurso para o OP. Tentar aprender como os sistemas operacionais funcionam, fazendo perguntas individuais como essa, provavelmente não é um processo produtivo.
Barmar
Seria útil para ver um exemplo mínimo de um shell: brennan.io/2015/01/16/write-a-shell-in-c
jinawee

Respostas:

5

Bem, a sequência exata pode variar, pois pode haver um alias ou função de shell que primeiro seja expandido / interpretado antes que o programa real seja executado e depois diferenças para um nome de arquivo qualificado ( /usr/libexec/foo) versus algo que será procurado em todos os diretórios da PATHvariável de ambiente (apenas foo). Além disso, os detalhes da execução podem complicar as coisas, pois foo | bar | zotexigem mais trabalho para o shell (um certo número de fork(2), dup(2)e, é claro pipe(2), entre outras chamadas do sistema), enquanto algo como exec fooé muito menos trabalho, pois o shell simplesmente se substitui por o novo programa (ou seja, não fork). Também são importantes grupos de processos (especialmente o grupo de processos em primeiro plano, todos os PIDsSIGINTquando alguém começa a mascarar Ctrl+ C, sessões e se o trabalho será executado em segundo plano, monitorado ( foo &) ou em segundo plano, ignorado ( foo & disown). Os detalhes de redirecionamento de E / S também mudarão as coisas, por exemplo, se a entrada padrão for fechada pelo shell ( foo <&-) ou se um arquivo for aberto como stdin ( foo < blah).

straceou similar será informativo sobre as chamadas de sistema específicas feitas ao longo desse processo, e deve haver páginas de manual para cada uma dessas chamadas. A leitura adequada no nível do sistema seria qualquer número de capítulos da "Programação Avançada no Ambiente UNIX" de Stevens, enquanto um livro de shell (por exemplo, "From Bash to Z Shell") cobrirá o lado do shell com mais detalhes.

agitar
fonte
Eu editei a questão de diminuir para um caso simples
Jake
1

Supondo que um exemplo de shell de livro (para maior clareza do código) já esteja em execução (para que o vinculador dinâmico esteja pronto), os comandos mencionados exigirão que o shell faça as seguintes chamadas de sistema:

  • read: obtém o próximo comando neste caso gcc
  • fork: são necessários dois processos, assumimos que o pai tenha 500 pid e o filho para ilustração.
  • o pai chamará espera (501), enquanto o filho chamará exec. Neste ponto, o shell não está mais rodando no pid 501. O gcc faz muitas chamadas de sistema, incluindo no mínimo abrir, fechar, ler, escrever, chmod, fork, exec, wait e exit.
  • quando as chamadas do gcc forem encerradas, a espera retornará, a gravação será chamada para exibir o prompt e o processo será repetido.

Comandos mais complicados, é claro, adicionam mais complicações a essa sequência básica. Dois exemplos mais simples de complicações básicas são o redirecionamento básico io, onde uma sequência dup aberta e fechada é inserida entre a bifurcação e os processos exec e background em que a espera é ignorada (e outra espera é adicionada a um manipulador de sigchld).

hildred
fonte
Pequena adição: a pergunta é feita sobre carregamento e vinculação dinâmica. Todo o código que está estaticamente vinculado, ou seja, realmente incluído no arquivo do programa, é feito pelo kernel antes do início do programa. Bibliotecas carregadas dinamicamente, ou seja, arquivos separados, são tratadas pelo próprio programa antes de iniciar main (). O código para isso é adicionado automaticamente pelo gcc.
Stig Hemmer