사람들이 호스팅된 VPS가 너무 많은 RAM을 사용하여 프로세스를 예기치 않게 종료하는 것에 대해 분명히 불평하는 일부 게시물을 온라인에서 보았습니다.
어떻게 이럴 수있어? 모든 최신 운영 체제는 물리적 RAM의 모든 항목에 대해 디스크 스왑을 사용하여 "무제한 RAM"을 제공한다고 생각합니다. 맞습니까?
"RAM 부족으로 인해 프로세스가 종료"되면 어떻게 되나요?
답변1
"RAM 부족으로 인해 프로세스가 종료"되면 어떻게 되나요?
Linux는 기본적으로 애플리케이션 코드에서 추가 메모리 요청을 거부하지 않는다는 말이 있습니다 malloc()
. 예를 들어. 1 사실 이는 사실이 아닙니다. 경험적 방법은 기본적으로 사용됩니다.
명백한 주소 공간 초과 할당은 거부됩니다. 일반적인 시스템의 경우. 스왑 사용량을 줄이기 위해 과잉 커밋을 허용하는 동시에 심각한 할당 실패를 보장합니다.
출처 [linux_src]/Documentation/vm/overcommit-accounting
(모든 참조는 3.11 트리에서 유래). 정확히 무엇이 "심각하게 미친 할당"인지 명확하지 않으므로 자세한 내용을 확인하려면 소스 코드를 자세히 조사해야 합니다. 우리는 또한 각주 2(아래)의 실험적 접근 방식을 사용하여 휴리스틱에 대한 약간의 성찰을 시도할 수 있습니다. 이를 기반으로 나의 초기 경험적 관찰은 이상적인 상황(== 시스템 유휴)에서 수행하지 않으면 다음과 같습니다. 스왑이 없으면 RAM의 약 절반을 할당할 수 있으며, 스왑이 있는 경우에는 RAM의 절반에 모든 스왑을 더해 할당하게 됩니다. 즉, 다소간프로세스당(단, 이 제한 사항에 유의하시기 바랍니다.예동적이며 상태에 따라 변경될 수 있습니다. 각주 5의 일부 관찰 내용을 참조하세요.
RAM 절반과 스왑의 기본값은 명시적으로 입니다 /proc/meminfo
. 이것이 의미하는 바입니다. 실제로 방금 논의한 제한과는 아무 관련이 없습니다 [src]/Documentation/filesystems/proc.txt
.
제출 제한 사항:오버커밋 비율('vm.overcommit_ratio')을 기준으로 현재 할당 가능한 총 메모리 양입니다.시스템에서.이 제한은 엄격한 오버커밋 계정이 활성화된 경우("vm.overcommit_memory"의 모드 2)에만 적용됩니다. CommitLimit은 다음 공식을 사용하여 계산됩니다. CommitLimit = ('vm.overcommit_ratio' * 물리적 RAM) + 스왑 예를 들어 1G 물리적 RAM 및 7G 스왑이 있는 시스템에서 'vm.overcommit_ratio'가 30이면 CommitLimit은 다음과 같습니다. 7.3G.
앞서 인용한 초과 커밋 회계 문서에는 기본값이 vm.overcommit_ratio
50이라고 명시되어 있습니다. 따라서 를 sysctl vm.overcommit_memory=2
사용하여 vm.covercommit_ratio를 조정 sysctl
하고 결과를 확인할 수 있습니다. 3기본 모드는 CommitLimit
시행되지 않고 "명백한 주소 공간 오버 커밋을 거부"하는 경우입니다 vm.overcommit_memory=0
.
기본 정책에는 "심각하게 미친 할당"을 방지하기 위해 프로세스당 경험적 제한이 있지만, 심각하게 미친 할당을 위해 전체 시스템을 무료로 남겨둡니다. 4 이는 어느 시점에서 메모리가 부족할 수 있으며 이를 통과해야 함을 의미합니다.OOM 킬러.
OOM 킬러는 무엇을 죽이나요? 메모리가 없을 때 메모리를 요청한 프로세스일 필요는 없습니다. 이는 반드시 과실이 있는 프로세스는 아니며, 더 중요한 것은 시스템에서 현재 문제를 가장 잘 제거하는 프로세스가 아닐 수도 있기 때문입니다.
이것은 다음에서 인용되었습니다.여기2.6.x 소스 코드는 다음과 같이 참조될 수 있습니다.
/*
* oom_badness - calculate a numeric value for how bad this task has been
*
* The formula used is relatively simple and documented inline in the
* function. The main rationale is that we want to select a good task
* to kill when we run out of memory.
*
* Good in this context means that:
* 1) we lose the minimum amount of work done
* 2) we recover a large amount of memory
* 3) we don't kill anything innocent of eating tons of memory
* 4) we want to kill the minimum amount of processes (one)
* 5) we try to kill the process the user expects us to kill, this
* algorithm has been meticulously tuned to meet the principle
* of least surprise ... (be careful when you change it)
*/
이는 타당한 이유인 것 같습니다. 그러나 #5(#1과 중복)는 포렌식을 수행하지 않고는 구현하기 어려운 것으로 보이며, #3은 #2와 중복됩니다. 따라서 2/3번과 4번으로 줄이는 것이 합리적일 수 있습니다.
가장 최근 소스(3.11)를 확인했는데 그 동안 이 댓글이 변경된 것을 확인했습니다.
/**
* oom_badness - heuristic function to determine which candidate task to kill
*
* The heuristic for determining which task to kill is made to be as simple and
* predictable as possible. The goal is to return the highest value for the
* task consuming the most memory to avoid subsequent oom failures.
*/
이것은 #2에 대해 좀 더 명확합니다."목표는 후속 OOM 오류를 방지하기 위해 가장 많은 메모리를 소비하는 작업을 [종료]하는 것입니다."그리고 힌트 #4("우리는 최소한의 프로세스 수를 종료하고 싶습니다(하나)).
OOM 킬러가 실제로 무엇인지 알고 싶다면 각주 5를 참조하세요.
1다행히 Giles가 내 망상을 없앴습니다. 댓글을 참조하세요.
2 다음은 더 많은 메모리 요청이 실패할 때를 결정하기 위해 점점 더 큰 메모리 블록을 요청하는 간단한 C 코드입니다.
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#define MB 1 << 20
int main (void) {
uint64_t bytes = MB;
void *p = malloc(bytes);
while (p) {
fprintf (stderr,
"%lu kB allocated.\n",
bytes / 1024
);
free(p);
bytes += MB;
p = malloc(bytes);
}
fprintf (stderr,
"Failed at %lu kB.\n",
bytes / 1024
);
return 0;
}
C를 모르시면 컴파일 gcc virtlimitcheck.c -o virtlimitcheck
해서 실행 하시면 됩니다 ./virtlimitcheck
. 프로세스가 요청한 공간을 전혀 사용하지 않기 때문에 완전히 무해합니다. 즉, 실제로 RAM을 사용하지 않습니다.
4GB 시스템과 6GB 스왑이 포함된 3.11 x86_64 시스템에서는 ~7400000kB에서 실패했으며 숫자가 변동하므로 상태가 요인일 수 있습니다. 이는 CommitLimit
in 에 가깝지만 /proc/meminfo
이를 통해 수정해도 vm.overcommit_ratio
아무런 차이가 없습니다. 그러나 64MB의 스왑 공간을 갖춘 3.6.11 32비트 ARM 448MB 시스템에서는 약 230MB에서 실패했습니다. 첫 번째 경우에는 그 양이 RAM 양의 거의 두 배인 반면, 두 번째 경우에는 RAM 양의 약 1/4이기 때문에 이는 흥미롭습니다. 즉, 스왑 양이 중요한 요인임을 강력히 시사합니다. 첫 번째 시스템에서 스왑 영역을 닫으면 오류 임계값이 소형 ARM 상자와 매우 유사한 비율인 약 1.95GB로 떨어졌을 때 이를 확인했습니다.
그러나 이것이 실제로 모든 프로세스에 존재합니까? 그런 것 같습니다. 아래의 짧은 프로그램은 사용자에게 메모리 블록을 정의하도록 요청하고, 성공하면 Enter 키를 누를 때까지 기다립니다. 이렇게 하면 여러 동시 인스턴스를 시도할 수 있습니다.
#include <stdio.h>
#include <stdlib.h>
#define MB 1 << 20
int main (int argc, const char *argv[]) {
unsigned long int megabytes = strtoul(argv[1], NULL, 10);
void *p = malloc(megabytes * MB);
fprintf(stderr,"Allocating %lu MB...", megabytes);
if (!p) fprintf(stderr,"fail.");
else {
fprintf(stderr,"success.");
getchar();
free(p);
}
return 0;
}
그러나 이는 사용량에 관계없이 RAM 및 스왑 공간의 양과 엄격하게 관련되지는 않습니다. 시스템 상태 효과에 대한 관찰은 각주 5를 참조하십시오.
3은 CommitLimit
허용되는 주소 공간의 양을 나타냅니다.체계vm.overcommit_memory = 2인 경우. 아마도 할당할 수 있는 메모리 양은 분명히 필드인 커밋된 메모리 양을 빼야 합니다 Committed_AS
.
#include <unistd.h>
이를 입증하기 위한 잠재적으로 흥미로운 실험은 virtlimitcheck.c(각주 2 참조)의 맨 위에 추가 하고 루프 fork()
앞에 하나를 추가하는 것 입니다 while()
. 지루한 동기화 없이 여기에 설명된 방식으로 작동한다고 보장할 수는 없지만 YMMV에서는 그렇게 될 가능성이 높습니다.
> sysctl vm.overcommit_memory=2
vm.overcommit_memory = 2
> cat /proc/meminfo | grep Commit
CommitLimit: 9231660 kB
Committed_AS: 3141440 kB
> ./virtlimitcheck 2&> tmp.txt
> cat tmp.txt | grep Failed
Failed at 3051520 kB.
Failed at 6099968 kB.
이는 의미가 있습니다. tmp.txt를 자세히 살펴보면 하나의 프로세스가 분명히 충분한 리소스를 요구할 때까지 프로세스가 점점 더 큰 할당으로 번갈아가는 것을 볼 수 있습니다(출력에 pid를 넣으면 더 쉽습니다). 너무 많은 리소스로 인해 다른 프로세스가 발생합니다. 실패. 승자는 CommitLimit
음수까지 모든 것을 자유롭게 가져갈 수 있습니다 Committed_AS
.
4 가상 주소 지정과 요청 페이징을 이해하지 못한다면 우선 과잉 커밋을 일으키는 원인은 커널이 사용자 공간 프로세스에 할당하는 것이 물리적 메모리가 아니라 물리적 메모리라는 점입니다. .가상 주소 공간. 예를 들어 프로세스가 무언가를 위해 10MB를 예약하면 일련의 (가상) 주소로 배치되지만 이러한 주소는 아직 실제 메모리와 일치하지 않습니다. 해당 주소에 액세스하면 다음과 같은 결과가 발생합니다.페이지 오류그런 다음 커널은 실제 값을 저장할 수 있도록 이를 실제 메모리에 매핑하려고 시도합니다. 프로세스는 종종 실제로 액세스하는 것보다 더 많은 가상 공간을 예약하므로 커널이 RAM을 가장 효율적으로 사용할 수 있습니다. 그러나 물리적 메모리는 여전히 제한된 리소스이므로 모든 물리적 메모리가 가상 주소 공간에 매핑되면 일부 가상 주소 공간을 제거하여 일부 RAM을 확보해야 합니다.
처음 5개경고: 이것을 사용하는 경우 vm.overcommit_memory=0
시스템이 약 90초 동안 정지되고 일부 프로세스가 종료되므로 먼저 작업을 저장하고 중요한 응용 프로그램을 모두 닫으십시오!
아이디어는포크 폭탄90초 후 시간 초과, 포크는 공간을 할당하며, 그 중 일부는 stderr에 보고하는 동안 RAM에 대량의 데이터를 씁니다.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
/* 90 second "Verbose hungry fork bomb".
Verbose -> It jabbers.
Hungry -> It grabs address space, and it tries to eat memory.
BEWARE: ON A SYSTEM WITH 'vm.overcommit_memory=0', THIS WILL FREEZE EVERYTHING
FOR THE DURATION AND CAUSE THE OOM KILLER TO BE INVOKED. CLOSE THINGS YOU CARE
ABOUT BEFORE RUNNING THIS. */
#define STEP 1 << 30 // 1 GB
#define DURATION 90
time_t now () {
struct timeval t;
if (gettimeofday(&t, NULL) == -1) {
fprintf(stderr,"gettimeofday() fail: %s\n", strerror(errno));
return 0;
}
return t.tv_sec;
}
int main (void) {
int forks = 0;
int i;
unsigned char *p;
pid_t pid, self;
time_t check;
const time_t start = now();
if (!start) return 1;
while (1) {
// Get our pid and check the elapsed time.
self = getpid();
check = now();
if (!check || check - start > DURATION) return 0;
fprintf(stderr,"%d says %d forks\n", self, forks++);
// Fork; the child should get its correct pid.
pid = fork();
if (!pid) self = getpid();
// Allocate a big chunk of space.
p = malloc(STEP);
if (!p) {
fprintf(stderr, "%d Allocation failed!\n", self);
return 0;
}
fprintf(stderr,"%d Allocation succeeded.\n", self);
// The child will attempt to use the allocated space. Using only
// the child allows the fork bomb to proceed properly.
if (!pid) {
for (i = 0; i < STEP; i++) p[i] = i % 256;
fprintf(stderr,"%d WROTE 1 GB\n", self);
}
}
}
먼저 이것을 gcc forkbomb.c -o forkbomb
시도해 보십시오. sysctl vm.overcommit_memory=2
아마도 다음과 같은 결과를 얻을 것입니다.
6520 says 0 forks
6520 Allocation succeeded.
6520 says 1 forks
6520 Allocation succeeded.
6520 says 2 forks
6521 Allocation succeeded.
6520 Allocation succeeded.
6520 says 3 forks
6520 Allocation failed!
6522 Allocation succeeded.
이런 환경에서는 이런 종류의 갈래 폭탄은 멀리 가지 못할 것입니다. "N 분기라고 가정"의 숫자는 전체 프로세스 수가 아니라 해당 분기로 이어지는 체인/분기의 프로세스 수입니다.
지금 사용해 보세요 vm.overcommit_memory=0
. stderr를 파일로 리디렉션하면 다음과 같은 대략적인 분석을 수행할 수 있습니다.
> cat tmp.txt | grep failed
4641 Allocation failed!
4646 Allocation failed!
4642 Allocation failed!
4647 Allocation failed!
4649 Allocation failed!
4644 Allocation failed!
4643 Allocation failed!
4648 Allocation failed!
4669 Allocation failed!
4696 Allocation failed!
4695 Allocation failed!
4716 Allocation failed!
4721 Allocation failed!
15개의 프로세스만 1GB를 할당하지 못했습니다. 경험적으로 overcommit_memory = 0을 나타냅니다.예국가의 영향을 받습니다. 프로세스는 몇 개인가요? tmp.txt의 끝부분을 보면 아마도 100,000보다 클 것입니다. 이제 실제로 1GB를 어떻게 사용할 수 있습니까?
> cat tmp.txt | grep WROTE
4646 WROTE 1 GB
4648 WROTE 1 GB
4671 WROTE 1 GB
4687 WROTE 1 GB
4694 WROTE 1 GB
4696 WROTE 1 GB
4716 WROTE 1 GB
4721 WROTE 1 GB
8 - 당시 사용 가능한 RAM이 약 3GB이고 스왑 공간이 6GB였기 때문에 다시 한 번 의미가 있습니다.
이 작업을 수행한 후 시스템 로그를 확인하십시오. OOM 킬러 보고 점수를 볼 수 있습니다(아마도 이는 oom_badness
.
답변2
1G의 데이터만 메모리에 로드하는 경우에는 이런 일이 발생하지 않습니다. 더 많이로드하면 어떻게 되나요? 예를 들어, 나는 종종 R에 로드해야 하는 수백만 개의 확률이 포함된 대용량 파일을 사용하여 작업합니다. 여기에는 약 16GB의 RAM이 필요합니다.
내 노트북에서 위 프로세스를 실행하면 8GB RAM이 가득 차면 미친 듯이 교체가 시작됩니다. 결과적으로 디스크에서 읽는 것이 RAM에서 읽는 것보다 훨씬 느리기 때문에 모든 것이 느려집니다. 노트북에 RAM이 2GB인데 여유 공간이 10GB밖에 없다면 어떻게 되나요? 프로세스가 모든 RAM을 차지하면 스왑에 쓰기 때문에 디스크도 가득 차고 더 많은 RAM과 스왑 공간이 없습니다(사람들은 바로 이런 이유 때문에 스왑 파일 대신 전용 파티션으로 스왑을 제한하는 경향이 있습니다). . 여기서 OOM 킬러가 작동하여 프로세스 종료를 시작합니다.
따라서 시스템에 실제로 메모리가 부족할 수 있습니다. 또한 스와핑이 많은 시스템은 스와핑으로 인해 I/O 작업 속도가 느려지기 때문에 이러한 일이 발생하기 오래 전에 사용하지 못하게 될 수 있습니다. 사람들은 가능한 한 교환을 피하고 싶어하는 경우가 많습니다. 빠른 SSD를 탑재한 고급 서버에서도 성능이 크게 저하됩니다. 클래식 7200RPM 드라이브가 장착된 내 노트북에서는 상당한 교체가 발생하면 기본적으로 시스템을 사용할 수 없게 됩니다. 많이 바꾸면 속도가 느려집니다. 문제가 되는 프로세스를 신속하게 종료하지 않으면 OOM 킬러가 개입할 때까지 모든 것이 중단됩니다.
답변3
더 이상 RAM이 없어도 프로세스는 종료되지 않으며, 다음과 같이 속여도 종료됩니다.
- Linux 커널은 일반적으로 프로세스가 실제 사용 가능한 메모리(RAM의 일부 + 모든 스왑 영역)보다 큰 가상 메모리 양을 할당(예: 예약)할 수 있도록 허용합니다.
- 프로세스가 예약한 페이지의 하위 집합에만 액세스하는 한 모든 것이 잘 작동합니다.
- 일정 시간이 지난 후 프로세스가 자신이 소유한 페이지에 액세스하려고 시도했지만 더 이상 사용할 수 있는 페이지가 없는 경우 메모리 부족 상태가 발생합니다.
- OOM 킬러는 프로세스 중 하나(새 페이지를 요청한 프로세스일 필요는 없음)를 선택하고 이를 종료하여 가상 메모리를 복원합니다.
예를 들어 스왑 영역이 최대 절전 모드 데몬 메모리 페이지로 채워져 있는 경우와 같이 시스템이 적극적으로 스왑을 수행하지 않는 경우에도 이러한 현상이 발생할 수 있습니다.
메모리를 과도하게 사용하지 않는 운영 체제에서는 이런 일이 절대 발생하지 않습니다. 이를 사용하면 임의 프로세스가 종료되지 않지만 가상 메모리가 소진되면 가상 메모리를 요청하는 첫 번째 프로세스가 malloc(또는 이와 유사한)을 잘못 반환합니다. 따라서 상황을 적절하게 처리할 수 있는 기회가 있습니다. 그러나 이러한 운영 체제에서는 사용 가능한 RAM이 있어도 시스템에 가상 메모리가 부족할 수 있는데, 이는 다소 혼란스럽고 종종 오해되는 상황입니다.
답변4
다른 답변에서 또 다른 관점을 추가하기 위해 많은 VPS는 특정 서버에서 여러 가상 머신을 호스팅합니다. 모든 개별 가상 머신에는 자체 사용을 위해 지정된 양의 RAM이 있습니다. 많은 공급자는 지정된 양보다 더 많은 RAM을 사용할 수 있는 "버스트 RAM"을 제공합니다. 이는 단기간 사용에만 해당되며, 이 연장된 기간을 초과하는 사용자는 호스트 과부하로 인해 다른 사용자가 영향을 받지 않도록 RAM 사용량을 줄이기 위해 호스트 종료 프로세스로 인해 불이익을 받을 수 있습니다.