Conhecendo o kernel Linux pelo /proc (parte 5) – Recursos da memória virtual

Conhecendo o kernel Linux pelo /proc (parte 5) – Recursos da memória virtual

No post anterior vimos comportamentos relacionados ao uso de memória de virtual, como a “sobre-alocação” de memória, uso de SWAP, estouro de memória RAM e o que pode ocorrer em máquinas com uma grande quantidade de memória RAM. Neste post vamos abordar recursos disponíveis através da memória virtual, como a memória compartilhada, o Page Cache e a abstração da arquitetura NUMA.

Memória compartilhada

Apesar da memória virtual isolar a área de memória de processos distintos, existem situações onde diferentes processos precisam se comunicar e compartilhar recursos entre si. Nestes cenários o kernel oferece o recurso de memória compartilhada, que permite que uma determinada região da memória seja compartilhada entre diversos processos.

Existem diversas formas de realizar o compartilhamento de memória no Linux, no entanto, para simplificar a implementação interna do Kernel, todos os métodos acabam realizando o mapeamento de um arquivo em memória, seja de forma explícita, ou implícita através de arquivos nos filesystems do tipo “tmpfs”.

MMAP com arquivo mapeado

A forma mais simples de compartilhar memória entre processos é abrir o mesmo arquivo em dois processos e realizar o mapeamento do arquivo em memória através da chamada “mmap” com a flag MAP_SHARED.

Para mostrar como isso funciona criamos o “shared_mem_mmap.c”, que abre um arquivo cujo o nome é definido através do seu primeiro parâmetro de execução, e em seguida é mapeado em memória com a flag “MAP_SHARED”. A ideia deste exemplo é simular uma situação onde diversos processos precisam compartilhar uma mesma configuração através da memória e por isso, assim que iniciado ele entra em looping onde o usuário pode alterar a “configuração”, visualizá-la ou encerrar sua execução.

Outro ponto importante, é que logo após a visualização da “configuração” forçamos a leitura de toda área de memória somente com propósito de forçar a alocação completa e assim poder observar como ela se comporta no “/proc”.

#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>



int main (int argc, char* argv[]){

        char *ptr, tmp;
	char option;
	struct stat st;
	int fd;
	size_t count=0;


	fd = open(argv[1],O_RDWR);
	if( fd < 0 ){
		printf("Falha ao abrir o arquivo\n");
		return 1;
	}
        stat(argv[1],&st);

        ptr=mmap(NULL,st.st_size,PROT_WRITE,MAP_SHARED,fd,0);
        if( ptr == MAP_FAILED ){
                printf("Falha ao realizar o mapeamento\n");
                return 1;
        }



	while(1){
		printf("Acoes:\n (1)Exibir configuração atual\n (2)Alterar a configuração\n (3)Encerrar\n");
		printf("Digite a opçao:");
		option= (char) getchar();
		while ((getchar()) != '\n');
		switch(option){
			case '1':
				printf("Configuração atual:\n");
                		printf("%s\n",ptr);
				for(count=0;count < st.st_size;count++){
					tmp = *(ptr+count); 
				}
				break;
			case '2':
				printf("Digite a nova configuração:\n");
                		fgets(ptr,st.st_size,stdin);
				printf("\n");
				break;
			case '3':
				printf("\nEncerrando\n");
				break;
			default :
				printf("\nInvalid Option");
		}
		if(option == '3'){
			break;
		}
	}
	


        munmap(ptr,st.st_size);
	close(fd);
}

Para realizar os testes com o nosso programa, inicialmente vamos gerar o nosso arquivo de “configuração”, denominado “file.cfg”, com 100MB através da ferramenta “dd” e em seguida vamos executá-lo em um terminal conforme os passos abaixo:

$ gcc shared_mem_mmap.c -o shared_mem_mmap.o
$ dd if=/dev/zero of=file.cfg bs=1048576 count=100
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0,286279 s, 366 MB/s
$ free -m
              total        used        free      shared  buff/cache   available
Mem:          19947        2970       10354         667        6622       15949
Swap:          2047           0        2047
$ ./shared_mem_mmap.o file.cfg 
Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:2
Digite a nova configuração:
iniciando config

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:1
Configuração atual:
iniciando config

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:

Agora, em um segundo terminal, vamos consultar o processo “free -m” e o “/proc/PID/status” para verificar como está seu consumo de memória:

# free -m
              total        used        free      shared  buff/cache   available
Mem:          19947        2973       10237         667        6736       15945
Swap:          2047           0        2047
# cat /proc/$(pgrep shared_mem_mmap)/status | egrep 'Vm|Rss'
VmPeak:	  106908 kB
VmSize:	  106908 kB
VmLck:	       0 kB
VmPin:	       0 kB
VmHWM:	  101716 kB
VmRSS:	  101716 kB
RssAnon:	      80 kB
RssFile:	  101636 kB
RssShmem:	       0 kB
VmData:	     176 kB
VmStk:	     132 kB
VmExe:	       4 kB
VmLib:	    2112 kB
VmPTE:	     256 kB
VmSwap:	       0 kB

Na saída acima, vemos um comportamento curioso, mesmo após utilizarmos os 100MB de memória, o “free” ainda mostra aproximadamente a mesma quantidade de memória disponível (available) e utilizada (used). Isso ocorre, pois o arquivo é carregado em memória através do “Page Cache”, onde o uso de memória é computado como cache e é exibido através da coluna “buff/cache” no “free”.

Através do campo VmRSS podemos ver que nosso processo está consumindo por volta 100MB de memória RAM e através do campo RssFile podemos ver que praticamente todo este consumo se trata do arquivo mapeado em memória. Neste caso sabemos qual é o arquivo responsável por este consumo, porém, na vida real, poderíamos descobrir qual é o arquivo analisando conteúdo do “/proc/PID/smaps”, conforme exemplo abaixo:

# cat /proc/$(pgrep shared_mem_mmap)/smaps | egrep -B4 -i 'Rss' 
56015416f000-560154170000 r-xp 00000000 08:02 114426097                  /home/william/Documents/memory-tests/shared_mem_mmap.o
Size:                  4 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   4 kB
--
....
--
56015507c000-56015509d000 rw-p 00000000 00:00 0                          [heap]
Size:                132 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   4 kB
--
7f5bed860000-7f5bf3c60000 -w-s 00000000 08:02 114426317                  /home/william/Documents/memory-tests/file.cfg
Size:             102400 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:              102400 kB
--
7f5bf3c60000-7f5bf3e47000 r-xp 00000000 08:02 10223863                   /lib/x86_64-linux-gnu/libc-2.27.so
Size:               1948 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                1248 kB
--
...
--
7ffed7d27000-7ffed7d48000 rw-p 00000000 00:00 0                          [stack]
Size:                132 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                  12 kB
--
7ffed7da9000-7ffed7dac000 r--p 00000000 00:00 0                          [vvar]
Size:                 12 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   0 kB
--
7ffed7dac000-7ffed7dae000 r-xp 00000000 00:00 0                          [vdso]
Size:                  8 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   4 kB
--
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Size:                  4 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   0 kB
#  cat /proc/$(pgrep shared_mem_mmap)/smaps | egrep -A20 -i 'file.cfg' 
7f5bed860000-7f5bf3c60000 -w-s 00000000 08:02 114426317                  /home/william/Documents/memory-tests/file.cfg
Size:             102400 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:              102400 kB
Pss:              102400 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:    102400 kB
Private_Dirty:         0 kB
Referenced:       102396 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                0 kB
VmFlags: wr sh mr mw me ms sd 

Na execução acima, inicialmente procuramos pelo arquivo com maior consumo de memória e em seguida realizamos um novo filtro exibindo somente o estado do mapeamento deste arquivo.

De posse dos dados presentes no “smaps” podemos concluir que:

  • O “file.cfg” é o maior consumidor de memória, pois tem o maior valor de “Rss”
  • O “file.cfg” é o maior arquivo mapeado, pois tem o maior valor no campo “Size”
  • O “file.cfg” foi acessado em quase sua totalidade, já que o campo “Referenced” é muito próximo ao campo “Size”
  • O “file.cfg” deve estar sendo acessado somente por este programa, pois o campo PSS é igual ao tamanho do arquivo
  • O mapeamento do “file.cfg” é do tipo compartilhado com permissão escrita, pois possui as flags “sh” e “wr” no campo “VmFlags”

Vale explicar que o campo PSS tenta dar uma ideia de proporcionalidade do uso de memória RAM por processo quando temos uso de memória compartilhada. Ou seja, ele basicamente irá dividir o tamanho de memória mapeada pelo número de processos que a compartilham. No nosso exemplo temos apenas um processo acessando a área de memória compartilhada, por isso o PSS é igual ao tamanho da área de memória.

Agora, em um terceiro terminal, vamos executar mais um processo do “shared_mem_mmap” apontando para o mesmo arquivo de “configuração”:

$ ./shared_mem_mmap.o file.cfg
Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:1
Configuração atual:
iniciando config

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:

Neste ponto, podemos ver que, de fato, a memória esta sendo compartilhada, pois, ao selecionar a opção “Exibir a configuração” temos o mesmo conteúdo inserido no processo do primeiro terminal. Agora, só para validar o funcionamento do processo inverso, vamos alterar a “configuração” através do processo do terceiro terminal:

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:2
Digite a nova configuração:
NOVA CONFIGURACAO

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:

Agora, se visualizarmos o conteúdo no primeiro terminal, teremos a “configuração” atualizada:

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:1
Configuração atual:
NOVA CONFIGURACAO

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:

Outro ponto que podemos observar, é o valor do campo PSS no /proc/PID/smaps, que agora deve ser a metade do tamanho da memória, já que temos 2 processos compartilhando este mesmo segmento:

cat /proc/$(pgrep --oldest shared_mem_mmap)/smaps | egrep -A20 -i 'file.cfg' 
7f5bed860000-7f5bf3c60000 -w-s 00000000 08:02 114426317                  /home/william/Documents/memory-tests/file.cfg
Size:             102400 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:              102400 kB
Pss:               51202 kB
Shared_Clean:     102396 kB
Shared_Dirty:          0 kB
Private_Clean:         4 kB
Private_Dirty:         0 kB
Referenced:       102400 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                0 kB
VmFlags: wr sh mr mw me ms sd 

MMAP com mapeamento anônimo

O MMAP também nos permite realizar o compartilhamento de memória através de um mapeamento anônimo, no entanto, neste caso o compartilhamento só será possível com processos filhos. Para realizar este tipo de mapeamento basta incluir a flag MAP_SHARED assim como foi feito no método anterior.

Para mostrar como isso funciona criamos o “shared_mem_mmap_anonymous.c”, onde realizamos um mapeamento de memória anônimo de 100MB com a flag “MAP_SHARED”. A ideia deste exemplo é simular uma situação onde temos um processo pai que efetua a gerência de “configurações” e um processo filho responsável por gerar logs. Neste caso o nosso processo de log será responsável por registrar em log as “configurações” atuais a cada 5 segundos. Assim como no exemplo anterior, no momento em que é iniciado, ele entra em looping onde o usuário pode alterar a “configuração”, visualizá-la ou encerrar sua execução.

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>



int main (int argc, char* argv[]){

        char *ptr;
	char option;
	size_t size = 1024*1024*100;
	int pid;


        ptr=mmap(NULL,size,PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
        if( ptr == MAP_FAILED ){
                printf("Falha ao realizar o mapeamento\n");
                return 1;
        }

	pid=fork();
	if(pid>0){

		while(1){
			printf("Acoes:\n (1)Exibir configuração atual\n (2)Alterar a configuração\n (3)Encerrar\n");
			printf("Digite a opçao:");
			option= (char) getchar();
			while ((getchar()) != '\n');
			switch(option){
				case '1':
					printf("Configuração atual:\n");
       		         		printf("%s\n",ptr);
					break;
				case '2':
					printf("Digite a nova configuração:\n");
                			fgets(ptr,size,stdin);
					printf("\n");
					break;
				case '3':
					printf("\nEncerrando\n");
					kill(pid,SIGTERM);
					break;
				default :
					printf("\nInvalid Option");
			}
			if(option == '3'){
				break;
			}
		}
	}else if(pid==0){
		FILE* log;
		log = fopen("/tmp/memory.log", "w+"); 
		
		if(log==NULL){
			printf("Falha ao abrir arquivo de log\n");
			return 1;
		}  
	        
	        fprintf(log,"Iniciando Logger\n");
		fflush(log);

		while(1){
			sleep(5);
			fprintf(log,"%s\n",ptr);
			fflush(log);
		}
		fclose(log);
	}else{
		printf("Falha ao criar processo\n");
		return 1;
	}


        munmap(ptr,size);
}

Para realizar os testes com o nosso programa, inicialmente vamos executá-lo em um terminal e observar seus logs em um segundo terminal, conforme os passos abaixo:

$ free -m 
              total        used        free      shared  buff/cache   available
Mem:          19947        3087        8567         731        8293       15768
Swap:          2047           0        2047
$ ./shared_mem_mmap_anonymous.o 
Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:1
Configuração atual:

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:2
Digite a nova configuração:
nova config

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar

Agora, em um segundo terminal vamos validar a presença do processo filho, verificar se o log está funcionando corretamente e como está o uso de memória:

# free -m 
              total        used        free      shared  buff/cache   available
Mem:          19947        3086        8465         831        8394       15668
Swap:          2047           0        2047
# ps auxf | grep  mmap
william   4944  0.0  0.5 106908 103748 pts/1   S+   22:56   0:00  |   |   \_ ./shared_mem_mmap_anonymous.o
william   4945  0.0  0.0 106908    72 pts/1    S+   22:56   0:00  |   |       \_ ./shared_mem_mmap_anonymous.o
# cat /tmp/memory.log 
Iniciando Logger





nova config

nova config

nova config
# cat /proc/$(pgrep -o shared_mem_mmap)/status | egrep 'Vm|Rss'
VmPeak:	  106908 kB
VmSize:	  106908 kB
VmLck:	       0 kB
VmPin:	       0 kB
VmHWM:	  103748 kB
VmRSS:	  103748 kB
RssAnon:	      80 kB
RssFile:	    1324 kB
RssShmem:	  102344 kB
VmData:	     176 kB
VmStk:	     132 kB
VmExe:	       4 kB
VmLib:	    2112 kB
VmPTE:	     252 kB
VmSwap:	       0 kB

Através da saída acima, podemos ver que temos um processo filho sendo executado e que o log está sendo gerado corretamente no arquivo “/tmp/memory.log”. Outro ponto que podemos observar aqui, é que neste caso é possível diferenciar essa área de memória compartilhada da área de “Page Cache” utilizada pelos arquivos mapeados em memória, isso é visto no campo RssShmem no “/proc/PID/status” e no free através do campo “shared”.

Essa diferenciação de uso é possível, pois, para realizar este tipo de compartilhamento o Kernel cria um arquivo a partir do “/dev/zero” e o armazena no “tmpfs”, em um ponto de montagem restrito ao Kernel (não visível ao VFS), e que pode pode ter seu consumo monitorado através do campo “Shmem” do “/proc/meminfo” conforme os comandos abaixo:

# grep 'Shmem:' /proc/meminfo 
Shmem:           850944 kB
# cat /proc/$(pgrep --oldest shared_mem_mmap)/smaps | egrep -A20 -i 'zero'
7f90484eb000-7f904e8eb000 -w-s 00000000 00:05 161584                     /dev/zero (deleted)
Size:             102400 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:              102400 kB
Pss:              102398 kB
Shared_Clean:          0 kB
Shared_Dirty:          4 kB
Private_Clean:    102396 kB
Private_Dirty:         0 kB
Referenced:       102400 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                0 kB
VmFlags: wr sh mr mw me ms sd 

SysVIPC

Outra maneira de realizar compartilhamento de memória é através do System V IPC, que é um mecanismo de comunicação interprocessos disponível na maior parte do sistemas Unix. Além de memória compartilhada ele também permite o uso de filas de mensagens e semáforos.

O SysVIPC nos permite compartilhar um determinado segmento de memória entre diversos processos. Para isso, ele fornece as chamadas “shmget” e “shmat”, que nos permitem criar e anexá-las a um determinado segmento de memória.

Para ilustrar como isso funciona criamos o “shared_mem_sysvipc.c”, onde realizamos criação de um segmento de memória identificado pelo número 334, de 100MB através do “shmget” e em seguida anexamos esse segmento através da “shmat”. Neste caso podemos iniciar diversos processos, pois como todos utilizam a mesma chave “334”, todos utilizarão o mesmo segmento de memória. Assim como no exemplo anterior, no momento que é iniciado, ele entra em looping e o usuário pode alterar a “configuração”, visualizá-la ou encerrar sua execução.

#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>



int main (int argc, char* argv[]){

        char *ptr, tmp;
	char option;
	int shm_id;
	size_t size = 1024*1024*100;
	size_t count;


	shm_id=shmget(334,size,IPC_CREAT|0666);

        if( shm_id < 0 ){
                printf("Falha alocacao de memoria \n");
                return 1;
        }
	
	ptr = shmat(shm_id,NULL,0);
        if( (void *) ptr < 0 ){
                printf("Falha em atachar memoria compartilhada \n");
                return 1;
        }




	while(1){
		printf("Acoes:\n (1)Exibir configuração atual\n (2)Alterar a configuração\n (3)Encerrar\n");
		printf("Digite a opçao:");
		option= (char) getchar();
		while ((getchar()) != '\n');
		switch(option){
			case '1':
				printf("Configuração atual:\n");
                		printf("%s\n",ptr);
				for(count=0;count<size;count++){
                                        tmp = *(ptr+count);
                                }

				break;
			case '2':
				printf("Digite a nova configuração:\n");
                		fgets(ptr,size-1024,stdin);
				printf("\n");
				break;
			case '3':
				printf("\nEncerrando\n");
				break;
			default :
				printf("\nInvalid Option");
		}
		if(option == '3'){
			break;
		}
	}
	


	shmdt(ptr);
	shmctl(shm_id,IPC_RMID,NULL);
}

Para realizar os testes com o nosso programa, inicialmente vamos executá-lo em um terminal e observar seus logs em um segundo terminal, conforme os passos abaixo:

$ gcc shared_mem_sysvipc.c -o shared_mem_sysvipc.o  
$ free -m
              total        used        free      shared  buff/cache   available
Mem:          19947        3747        2610        1161       13589       14679
Swap:          2047           0        2047
$ ./shared_mem_sysvipc.o 
Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:1
Configuração atual:

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:2
Digite a nova configuração:
Teste Config

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:

Agora, em um segundo terminal, vamos executar mais um processo do nosso programa e exibir o conteúdo da “configuração atual” para validar se a memória realmente está compartilhada:

$ ./shared_mem_sysvipc.o 
Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:1
Configuração atual:
Teste Config

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao: 

Com as duas instâncias do nosso programa em execução, em um terceiro terminal, vamos verificar como ficou o consumo de memória:

# free -m
              total        used        free      shared  buff/cache   available
Mem:          19947        3752        2470        1261       13724       14573
Swap:          2047           0        2047
# grep 'Shmem:' /proc/meminfo 
Shmem:           1292200 kB
# cat /proc/$(pgrep --oldest shared_mem_ )/smaps | egrep -A20 -i 'deleted'
7f3c11582000-7f3c17982000 rw-s 00000000 00:05 3440674                    /SYSV0000014e (deleted)
Size:             102400 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:              102400 kB
Pss:               51200 kB
Shared_Clean:     102396 kB
Shared_Dirty:          4 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:       102400 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                0 kB
VmFlags: rd wr sh mr mw me ms sd 

Conforme podemos ver acima, neste caso também é possível identificar o uso da memória compartilhada através do campo “shared” do free e através do campo “Shmem” do “/proc/meminfo”. A principal diferença neste caso é que o arquivo não é gerado a partir do “/dev/zero”, e sim, gerado a partir de um arquivo com cuja nomeclatura é “SYSV” seguido do identificador do segmento, que no nosso caso é 334, 14e em hexadecimal.

Outro ponto importante, é que no caso do SysVIPC, existem alguns limites configuráveis através do “/proc”, que são:

  • /proc/sys/kernel/shmmax: define o tamanho máximo de um segmento de memória compartilhado
  • /proc/sys/kernel/shmall: define a quantidade máxima de memória a ser utilizada em todo o sistema por segmentos de memória compartilhados
  • /proc/sys/kernel/shmmni: define a quantidade máxima de segmentos de memória compartilhados no sistema

Na execução abaixo, vemos que, devido ao programa utilizar um segmento de 100MB de memória, se reduzirmos o limite para 90MB teremos falha na alocação de memória:

# cat /proc/sys/kernel/shmmax 
18446744073692774399
# echo 94371840 >  /proc/sys/kernel/shmmax
# ./shared_mem_sysvipc.o 
Falha alocacao de memoria 
# echo 18446744073692774399 > /proc/sys/kernel/shmmax
# ./shared_mem_sysvipc.o 
Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:3

Encerrando

POSIX

Por fim, ainda temos o compartilhamento de memória através do padrão POSIX, que consiste em abrir/criar um “pseudo” arquivo através da chamada “shm_open” e em seguida mapeá-lo em memória através da chamada “mmap” com a flag “MAP_SHARED”.

Para demonstrar seu funcionamento criamos o “shared_mem_posix.c”, que abre/cria um “pseudo” arquivo através do “shm_open” cujo o nome é definido através do seu primeiro parâmetro de execução, em seguida definimos seu tamanho através da chamada “ftruncate” e por fim realizamos o mapeamento em memória através da chamada “mmap” com a flag “MAP_SHARED”. Assim como nos exemplos anteriores, a ideia deste exemplo é simular uma situação onde diversos processos precisam compartilhar uma configuração através da memória e por isso, assim que iniciado ele entra em looping onde o usuário poderá alterar a “configuração”, visualizá-la ou encerrar sua execução.

#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>



int main (int argc, char* argv[]){

        char *ptr, tmp;
	char option;
	size_t size = 1024*1024*100;
	size_t count;
	int fd;


	
	fd = shm_open(argv[1],O_RDWR|O_CREAT,S_IRWXU);
	if( fd <0 ){
		printf("Falha ao abrir o arquivo\n");
		return 1;
	}
	ftruncate(fd,size);

        ptr=mmap(NULL,size,PROT_WRITE,MAP_SHARED,fd,0);
        if( ptr == MAP_FAILED ){
                printf("Falha ao realizar o mapeamento\n");
                return 1;
        }



	while(1){
		printf("Acoes:\n (1)Exibir configuração atual\n (2)Alterar a configuração\n (3)Encerrar\n");
		printf("Digite a opçao:");
		option= (char) getchar();
		while ((getchar()) != '\n');
		switch(option){
			case '1':
				printf("Configuração atual:\n");
                		printf("%s\n",ptr);
				for(count=0;count<size;count++){
                                        tmp = *(ptr+count);
                                }
				break;
			case '2':
				printf("Digite a nova configuração:\n");
                		fgets(ptr,size,stdin);
				printf("\n");
				break;
			case '3':
				printf("\nEncerrando\n");
				break;
			default :
				printf("\nInvalid Option");
		}
		if(option == '3'){
			break;
		}
	}
	


        munmap(ptr,size);
	shm_unlink(argv[1]);
}

Para realizar os testes com o nosso programa, vamos executá-lo inicialmente em um primeiro terminal, conforme os passos abaixo:

$ gcc shared_mem_posix.c -o shared_mem_posix.o -lrt
$ free -m
              total        used        free      shared  buff/cache   available
Mem:          19947        8971        1333        1410        9641        9203
Swap:          2047          65        1982
$ ./shared_mem_posix.o blog4linux
Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:2
Digite a nova configuração:
ConfigV2

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:1
Configuração atual:
ConfigV2

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:

Agora, em um segundo terminal, vamos executar mais um processo do nosso programa e exibir o conteúdo da “configuração atual” para validar se a memória realmente está compartilhada:

$ ./shared_mem_posix.o blog4linux
Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:1
Configuração atual:
ConfigV2

Acoes:
 (1)Exibir configuração atual
 (2)Alterar a configuração
 (3)Encerrar
Digite a opçao:

Em um terceiro terminal, vamos analisar o uso de memória deste meétodo de compartilhamento de memória:

$ free -m
              total        used        free      shared  buff/cache   available
Mem:          19947        8975        1229        1510        9742        9100
Swap:          2047          65        1982
$ grep 'Shmem:' /proc/meminfo
Shmem:           1547044 kB
$ cat /proc/$(pgrep -o shared_mem_)/status | egrep 'Vm|Rss'
VmPeak:	  111172 kB
VmSize:	  111172 kB
VmLck:	       0 kB
VmPin:	       0 kB
VmHWM:	  101768 kB
VmRSS:	  101768 kB
RssAnon:	     112 kB
RssFile:	    1504 kB
RssShmem:	  100152 kB
VmData:	     212 kB
VmStk:	     132 kB
VmExe:	       4 kB
VmLib:	    2244 kB
VmPTE:	     260 kB
VmSwap:	       0 kB
$ cat /proc/$(pgrep --oldest shared_mem_ )/smaps | egrep -A20 -i 'blog4linux'
7fd420ed2000-7fd4272d2000 -w-s 00000000 00:18 679                        /dev/shm/blog4linux
Size:             102400 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:              102400 kB
Pss:               51200 kB
Shared_Clean:     102396 kB
Shared_Dirty:          4 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:       102400 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                0 kB
VmFlags: wr sh mr mw me ms sd 
$ ls -lh  /proc/$(pgrep --oldest shared_mem_ )/fd
total 0
lrwx------ 1 william william 64 dez  4 01:24 0 -> /dev/pts/15
lrwx------ 1 william william 64 dez  4 01:24 1 -> /dev/pts/15
lrwx------ 1 william william 64 dez  4 01:24 2 -> /dev/pts/15
lrwx------ 1 william william 64 dez  4 01:24 3 -> /dev/shm/blog4linux
$ ls -lh  /dev/shm/blog4linux 
-rwx------ 1 william william 100M dez  4 01:17 /dev/shm/blog4linux
$ mount | grep shm
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
$ cat /dev/shm/blog4linux 
ConfigV3

Como podemos ver na saída acima, seu comportamento é bem similar ao dos métodos anteriores, onde é possível identificar o uso de memória compartilhada através de “shared” do free e do campo “Shmem” no “/proc/meminfo”. A grande diferença neste caso, é que o nosso pseudo arquivo “blog4linux”, é criado em um tmpfs que se encontra montado em “/dev/shm”, e que é visível para os usuários.

Observações:

Um ponto importante a se observar é que em todos os casos é possível ver que o “free” também mostra um aumento do consumo de memória na coluna “buffer/cache”. Isso ocorre devido a todos os métodos trabalharem utilizando arquivos carregados em memória, seja de forma implícita ou explícita, de forma que essas páginas de memória acabam sendo gerenciadas pelo “Page Cache”.

Page cache

O “Page Cache” é um recurso do Kernel destinado a “cachear” arquivos em memória de forma transparente e dinâmica, ou seja, toda vez que realizamos um mapeamento de arquivo em memória, ou que realizamos a leitura/escrita de um arquivo em disco, o Kernel irá armazenar o arquivo em memória – sempre que existir memória RAM disponível no sistema.

Esse recurso, além de evitar leituras desnecessárias em disco, também permite a escrita atrasada no disco, pois a escrita é realizada primeiramente nas páginas do “Page Cache”, as quais posteriormente serão efetivamente escritas em disco.

As páginas contendo dados ainda não escritos em disco disco são chamadas de “página sujas”, e podem ser “limpas”(escritas efetivamente em disco) de diversas formas: por tempo máximo de execução, de forma explícita por um chamada “fsync” na aplicação , pelo comando sync, ou por estar próxima dos seus limites de armazenamento de páginas sujas.

Vale lembrar que mesmo que a área utilizada alocada pelo “Page Cache” seja grande, apenas as páginas sujas realmente importam para efeitos de uso de memória, pois as páginas limpas podem ser liberadas instantaneamente caso o sistema necessite de memória.

Por ser dinâmico e ter a tendência de utilizar toda a memória disponível no sistema operacional, este recurso causava muita confusão a respeito do uso real de memória em distribuições mais antigas, onde o comando “free” não exibia a coluna “available”, de forma que muitos acreditavam que a memória disponível no sistema era exibida na coluna “free”, e que seu sistema estava constantemente no limite de memória.

Para acompanhar o consumo de memória do “Page Cache” em mais detalhes podemos inspecionar o “/proc/meminfo” conforme os comandos abaixo:

# grep 'Cached' /proc/meminfo 
Cached:          7573200 kB
# grep 'Dirty' /proc/meminfo 
Dirty:              3884 kB

No comando acima podemos ver que, apesar de termos aproximadamente 7,2GB de memória sendo utilizada para o cache, apenas 3,7MB deles são páginas sujas, o que significa que temos praticamente 7GB de memória disponível para o sistema caso seja necessário.

Para efeito de execução de testes de desempenho, o Kernel permite realizar um descarte das página limpas do cache de maneira forçada escrevendo o número “1” no “/proc/sys/vm/drop_caches”.

# grep 'Cached' /proc/meminfo 
Cached:          5586108 kB
# echo 1 > /proc/sys/vm/drop_caches 
# grep 'Cached' /proc/meminfo 
Cached:          2637628 kB

No exemplo acima vimos que após realizar o “drop_caches”, o “Page Cache” foi reduzido de aproximadamente 5,3GB para 2,6GB. A redução normalmente não é total pois temos as páginas sujas e também devido aos mecanismos de memória compartilhada utilizarem esta área de memória para armazenar seus segmentos de memória.

Somente para se ter uma ideia da diferença de desempenho entre a leitura de um arquivo em “cache” e outro sem “cache” criamos o “file_reader.c”, que apenas realiza a leitura completa de um arquivo definido por parâmetro:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
	int fd;
	char buff[100];
	fd=open(argv[1],O_RDWR);
	while(read(fd,buff,100)){
	}

}

Para realizar nosso teste de desempenho, inicialmente iremos criar um arquivo de 100MB através do “dd” e seguida iremos executar o “file_reader.c” duas vezes com o comando time, sendo a segunda execução após uma limpeza do “Page Cache”, conforme mostrado nos comandos abaixo:

$ gcc file_reader.c -o file_reader.o
$ sudo su
# dd bs=1048576 count=100 if=/dev/zero of=file
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0,358191 s, 293 MB/s
# time ./file_reader.o file

real	0m0,707s
user	0m0,341s
sys	0m0,360s
# echo 1 > /proc/sys/vm/drop_caches
# time ./file_reader.o file

real	0m5,608s
user	0m0,766s
sys	0m0,973s

Na execução acima é bem claro o ganho desempenho, quando temos o cache populado: a leitura levou apenas aproximadamente 700ms, enquanto que, quando temos o cache limpo a leitura levou mais de 5 segundos, ou seja, sem o cache a leitura do arquivo levou praticamente 5 vezes mais tempo para ser realizada.

Para realizar um teste de desempenho de gravação vamos utilizar o próprio comando “dd”, criando duas vezes um arquivo de 100MB, uma vez utilizando a flag “sync” que força um fsync a cada escrita e uma segunda execução sem a flag “sync”, ou seja deixando para que o mecanismo de limpeza do “Page Cache” realize a escrita em disco posteriormente, conforme podemos ver nos comandos abaixo:

$dd bs=1048576 count=100 if=/dev/zero of=file oflag=sync
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 16,5911 s, 6,3 MB/s
$ dd bs=1048576 count=100 if=/dev/zero of=file
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0,286432 s, 366 MB/s

Nos resultados acima, também vimos um claro ganho de desempenho quando deixamos o “Page Cache” trabalhar, uma diferença de 366MB por segundo para 6MB por segundo quando forçamos a sincronia das operações de escrita e praticamente deixamos de usar o “Page Cache” para isso.

No entanto, o que muitos devem estar se perguntando é: quando não forçamos a sincronia, em quanto tempo teremos esse arquivo escrito em disco? Pois se ocorrer um crash neste período podem ocorrer perdas de dados….

A resposta é: normalmente é muito rápido, porém depende de vários fatores que vamos ver mais a frente, mas, somente a título de curiosidade podemos acompanhar (através de um segundo terminal) o campo Dirty do “/proc/meminfo” durante a criação do arquivo.

# while [ 1=1 ] ; do  grep 'Dirty' /proc/meminfo; sleep 2 ; done
Dirty:               400 kB
Dirty:               480 kB
Dirty:               548 kB
Dirty:               724 kB
Dirty:                84 kB
Dirty:            839856 kB
Dirty:            754104 kB
Dirty:            559932 kB
Dirty:            385148 kB
Dirty:            262432 kB
Dirty:            145812 kB
Dirty:               284 kB
Dirty:               272 kB
Dirty:              1376 kB
Dirty:              1544 kB
Dirty:              1944 kB
Dirty:              2084 kB
Dirty:               568 kB
$ dd bs=1048576 count=1000 if=/dev/zero of=file
1000+0 records in
1000+0 records out
1048576000 bytes (1,0 GB, 1000 MiB) copied, 13,1907 s, 79,5 MB/s

Na execução acima, vemos que tivemos um pico de aproximadamente 820MB de páginas sujas, as quais levaram aproximadamente 10 segundos para serem escritas em disco.

Atualmente o processo de gravação das páginas sujas é realizada pelo Kernel através das “worker” threads (“kworker”), no entanto em versões mais antigas existiam threads dedicadas a realizar a escrita para cada disco do sistema, denominadas “flush threads” e em versões anteriores ao kernel 2.6.32 a thread responsável pela gravação era a “pdflush”.

Apesar das mudanças de implementação ao longo do tempo, o mecanismo de limpeza se manteve similar, essas threads trabalham em turnos cujo o intervalo é definido em centésimos de segundos através do “/proc/sys/vm/dirty_writeback_centisecs”. As páginas elegíveis para limpeza em cada turno são definidas a partir do “/proc/sys/vm/dirty_expire_centisecs”, onde é definido a partir de quanto tempo de vida uma página esta elegível a ser limpa.

Outro ponto importante, é que mesmo fora do seu “turno”, as threads de limpeza podem ser iniciadas, caso a quantidade de páginas a sujas atinja os valores dos parâmetros “/proc/vm/sys/dirty_background_ratio” ou “/proc/vm/sys/dirty_background_bytes”, onde podemos definir uma porcentagem máxima de páginas sujas em relação a memória disponível ou uma quantidade (absoluta) máxima de páginas sujas em bytes respectivamente.

Para ilustrar o funcionamento do processo de limpeza vamos manter um looping monitorando o estado das páginas sujas, conforme o comando abaixo:

# cat /proc/sys/vm/dirty_writeback_centisecs
500
# cat /proc/sys/vm/dirty_expire_centisecs 
300
# cat /proc/sys/vm/dirty_background_ratio 
10
# while [ 1=1 ] ; do  grep 'Dirty' /proc/meminfo; sleep 2 ; done
Dirty:              1820 kB
Dirty:              2380 kB
Dirty:               136 kB
Dirty:               236 kB
Dirty:               480 kB
Dirty:              1824 kB
Dirty:              2096 kB
Dirty:               352 kB
Dirty:               860 kB
Dirty:              1028 kB
Dirty:               880 kB
Dirty:              2252 kB
Dirty:               180 kB
Dirty:               364 kB
Dirty:               700 kB
Dirty:               384 kB
Dirty:               520 kB
Dirty:               720 kB
Dirty:              1368 kB
Dirty:              2388 kB
Dirty:               144 kB

Conforme vimos acima, com a configuração atual, a quantidade de páginas sujas é constantemente alterada, mantendo-se sempre abaixo de 3MB.

Agora se alterarmos o “dirty_writeback_centisecs” para zero, desabilitando o mecanismo de writeback, veja o que ocorre:

Dirty:              1880 kB
Dirty:              2872 kB
Dirty:              4344 kB
Dirty:              4620 kB
Dirty:              4812 kB
Dirty:              5720 kB
Dirty:              5772 kB
Dirty:              5984 kB
Dirty:              6136 kB
Dirty:              6176 kB
Dirty:              6400 kB
Dirty:              6756 kB
Dirty:              6820 kB
Dirty:              7324 kB
Dirty:              8560 kB
Dirty:              9528 kB
Dirty:             10628 kB
Dirty:             11328 kB
Dirty:             13320 kB
Dirty:             13540 kB
Dirty:             14480 kB
Dirty:             14648 kB
Dirty:             14920 kB
Dirty:             15744 kB
Dirty:             15784 kB
Dirty:             16292 kB
Dirty:             16532 kB
Dirty:             16584 kB
Dirty:             17000 kB
Dirty:             17132 kB
Dirty:             17900 kB
Dirty:             18696 kB
Dirty:             18824 kB
Dirty:             19728 kB
Dirty:             19768 kB
Dirty:             20016 kB
Dirty:             20848 kB
Dirty:             21100 kB
Dirty:             21600 kB
Dirty:             35400 kB
Dirty:             35492 kB
Dirty:             36292 kB
Dirty:             36612 kB
Dirty:             37160 kB
Dirty:             37940 kB
Dirty:             38092 kB
Dirty:             38628 kB
Dirty:             39556 kB
Dirty:             39804 kB
Dirty:             40028 kB
...

Neste cenário temos a quantidade de páginas sujas aumentando constantemente, porém caso executarmos o comando sync em um terminal, vejamos o que ocorre:

Dirty:             42452 kB
Dirty:             42480 kB
Dirty:             42600 kB
Dirty:                60 kB // Momento onde foi executado o sync
Dirty:               304 kB
Dirty:                 8 kB
Dirty:               112 kB
Dirty:               668 kB
Dirty:                88 kB
Dirty:               472 kB
Dirty:               532 kB
Dirty:               748 kB
Dirty:              2272 kB
Dirty:              2388 kB
Dirty:              2508 kB
Dirty:              2536 kB
Dirty:              2604 kB
Dirty:              4044 kB

Na saída acima, fica evidente que mesmo com o processo de writeback desativado, ainda é possível realizar a limpeza das páginas sujas através de chamadas de sync no sistema.

Outro ponto importante, é que mesmo com o writeback desabilitado (definido para zero), caso o número de páginas sujas atinja o percentual do “dirty_background_ratio” ou o valor absoluto em “dirty_background_bytes”, o kernel irá iniciar o processo de limpeza, conforme podemos ver na demonstração abaixo:

# cat /proc/sys/vm/dirty_writeback_centisecs
0
# echo 10362880 > /proc/sys/vm/dirty_background_bytes 
# while [ 1=1 ] ; do  grep 'Dirty' /proc/meminfo; sleep 2 ; done
Dirty:             20336 kB
Dirty:             21368 kB
Dirty:             22088 kB
Dirty:              6916 kB
Dirty:              7980 kB
Dirty:              8228 kB
Dirty:              8396 kB
Dirty:              8452 kB
Dirty:              8568 kB
Dirty:              9012 kB
Dirty:             10044 kB
Dirty:              5232 kB
Dirty:              5488 kB
Dirty:              6008 kB
Dirty:              6676 kB
Dirty:              7532 kB
Dirty:              8348 kB
Dirty:              9140 kB
Dirty:              9892 kB
Dirty:             10704 kB
Dirty:             10412 kB
Dirty:             11240 kB
Dirty:             11976 kB
Dirty:              1976 kB
Dirty:              4200 kB
Dirty:              4408 kB
Dirty:              4480 kB
Dirty:              4624 kB
Dirty:              5568 kB
Dirty:              6580 kB
Dirty:              7124 kB
Dirty:              7968 kB
Dirty:              9592 kB
Dirty:              8568 kB
Dirty:             11016 kB
Dirty:             11224 kB
Dirty:              3196 kB
Dirty:              3460 kB

A execução acima, mostra que, ao definir o “dirty_background_bytes” para aproximandamente 10MB, toda vez que o número de paginas sujas ultrapassar esse limite, o processo de limpeza será ativado e essa quantidade de páginas será reduzida imediatamente.

Quando a quantidade de páginas sujas for muito grande, e as threads de sincronia não conseguirem dar conta da gravação, as páginas sujas poderão se acumular até um teto definido através dos parâmetros “/proc/sys/vm/dirty_ratio” ou “/proc/sys/vm/dirty_bytes”, onde definimos uma porcentagem máxima de páginas sujas em relação a memória disponível ou uma quantidade absoluta máxima de páginas sujas em bytes respectivamente e ao atingir este teto, todas as novas operações de IO serão bloqueadas até que as páginas sejam sejam escritas em disco causando uma percepção de “congelamento” momentâneo da máquina.

Devido a isso, principalmente em máquinas com grande quantidade de memória RAM, é recomendável que se mantenha o “dirty_background_ratio” com valores baixos, para evitar acúmulo de páginas sujas, e que o “diry_ratio” também seja baixo, pois caso o valor seja muito alto e aconteça de se alcançar esse limite, a máquina pode passar algum tempo congelada efetuando as operações dedicadas a escrita de páginas sujas. Imagine uma máquina de 2TB de RAM com 1TB de RAM disponível, caso o dirty_ratio seja 20 e esse limite seja atingido, teremos por volta de 200GB de dados a serem escritos em disco.

Uma boa prática em máquinas com muita memória RAM, é definir estes parâmetros através de tamanhos absolutos (“dirty_background_bytes” e “dirty_bytes”), já que em alguns casos 1% já pode ser considerado um valor alto.

NUMA

A arquitura NUMA (Non-Uniform Memory Access) consiste de um hardware onde temos diversos agrupamentos de CPUs e memórias, denominadas células, interconectados por barramentos. A principal questão nesta arquitetura é que o tempo de acesso a memória varia de acordo com a “distância” entre a o processador e a memória.

O Linux utiliza a nomenclatura “node” para se referir a uma célula de processador e memória, e trata cada “node” de forma independente quando está realizando a alocação física de memória, ou seja, cada node terá seu endereçamento, controle de páginas livres, páginas em uso e tudo que é necessário para a alocação e liberação de memória.

Para otimizar o desempenho, o Kernel sempre dá preferência pela alocação de memória física no mesmo “node” do processador que se encontra executando o programa e durante as trocas de contexto o escalonador de tarefas tende a manter o processo no mesmo processador, sempre evitando o acesso a memória de outros nodes.

Graças ao uso do conceito de memória virtual o Kernel consegue abstrair todo esse mecanismo, de forma que, para a aplicação, isso se torna completamente transparente.

Para checar se sua máquina utiliza arquitetura NUMA basta consultar a quantidade nodes no sistema, o que pode ser feito de diversas formas:

# lscpu | grep -i numa
NUMA node(s):        1
NUMA node0 CPU(s):   0-3
# cat /sys/devices/system/node/online
0
# cat /proc/zoneinfo  | grep Node
Node 0, zone      DMA
Node 0, zone    DMA32
Node 0, zone   Normal
Node 0, zone  Movable
Node 0, zone   Device

Na saída acima fica claro que a máquina em questão não utiliza NUMA, pois temos apenas um “node” disponível.

Ainda assim é possível visualizar as estatísticas referente a NUMA desse “node” através do “/proc/zoneinfo” conforme o comando abaixo:

# cat /proc/zoneinfo  | grep -A 30 Normal | grep numa 
      numa_hit     319270577
      numa_miss    0
      numa_foreign 0
      numa_interleave 45129
      numa_local   319270577
      numa_other   0

No comando acima estamos exibindo a estatística de NUMA da zona “normal” (veremos mais sobre as zonas no próximo post), e conforme esperado, temos 100% (numa_local=numa_hit e numa_foreign=0 ) das alocações acontecendo no próprio node e nenhuma alocação vinda de outro “node” (numa_miss=0).

Próximos passos

No próximo post iremos encerrar o tema de memória explicando como o Kernel realiza o gerenciamento da memória física e o que podemos observar através do “/proc”.

 

Líder em Treinamento e serviços de Consultoria, Suporte e Implantação para o mundo open source. Conheça nossas soluções:

CURSOSCONSULTORIA

Anterior Descubra as vantagens e benefícios dos softwares Open Source
Próxima Como a Computação em Nuvem Revolucionou o Big Data: Uma Análise Detalhada

About author

william.welter
william.welter 5 posts

Formado em ciência da computação, com as certificações LPIC-3, LPI DevOps Tools Engineer, LFCS, LFCE, ZCE-PHP5.3, ZFCA, PP9A, desenvolvedor PHP e C, com foco em Linux e software livre. Atualmente é gerente e líder técnico do time de consultoria e suporte na 4Linux, a qual atua principalmente com ferramentas DevOps, containers, cloud, monitoramento e sistemas de missão critica.

View all posts by this author →

Você pode gostar também

Infraestrutura TI

Guia passo a passo para instalar o Ceph em ambiente mononode

Houve um tempo atrás que tive que criar um Storage para um ambiente de Openstack usando o FreeNAS. Devo dizer que não encontrei uma única documentação que explicasse como seria

Infraestrutura TI

Crie sua primeira aplicação em Python: Guia passo a passo

Neste post estaremos aprendendo a criar uma aplicação simples utiliando a linguagem de programação “Python”.   Primeiramente, obviamente, será necessário ter o python instalado em nossas máquinas, podemos adquirí-lo de

Infraestrutura TI

Domine o Linux: Torne-se um Administrador de Sistemas Linux

Linux System Administration Saber como se comportar como um Administrador de Sistemas Linux tornou-se primordial, mas o comportamento não se resume apenas em saber ligar ou desligar, ou até mesmo