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 7 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

DevOps

Automatizando Tarefas com Shell Script

Já se aventurou pela linha de comando de sistemas Unix ou Linux? Se sua resposta for sim, então provavelmente já ouviu falar de Shell Script. Mas o que seria exatamente

Infraestrutura TI

Descubra o poder do comando sed para manipulação de texto no Linux

No vasto universo de linha de comando, existem ferramentas extremamente versáteis e poderosas que podem ser utilizadas em diversas situações, e dentre dezenas de ferramentas, podemos facilmente destacar o sed,

DevOps

Transforme sua empresa com soluções open source para alta demanda

Conheça nossa solução baseada 100% em software open source. Estamos vivendo um momento ímpar. De repente, as empresas foram obrigadas a se reinventar. O negócio foi afetado e como a