Compartilhamento de tecnologia

Servidor reator de alta simultaneidade [médio]

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

4. Controle de processos e sincronização de processos

1. Sinal

1.1 Conceitos básicos de sinais

Sinal (sinal) é uma interrupção de software. É um método de transmissão de mensagens entre processos. É usado para notificar o processo de que ocorreu um evento, mas não pode transmitir nenhum dado ao processo.

Existem muitos motivos pelos quais os sinais são gerados no Shell, que você pode usar.killekillallComando para enviar sinal:

kill -信号的类型 进程编号
killall -信号的类型 进程名
  • 1
  • 2

1.2 Tipos de sinais

nome do sinalvalor do sinalAção de processamento padrãoMotivo da sinalização
SUSPIRO1AO terminal trava ou o processo de controle termina
ASSINATURA2AInterrupção de teclado Ctrl+c
SIGQUIT3CA tecla Escape do teclado é pressionada
SIGILO4CInstrução ilegal
ARMADILHA SIG5Cinstruções de ponto de interrupção
SIGABRTE6CSinal de aborto emitido por abort(3)
SIGBUS7Cerro de ônibus
SIGFPE8Cexceção de ponto flutuante
SIGMATAR9Akill -9 mata o processo, este sinal não pode ser capturado e ignorado
SIGUSR110ASinal definido pelo usuário 1
SIGSEGV11CReferência de memória inválida (matriz fora dos limites, operação de ponteiro nulo)
SIGUSR212ASinal definido pelo usuário 2
Tubo Sigpipe13AGravar dados em um pipe sem um processo de leitura
SIGALRM14ASinal de despertador, sinal enviado pela função alarm()
SIGTERM15ASinal de terminação, o sinal enviado por padrão
SIGSTKFLT16Aerro de pilha
ASSINATURA17BEmitido quando o processo filho termina
SIGCONT18ERetomar um processo interrompido
SIGSTOP19EParar processo
SIGTSTP20ETerminal pressione a tecla parar
SIGTTIN21ESolicitações de processo em segundo plano para ler o terminal
SIGTOU22EO processo em segundo plano solicita gravação no terminal
SIGURG23BDetecção de condição de emergência (soquetes)
CPU SIGX24CLimite de tempo da CPU excedido
SIGXFSZ25CLimite de tamanho de arquivo excedido
SIGVTALRM26Asinal de relógio virtual
PROFISSÃO SIG27AAnalise os sinais do relógio
Guincho de Sinalização28BMudanças no tamanho da janela
Pesquisa SigPoll29BPesquisa (Sys V)
SIGPWR30Afalha de eletricidade
SIGSYS31CChamada de sistema ilegal

A ação padrão de A é encerrar o processo.

A ação padrão de B é ignorar este sinal.

A ação padrão de C é encerrar o processo e fazer um dump da imagem do kernel.

A ação padrão de D é interromper o processo e o programa que entra no estado parado pode continuar a ser executado.

1.3 Processamento de sinal

Existem três maneiras de os processos lidarem com sinais:

  1. A operação padrão do sistema é usada para lidar com este sinal. A operação padrão da maioria dos sinais é encerrar o processo.
  2. Defina a função de processamento de interrupção Depois de receber o sinal, a função irá lidar com isso.
  3. Ignore um sinal e não faça nada com ele, como se nunca tivesse acontecido.

signal()As funções podem definir como o programa trata os sinais.

Declaração de função:

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • 1
  • 2
  • 3
  • 4

Descrição do parâmetro:

  • sig:Especifique o sinal a ser capturado.
  • func : Ponteiro para função de processamento de sinal. A função de processamento precisa receber um parâmetro inteiro, que é o número do sinal capturado.
  1. SIG_DFL A macro :SIG_DFL representa o método de processamento de sinal padrão.usarSIG_DFLcomosignalO segundo parâmetro da função indica que o método de processamento padrão do sistema é usado para o sinal.
  2. SIG_IGN A macro :SIG_IGN significa ignorar o sinal.usarSIG_IGNcomosignal O segundo parâmetro da função indica que quando o processo receber o sinal, ele irá ignorá-lo e não realizará nenhum processamento. Isso pode evitar que processos sejam encerrados ou interrompidos inesperadamente em determinadas circunstâncias.
  3. SIG_ERR:SIG_ERR Macros são usadas para indicar erros.não é tãosignalO segundo parâmetro da função é usado comosignal O valor de retorno da função indica que a chamada falhou.sesignalSe a chamada para a função falhar, ela retornaráSIG_ERR .Isso normalmente é usado para detectar e processarsignalErro na chamada de função.

imagem-20240709113614147

imagem-20240709113230874

imagem-20240709113240944

1.4 Qual é a utilidade dos sinais?

O programa de serviço é executado em segundo plano. Se você quiser interrompê-lo, matá-lo não é uma boa ideia, porque quando o processo é encerrado, ele morre repentinamente e nenhum trabalho posterior é organizado.

Se você enviar um sinal para o programa de serviço, após receber o sinal, o programa de serviço chamará uma função e escreverá o código posterior na função, e o programa poderá sair de maneira planejada.

Enviar um sinal 0 ao programa de serviço pode detectar se o programa está ativo.

imagem-20240709135336848

1.5 Envio de sinais

O sistema operacional Linux fornece kill ekillall O comando envia um sinal para o programa No programa, você pode usar.kill() As funções da biblioteca enviam sinais para outros processos.

Declaração de função:

int kill(pid_t pid, int sig);
  • 1

kill() A função recebe os parâmetrossig O sinal especificado é passado para o parâmetropid processo especificado.

parâmetro pid Existem várias situações:

  1. pid > 0 Passe o sinal para o processo comopid processo.
  2. pid = 0 Passe o sinal para todos os processos no mesmo grupo de processos do processo atual. Geralmente é usado pelo processo pai para enviar sinais ao processo filho.
  3. pid < -1 Passe o sinal para o ID do grupo de processos de|pid| de todos os processos.
  4. pid = -1 Passa o sinal para todos os processos que têm permissão para enviar o sinal, mas não para o processo que enviou o sinal.

2. Encerramento do processo

Existem 8 maneiras de encerrar um processo, sendo 5 delas encerrações normais, são elas:

  1. existir main() Para funçõesreturn retornar;
  2. Chamado em qualquer função exit() função;
  3. Chamado em qualquer função _exit() ou_Exit() função;
  4. O último thread inicia a partir de sua rotina de inicialização (função principal do thread) com return retornar;
  5. Chamado no último tópico pthread_exit() retornar;

Existem três maneiras de encerrar de forma anormal, são elas:

  1. transferir abort() abortar função;
  2. Um sinal é recebido;
  3. O último thread responde à solicitação de cancelamento.

2.1 Situação de encerramento do processo

existir main() Na função,return O valor retornado é o status de encerramento, caso contrárioreturn declaração ou chamadaexit(), então o status de encerramento do processo é 0.

No shell, visualize o status do encerramento do processo:

echo $?
  • 1

3 funções para encerrar o processo normalmente (exit() e_Exit() é especificado pela ISO C,_exit() é especificado pelo POSIX):

void exit(int status);
void _exit(int status);
void _Exit(int status);
  • 1
  • 2
  • 3

status O status de encerramento do processo.

imagem-20240709143530327

imagem-20240709143615950

2.2 Problema de liberação de recursos

  • return Indica que quando a função retornar, o destruidor do objeto local será chamado.main() em funçãoreturn O destruidor do objeto global também é chamado.
  • exit() Indica para encerrar o processo, o destruidor do objeto local não será chamado, apenas o destruidor do objeto global será chamado.
  • _exit() e_Exit() Saia diretamente e nenhum trabalho de limpeza será realizado.

2.3 Função de encerramento do processo

O processo está disponível atexit() O registro de função encerra funções (até 32), essas funções serãoexit() Chamado automaticamente.

int atexit(void (*function)(void));
  • 1

exit() A ordem em que as funções de encerramento são chamadas é invertida a partir do momento do registro.

imagem-20240709143824286

imagem-20240709143830549

3. Chame o programa executável

3.1 função sistema()

system()A função fornece um método simples para executar o programa, passando o programa e os parâmetros que precisam ser executados como uma string.system()Apenas funcione.

Declaração de função:

int system(const char * string);
  • 1

system()O valor de retorno da função é mais problemático.

  1. Se o programa executado não existir,system()A função retorna diferente de zero;
  2. Se a execução do programa for bem-sucedida e o status de execução do programa executado for 0,system()A função retorna 0;
  3. Se a execução do programa for bem-sucedida e o status de encerramento do programa executado não for 0,system()A função retorna diferente de zero.

3.2 família de funções exec

execAs famílias de funções fornecem outra maneira de chamar programas (binários ou scripts de shell) dentro de um processo.

execA família de funções é declarada da seguinte forma:

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Perceber

  1. Se a execução do programa falhar, -1 será retornado diretamente e o motivo da falha será armazenado emerrnomeio.
  2. O número do processo do novo processo é o mesmo do processo original, mas o novo processo substitui o segmento de código, o segmento de dados e a pilha do processo original.
  3. Se a execução for bem-sucedida, a função não retornará quando chamada com sucesso no programa principal.execDepois disso, o programa chamado substituirá o programa chamador, ou seja,execNenhum código após a função será executado.
  4. No desenvolvimento real, o mais comumente usado éexecl()eexecv(), outros raramente são usados.

4. Crie um processo

4.1 Processos Linux 0, 1 e 2

Todos os processos em todo o sistema Linux estão em uma estrutura em árvore.

  • **Processo nº 0 (processo do sistema)** é o ancestral de todos os processos. Ele criou os processos nº 1 e nº 2.
  • **Processo nº 1 (systemd)** é responsável por realizar a inicialização do kernel e configuração do sistema.
  • **Processo nº 2 (kthreadd)** é responsável pelo agendamento e gerenciamento de todos os threads do kernel.

usarpstreeVocê pode visualizar a árvore do processo com o comando:

pstree -p 进程编号
  • 1

4.2 Identificação do processo

Cada processo possui um ID de processo exclusivo representado por um número inteiro não negativo. Embora exclusivos, os IDs de processo podem ser reutilizados. Quando um processo termina, seu ID de processo torna-se candidato à reutilização. O Linux usa um algoritmo de reutilização atrasada para que o ID de um processo recém-criado seja diferente do ID usado pelo processo encerrado recentemente. Isso evita que novos processos sejam confundidos com um processo encerrado usando o mesmo ID.

Função para obter o ID do processo:

pid_t getpid(void);    // 获取当前进程的ID。
pid_t getppid(void);   // 获取父进程的ID。
  • 1
  • 2

4.3 função fork()

Um processo existente pode chamarfork()A função cria um novo processo.

Declaração de função:

pid_t fork(void);
  • 1

Depende defork()O novo processo criado é chamado de processo filho.

fork() A função é chamada uma vez, mas retorna duas vezes. A diferença entre os dois retornos é que o valor de retorno do processo filho é 0, enquanto o valor de retorno do processo pai é o ID do processo filho recém-criado.

O processo filho e o processo pai continuam em execuçãofork()O código depois disso, O processo filho é uma cópia do processo pai. O processo filho possui uma cópia do espaço de dados, heap e pilha do processo pai (nota: o processo filho possui uma cópia, não compartilhada com o processo pai).

fork()Depois disso, a ordem de execução dos processos pai e filho é indefinida.

imagem-20240709221535371

imagem-20240709221546617

4.4 Dois usos de fork()

  1. O processo pai deseja copiar a si mesmo e, em seguida, o processo pai e o processo filho executam códigos diferentes.Esse uso é muito comum em programas de serviço de rede. O processo pai aguarda a solicitação de conexão do cliente. Quando a solicitação chega, o processo pai chama.fork(), deixe o processo filho lidar com essas solicitações, enquanto o processo pai continua aguardando a próxima solicitação de conexão.
  2. O processo deseja executar outro programa.Este uso é muito comum em shells, o processo filho começa emfork()Chamado imediatamente após o retornoexec

4.5 Arquivos compartilhados

fork()Um recurso é que os descritores de arquivo abertos no processo pai serão copiados para o processo filho, e o processo pai e o processo filho compartilham o mesmo deslocamento de arquivo.

Se um processo pai e um processo filho gravarem em um arquivo apontado pelo mesmo descritor sem qualquer forma de sincronização, suas saídas poderão ser misturadas entre si.

imagem-20240709222929369

imagem-20240709222803641

Neste ponto você pode ver que existem apenas 100.000 linhas de dados.

imagem-20240709222853769

imagem-20240709223236254

Neste momento, deve haver 200.000 linhas de dados. Uma linha a menos pode ser porque a operação de gravação do arquivo não é atômica. Na ausência de um mecanismo de sincronização, dois processos podem tentar gravar partes diferentes do arquivo ao mesmo tempo. fazendo com que a gravação falhe.

4.6 função vfork()

vfork()Chamadas de função e valores de retorno são iguais afork()O mesmo, mas sua semântica é diferente.

vfork()A função é usada para criar um novo processo cujo objetivo éexecUm novo programa que não copia o espaço de endereço do processo pai porque o processo filho chama imediatamenteexec , portanto, o espaço de endereço do processo pai não será usado. Se o processo filho usar o espaço de endereço do processo pai, poderão ocorrer resultados desconhecidos.

vfork()efork()Outra diferença é:vfork()Certifique-se de que o processo filho seja executado primeiro e chame-o no processo filhoexecouexitEntão o processo pai retoma a operação.

5. Processo zumbi

No sistema operacional, um processo zumbi refere-se a um processo filho que foi encerrado, mas seu processo pai ainda não leu seu status de saída. Embora o processo zumbi não esteja mais em execução, ele ainda ocupa uma entrada na tabela de processos para que o kernel possa salvar as informações de status de saída do processo (como ID do processo, status de saída, etc.) até que o processo pai leia essas informações.

5.1 Causas de processos zumbis

Se o processo pai sair antes do processo filho, o processo filho será hospedado pelo processo 1 (essa também é uma forma de permitir que o processo seja executado em segundo plano).

Se o processo filho sair antes do processo pai e o processo pai não processar as informações de saída do processo filho, o processo filho se tornará um processo zumbi.

5.2 Os malefícios dos processos zumbis

O kernel retém uma estrutura de dados para cada processo filho, incluindo número do processo, status de encerramento, tempo de CPU usado, etc. Se o processo pai processar as informações de saída do processo filho, o kernel liberará essa estrutura de dados. Se o processo pai não processar as informações de saída do processo filho, o kernel não liberará essa estrutura de dados e o número do processo filho estará sempre ocupado. Os números de processos disponíveis no sistema são limitados. Se um grande número de processos zumbis for gerado, o sistema não será capaz de gerar novos processos porque não há números de processos disponíveis.

5.3 Métodos para evitar processos zumbis

  1. Manipulando o sinal SIGCHLD : Quando o processo filho termina, o kernel enviará o sinal SIGCHLD para o processo pai.Se o processo pai usarsignal(SIGCHLD, SIG_IGN)Notifique o kernel que ele não está interessado na saída do processo filho, e sua estrutura de dados será liberada imediatamente após a saída do processo filho.
  2. usarwait()/waitpid()função: O processo pai espera que o processo filho termine chamando essas funções e obtém seu status de saída, liberando assim os recursos ocupados pelo processo filho.
pid_t wait(int *stat_loc); 
pid_t waitpid(pid_t pid, int *stat_loc, int options); 
pid_t wait3(int *status, int options, struct rusage *rusage); 
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
  • 1
  • 2
  • 3
  • 4

O valor de retorno é o número do processo filho.

stat_loc A informação sobre o encerramento do processo filho é:

a) Se encerrado normalmente, a macro WIFEXITED(stat_loc) Retornar verdadeiro, macroWEXITSTATUS(stat_loc) O status de rescisão pode ser obtido;

b) Se terminar de forma anormal, a macro WTERMSIG(stat_loc) Obtém o sinal para encerrar o processo.

imagem-20240709230911352

imagem-20240709231034423

imagem-20240709231050581

imagem-20240709231124375

imagem-20240709231140813

Se o processo pai estiver ocupado, você poderá capturar SIGCHLD Sinal, chamado na função de processamento de sinalwait()/waitpid()

imagem-20240709231439475

imagem-20240709231422927

6. Vários processos e sinais

[Enviar sinais entre processos](##1.5 Enviar sinais)

Em um programa de serviço multiprocesso, se o subprocesso receber um sinal de saída, o subprocesso sairá sozinho.

Se o processo pai receber um sinal de saída, ele deverá enviar sinais de saída para todos os processos filhos e então sair.

imagem-20240711222919564

imagem-20240711222900141

imagem-20240711223111481

7. Memória compartilhada

Multi-threads compartilham o espaço de endereço de um processo,Se vários threads precisarem acessar a mesma memória, basta usar variáveis ​​globais.

Em múltiplos processos, o espaço de endereço de cada processo é independente e não compartilhado.Se vários processos precisarem acessar a mesma memória, variáveis ​​globais não poderão ser usadas, apenas memória compartilhada poderá ser usada.

A memória compartilhada permite que vários processos (nenhuma relação sanguínea entre processos é necessária) acessem o mesmo espaço de memória. É a maneira mais eficaz de compartilhar e transferir dados entre vários processos. Os processos podem conectar a memória compartilhada ao seu próprio espaço de endereço. Se um processo modificar os dados na memória compartilhada, os dados lidos por outros processos também serão alterados.

A memória compartilhada não fornece um mecanismo de bloqueio, ou seja, quando um processo lê/grava na memória compartilhada, ele não impede que outros processos a leiam/escrevam.Se você deseja bloquear a leitura/gravação da memória compartilhada, você pode usar um semáforo . O Linux fornece um conjunto de funções para operar memória compartilhada.

7.1 função shmget

Esta função é usada para criar/adquirir memória compartilhada.

 int shmget(key_t key, size_t size, int shmflg);
  • 1
  • chave O valor chave da memória compartilhada é um número inteiro (typedef unsigned int key_t), geralmente em hexadecimal, por exemplo 0x5005, as chaves das diferentes memórias compartilhadas não podem ser iguais.
  • tamanho Tamanho da memória compartilhada, em bytes.
  • embaralhar As permissões de acesso para memória compartilhada são iguais às permissões para arquivos, por exemplo0666|IPC_CREAT Indica que se a memória compartilhada não existir, crie-a.
  • valor de retorno: Retorna o id da memória compartilhada (um número inteiro maior que 0) em caso de sucesso, -1 em caso de falha (o sistema tem memória insuficiente e sem permissões).

imagem-20240711224223200

imagem-20240711224212293

usar ipcs -m Você pode visualizar a memória compartilhada do sistema, incluindo: chave, ID de memória compartilhada (shmid), proprietário, permissões (perms) e tamanho (bytes).

usar ipcrm -m 共享内存id A memória compartilhada pode ser excluída manualmente da seguinte forma:

imagem-20240711225202860

Nota: Os contêineres não podem ser usados ​​para tipos de dados em memória compartilhada, apenas tipos de dados básicos podem ser usados.

7.2 função shmat

Esta função é usada para conectar a memória compartilhada ao espaço de endereço do processo atual.

void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 1
  • coisa ruim Depende deshmget() O identificador de memória compartilhada retornado pela função.
  • shmaddr Especifique o local do endereço onde a memória compartilhada está conectada ao processo atual. Geralmente preencha 0 para permitir que o sistema selecione o endereço da memória compartilhada.
  • embaralhar Bit de sinalização, geralmente preenchido com 0.

Retorna o endereço inicial da memória compartilhada quando a chamada é bem-sucedida e retorna quando falha. (void *)-1

7.3 função shmdt

Esta função é usada para separar a memória compartilhada do processo atual, o que equivale a shmat() A operação inversa de uma função.

int shmdt(const void *shmaddr);
  • 1
  • shmaddr shmat() O endereço retornado pela função.

Retorna 0 se a chamada for bem-sucedida e -1 se falhar.

7.4 função shmctl

Esta função é usada para operar a memória compartilhada. A operação mais comumente usada é excluir a memória compartilhada.

int shmctl(int shmid, int command, struct shmid_ds *buf);
  • 1
  • coisa ruim shmget() O ID da memória compartilhada retornado pela função.
  • comando Instruções para operar a memória compartilhada Se desejar excluir a memória compartilhada, preencha.IPC_RMID
  • buf O endereço da estrutura de dados que opera a memória compartilhada. Se desejar excluir a memória compartilhada, preencha 0.

Retorna 0 se a chamada for bem-sucedida e -1 se falhar.

Observe, use root A memória compartilhada criada não pode ser excluída por usuários comuns, independentemente das permissões criadas.

imagem-20240711230653886

imagem-20240711230522921

7.5 Fila Circular

7.6 Fila circular baseada em memória compartilhada

Retorna 0 se a chamada for bem-sucedida e -1 se falhar.

7.4 função shmctl

Esta função é usada para operar a memória compartilhada. A operação mais comumente usada é excluir a memória compartilhada.

int shmctl(int shmid, int command, struct shmid_ds *buf);
  • 1
  • coisa ruim shmget() O ID da memória compartilhada retornado pela função.
  • comando Instruções para operar a memória compartilhada Se desejar excluir a memória compartilhada, preencha.IPC_RMID
  • buf O endereço da estrutura de dados que opera a memória compartilhada. Se desejar excluir a memória compartilhada, preencha 0.

Retorna 0 se a chamada for bem-sucedida e -1 se falhar.

Observe, use root A memória compartilhada criada não pode ser excluída por usuários comuns, independentemente das permissões criadas.

[As imagens do link externo estão sendo transferidas...(img-v6qW3XRA-1720711279572)]

[As imagens do link externo estão sendo transferidas...(img-CG0tGAne-1720711279572)]A transferência da imagem do link externo falhou. O site de origem pode ter um mecanismo anti-leeching. Recomenda-se salvar a imagem e carregá-la diretamente.
A transferência da imagem do link externo falhou. O site de origem pode ter um mecanismo anti-leeching. Recomenda-se salvar a imagem e carregá-la diretamente.
A transferência da imagem do link externo falhou. O site de origem pode ter um mecanismo anti-leeching. Recomenda-se salvar a imagem e carregá-la diretamente.

7.5 Fila Circular

7.6 Fila circular baseada em memória compartilhada