Princípio do Redis - explicação detalhada do IO

O endereço do texto original é atualizado e o efeito de leitura é melhor!

Princípio Redis - explicação detalhada de IO | mastro de programação CoderMast icon-default.png?t=N5K3https://www.codermast.com/database/redis/redis-IO.html

Espaço do usuário e espaço do kernel

A versão de distribuição de qualquer sistema Linux, seu kernel do sistema é o Linux. Todos os nossos aplicativos precisam interagir com o hardware por meio do kernel do Linux.

 

Para evitar que os aplicativos do usuário causem conflitos ou até pânico no kernel, os aplicativos do usuário são separados do kernel:

  • O espaço de endereçamento de memória é dividido em duas partes: espaço do kernel e espaço do usuário

Para um sistema operacional de 32 bits, o endereço de endereçamento é 0 ~ 2322 ^ {32}232

  • Somente instruções restritas (Ring3) podem ser executadas no espaço do usuário, e os recursos do sistema não podem ser chamados diretamente, devendo ser acessados ​​através da interface fornecida pelo kernel
  • O espaço do kernel pode executar comandos privilegiados (Ring0) e chamar todos os recursos do sistema

Quando um processo é executado no espaço do usuário, ele é chamado de modo de usuário e, quando é executado no espaço do kernel, é chamado de modo do kernel.

Para melhorar a eficiência de IO, o sistema Linux adicionará buffers no espaço do usuário e no espaço do kernel:

  • Ao gravar dados, os dados do buffer do usuário devem ser copiados para o buffer do kernel e, em seguida, gravados no dispositivo
  • Ler dados é ler dados do dispositivo para o buffer do kernel e, em seguida, copiá-los para o buffer do usuário

 

5 modelos IO

  1. Bloqueando IO (bloqueando IO)
  2. Non-blocking IO (Nonblocking IO)
  3. Multiplexação IO (Multiplexação IO)
  4. E/S acionada por sinal (E/S acionada por sinal)
  5. E/S assíncrona (E/S assíncrona)

#blocking IO

Como o nome indica, bloquear IO significa que ele deve bloquear e aguardar durante os dois estágios de espera por dados e cópia de dados para o espaço do usuário .

 

  1. Threads de usuário emitem solicitações de E/S
  2. O kernel verificará se os dados estão prontos, caso contrário, aguardará para sempre e o thread do usuário estará em um estado bloqueado e o thread do usuário estará em um estado bloqueado
  3. Quando os dados estiverem prontos, o kernel copiará os dados para o thread do usuário e retornará o resultado para o thread do usuário, e o thread do usuário desbloqueará o estado

Pode-se observar que no modelo IO de bloqueio, o processo do usuário é bloqueado em dois estágios.

# IO sem bloqueio

A operação recvfrom de IO sem bloqueio retorna o resultado imediatamente, em vez de bloquear o processo do usuário.

 

  1. Aguardando o estágio de dados, se os dados não estiverem prontos, retorne EWOULDBLOCK imediatamente. Nesse processo, o processo do usuário não bloqueia, mas o processo do usuário sempre iniciará solicitações e estará ocupado com o treinamento de rotação até que o processamento do kernel comece a interromper o treinamento de rotação.
  2. Depois que os dados estiverem prontos, os dados são copiados do kernel para o espaço do usuário. O processo do usuário é bloqueado durante esta fase.

Pode-se observar que no modelo IO sem bloqueio, o processo do usuário é não bloqueador na primeira fase e bloqueado na segunda fase. Embora seja sem bloqueio, o desempenho não foi aprimorado e o mecanismo de espera ocupada fará com que a CPU fique ociosa e o uso da CPU aumentará acentuadamente.

# multiplexação IO

Seja IO bloqueante ou IO não bloqueante, o aplicativo do usuário precisa chamar recvfrom para obter dados no primeiro estágio. A diferença está no método de processamento quando não há dados:

  • Se não houver dados quando recvfrom for chamado, o bloqueio de IO fará com que o processo seja bloqueado e o IO sem bloqueio fará com que a CPU fique ociosa, o que não pode desempenhar o papel da CPU.
  • Se houver dados quando recvfrom for chamado, o processo do usuário pode entrar diretamente no segundo estágio para ler e processar os dados

Por exemplo, quando o servidor processa solicitações de Socket do cliente, no caso de um único thread, ele só pode processar um Socket por vez. Se o soquete que está sendo processado não estiver pronto (os dados não podem ser lidos ou gravados), o thread será bloqueado e todos os outros soquetes do cliente devem esperar e o desempenho é naturalmente ruim.

Descritor de Arquivo (File Descriptor): conhecido como FD, é um inteiro não assinado incrementado a partir de 0, usado para associar um arquivo no Linux. Tudo no Linux é um arquivo, como arquivos comuns, vídeos, dispositivos de hardware, etc., claro, incluindo soquetes de rede (Socket)

Multiplexação de IO: usa um único thread para monitorar vários FDs ao mesmo tempo e é notificado quando um determinado FD é legível ou gravável, para evitar espera inválida e fazer uso total dos recursos da CPU.

 

Existem três maneiras de implementar a tecnologia de multiplexação IO:

  • selecionar
  • enquete
  • epoll

diferença:

  • select e poll apenas notificarão o processo do usuário que o FD está pronto, mas não tem certeza de qual é o FD, e o processo do usuário precisa percorrer o FD um por um para confirmar
  • O epoll notificará o processo do usuário de que o FD está pronto e, ao mesmo tempo, gravará o FD pronto no espaço do usuário e localizará diretamente o FD pronto

# SELECIONE

select é a implementação mais antiga de multiplexação de E/S no Linux:

// 定义类型别名 __fd_mask,本质是 long int
typedef long int __fd_mask;

/* fd_set 记录要监听的fd集合,及其对应状态 */
typedef struct {
    // fds_bits是long类型数组,长度为 1024/32 = 32
    // 共1024个bit位,每个bit位代表一个fd,0代表未就绪,1代表就绪
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
    // ...
} fd_set;


// select函数,用于监听多个fd的集合
int select(
    int nfds,// 要监视的fd_set的最大fd + 1
    fd_set *readfds,// 要监听读事件的fd集合
    fd_set *writefds,// 要监听写事件的fd集合
    fd_set *exceptfds,  // 要监听异常事件的fd集合
    // 超时时间,nulT-永不超时;0-不阻塞等待;大于0-固定等待时间
    struct timeval *timeout
);

 

O processo específico é o seguinte:

  1. Crie fd_set rfds no espaço do usuário
  2. Se você deseja monitorar fd = 1, 2, 5
  3. Execute select(5 + 1, rfds, null, null, 3) no espaço do usuário
  4. Copie o array fd_set rfds criado no espaço do usuário para o espaço do kernel
  5. Atravessando o array fd_set rfds copiado no espaço do kernel
  6. Se não estiver pronto, defina o fd nesse local como 0.

Problemas com o modo de seleção:

  • É necessário copiar todo o fd_set do espaço do usuário para o espaço do kernel e copiá-lo novamente para o espaço do usuário após selecionar
  • Select não pode saber qual fd está pronto, ele precisa percorrer fd_set
  • O número de fds monitorados pelo fd_set não pode exceder 1024,

# ENQUETE

O modo de pesquisa fez uma melhoria simples no modo de seleção, mas a melhoria de desempenho não é óbvia. Alguns códigos de chave são os seguintes:

// pollfd 中的事件类型
#define POLLIN      //可读事件
#define POLLOUT     //可写事件
#define POLLERR     //错误事件
#define POLLNVAL    //fd未打开

// pollfd结构
struct pollfd{
    int fd;             // 要监听的 fd
    *short int events;  // 要监听的事件类型:读、写、异常
    short int revents;  // 实际发生的事件类型
}

// poll函数
int poll(
    struct pollfd xfds, // pollfd数组,可以自定义大小
    nfds_t nfds,        // 数组元素个数
    int timeout         // 超时时间
);

Processo de E/S:

  1. Crie uma matriz pollfd, adicione as informações fd em questão a ela e personalize o tamanho da matriz
  2. Chame a função poll, copie o array pollfd para o espaço do kernel, transfira para a lista encadeada para armazenamento, sem limite superior
  3. O kernel percorre fd para determinar se está pronto
  4. Depois que os dados estiverem prontos ou expirarem, copie o array pollfd para o espaço do usuário e retorne o número fd pronto n
  5. O processo do usuário julga se n é maior que 0
  6. Se for maior que 0, percorra o array pollfd e encontre o fd pronto

Compare com SELECT:

  • O valor fixo de fd_set no modo select é 1024, enquanto pollfd usa uma lista encadeada no kernel, que é teoricamente infinita
  • Quanto mais FDs você ouvir, mais tempo levará cada travessia e, em vez disso, o desempenho diminuirá.

# EPOLL

O modo epoll é uma melhoria dos modos select e poll, fornecendo três funções:

struct eventpoll{
    //...
    struct rb_root rbr; // 一颗红黑树,记录要监听的fd
    struct list_head rdlist;  // 一个链表,记录就绪的 FD
    //...
}

// 1.会在内核创建eventpolL结构体,返回对应的句柄epfd
int epoll create(int size);

// 2.将一个FD添加到epol的红黑树中,并设置ep_poli_calLback
// calTback触发时,就把对应的FD加入到rdlist这个就绪列表中
int epoll _ctl(
    int epfd,   // epoll实例的句柄
    int op,     // 要执行的操作,包括:ADD、MOD、DEL
    int fd,     // 要监听的 FD
    struct epoll_event *event // 要监听的事件类型: 读、写、异常等
);

// 3.检查rdlist列表是否为空,不为空则返回就绪的FD的数量
int epoll wait(
    int epfd,       // eventpoll 实例的句柄
    struct epoll_event *events, // 空event 数组,用于接收就绪的 FD
    int maxevents,  // events 数组的最大长度
    int timeout // 超时时间,-1永不超时;0不阻塞;大于0为阻塞时间
);

 

#mecanismo de notificação de eventos

Quando o FD tem dados para ler, podemos chamar epoll_wait para ser notificado, mas existem dois modos de notificação de tempo:

  • Nível acionado: LT para abreviar. Quando o FD tiver dados para ler, ele repetirá a notificação várias vezes até que o processamento dos dados seja concluído. É o modo padrão do epoll.
  • Edge Triggered: ET para abreviar. Quando o FD tiver dados para ler, será notificado apenas uma vez, independentemente de os dados terem sido processados ​​ou não

por exemplo

  1. Suponha que o FD correspondente a um Socket cliente tenha sido registrado na instância epoll
  2. Client Socket enviou 2kb de dados
  3. O servidor chama epoll_wait e é notificado de que o FD está pronto
  4. O servidor lê 1kb de dados do FD
  5. Volte para a etapa 3 (chame epoll_wait novamente para formar um loop)

para concluir

  • O modo ET evita o fenômeno de grupo chocante que pode ocorrer no modo LT
  • O modo ET é melhor combinado com IO sem bloqueio para ler dados FD, que são mais complicados que LT

# processo de serviço WEB

Fluxograma básico do serviço web baseado no modo epoll: 

#Resumo_ _

Existem três problemas com o modo de seleção:

  • O número máximo de FDs que podem ser monitorados não excede 1024
  • Toda vez que você selecionar, você precisa copiar todos os FDs a serem monitorados para o espaço do kernel
  • Sempre que for necessário percorrer todos os DFs para determinar o estado pronto

Problemas com o modo de enquete:

  • poll usa a lista vinculada para resolver o problema de monitorar o limite superior do FD no select, mas ainda precisa percorrer todos os FDs. Se houver mais monitores, o desempenho cairá

Como resolver esses problemas no modo epoll:

  • Com base na árvore rubro-negra na instância epoll para salvar os FDs a serem monitorados, não há limite superior em teoria, e a eficiência de adicionar, excluir, modificar e verificar é muito alta, e o desempenho não diminuirá significativamente à medida que o número de FDs monitorados aumenta
  • Cada FD só precisa executar epoll_ctl uma vez para adicionar à árvore rubro-negra, e não há necessidade de passar nenhum parâmetro para cada epoll_wait no futuro, e não há necessidade de copiar repetidamente FD para o espaço do kernel
  • O kernel copiará diretamente o FD pronto para o local especificado no espaço do usuário, e o processo do usuário pode saber quem é o FD pronto sem passar por todos os FDs

# IO acionada por sinal

IO orientada por sinal é estabelecer uma associação de sinal SIGIO com o kernel e definir um retorno de chamada. Quando o kernel tiver um FD pronto, ele enviará um sinal SIGIO para notificar o usuário. Durante esse período, o aplicativo do usuário pode executar outros serviços sem bloqueio e espera.

 

Quando há um grande número de operações IO, há muitos sinais e a função de processamento SIGIO não pode lidar com isso a tempo, o que pode causar o estouro da fila de sinais.

Além disso, o desempenho da interação de sinal frequente entre o espaço do kernel e o espaço do usuário também é baixo.

#E/S assíncrona

Todo o processo de IO assíncrono é sem bloqueio. Depois que o processo do usuário chama a API assíncrona, ele pode fazer outras coisas. O kernel espera que os dados estejam prontos e os copia para o espaço do usuário antes de enviar o sinal para notificar o processo do usuário.

 

No modelo de IO assíncrono, o processo do usuário não bloqueia em ambas as fases.

Embora o modelo de IO assíncrono seja muito simples, sob alto acesso simultâneo, um grande número de solicitações será processado no kernel, o que pode facilmente levar a uma falha do kernel.

# síncrono e assíncrono

Se a operação IO é síncrona ou assíncrona depende do processo de cópia de dados entre o espaço do kernel e o espaço do usuário (operação IO de leitura e gravação de dados), ou seja, se a segunda fase é síncrona ou assíncrona:

Acho que você gosta

Origin blog.csdn.net/qq_33685334/article/details/131324401
Recomendado
Clasificación