Qual é a exec()
função e sua família? Por que essa função é usada e como funciona?
Por favor, alguém explique essas funções.
Qual é a exec()
função e sua família? Por que essa função é usada e como funciona?
Por favor, alguém explique essas funções.
Respostas:
De forma simplista, no UNIX, você tem o conceito de processos e programas. Um processo é um ambiente no qual um programa é executado.
A ideia simples por trás do "modelo de execução" do UNIX é que existem duas operações que você pode fazer.
O primeiro é para fork()
, que cria um novo processo contendo uma duplicata (principalmente) do programa atual, incluindo seu estado. Existem algumas diferenças entre os dois processos que lhes permitem descobrir quem é o pai e quem é o filho.
A segunda é para exec()
, que substitui o programa no processo atual por um programa totalmente novo.
A partir dessas duas operações simples, todo o modelo de execução do UNIX pode ser construído.
Para adicionar mais alguns detalhes ao acima:
O uso fork()
e exec()
exemplifica o espírito do UNIX, pois fornece uma maneira muito simples de iniciar novos processos.
A fork()
chamada torna quase uma duplicação do processo atual, idêntico em quase todos os sentidos (nem tudo é copiado, por exemplo, limites de recursos em algumas implementações, mas a ideia é criar uma cópia o mais próxima possível). Apenas um processo chama, fork()
mas dois processos retornam dessa chamada - parece bizarro, mas é realmente muito elegante
O novo processo (chamado de filho) obtém um ID de processo (PID) diferente e tem o PID do processo antigo (o pai) como seu PID pai (PPID).
Como os dois processos agora estão executando exatamente o mesmo código, eles precisam ser capazes de dizer qual é qual - o código de retorno fork()
fornece essa informação - o filho obtém 0, o pai obtém o PID do filho (se o fork()
falhar, não filho é criado e o pai recebe um código de erro).
Dessa forma, o pai conhece o PID do filho e pode se comunicar com ele, matá-lo, esperá-lo e assim por diante (a criança sempre pode encontrar seu processo pai com uma chamada para getppid()
).
A exec()
chamada substitui todo o conteúdo atual do processo por um novo programa. Ele carrega o programa no espaço do processo atual e o executa a partir do ponto de entrada.
Portanto, fork()
e exec()
são frequentemente usados em sequência para fazer um novo programa rodar como filho de um processo atual. Os shells normalmente fazem isso sempre que você tenta executar um programa como find
- o shell bifurca, então o filho carrega o find
programa na memória, configurando todos os argumentos da linha de comando, E / S padrão e assim por diante.
Mas eles não precisam ser usados juntos. É perfeitamente aceitável que um programa chame fork()
sem seguimento exec()
se, por exemplo, o programa contiver código pai e filho (você precisa ter cuidado com o que faz, cada implementação pode ter restrições).
Isso era muito usado (e ainda é) para daemons que simplesmente ouvem em uma porta TCP e bifurcam uma cópia de si mesmos para processar uma solicitação específica enquanto o pai volta a ouvir. Para essa situação, o programa contém o código pai e o filho.
Da mesma forma, os programas que sabem que foram concluídos e apenas desejam executar outro programa não precisam fazê-lo fork()
, exec()
e wait()/waitpid()
para a criança. Eles podem simplesmente carregar o filho diretamente em seu espaço de processo atual com exec()
.
Algumas implementações do UNIX têm um otimizado fork()
que usa o que eles chamam de cópia na gravação. Este é um truque para atrasar a cópia do espaço do processo fork()
até que o programa tente alterar algo nesse espaço. Isso é útil para aqueles programas que usam apenas fork()
e não exec()
porque não precisam copiar um espaço de processo inteiro. No Linux, fork()
apenas faz uma cópia das tabelas de páginas e uma nova estrutura de tarefas, exec()
fará o trabalho árduo de "separar" a memória dos dois processos.
Se o exec
for chamado em seguida fork
(e isso é o que acontece principalmente), isso causa uma gravação no espaço do processo e é então copiado para o processo filho, antes que as modificações sejam permitidas.
O Linux também possui um vfork()
, ainda mais otimizado, que compartilha quase tudo entre os dois processos. Por causa disso, existem certas restrições quanto ao que a criança pode fazer, e os pais param até que a criança chame exec()
ou _exit()
.
O pai deve ser interrompido (e o filho não tem permissão para retornar da função atual) uma vez que os dois processos compartilham a mesma pilha. Isso é um pouco mais eficiente para o caso de uso clássico de fork()
seguido imediatamente por exec()
.
Note-se que há uma família inteira de exec
chamadas ( execl
, execle
, execve
e assim por diante), mas exec
no contexto aqui significa qualquer um deles.
O diagrama a seguir ilustra a fork/exec
operação típica em que o bash
shell é usado para listar um diretório com o ls
comando:
+--------+
| pid=7 |
| ppid=4 |
| bash |
+--------+
|
| calls fork
V
+--------+ +--------+
| pid=7 | forks | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash | | bash |
+--------+ +--------+
| |
| waits for pid 22 | calls exec to run ls
| V
| +--------+
| | pid=22 |
| | ppid=7 |
| | ls |
V +--------+
+--------+ |
| pid=7 | | exits
| ppid=4 | <---------------+
| bash |
+--------+
|
| continues
V
exec
o utilitário é usado para redirecionar o IO do processo atual? Como o caso "nulo", executando exec sem um argumento, foi usado para essa convenção?
exec
no meio de substituir o programa atual (o shell) neste processo por outro, não especificar esse outro programa para substituí-lo pode simplesmente significar que você não deseja substituí-lo.
exec
ser chamado sem um programa. Mas é um pouco estranho neste cenário, já que a utilidade original de redirecionar para um novo programa - um programa que seria realmente exec
executado - desaparece e você tem um artefato útil, redirecionando o programa atual - que não está sendo exec
usado ou iniciado de qualquer forma - em vez disso.
As funções na família exec () têm comportamentos diferentes:
Você pode misturá-los, portanto, você tem:
Para todos eles, o argumento inicial é o nome de um arquivo a ser executado.
Para obter mais informações, leia a página de manual exec (3) :
man 3 exec # if you are running a UNIX system
execve()
em sua lista, que é definida por POSIX, e adicionou execvpe()
, que não é definida por POSIX (principalmente por razões de precedente histórico; ele completa o conjunto de funções). Caso contrário, uma explicação útil da convenção de nomenclatura para a família - um complemento útil para paxdiablo 'uma resposta que explica mais sobre o funcionamento das funções.
execvpe()
(et al) não lista execve()
; ele tem sua própria página de manual separada (pelo menos no Ubuntu 16.04 LTS) - a diferença é que as outras exec()
funções da família estão listadas na seção 3 (funções), enquanto que execve()
estão listadas na seção 2 (chamadas de sistema). Basicamente, todas as outras funções da família são implementadas em termos de uma chamada para execve()
.
A exec
família de funções faz com que seu processo execute um programa diferente, substituindo o antigo programa que estava rodando. Ou seja, se você ligar
execl("/bin/ls", "ls", NULL);
então o ls
programa é executado com o id do processo, diretório de trabalho atual e usuário / grupo (direitos de acesso) do processo que chamou execl
. Depois disso, o programa original não está mais funcionando.
Para iniciar um novo processo, a fork
chamada do sistema é usada. Para executar um programa sem substituir o original, você precisa fork
, então exec
.
qual é a função exec e sua família.
A exec
família função é todas as funções usadas para executar um arquivo, como execl
, execlp
, execle
, execv
, e execvp
.Eles são todos os frontends para execve
e fornecer diferentes métodos de chamá-lo.
porque esta função é usada
As funções Exec são usadas quando você deseja executar (lançar) um arquivo (programa).
e como isso funciona.
Eles funcionam substituindo a imagem do processo atual por aquela que você iniciou. Eles substituem (encerrando) o processo atualmente em execução (aquele que chamou o comando exec) pelo novo processo que foi iniciado.
Para mais detalhes: veja este link .
exec
é frequentemente usado em conjunto com fork
, pelo qual vi que você também perguntou, então discutirei isso com isso em mente.
exec
transforma o processo atual em outro programa. Se você já assistiu Doctor Who, então é como quando ele se regenera - seu antigo corpo é substituído por um novo corpo.
A maneira como isso acontece com o seu programa exec
é que muitos dos recursos que o kernel do sistema operacional verifica para ver se o arquivo que você está passando exec
como o argumento do programa (primeiro argumento) é executável pelo usuário atual (id do usuário do processo fazer a exec
chamada) e, em caso afirmativo, ele substitui o mapeamento de memória virtual do processo atual por uma memória virtual do novo processo e copia os dados argv
e envp
que foram passados na exec
chamada para uma área desse novo mapa de memória virtual. Várias outras coisas também podem acontecer aqui, mas os arquivos que foram abertos para o programa que chamou exec
ainda estarão abertos para o novo programa e eles compartilharão o mesmo ID de processo, mas o programa que chamou exec
irá parar (a menos que exec falhou).
O motivo pelo qual isso é feito dessa maneira é que, separando a execução de um novo programa em duas etapas como essa, você pode fazer algumas coisas entre as duas etapas. A coisa mais comum a fazer é certificar-se de que o novo programa possui determinados arquivos abertos como determinados descritores de arquivo. (lembre-se de que os descritores de arquivo não são iguais FILE *
, mas são int
valores que o kernel conhece). Fazendo isso, você pode:
int X = open("./output_file.txt", O_WRONLY);
pid_t fk = fork();
if (!fk) { /* in child */
dup2(X, 1); /* fd 1 is standard output,
so this makes standard out refer to the same file as X */
close(X);
/* I'm using execl here rather than exec because
it's easier to type the arguments. */
execl("/bin/echo", "/bin/echo", "hello world");
_exit(127); /* should not get here */
} else if (fk == -1) {
/* An error happened and you should do something about it. */
perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */
Isso realiza a execução de:
/bin/echo "hello world" > ./output_file.txt
a partir do shell de comando.
Quando um processo usa fork (), ele cria uma cópia duplicada de si mesmo e essa duplicata se torna filha do processo. O fork () é implementado usando uma chamada de sistema clone () no linux que retorna duas vezes do kernel.
Vamos entender isso com um exemplo:
pid = fork();
// Both child and parent will now start execution from here.
if(pid < 0) {
//child was not created successfully
return 1;
}
else if(pid == 0) {
// This is the child process
// Child process code goes here
}
else {
// Parent process code goes here
}
printf("This is code common to parent and child");
No exemplo, assumimos que exec () não é usado dentro do processo filho.
Mas um pai e um filho diferem em alguns dos atributos do PCB (bloco de controle do processo). Esses são:
Mas e quanto à memória infantil? Um novo espaço de endereço foi criado para uma criança?
As respostas em não. Após o fork (), pai e filho compartilham o espaço de endereço de memória do pai. No Linux, esse espaço de endereço é dividido em várias páginas. Somente quando a criança escreve em uma das páginas de memória dos pais, uma duplicata dessa página é criada para a criança. Isso também é conhecido como cópia na gravação (Copiar páginas pai apenas quando o filho gravar nelas).
Vamos entender o copy on write com um exemplo.
int x = 2;
pid = fork();
if(pid == 0) {
x = 10;
// child is changing the value of x or writing to a page
// One of the parent stack page will contain this local variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.
}
else {
x = 4;
}
Mas por que a cópia na gravação é necessária?
A criação de um processo típico ocorre por meio da combinação fork () - exec (). Vamos primeiro entender o que exec () faz.
O grupo de funções Exec () substitui o espaço de endereço da criança por um novo programa. Depois que exec () é chamado dentro de um filho, um espaço de endereço separado será criado para o filho, que é totalmente diferente do pai.
Se não houvesse cópia no mecanismo de gravação associado a fork (), páginas duplicadas teriam sido criadas para o filho e todos os dados teriam sido copiados para as páginas do filho. Alocar nova memória e copiar dados é um processo muito caro (leva tempo do processador e outros recursos do sistema). Também sabemos que, na maioria dos casos, a criança chamará exec () e isso substituirá a memória da criança por um novo programa. Portanto, a primeira cópia que fizemos teria sido um desperdício se a cópia com escrita não estivesse lá.
pid = fork();
if(pid == 0) {
execlp("/bin/ls","ls",NULL);
printf("will this line be printed"); // Think about it
// A new memory space will be created for the child and that memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
wait(NULL);
// parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.
Por que o pai espera por um processo filho?
Por que a chamada de sistema exec () é necessária?
Não é necessário usar exec () com fork (). Se o código que o filho irá executar estiver dentro do programa associado ao pai, exec () não é necessário.
Mas pense nos casos em que a criança precisa executar vários programas. Vejamos o exemplo do programa shell. Ele suporta vários comandos como find, mv, cp, date etc. Será correto incluir o código do programa associado a esses comandos em um programa ou fazer com que o filho carregue esses programas na memória quando necessário?
Tudo depende do seu caso de uso. Você tem um servidor web que fornece uma entrada x que retorna 2 ^ x aos clientes. Para cada solicitação, o servidor da web cria um novo filho e pede para ele computar. Você escreverá um programa separado para calcular isso e usar exec ()? Ou você apenas escreverá código de computação dentro do programa pai?
Normalmente, a criação de um processo envolve uma combinação de chamadas fork (), exec (), wait () e exit ().
As exec(3,3p)
funções substituem o processo atual por outro. Ou seja, o processo atual para e outro é executado, assumindo alguns dos recursos que o programa original tinha.