Unix 서버에서 메모리 부족 (OOM) 상황을 시뮬레이션하는 프로그램을 만들고 싶습니다. 나는이 매우 간단한 메모리 먹는 사람을 만들었습니다 :
#include <stdio.h>
#include <stdlib.h>
unsigned long long memory_to_eat = 1024 * 50000;
size_t eaten_memory = 0;
void *memory = NULL;
int eat_kilobyte()
{
memory = realloc(memory, (eaten_memory * 1024) + 1024);
if (memory == NULL)
{
// realloc failed here - we probably can't allocate more memory for whatever reason
return 1;
}
else
{
eaten_memory++;
return 0;
}
}
int main(int argc, char **argv)
{
printf("I will try to eat %i kb of ram\n", memory_to_eat);
int megabyte = 0;
while (memory_to_eat > 0)
{
memory_to_eat--;
if (eat_kilobyte())
{
printf("Failed to allocate more memory! Stucked at %i kb :(\n", eaten_memory);
return 200;
}
if (megabyte++ >= 1024)
{
printf("Eaten 1 MB of ram\n");
megabyte = 0;
}
}
printf("Successfully eaten requested memory!\n");
free(memory);
return 0;
}
memory_to_eat
정확히 50GB의 RAM 인 정의 된만큼 많은 메모리를 사용 합니다. 메모리를 1MB 씩 할당하고 더 많은 할당에 실패한 지점을 정확하게 인쇄하여 어느 최대 값을 먹을 수 있는지 알 수 있습니다.
문제는 작동한다는 것입니다. 실제 메모리가 1GB 인 시스템에서도 가능합니다.
맨 위로 확인하면 프로세스가 50GB의 가상 메모리와 1MB 미만의 상주 메모리 만 사용한다는 것을 알 수 있습니다. 실제로 그것을 소비하는 메모리 이터를 만드는 방법이 있습니까?
시스템 사양 : Linux 커밋 3.16 ( Debian )은 스왑 및 가상화없이 오버 커밋이 활성화 된 (확실한 체크 아웃 방법) 가능성이 높습니다.
답변
귀하의 경우 malloc()
구현 (AN 통해 시스템 커널에서 메모리를 요청 sbrk()
또는 mmap()
시스템 호출), 커널은 당신이 메모리를 요청하고 그것이 당신의 주소 공간 내에 배치 될 위치하는 메모를합니다. 실제로 해당 페이지를 매핑하지는 않습니다 .
프로세스가 이후에 새로운 영역 내에서 메모리에 액세스 할 때, 하드웨어는 세그먼테이션 결함을 인식하고 커널에게 조건을 알려줍니다. 그런 다음 커널은 자체 데이터 구조에서 페이지를 찾고 페이지에 0 페이지가 있어야 함을 발견하여 0 페이지에 맵핑하고 (아마 먼저 페이지 캐시에서 페이지를 제거 할 수 있음) 인터럽트에서 리턴합니다. 프로세스는 이런 일이 발생했다는 것을 인식하지 못합니다. 커널 작업은 완전히 투명합니다 (커널이 작동하는 동안 짧은 지연 시간 제외).
이 최적화는 시스템 호출이 매우 빠르게 리턴되도록하며, 가장 중요한 것은 맵핑 할 때 프로세스에 자원이 커미트되지 않도록합니다. 따라서 프로세스는 메모리를 너무 많이 차지할 염려없이 정상적인 환경에서는 필요하지 않은 오히려 큰 버퍼를 예약 할 수 있습니다.
따라서 메모리 사용자를 프로그래밍하려면 할당 한 메모리로 실제로 무언가를 수행해야합니다. 이를 위해 코드에 한 줄만 추가하면됩니다.
int eat_kilobyte()
{
if (memory == NULL)
memory = malloc(1024);
else
memory = realloc(memory, (eaten_memory * 1024) + 1024);
if (memory == NULL)
{
return 1;
}
else
{
//Force the kernel to map the containing memory page.
((char*)memory)[1024*eaten_memory] = 42;
eaten_memory++;
return 0;
}
}
각 페이지 내에서 단일 바이트 (X86에는 4096 바이트 포함)에 쓰면 충분합니다. 커널에서 프로세스로의 모든 메모리 할당은 메모리 페이지 단위로 이루어지기 때문입니다. 이는 더 작은 단위로 페이징을 허용하지 않는 하드웨어 때문입니다.
답변
모든 가상 페이지는 동일한 0으로 된 실제 페이지에 맵핑시 복사시 시작됩니다. 실제 페이지를 사용하려면 각 가상 페이지에 무언가를 작성하여 더티 페이지를 더럽힐 수 있습니다.
루트로 실행하는 경우 mlock(2)
또는 mlockall(2)
커널을 사용 하여 할당 된 페이지를 더럽 히지 않고 페이지를 연결하도록 할 수 있습니다. 루트가 아닌 일반 사용자는 ulimit -l
64kiB 만 있습니다.
다른 많은 사람들이 제안했듯이 Linux 커널은 쓰지 않는 한 실제로 메모리를 할당하지 않는 것 같습니다.
OP가 원하는 것을 수행하는 코드의 개선 된 버전 :
또한 정수 %zi
를 인쇄 하는 데 사용하여 printf 형식 문자열이 memory_to_eat 및 eaten_memory 유형과 일치하지 않는 문제를 해결합니다 size_t
. 먹는 메모리 크기는 kiB 단위로 선택적으로 명령 행 인수로 지정할 수 있습니다.
전역 변수를 사용하고 4k 페이지 대신 1k 씩 증가하는 지저분한 디자인은 변하지 않습니다.
#include <stdio.h>
#include <stdlib.h>
size_t memory_to_eat = 1024 * 50000;
size_t eaten_memory = 0;
char *memory = NULL;
void write_kilobyte(char *pointer, size_t offset)
{
int size = 0;
while (size < 1024)
{ // writing one byte per page is enough, this is overkill
pointer[offset + (size_t) size++] = 1;
}
}
int eat_kilobyte()
{
if (memory == NULL)
{
memory = malloc(1024);
} else
{
memory = realloc(memory, (eaten_memory * 1024) + 1024);
}
if (memory == NULL)
{
return 1;
}
else
{
write_kilobyte(memory, eaten_memory * 1024);
eaten_memory++;
return 0;
}
}
int main(int argc, char **argv)
{
if (argc >= 2)
memory_to_eat = atoll(argv[1]);
printf("I will try to eat %zi kb of ram\n", memory_to_eat);
int megabyte = 0;
int megabytes = 0;
while (memory_to_eat-- > 0)
{
if (eat_kilobyte())
{
printf("Failed to allocate more memory at %zi kb :(\n", eaten_memory);
return 200;
}
if (megabyte++ >= 1024)
{
megabytes++;
printf("Eaten %i MB of ram\n", megabytes);
megabyte = 0;
}
}
printf("Successfully eaten requested memory!\n");
free(memory);
return 0;
}
답변
합리적인 최적화가 이루어지고 있습니다. 런타임은 실제로 사용할 때까지 메모리를 확보 하지 않습니다 .
간단한은 memcpy
이 최적화를 회피하기에 충분합니다. ( calloc
사용 시점까지 여전히 메모리 할당을 최적화하는 것을 알 수 있습니다 .)
답변
이것에 대해 확실하지 않지만 내가 할 수있는 유일한 설명은 Linux가 COW (Copy-On-Write) 운영 체제라는 것입니다. 하나를 호출 fork
하면 두 프로세스 모두 동일한 물리적 메모리를 가리 킵니다. 메모리는 한 프로세스가 실제로 메모리에 기록 된 후에 만 복사됩니다.
여기서 실제 물리 메모리는 무언가를 쓰려고 할 때만 할당됩니다. 커널의 메모리 예약을 호출 sbrk
하거나 mmap
업데이트 만 할 수 있습니다. 실제 RAM은 실제로 메모리에 액세스하려고 할 때만 할당 될 수 있습니다.
답변
