Reaprendendo o sistema operacional ---- 17 | Processos e threads: Onde está a sobrecarga de processos maior do que threads?

índice

 

Um, processo e thread

2. Alocação de recursos

Três, processo leve

Quatro, compartilhamento de tempo e agendamento

Cinco, alocação de fragmentos de tempo

Seis, o status de processos e threads

Sete, processo e design de rosca

7.1 Representação de processos e threads

7.2 Plano de isolamento

7.3 Troca de processo (thread)

7.4 Processamento multi-core

7.5 API para criação de processos (threads)

Oito o problema


Um, processo e thread

Processo, como o nome indica, é o aplicativo em execução, que é a cópia de execução do software. O segmento é um processo leve.

Processo é a unidade básica para alocar recursos. Por muito tempo, os threads são chamados de Light Weighted Process (Light Weighted Process), que é a unidade básica de execução do programa.

Na época em que os computadores acabavam de nascer, os programadores pegavam um cartão de memória flash com um programa escrito e o inseriam na máquina, e então a energia elétrica empurrava o chip para calcular. O chip lê uma instrução do cartão de memória flash todas as vezes, e então lê a próxima instrução após a execução. Depois que todas as instruções na memória flash são executadas, o computador é desligado.

 

Naquela época, esse modelo de tarefa única era chamado de Trabalho . Naquela época, os computadores eram projetados para processar vários trabalhos.

Após o surgimento da interface gráfica, as pessoas começaram a usar computadores para escritório, compras, bate-papo, jogos, etc. Portanto, o programa que estava sendo executado por uma máquina seria cortado para frente e para trás a qualquer momento. Então, as pessoas pensaram em projetar processos e threads para resolver esse problema.

Cada aplicativo, como um jogo, é um processo após a execução. No entanto, o jogo requer renderização de gráficos, rede e operações do usuário. Esses comportamentos não podem bloquear um ao outro e devem ser executados ao mesmo tempo, portanto, são projetados como threads .

2. Alocação de recursos

Para projetar processos e threads, o sistema operacional precisa pensar na alocação de recursos. Os três recursos mais importantes são:

  1. Recursos de computação (CPU)
  2. Recursos de memória
  3. Recurso de arquivo

No design do sistema operacional inicial, não havia threads. Todos os três tipos de recursos eram alocados para processos . Vários processos eram executados alternadamente por meio da tecnologia de compartilhamento de tempo e os processos comunicados por meio da tecnologia de pipeline .

Mas, ao fazer isso, os designers descobrem que os usuários (programadores) geralmente precisam abrir vários processos para um aplicativo, porque os aplicativos sempre têm muitas coisas que devem ser feitas em paralelo.

Paralelismo não significa simultaneidade absoluta , mas a necessidade de fazer com que essas coisas pareçam estar acontecendo ao mesmo tempo - como a renderização de gráficos e a resposta à entrada do usuário.

Então os projetistas pensaram que dentro do processo, uma espécie de unidade de execução do programa é necessária, e apenas recursos da CPU são alocados, que é o thread .

Três, processo leve

Depois que o encadeamento é projetado, ele é chamado de processo leve porque é apenas recursos de computação alocados (CPU). A maneira de ser alocado é agendar threads pelo sistema operacional. Depois que o sistema operacional cria um processo, o programa de entrada do processo é atribuído a uma thread principal para execução, então parece que o sistema operacional está agendando o processo, mas na verdade é uma thread no processo de agendamento.

Esse tipo de encadeamento que é agendado diretamente pelo sistema operacional, também nos tornamos um encadeamento no nível do kernel. Além disso, em algumas linguagens de programação ou aplicativos, os próprios usuários (programadores) também implementam threads. É equivalente ao escalonamento do sistema operacional da thread principal, e o programa da thread principal usa algoritmos para implementar sub-threads.Esta situação é chamada de threads no nível do usuário. A API PThread do Linux é uma thread no nível do usuário e a API KThread é uma thread no nível do kernel.

Quatro, compartilhamento de tempo e agendamento

Como geralmente o número de núcleos de CPU em uma máquina é pequeno (de alguns a dezenas) e o número de processos e threads é grande (de dezenas a centenas ou até mais), você pode compará-lo com menos motores e mais máquinas, portanto, os processos são O sistema operacional só pode ser enfileirado para execução um por um. Cada processo receberá um intervalo de tempo alocado pelo sistema operacional ao ser executado, caso esse tempo seja excedido, será o próximo processo (thread) a ser executado. Novamente, os sistemas operacionais modernos agendam threads diretamente, não processos.

Cinco, alocação de fragmentos de tempo

Conforme mostrado na figura abaixo, o processo 1 requer 2 fatias de tempo, o processo 2 tem apenas 1 fatia de tempo e o processo 3 requer 3 fatias de tempo. Portanto, quando o processo 1 é executado na metade, ele será suspenso primeiro e, em seguida, o processo 2 começará a ser executado; o processo 2 pode ser executado de uma vez e, em seguida, o processo 3 começará a ser executado, mas o processo 3 não pode ser executado em um Após a execução de 1 segmento de tempo, o Processo 1 começa a ser executado e se repete assim. Esta é a tecnologia de compartilhamento de tempo.

A imagem a seguir é mais intuitiva. O processo P1 primeiro executa um segmento de tempo, depois o processo P2 começa a executar um segmento de tempo, depois processa P3 e, em seguida, processa P4 ...

Observe que as duas imagens acima são demonstradas em unidades de processos. Se você mudar para threads, o sistema operacional ainda os tratará dessa maneira.

Seis, o status de processos e threads

Um processo (thread) passa pelos três estados a seguir:

Após a criação do processo (thread), ele começa a enfileirar, neste momento estará no estado "Pronto" (Pronto);

Quando for a vez do processo (thread) ser executado, ele se tornará "Running";

Quando um processo (thread) fica sem intervalos de tempo alocados pelo sistema operacional, ele retorna ao estado "Pronto".

Sempre usei processos (threads) aqui porque o sistema operacional antigo agenda processos sem threads; os sistemas operacionais modernos agendam threads.

Às vezes, um processo (thread) irá esperar que o disco leia os dados ou que a impressora responda.Neste momento, o próprio processo entrará no estado de "Bloco".

Como a resposta do computador não pode ser dada imediatamente neste momento, ele precisa esperar que o processamento do disco e da impressora seja concluído e notificar a CPU por meio de interrupções, e então a CPU executa um programa de controle de interrupção curto para transferir o controle para o O processo bloqueado (thread) é colocado no estado "Pronto" e colocado na fila novamente.

Além disso, uma vez que um processo (thread) entra no estado de bloqueio, o processo (thread) não tem nada a fazer neste momento, mas não pode ser re-enfileirado (porque precisa aguardar a interrupção), portanto, o processo (thread) precisa para adicionar um estado de "bloqueio" (Bloco)

Observe que como um processo (thread) que está "Pronto" ainda está enfileirado, o programa no processo (thread) não pode ser executado, ou seja, não acionará a operação de leitura de dados do disco. Neste momento, "Pronto" (Pronto) O estado não pode ser alterado para um estado bloqueado, portanto, não há nenhuma seta de pronto para bloqueado na figura abaixo.

E se o processo (thread) no estado "Bloco" receber os dados lidos do disco, ele precisa ser enfileirado novamente, para que não possa retornar diretamente ao estado "Em execução", portanto, não há seta do estado de bloqueio para o estado de execução.

Sete, processo e design de rosca

Em seguida, pensamos sobre várias restrições de design principais:

Como os processos e threads são representados na memória? Quais campos são obrigatórios?

Os processos representam aplicações e precisam ser isolados uns dos outros.Como projetar este esquema de isolamento?

O sistema operacional agenda threads e alterna constantemente entre elas. Como perceber essa situação?

Precisa oferecer suporte a um ambiente de núcleo multi-CPU, como projetar para esta situação?

7.1 Representação de processos e threads

Ele pode ser projetado assim: Duas tabelas são projetadas na memória, uma é a tabela de processos e a outra é a tabela de threads.

A tabela de processos registra o local de armazenamento do processo na memória, qual é o seu PID, qual é o seu estado atual, quanta memória está alocada, a qual usuário pertence, etc. Aqui está a tabela de processos. Sem essa tabela, o processo será perdido e o sistema operacional não sabe qual processo ele possui. Esta tabela pode ser considerada diretamente no kernel.

Para subdivisão, a tabela de processos precisa desses tipos de informações.

  • Informação descritiva: Esta parte é o número de identificação único que descreve o processo, ou seja, o PID, incluindo o nome do processo, o usuário ao qual pertence, e assim por diante.
  • Informações de recursos: esta parte é usada para registrar os recursos pertencentes ao processo, como a forma como o processo e a memória virtual são mapeados, quais arquivos pertencem, quais dispositivos de E / S estão em uso, etc. Claro, dispositivos de E / S também são arquivos.
  • Layout da memória: O sistema operacional também estipula como o processo usa a memória. Conforme mostrado na figura abaixo, ele descreve como um processo é aproximadamente dividido em várias áreas e para que cada área é usada. Chamamos cada área de segmento.

O sistema operacional também precisa de uma tabela para gerenciar threads, que é a tabela de threads. Thread também precisa de ID, que pode ser chamado ThreadID. Em seguida, o thread precisa registrar seu próprio status de execução (bloqueado, em execução, pronto), prioridade, contador de programa e os valores de todos os registradores e assim por diante. Os threads precisam registrar os valores dos contadores e registradores do programa porque vários threads precisam compartilhar a mesma CPU, e os threads frequentemente alternam para frente e para trás, portanto, os valores dos registradores e ponteiros do PC precisam ser armazenados na memória.

Há um relacionamento de mapeamento entre threads de nível de usuário e threads de nível de kernel, portanto, você pode considerar a manutenção de uma tabela de threads de nível de kernel no kernel, incluindo os campos mencionados acima.

Se você considerar esse relacionamento de mapeamento, como o mapeamento muitos-para-muitos de nm, as informações do encadeamento ainda podem ser armazenadas no processo e o encadeamento no nível do kernel é usado sempre que é executado. É equivalente a um pool de threads no kernel, aguardando o espaço do usuário para usar. Cada vez que uma thread no nível do usuário passa pelo contador do programa, etc., após o término da execução, a thread do kernel não é destruída e aguarda a próxima tarefa. Na verdade, existem muitas implementações flexíveis aqui. De modo geral, criar um processo é caro e caro; criar um thread tem uma pequena sobrecarga e um custo baixo.

7.2 Plano de isolamento

Um grande número de processos está sendo executado no sistema operacional. Para evitar que eles interfiram uns com os outros, você pode considerar a alocação de áreas de memória completamente isoladas umas das outras. Mesmo que o programa interno do processo leia o mesmo endereço, o endereço físico real não será o mesmo. É como se o edifício nº 10 808 no distrito A e o edifício nº 10 808 no distrito B não fossem um conjunto de casas. Este método é chamado de espaço de endereço.

Portanto, em circunstâncias normais, o processo A não pode acessar a memória do processo B, a menos que o processo A encontre uma vulnerabilidade em um sistema operacional e manipule maliciosamente a memória do processo B.

Para vários threads em um processo, você pode considerar o compartilhamento dos recursos de memória alocados pelo processo, para que os threads precisem apenas de recursos de execução alocados.

7.3 Troca de processo (thread)

Os processos (threads) são constantemente trocados no sistema operacional, e apenas a troca de threads nos sistemas operacionais modernos. Cada vez que você muda, você precisa salvar a memória do valor do registro atual.Note que o ponteiro do PC também é uma espécie de registro. Quando a execução é retomada, é necessário ler todos os registros da memória, restaurar o estado anterior e, em seguida, executar.

O conteúdo mencionado acima pode ser resumido nas 5 etapas a seguir:

  1. Quando o sistema operacional descobre que um processo (thread) precisa ser trocado, é muito perigoso controlar diretamente o salto do ponteiro do PC, então o sistema operacional precisa enviar um sinal de "interrupção" para a CPU para parar o processo de execução (thread )
  2. Quando a CPU recebe um sinal de interrupção, o processo de execução (thread) irá parar imediatamente. Observe que, como o processo (thread) é interrompido imediatamente, ele não tem tempo para salvar seu próprio estado, portanto, o sistema operacional subsequente deve concluir esta questão.
  3. Após o sistema operacional assumir a interrupção, enquanto os dados do registro não foram destruídos, uma pequena seção do programa de nível muito baixo (geralmente escrito em assembly) deve ser executada imediatamente para ajudar o registro a salvar o estado do processo anterior (thread )
  4. Depois que o sistema operacional salva o estado do processo, ele executa o escalonador e determina o próximo processo (thread) a ser executado.
  5. Finalmente, o sistema operacional executa o próximo processo (thread).

Obviamente, após um processo (thread) ser selecionado para execução, ele continuará a completar a tarefa quando foi interrompido. Isso requer que o sistema operacional execute uma pequena seção do programa subjacente para ajudar o processo (thread) a restaurar o estado .

Um algoritmo possível é por meio da estrutura de dados da pilha . Depois que o processo (thread) é interrompido, o sistema operacional é responsável por enviar os dados-chave (como registradores) na pilha. Ao restaurar a execução, o sistema operacional é responsável por sair da pilha e restaurar o valor do registro.

7.4 Processamento multi-core

Em um sistema multi-core, os princípios de design que mencionamos acima ainda são válidos, mas com mais poder, processos (threads) que podem ser executados em paralelo. Normalmente, se a CPU possui vários núcleos, ela pode executar vários processos (threads) em paralelo. Aqui, enfatizamos um conceito, geralmente dizemos simultaneidade, o inglês é simultâneo, o que significa que várias tarefas parecem ser executadas ao mesmo tempo dentro de um período de tempo (multi-core não é necessário); enquanto paralelo, o inglês é paralelo, tarefas deve ser executado absolutamente ao mesmo tempo (requer Multi-core).

Por exemplo, uma CPU de 4 núcleos parece ter 4 pipelines e pode executar 4 tarefas em paralelo. A execução de vários threads em um processo causará condições de corrida, que apresentaremos a você na seção "19 Aulas" de bloqueios e semáforos. Como o sistema operacional oferece a capacidade de salvar e restaurar o estado do processo, o processo (thread) também pode ser alternado entre vários núcleos.

7.5 API para criação de processos (threads)

A maneira mais direta de um usuário criar um processo é executar um programa a partir da linha de comando ou clicar duas vezes para abrir um aplicativo. Mas para os programadores, há claramente uma necessidade de um design melhor.

Do ponto de vista de um designer, você pode pensar assim: primeiro, deve haver uma API para abrir um aplicativo, por exemplo, um aplicativo pode ser aberto por meio de uma função; por outro lado, se o programador deseja executar um processo de inicialização, o programa atual O status de é copiado várias vezes e se torna um único processo de execução, então o sistema operacional fornece a instrução fork.

Ou seja, cada bifurcação criará um processo clonado, esse processo clonado, todos os estados são iguais ao processo original, mas terão seu próprio espaço de endereço. Se você deseja criar dois processos clone, você deve bifurcar duas vezes.

Você pode perguntar: E se eu apenas quiser iniciar um novo programa?

Eu disse acima: o sistema operacional fornece uma API para iniciar novos programas.

Você também pode perguntar: Se eu quiser apenas usar um novo processo para executar um pequeno programa, por exemplo, toda vez que o servidor receber uma solicitação do cliente, quero usar um processo para lidar com a solicitação.

Se for esse o caso, sugiro que você não inicie o processo separadamente, mas use threads. Como o custo de criação do processo é muito alto, não é recomendável fazer tais coisas: criar entradas, alocar memória, principalmente para formar um segmento na memória, dividido em diferentes áreas. Normalmente, tendemos a criar mais tópicos.

Diferentes linguagens de programação fornecem APIs para a criação de threads. Por exemplo, Java tem a classe Thread; go tem go-routine (observe que não é uma co-rotina, mas uma thread).

Oito o problema

Onde está a sobrecarga do processo maior do que a do thread?

Criar um processo no Linux irá criar naturalmente um thread, que é o thread principal. A criação de um processo requer a divisão de um espaço de memória completo para o processo, e há várias operações de inicialização, como segmentação da memória (pilha, área do corpo, etc.). Criar um thread é muito mais simples. Você só precisa determinar o valor do ponteiro e do registro do PC e atribuir uma pilha ao thread para executar o programa. Vários threads no mesmo processo podem reutilizar a pilha. Portanto, criar um processo é mais lento do que criar um thread e a sobrecarga de memória do processo é maior.

Forquilha()

Forquilha()

Forquilha()

print ("Olá, Mundo \ n")

Depois que este programa for executado, quantas vezes a saída Hello World será impressa?

A resposta pode ser obtida analisando cuidadosamente os procedimentos a seguir. 


#include <unistd.h>
#include <stdio.h> 
int main () 
{ 
	pid_t fpid; //fpid表示fork函数返回的值
	int count=0;
	fpid=fork(); 
	if (fpid < 0) 
		printf("error in fork!"); 
	else if (fpid == 0) {
		printf("i am the child process, my process id is %d/n",getpid()); 
		printf("我是爹的儿子/n");
		count++;
	}
	else {
		printf("i am the parent process, my process id is %d/n",getpid()); 
		printf("我是孩子他爹/n");
		count++;
	}
	printf("统计结果是: %d/n",count);
	return 0;
}

 Antes da instrução fpid = fork (), apenas um processo está executando este código, mas após esta instrução, ele se torna dois processos em execução. Os dois processos são quase idênticos, e a próxima instrução a ser executada Ambos são if (fpid <0) ......
    Por que os fpids dos dois processos são diferentes? Isso está relacionado às características da função fork. . Uma das coisas maravilhosas sobre chamada fork é que ele só é chamado uma vez, mas ele pode retornar duas vezes Ele pode ter três valores de retorno diferentes:
    1) No processo pai, fork retorna o ID do processo do processo filho recém-criado;
    2 ) No processo filho , fork retorna o ID do processo filho recém-criado. Durante o processo, fork retorna 0;
    3) Se ocorrer um erro, fork retorna um valor negativo;

    Depois que a função fork for executada, se o novo processo for criado com sucesso, dois processos aparecerão, um é o processo filho e o outro é o processo pai. No processo filho, a função fork retorna 0, e no processo pai, fork retorna o ID do processo filho recém-criado. Podemos usar o valor retornado por fork para determinar se o processo atual é um processo filho ou um processo pai.

    Citando um internauta para explicar por que o valor de fpid é diferente no processo pai-filho. "Na verdade, é equivalente a uma lista vinculada. O processo forma uma lista vinculada. O fpid (p significa ponto) do processo pai aponta para a id do processo filho. Porque o processo filho não tem um processo filho , seu fpid é 0.
    Pode haver dois motivos para erros de bifurcação:
    1) O número atual de processos atingiu o limite superior especificado pelo sistema e o valor de errno é definido como EAGAIN.
    2) A memória do sistema é insuficiente, e o valor de errno é definido como ENOMEM.
    Após a criação do novo processo com sucesso, dois aparecem no sistema. Basicamente o mesmo processo, não há uma ordem fixa para a execução desses dois processos. Qual processo é executado primeiro depende do sistema estratégia de escalonamento do processo.
    Cada processo tem um identificador de processo único (diferente) (ID do processo), que pode ser obtido através da função getpid (), e há uma variável que registra o pid do processo pai e o valor da variável pode ser obtido por meio da função getppid (). Depois que o
    fork é executado, dois processos aparecem,

 Algumas pessoas dizem que o conteúdo dos dois processos é exatamente o mesmo e como os resultados impressos são diferentes, devido às condições de julgamento. A lista acima contém apenas o código e as instruções do processo, além das variáveis.
    Depois de executar fork, a variável do processo 1 é count = 0, fpid! = 0 (processo pai). As variáveis ​​do processo 2 são count = 0 e fpid = 0 (processo filho) .As variáveis ​​desses dois processos são independentes e existem em endereços diferentes e não são compartilhadas. Preste atenção a isso. Pode-se dizer que usamos fpid para identificar e manipular processos pai-filho.
    Outros podem se perguntar por que o código não é copiado de #include. Isso ocorre porque fork copia a situação atual do processo. Quando o fork é executado, o processo foi executado. Int count = 0; fork apenas copia o próximo a ser Código executado para um novo processo.


#include <unistd.h>
#include <stdio.h>
int main(void)
{
   int i=0;
   printf("i son/pa ppid pid  fpid/n");
   //ppid指当前进程的父进程pid
   //pid指当前进程的pid,
   //fpid指fork返回给当前进程的值
   for(i=0;i<2;i++){
       pid_t fpid=fork();
       if(fpid==0)
    	   printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);
       else
    	   printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
   }
   return 0;
}

Referência: processo de compreensão da bifurcação https://blog.csdn.net/jason314/article/details/5640969

Acho que você gosta

Origin blog.csdn.net/MyySophia/article/details/114106985
Recomendado
Clasificación