Introdução ao Linux --- Memória Compartilhada

O princípio da memória compartilhada

Pelo conteúdo anterior, sabemos que diferentes processos podem mapear seus dados para diferentes locais da memória por meio de espaço de endereço virtual e tabelas de páginas, como na imagem a seguir:
Insira a descrição da imagem aqui

Cada processo vai pensar que ocupa toda a memória através da tabela de páginas, mas na verdade esta tabela de páginas é apenas uma inércia de mapeamento, que mapeia os dados de diferentes processos para diferentes locais da memória, para que a independência do processo possa ser garantido até certo ponto. Pelo exposto, sabemos que quando um processo é criado, o sistema operacional solicitará um espaço na memória e, em seguida, mapeará o espaço para o endereço virtual do processo por meio da tabela de páginas. Então há um problema. Ao criar um processo, o sistema operacional pode solicitar um espaço na memória. Se um pedaço de espaço for mapeado, o sistema operacional também pode solicitar um pedaço de espaço na memória e mapeá-lo em outros momentos? A resposta é sim, então o sistema operacional pode solicitar um espaço na memória e mapeá-lo para diferentes processos por meio da tabela de páginas e do espaço de endereço do processo, de modo que diferentes processos possam ver a mesma área na memória por meio da tabela de páginas. então, um processo pode gravar dados nesta área e outro processo pode ler dados para esse processo. Então chamamos esse método de comunicação de memória compartilhada, que pode ser alcançada vendo o mesmo espaço. Comunicação. Quando os processos não querem mais se comunicar
no No futuro, eles podem cancelar o relacionamento de mapeamento entre o processo e a memória (chamado de desassociação) e então liberar a memória (chamado de liberação de memória compartilhada). Em seguida, chamamos a forma como o sistema operacional se aplica a um pedaço de espaço. Para memória compartilhada , o processo de mapeamento da memória compartilhada e do espaço de endereço do processo é chamado de montagem.

Compreendendo a memória compartilhada

Em estudos anteriores, sabemos que malloc também pode permitir que programas solicitem espaço na memória. Podemos usar malloc para implementar memória compartilhada e conseguir comunicação? A resposta é não. Embora malloc possa solicitar espaço na memória física, ele também pode estabelecer uma conexão entre a memória física e o espaço de endereço do processo. No entanto, o espaço obtido pelo mlloc de um processo não pode ser visto por outro processo. Essa memória só pode ser aplicado. Usado por processos, então malloc não pode implementar memória compartilhada, então a comunicação entre processos implementada pela memória compartilhada aqui foi especialmente projetada por programadores posteriores. Há um conjunto separado de métodos e sistemas de uso. Em segundo lugar, a memória compartilhada é um forma de comunicação.Todos Qualquer processo que queira se comunicar pode utilizá-lo. Portanto, deve haver muita memória compartilhada no sistema operacional ao mesmo tempo. Por fim, vejamos o conceito de memória compartilhada: o método de comunicação que permite que diferentes processos vejam a mesma memória é chamado de memória compartilhada. Então vamos dar uma olhada nas funções relacionadas à memória compartilhada e seu uso correspondente.

função shmget

A declaração desta função é a seguinte: int shmget(key_t key,size_t size,int shmflg)A função geral desta função é criar memória compartilhada. A função tem três parâmetros: O parâmetro shmflg é usado para receber os bits de flag e flag usados ​​pela função. Existem dois bits de flag:
Insira a descrição da imagem aqui
IPC_CREAT representa a memória compartilhada criada se Se não existir, crie a memória compartilhada. Se a memória compartilhada existir, obtenha a memória compartilhada, ou seja, obtenha o endereço da memória compartilhada. O bit de flag IPC_EXCL não pode ser usado sozinho e deve ser usado em conjunto com IPC_CREAT, o que significa que se não existir, crie a memória compartilhada. Se esta função existir, ela reportará diretamente um erro e retornará, portanto, para usuários que usam este bit de flag, se a criação for bem-sucedida, então esta memória deve ser uma nova memória compartilhada que acabou de ser criada. O parâmetro size é usado para representar o tamanho da memória compartilhada. Você pode passar para este parâmetro tantos dados quantos desejar para aplicar a um pedaço de espaço para comunicação. Valor de retorno: Se a criação for bem-sucedida, o identificador do compartilhado a memória será retornada. Normalmente usamos este identificador O identificador é chamado shmid e retorna -1 se a criação falhar. Este identificador é essencialmente um subscrito de array, mas o valor deste subscrito é diferente em diferentes sistemas operacionais. Alguns sistemas operacionais começam em 0 1 2 e alguns O sistema operacional começa com dezenas de milhares. Embora seja um subscrito de array, não tem nada a ver com o subscrito do sistema de arquivos. No futuro, quando quisermos operar a memória compartilhada criada, podemos usar o identificador shmid para operar na memória compartilhada. Basta operar. A função do parâmetro key é expressar exclusividade. O valor da chave não é importante. O importante é que ela possa ser identificada exclusivamente. O valor da chave pode ser gerado usando a função ftok. Vamos dar uma olhada na declaração e função da função ftok:
Insira a descrição da imagem aqui
ftok requer um parâmetro de string e um parâmetro inteiro e, em seguida, alguns algoritmos podem ser usados ​​​​internamente para gerar um valor de chave exclusivo. Em seguida, os dois processos podem acessar a mesma memória compartilhada com base no mesmo valor de chave. Para obter o mesmo, basta passar o mesmo parâmetros para ftok para a chave. A função ftok pode ser executada com êxito ou falhar. Se for bem-sucedida, a função ftok retornará o valor da chave. Se a criação falhar, a função retornará -1 e o código de erro correspondente.

A diferença entre chave e shmid

Ao usar malloc para solicitar espaço na linguagem C, você solicitará um espaço extra. O espaço extra será usado para manter o espaço que você acabou de solicitar. O mesmo se aplica à memória compartilhada. Ao solicitar espaço, a memória compartilhada também precisa abrir um espaço para gerenciamento. O espaço solicitado, então memória compartilhada = bloco de memória física + atributos relacionados à memória compartilhada, cada processo pode solicitar memória compartilhada como o sistema operacional, e há vários processos no sistema operacional, então é provável que haja várias memórias compartilhadas no sistema operacional.Quando o processo seleciona a memória compartilhada para leitura ou gravação, como ele garante que a memória compartilhada seja aquela que especificamos? A resposta é garantir a exclusividade da memória compartilhada por meio do valor da chave. Então há um problema aqui. Dizemos que a função shmget retornará um valor. Chamamos esse valor de shmid. Podemos usar isso quando quisermos operar em memória compartilhada. memória no futuro. shmid para operar, ou seja, shmid e valor-chave têm a mesma função e podem identificar memória compartilhada, então por que criar duas coisas? Não basta usar apenas um para identificação? Então, aqui posso dar um exemplo da vida cotidiana para explicar. A China tem uma população de 1,4 bilhão. Todos terão um número de identificação único após o nascimento. Através desse número de identificação, podemos identificar uma pessoa. Então, por que dar um nome a todos? Não basta usar apenas o número de identificação em vez do nome? Certo! Podemos pensar novamente: todo mundo tem um número de identificação exclusivo, então por que os alunos precisam de uma carteira de estudante e as pessoas na empresa precisam de uma carteira de funcionário? Certo! Não seria suficiente apenas usar o número de identificação para calibrá-lo? A razão pela qual isso é feito é porque o uso de um nome pode tornar a interação social mais conveniente e identificar uma pessoa mais rapidamente em um curto espaço de tempo.Em empresas e escolas, números de trabalho ou de estudante são usados ​​para identificar uma pessoa por causa do número de trabalho e do aluno. número. Pode ser mais conveniente e fácil gerenciar este grupo. Se todos usarem números de identificação, a identificação e o gerenciamento ficarão inchados. O mesmo é verdade aqui. É muito difícil para nós, usuários, usar valores-chave para calibrar valores compartilhados memória. É muito complicado, então usamos shmid para identificar a memória compartilhada, o que é mais conveniente e fácil. O sistema operacional não se importa com o problema. Para identificar a memória compartilhada com mais rigor, ele deve usar valores-chave mais complexos para calibração, então, por exemplo, quando as agências governamentais lidam com os assuntos de uma pessoa específica, elas usarão o número de identificação para identificar essa pessoa, porque o número de identificação é mais complexo, mais preciso e confiável, então esta é a diferença entre chave e shmid . Diferente, então no sistema operacional, se dois processos quiserem ver a mesma memória compartilhada, eles só precisam usar o mesmo valor de chave. Após demarcar a mesma memória por chave, o shmid pode ser usado para operar a memória. Onde está a chave valor armazenado? E quanto? Dizemos que existem múltiplas memórias compartilhadas no sistema operacional, então precisamos primeiro descrever a organização. Em seguida, há um campo na estrutura shm que descreve a memória compartilhada especificamente para registrar o valor-chave da memória compartilhada, então a chave O valor será definido por meio da chamada do sistema shmget. Os atributos de memória compartilhada são usados ​​para indicar a exclusividade da memória compartilhada no kernel. shmid e key são equivalentes ao fd e inode do sistema de arquivos. Uma é para o sistema operacional veja e o outro é para nossos operadores usarem. Sim, a razão pela qual o sistema operacional faz isso é para facilitar o desacoplamento de identificadores. A camada do usuário e a camada do sistema operacional usam coisas diferentes para identificar a memória, de modo que a camada do usuário e a camada do sistema operacional não interfere entre si, ou seja, são uma camada. Se houver um problema, isso não afetará outro nível. Por exemplo, se houver um problema com seu número de estudante ou de trabalho, ele não afetará seu número de identificação. Portanto, esta é a diferença entre key e shmid.

ipcs -m e shmctl

Com as duas funções acima, podemos implementar a comunicação entre os dois processos. Primeiro, crie os seguintes arquivos: O
Insira a descrição da imagem aqui
arquivo comm.hpp fornece métodos operacionais relacionados à memória compartilhada, entre o arquivo client.cc e o arquivo server.cc. Para implementar comunicação, primeiro implemente o arquivo comm.hpp. A premissa de criar memória compartilhada é ver a mesma memória compartilhada, então temos que fornecer uma função para retornar o valor da chave. Como existem apenas dois processos para implementar a comunicação, definimos dois Cada macro representa dois endereços respectivamente:

#define PATHNAME "."
#define PROJ_ID  0x66
key_t getKey()
{
    
    
    
}

Em seguida, chame a função ftok na função para obter o valor da chave. Como o processo de criação pode falhar, você deve usar a instrução if para determinar se o valor da chave é menor que 0, imprimir uma mensagem de erro e usar a função de saída para finalizar o processo. O código aqui é o seguinte:

key_t getKey()
{
    
    
    key_t k =ftok(PATHNAME,PROJ_ID);
    if(k<0)
    {
    
    
        cerr<<errno<<" : "<<strerror(errno)<<endl;
        exit(2);
    }
    return k;
}

Depois de obter o valor da chave, podemos criar memória compartilhada, mas apenas um processo precisa criar memória compartilhada e o outro processo só precisa obter o shmid da memória.Portanto, os bits de flag passados ​​​​pelos dois processos ao chamar a função shmget são diferentes, então criamos uma função chamada getShmHelper é especialmente usada para chamar a função shmget e, em seguida, criar duas funções. A função getShm chama a função getShmHelper para obter o shmid do processo. A função createShm chama a função getShmHelper para criar memória compartilhada. Como a função shmget requer dois parâmetros, a função getShmHelper também requer dois. Um parâmetro representa o valor da chave e o outro representa o bit de marca da função. Porque as duas funções classificam a função shmget, a função shmget e a função createshm só precisa de um parâmetro para representar o valor da chave. Então a implementação da função aqui é a seguinte:

int getShmHelper(key_t k,int flags)
{
    
    
    int shmid =shmget(k,flags);
    if(shmid<0)
    {
    
    
        cerr<<errno<<" : "<<strerror(errno)<<endl;
        exit(2);
    }
    return shmid;
}

int createShm(key_t k)
{
    
    
    return getShmHelper(k,IPC_CREAT|IPC_EXCL);
}

int getShm(key_t k)
{
    
    
    return getShmHelper(k,IPC_CREAT);
}

Em seguida, crie memória compartilhada no arquivo do servidor e obtenha a memória compartilhada no arquivo do cliente. Em seguida, primeiro chame a função getkey e, em seguida, chame a função creatShm e a função getShm. O código aqui é o seguinte:

//server.cc
#include"comm.hpp"
int main()
{
    
    
    key_t key=getKey();
    printf("key: 0x%x\n", key);//key
    int shmid=createShm(key);
    printf("shmid: %d\n", shmid); //shmid 
    return 0;
}
//client.cc
#include"comm.hpp"
int main()
{
    
    
    key_t key=getKey();
    printf("key: 0x%x\n", key);//key
    int shmid=getShm(key);
    printf("shmid: %d\n", shmid); //shmid 
    return 0;
}

Em seguida, os resultados da execução do código são os seguintes:
Insira a descrição da imagem aquiVocê pode ver que quando o programa do servidor é executado pela primeira vez, o valor da chave e o shmid são impressos com sucesso sem problemas. Após a impressão, o programa termina, mas quando executamos o programa novamente, descobrimos que há um problema aqui. O programa nos disse que o arquivo foi salvo. Em seguida, executamos a função cliente e descobrimos que o resultado da execução da função cliente também estava correto. Esta é a característica de memória compartilhada. O ciclo de vida da memória compartilhada depende do sistema operacional e não do processo. Portanto, a saída do processo não afetará a memória compartilhada, que é um recurso comum da comunicação entre sistema e versão. Use o comando ipcs -m para visualizar a memória compartilhada que existe no sistema operacional atual:
Insira a descrição da imagem aqui
Você pode ver que há atualmente uma memória compartilhada. A chave da memória compartilhada é exatamente a mesma impressa pelo processo acima. O o valor de shmid é 1, e o proprietário representa a memória compartilhada atual. O proprietário, perms representa a permissão atual para contribuir com memória, bytes representa o tamanho da memória compartilhada, nattach representa quantos processos atualmente apontam para esta memória compartilhada, status representa o status da memória compartilhada, então se quisermos deletar a memória compartilhada, podemos usar o comando ipcrm -m shmid
Insira a descrição da imagem aqui

Portanto, este é o método de instrução para excluir a memória compartilhada. Você pode usar shmctl no programa para controlar o processo. Vamos dar uma olhada na introdução desta função: o primeiro parâmetro indica qual memória compartilhada é controlada (o controle aqui inclui a
Insira a descrição da imagem aqui
exclusão ). O segundo parâmetro é um bit de flag que indica como controlar a memória compartilhada. Vamos dar uma olhada nos bits de flag do segundo parâmetro:
Insira a descrição da imagem aqui

A função de IPC_STAT é obter os atributos da memória compartilhada atual. IPC_SAT é projetar os atributos de memória compartilhada que fornecemos à memória compartilhada executada. IPC_RMID é excluir a memória compartilhada especificada, porque precisamos obter os atributos da memória compartilhada memória e defini-los na memória compartilhada. Atributos de memória, então precisamos de um terceiro parâmetro aqui. Se quisermos obter os atributos, podemos passar um ponteiro de estrutura correspondente para este parâmetro. Se quisermos definir os atributos na memória compartilhada , podemos Os atributos a serem definidos são colocados neste parâmetro. Se você deseja apenas excluí-los, defina este parâmetro como vazio. O valor de retorno desta função é o seguinte:
Insira a descrição da imagem aqui
Se a exclusão falhar, esta função retornará -1, então podemos usar esta função para fazer um julgamento. Então, com esta função, podemos adicionar uma função delShm à função comm.hpp para exclusão. A memória compartilhada é implementada internamente chamando a função shmctl. O código aqui é o seguinte:

void delShm(int shmid)
{
    
    
    if(shmctl(shmid,IPC_RMID,nullptr)==-1)
    {
    
    
        cerr<<errno<<" : "<<strerror(errno)<<endl;
    }
}

Então você pode chamar esta função no arquivo do servidor:

#include"comm.hpp"
int main()
{
    
    
    key_t key=getKey();
    printf("key: 0x%x\n", key);//key
    int shmid=createShm(key);
    printf("shmid: %d\n", shmid); //shmid 
    delShm(shmid);
    return 0;
}

Então você pode ver que chamar o programa executável do servidor várias vezes não causará erros no arquivo:
Insira a descrição da imagem aqui

merda

Primeiro, vamos dar uma olhada na introdução desta função:
Insira a descrição da imagem aqui
o primeiro parâmetro é usado para indicar qual memória compartilhada está associada a ela. Se você quiser associá-la a qual memória compartilhada, basta passar o shmid correspondente. O segundo parâmetro indica que a memória compartilhada é mapeada para o virtual A maioria dos parâmetros pode ser passada diretamente como nullptr independente de qual posição no espaço de endereço. O terceiro parâmetro é usado para indicar as permissões do espaço compartilhado. Não é necessário passar 0 diretamente aqui , o que significa que pode ser lido ou escrito.O retorno da função O valor representa o endereço inicial do espaço compartilhado. Se o valor de retorno for -1 após ser convertido para um tipo inteiro, significa que o link falhou. Nosso sistema operacional usa um sistema de 64 bits, então o tamanho do ponteiro é de 8 bytes, então long long deve ser usado para receber então, com esta função, podemos criar uma função para ajudar especificamente o processo a se conectar à memória compartilhada:

void *attachShm(int shmid)
{
    
    
   void * mem=shmat(shmid,nullptr,0);
   if((long long)mem==-1)
   {
    
    
       cout<<errno<<" : "<<strerror(errno)<<endl;
       exit(3);
   }
   return mem;
}

merda

Como existe uma função que pode conectar e contribuir com memória, também existe uma função que pode desconectar o processo da memória compartilhada. Então essa função é shmdt. A declaração da função é a seguinte: Esta função possui apenas um parâmetro. Se você deseja conectar este processo
Insira a descrição da imagem aqui
com Qual memória compartilhada não está associada, o endereço da memória compartilhada é passado, que é o valor de retorno da função shmat. Vamos dar uma olhada no valor de retorno desta função:
Insira a descrição da imagem aqui
Se a desassociação for bem-sucedida, esta função retornará 0 e, se falhar, retornará -1, então podemos criar uma função para encapsular esta função:

void detachShm(void *start)
{
    
    
    if(shmdt(start) == -1)
    {
    
    
        std::cerr <<"shmdt: "<< errno << ":" << strerror(errno) << std::endl;
    }
}

Comunicação de memória compartilhada

Então é aqui que o servidor envia as informações e o cliente lê a confiança. Antes de enviar as informações, elas devem ser vinculadas à memória compartilhada. Após a conclusão do link, a associação deve ser cancelada e a memória compartilhada excluída:

#include"comm.hpp"
int main()
{
    
    
    key_t key=getKey();
    printf("key: 0x%x\n", key);//key
    int shmid=createShm(key);
    printf("shmid: %d\n", shmid); //shmid 
    char * start =(char*)attachShm(shmid);//链接共享内存
    printf("attach success start:%s",start);
    //通信
    
    detachShm(start);//取消链接
    delShm(shmid);
    return 0;
}

Depois de usar malloc na linguagem C, obteremos o endereço inicial do espaço do aplicativo e então poderemos usar esse endereço para gravar informações na memória. O mesmo princípio se aplica aqui. Depois de obter o endereço inicial, podemos inseri-lo diretamente de acordo com o endereço.Escreva o conteúdo, as informações a serem escritas são as seguintes:

const char* message = "hello server, 我是另一个进程,正在和你通信";
pid_t id = getpid();
int cnt = 1;

Então podemos criar um loop e usar a função fprintf para gravar dados nele. A função fprintf é declarada da seguinte forma: o
Insira a descrição da imagem aqui
primeiro parâmetro indica o local que você deseja escrever, então podemos passar o ponteiro inicial acima. , então o código aqui é o seguinte:

#include"comm.hpp"
int main()
{
    
    
    key_t key=getKey();
    printf("key: 0x%x\n", key);//key
    int shmid=createShm(key);
    printf("shmid: %d\n", shmid); //shmid 
    char * start =(char*)attachShm(shmid);//链接共享内存
    printf("attach success start:%s",start);
    //通信
    const char* message = "hello server, 我是另一个进程,正在和你通信";
    pid_t id = getpid();
    int cnt = 1;
    while(true)
    {
    
    
        sleep(1);
        snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);
    }
    detachShm(start);//取消链接
    delShm(shmid);
    return 0;
}

Então, no cliente, você pode usar um loop while e a função printf para imprimir o conteúdo do endereço:

#include"comm.hpp"
int main()
{
    
    
    key_t key=getKey();
    printf("key: 0x%x\n", key);//key
    int shmid=getShm(key);
    printf("shmid: %d\n", shmid); //shmid 
    char* start=(char*)attachShm(shmid);
    while(true)
    {
    
    
        printf("client say: %s",start);
        sleep(1);
    }
    detachShm(start);
    delShm(shmid);
    return 0;
}

Então executamos primeiro o processo do cliente, e você pode ver o seguinte cenário:
Insira a descrição da imagem aqui
Ele mostra diretamente que não há permissão e ao executar o servidor, mostra que o arquivo já existe. Se isso acontecer, significa que o cliente ainda não foi executado para excluir a memória compartilhada. Ele foi encerrado, então por que isso acontece? Vamos dar uma olhada nas propriedades da memória compartilhada:
Insira a descrição da imagem aqui
Há uma permissão chamada prms nessas propriedades. Essa permissão é 0, o que significa que ninguém tem permissões de leitura e gravação, portanto não haverá permissões. Então, se você quiser, projete esta memória As permissões devem ser adicionadas após o bit de marcação na função shmget, então aqui está a permissão adicionada na função creatShm:

int createShm(key_t k)
{
    
    
    return getShmHelper(k,IPC_CREAT|IPC_EXCL|0600);
}

Se você executá-lo novamente, aparecerá a seguinte situação:
Insira a descrição da imagem aqui

Insira a descrição da imagem aqui
Então isso é comunicação de memória compartilhada

Vantagens da memória compartilhada

A memória compartilhada é a mais rápida entre todas as comunicações e pode reduzir bastante o número de cópias, porque a memória compartilhada grava os dados diretamente na memória e então o leitor entra diretamente na memória para ler, mas o pipeline não é assim, por exemplo, o mesmo Os dados que usam o pipeline requerem 4 cópias de dados: primeiro insira os dados no buffer, depois insira-os no pipeline, depois insira-os no buffer correspondente à extremidade de saída e, em seguida, envie-os para outros lugares, para que ele vá através de quatro cópias, e a memória compartilhada Você só precisa copiar duas vezes, gravar os dados na memória e, em seguida, gravar os dados da memória em outros lugares. Essa é a vantagem da memória compartilhada.

Desvantagens da memória compartilhada

A memória compartilhada não possui operações de sincronização e exclusão mútua, ou seja, não há proteção para os dados. Quando falamos sobre pipelines antes, dissemos que o status de uma extremidade do pipeline afetará a outra extremidade. Por exemplo, nenhum novo os dados são gravados no segmento de gravação. Quando o leitor não grava novos dados, o leitor lerá os dados antigos, como alterar o tempo de suspensão do segmento de gravação. A seguinte situação ocorrerá em 5 segundos: o mesmo conteúdo é
Insira a descrição da imagem aqui
lido várias vezes, então esta é a deficiência da memória compartilhada. Ela não protege a memória e não há exclusão mútua e sincronização entre a extremidade de leitura e o segmento de gravação.

Características da memória compartilhada

Geralmente, é recomendado que a memória compartilhada seja um número inteiro múltiplo de 4 KB. Se o valor fornecido não for um número inteiro múltiplo de 4 KB, o sistema operacional irá arredondar. Por exemplo, se você solicitar 4.097 bytes, mas o espaço solicitado pelo sistema operacional sistema tem 8.192 bytes. , mas quando verificamos as propriedades, ainda tem 4.097 bytes. O motivo é que o que o sistema operacional se aplica e o que você pode usar são duas coisas diferentes. Se o sistema operacional fornecer menos do que você deseja , então se houver um acesso fora dos limites no futuro, será um problema do sistema operacional, mas se eu solicitar mais e você pedir menos, isso não significa que você pode usar legalmente todo o espaço que eu solicitado. Essa é a característica da memória compartilhada. Ao aplicar, você arredonda. Quando você usa, você só pode usar de acordo com sua aplicação. tamanho usado.

おすすめ

転載: blog.csdn.net/qq_68695298/article/details/132818550