나는 사람들의 웹 주위에 여러 개의 게시물이 너무 많은 RAM을 사용했기 때문에 호스팅 VPS가 예기치 않게 프로세스를 종료한다고 불평하는 것을 보았습니다.
이것이 어떻게 가능한지? 모든 최신 OS는 물리적 RAM을 넘어 서면 디스크 스왑을 사용하여 “무한 RAM”을 제공한다고 생각했습니다. 이 올바른지?
“RAM이 부족하여 프로세스가 종료 된 경우”어떻게됩니까?
답변
“RAM이 부족하여 프로세스가 종료 된 경우”어떻게됩니까?
때때로 리눅스는 기본적으로 애플리케이션 코드에서 더 많은 메모리에 대한 요청을 거부하지 않습니다 malloc()
. 1 이것은 사실이 아닙니다. 기본적으로 휴리스틱을 사용합니다.
주소 공간의 명백한 초과 커밋은 거부됩니다. 일반적인 시스템에 사용됩니다. 오버 커밋을 통해 스왑 사용량을 줄이면서 심각한 와일드 할당 실패를 보장합니다.
From [linux_src]/Documentation/vm/overcommit-accounting
(모든 따옴표는 3.11 트리에서 온 것입니다). 정확히 “심각한 와일드 할당”으로 간주되는 것은 명확하지 않으므로 세부 정보를 확인하려면 소스를 살펴 봐야합니다. 우리는 또한 각주 2 (아래)의 실험 방법을 사용하여 휴리스틱을 반영하려고 시도 할 수 있습니다. 스왑이 없으면 RAM의 약 절반을 할당 할 수 있으며 스왑이있는 경우 RAM의 약 절반과 모든 스왑을 얻을 수 있습니다. 프로세스마다 다소 차이 가 있습니다 (그러나이 제한 은 동적이며 상태로 인해 변경 될 수 있습니다. 각주 5의 일부 관찰 참조).
RAM 및 스왑의 절반은 명시 적으로의 “CommitLimit”필드에 대한 기본값입니다 /proc/meminfo
. 이것이 의미하는 바는 다음과 같습니다 (방금 논의한 한도와는 아무런 관련이 없음 [src]/Documentation/filesystems/proc.txt
).
CommitLimit : 오버 커밋 비율 ( ‘vm.overcommit_ratio’)에 따라 현재 시스템에 할당 할 수있는 총 메모리 양입니다 . 이 제한은 엄격한 오버 커밋 계정이 활성화 된 경우에만 준수됩니다 ( ‘vm.overcommit_memory’의 모드 2). CommitLimit은 다음 공식으로 계산됩니다. CommitLimit = ( ‘vm.overcommit_ratio’* 실제 RAM) + 스왑 예를 들어, 물리적 RAM이 1G이고 ‘vm.overcommit_ratio’가 30 인 7G 스왑이있는 시스템에서는 7.3G의 CommitLimit.
이전에 인용 된 초과 커미트 회계 문서에는 기본값 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 망상 Gilles는 고맙게도 나를 제거했습니다. 의견을 참조하십시오.
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 시스템에서 ~ 7400000 kB에서 실패했습니다. 숫자가 변동하므로 상태가 요인 일 수 있습니다. 이것은 우연히 CommitLimit
in에 가깝지만 /proc/meminfo
via를 수정 vm.overcommit_ratio
해도 아무런 차이가 없습니다. 그러나 64MB의 스왑이있는 3.6.11 32 비트 ARM 448MB 시스템에서는 ~ 230MB에서 실패합니다. 첫 번째 경우에는 RAM의 양이 거의 두 배이므로 두 번째는 약 1/4이므로 스왑의 양을 크게 암시합니다. 이는 실패 임계 값이 ~ 1.95GB로 떨어졌을 때 첫 번째 시스템에서 스왑을 해제하여 확인한 것으로, 작은 ARM 상자와 매우 유사한 비율입니다.
그러나 이것은 실제로 프로세스 당입니까? 것 같습니다. 아래의 짧은 프로그램은 사용자 정의 메모리 청크를 요청하고 성공하면 리턴을 누르기를 기다립니다.이 방법으로 여러 개의 동시 인스턴스를 시도 할 수 있습니다.
#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 초 후에 시간이 초과 되는 포크 폭탄 을 실행하는 것입니다. 포크 는 공간을 할당하고 일부는 RAM에 많은 양의 데이터를 쓰고 stderr 에보 고합니다.
#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!
= overcommit_memory에 0의 발견이 있음을 보여주는 – 15 과정은 1GB의 할당 실패 입니다 상태에 의해 영향을 미쳤다. 몇 개의 프로세스가 있었습니까? tmp.txt의 끝을 살펴보면 아마 10 만 이상일 것입니다. 이제 실제로 어떻게 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
여덟-다시 RAM이 3GB이고 스왑이 6GB 였기 때문에 다시 말이됩니다.
이 작업을 수행 한 후 시스템 로그를 살펴보십시오. OOM 킬러보고 점수 (다른 것들 중에서도)가 표시됩니다. 아마도 이것은와 관련이 oom_badness
있습니다.
답변
1G의 데이터 만 메모리에로드하는 경우에는 발생하지 않습니다. 훨씬 더 많이 적재하면 어떻게 되나요? 예를 들어, 종종 R에로드해야하는 수백만 개의 확률을 포함하는 거대한 파일을 사용하여 작업합니다. 약 16GB의 RAM이 필요합니다.
내 랩톱에서 위의 프로세스를 실행하면 8GB의 RAM이 채워지는 즉시 미친 것처럼 스왑이 시작됩니다. 그러면 디스크에서 읽는 것이 RAM에서 읽는 것보다 훨씬 느리기 때문에 모든 것이 느려집니다. RAM이 2GB이고 여유 공간이 10GB 인 랩톱이있는 경우 어떻게합니까? 프로세스가 모든 RAM을 가져 오면 스왑에 쓰기 중이므로 디스크가 가득 차고 더 이상 RAM과 스왑 공간이 남지 않습니다 (사람들은 스왑을 전용 파티션으로 제한하지 않는 경향이 있습니다) 바로 그 이유로 스왑 파일). OOM 킬러가 들어 와서 킬링 프로세스가 시작됩니다.
따라서 시스템에 실제로 메모리가 부족할 수 있습니다. 또한 스와핑으로 인한 I / O 작업이 느리기 때문에 이러한 문제가 발생하기 훨씬 전에 스와핑 시스템을 사용할 수 없게됩니다. 일반적으로 가능한 한 많이 교환하지 않기를 원합니다. 빠른 SSD가 장착 된 고급 서버에서도 성능이 크게 저하됩니다. 클래식 7200RPM 드라이브가 장착 된 랩톱에서 중요한 스와핑은 본질적으로 시스템을 사용할 수 없게 만듭니다. 스왑이 많을수록 느려집니다. 문제가되는 프로세스를 중단시키지 않으면 OOM 킬러가 들어올 때까지 모든 것이 중단됩니다.
답변
더 이상 RAM이 없을 때 프로세스가 종료되지 않고 이러한 방식으로 속이는 경우 프로세스가 종료됩니다.
- 리눅스 커널은 일반적으로 프로세스가 실제로 사용 가능한 것보다 더 많은 양의 가상 메모리를 할당 (즉, 예약) 할 수있게합니다 (RAM의 일부 + 모든 스왑 영역)
- 프로세스가 예약 한 페이지의 하위 집합에만 액세스하는 한 모든 것이 제대로 실행됩니다.
- 일정 시간이 지나면 프로세스가 소유 한 페이지에 액세스하려고하지만 더 이상 사용 가능한 페이지가 없으면 메모리 부족 상황이 발생합니다.
- OOM 킬러는 새 페이지를 요청한 프로세스가 아닌 프로세스 중 하나를 선택하고 가상 메모리를 복구하기 위해 프로세스를 종료합니다.
스왑 영역이 휴면 데몬 메모리 페이지로 채워진 경우와 같이 시스템이 활발하게 스와핑되지 않은 경우에도 발생할 수 있습니다.
메모리를 초과 커밋하지 않는 OS에서는 이런 일이 발생하지 않습니다. 그것들을 사용하면 임의의 프로세스가 종료되지 않지만 가상 메모리가 소진되는 동안 요청하는 첫 번째 프로세스에는 malloc (또는 유사한) 오류가 발생합니다. 따라서 상황을 올바르게 처리 할 수있는 기회가 주어집니다. 그러나 이러한 OS에서는 여전히 사용 가능한 RAM이있는 동안 시스템에 가상 메모리가 부족할 수 있으며, 이는 상당히 혼란스럽고 일반적으로 오해됩니다.
답변
사용 가능한 RAM이 소진되면 커널은 처리 비트를 디스크로 교체하기 시작합니다. 실제로, 커널은 RAM이 거의 소진 될 때 a를 교환하기 시작합니다. 유휴 모멘트가있을 때 사전에 교환을 시작하여 응용 프로그램이 갑자기 더 많은 메모리를 필요로하는 경우보다 신속하게 응답합니다.
RAM은 프로세스 메모리를 저장하는 데만 사용되지 않습니다. 일반적인 정상 시스템에서는 RAM이 절반 만 프로세스에 사용되고 나머지 절반은 디스크 캐시 및 버퍼에 사용됩니다. 이는 실행중인 프로세스와 파일 입력 / 출력간에 균형이 잘 맞습니다.
스왑 공간은 무한하지 않습니다. 프로세스가 점점 더 많은 메모리를 계속 할당하면 RAM의 스필 오버 데이터가 스왑을 채 웁니다. 이 경우 더 많은 메모리를 요청하려는 프로세스는 요청이 거부 된 것을 볼 수 있습니다.
기본적으로 Linux는 메모리를 초과 커밋 합니다. 이는 프로세스가 예약되었지만 사용하지 않은 메모리로 프로세스를 실행할 수 있음을 의미합니다. 오버 커밋을 가진 주된 이유는 방법이다 분기 작품. 프로세스가 하위 프로세스를 시작하면 자식 프로세스는 개념적으로 부모의 메모리 복제본에서 작동합니다. 두 프로세스는 처음에 동일한 내용의 메모리를 갖지만 프로세스가 자체 공간에서 각각 변경하면 내용이 달라집니다. 이를 완전히 구현하려면 커널은 부모의 모든 메모리를 복사해야합니다. 이렇게하면 분기 속도가 느려져 커널 이 쓰기시 복사를 수행합니다. . 처음에는 아이가 부모와 모든 기억을 공유합니다. 프로세스가 공유 페이지에 쓸 때마다 커널은 해당 페이지의 사본을 만들어 공유를 중단합니다.
종종 어린이는 많은 페이지를 그대로 둡니다. 커널이 각 포크에 부모의 메모리 공간을 복제하기에 충분한 메모리를 할당하면 자식 프로세스가 사용하지 않을 예약에 많은 메모리가 낭비됩니다. 따라서 오버 커밋 : 커널 은 자식이 필요로하는 페이지 수를 추정 하여 해당 메모리의 일부만 예약 합니다.
프로세스가 일부 메모리를 할당하려고 시도하고 메모리가 충분하지 않으면 프로세스는 오류 응답을 수신하고 적절하게 처리합니다. 프로세스가 공유 해제해야하는 공유 페이지에 기록하여 간접적으로 메모리를 요청하는 경우에는 다른 이야기입니다. 이 상황을 응용 프로그램에보고 할 수있는 방법은 없습니다. 여기에는 쓰기 가능한 데이터가 있고 읽을 수 있다고 생각합니다. 글쓰기는 단지 좀 더 정교한 작업이 필요하다는 것입니다. 커널이 새 메모리 페이지를 제공 할 수 없으면 요청 프로세스를 종료하거나 메모리를 채우기 위해 다른 프로세스를 종료하기 만하면됩니다.
이 시점에서 요청 프로세스를 종료하는 것이 확실한 해결책이라고 생각할 수 있습니다. 그러나 실제로 이것은 그렇게 좋지 않습니다. 프로세스는 현재 페이지 중 하나에 만 액세스해야하는 중요한 프로세스 일 수 있지만 덜 중요하지 않은 다른 프로세스가 실행될 수 있습니다. 따라서 커널에는 복잡한 휴리스틱이 포함되어있어 유명한 OOM 킬러 인 프로세스를 죽일 프로세스를 선택할 수 있습니다 .
답변
다른 답변에서 다른 관점으로 추가하기 위해 많은 VPS는 특정 서버에서 여러 가상 시스템을 호스팅합니다. 모든 단일 VM에는 자체 사용을 위해 지정된 양의 RAM이 있습니다. 많은 공급 업체가 “버스트 RAM”을 제공하며,이를 통해 지정된 양을 초과하여 RAM을 사용할 수 있습니다. 이는 단기간 사용을위한 것으로,이 시간을 초과하는 사람들은 사용중인 RAM의 양을 줄이기 위해 프로세스를 종료하는 호스트에 의해 처벌을 받아 다른 사람들이 겪지 않도록 할 수 있습니다 오버로드 된 호스트 시스템
답변
언젠가 리눅스는 외부 가상 공간을 차지합니다. 이것이 스왑 파티션입니다. Ram이 채워지면 Linux는이 스왑 영역을 사용하여 우선 순위가 낮은 프로세스를 실행합니다.