อาจเกิดอะไรขึ้นถ้ากระบวนการ "ถูกฆ่าเนื่องจาก RAM ต่ำ"
บางครั้งก็บอกว่าลินุกซ์ที่เริ่มต้นไม่เคยปฏิเสธการร้องขอสำหรับหน่วยความจำเพิ่มเติมจากรหัสโปรแกรม - malloc()
เช่น 1 สิ่งนี้ไม่เป็นความจริง ค่าเริ่มต้นจะใช้การวิเคราะห์พฤติกรรมด้วยเหตุนี้
ชัดเจนเกินขอบเขตของที่อยู่จะถูกปฏิเสธ ใช้สำหรับระบบทั่วไป มันช่วยให้มั่นใจได้ว่าการจัดสรรที่รุนแรงจะล้มเหลวในขณะที่ยอมให้ overcommit ลดการใช้ swap
จาก[linux_src]/Documentation/vm/overcommit-accounting
(ราคาทั้งหมดมาจากต้นไม้ 3.11) สิ่งที่นับว่าเป็นการ "จัดสรรป่าอย่างจริงจัง" นั้นไม่ชัดเจนดังนั้นเราจะต้องผ่านแหล่งข้อมูลเพื่อกำหนดรายละเอียด เราสามารถใช้วิธีการทดลองในเชิงอรรถ 2 (ด้านล่าง) เพื่อลองและรับการสะท้อนของฮิวริสติก - จากนั้นการสังเกตเชิงประจักษ์เริ่มต้นของฉันคือภายใต้สถานการณ์ในอุดมคติ (== ระบบไม่ได้ทำงาน) ถ้าคุณไม่ได้ ' ไม่มีการสลับใด ๆ คุณจะได้รับอนุญาตให้จัดสรร RAM ครึ่งหนึ่งของคุณและถ้าคุณมีการสลับคุณจะได้รับ RAM ประมาณครึ่งหนึ่งบวกกับการสลับทั้งหมดของคุณ นั่นคือมากขึ้นหรือน้อยลงต่อกระบวนการ (แต่โปรดทราบว่าขีด จำกัด นี้เป็นแบบไดนามิกและอาจมีการเปลี่ยนแปลงเนื่องจากสถานะให้ดูข้อสังเกตบางอย่างในเชิงอรรถ 5)
ครึ่ง RAM บวกแลกเปลี่ยนของคุณอย่างชัดเจนเริ่มต้นสำหรับ "CommitLimit" /proc/meminfo
ในสนาม นี่คือความหมาย - และโปรดทราบว่าจริง ๆ แล้วไม่มีอะไรเกี่ยวข้องกับขีด จำกัด ที่เพิ่งกล่าวถึง (จาก[src]/Documentation/filesystems/proc.txt
):
CommitLimit:ขึ้นอยู่กับอัตราส่วน overcommit ('vm.overcommit_ratio') นี่เป็นจำนวนหน่วยความจำทั้งหมดที่มีอยู่ในปัจจุบันซึ่งสามารถจัดสรรให้กับระบบได้ ข้อ จำกัด นี้จะปฏิบัติตามเฉพาะเมื่อเปิดใช้งานการบัญชี overcommit ที่เข้มงวด (โหมด 2 ใน 'vm.overcommit_memory') CommitLimit ถูกคำนวณด้วยสูตรต่อไปนี้: CommitLimit = ('vm.overcommit_ratio' * ฟิสิคัลแรม) + Swap ตัวอย่างเช่นบนระบบที่มี 1G ของฟิสิคัลแรมและ 7G ของการสลับกับ 'vm.overcommit_ratio' 30 CommitLimit 7.3G
เอกสาร overcommit-accounting ที่ยกมาก่อนหน้านี้ระบุว่าค่าเริ่มต้นvm.overcommit_ratio
คือ 50 ดังนั้นหากคุณsysctl vm.overcommit_memory=2
คุณสามารถปรับ vm.covercommit_ratio (พร้อมsysctl
) และดูผลที่ตามมา 3 โหมดเริ่มต้นเมื่อCommitLimit
ไม่ได้บังคับใช้และมีเพียง "overcommits ชัดเจนของพื้นที่ที่อยู่จะปฏิเสธ" 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 killer ดูเชิงอรรถ 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 ใด ๆ เลย
สำหรับระบบ 3.11 x86_64 ที่มีระบบ 4 GB และการแลกเปลี่ยน 6 GB ฉันล้มเหลวที่ ~ 7400000 kB; จำนวนผันผวนดังนั้นรัฐอาจเป็นปัจจัย นี่เป็นเรื่องบังเอิญใกล้กับCommitLimit
ใน/proc/meminfo
แต่การแก้ไขผ่านทางนี้vm.overcommit_ratio
ไม่ได้สร้างความแตกต่างใด ๆ สำหรับระบบ ARM 448 MB ขนาด 3.6.11 32 บิตพร้อม swap 64 MB แต่ฉันล้มเหลวที่ ~ 230 MB สิ่งนี้น่าสนใจเนื่องจากในกรณีแรกจำนวนเกือบสองเท่าของจำนวน RAM ในขณะที่ในครั้งที่สองนั้นประมาณ 1/4 ซึ่งหมายความว่าจำนวน swap อย่างมากเป็นปัจจัย สิ่งนี้ได้รับการยืนยันโดยการปิดสวิตช์ในระบบแรกเมื่อเกณฑ์ความล้มเหลวลดลงเหลือ ~ 1.95 GB อัตราส่วนที่คล้ายกันมากกับกล่อง 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ในตอนนี้ถ้าคุณยังไม่เข้าใจการกำหนดที่อยู่เสมือนจริงและความต้องการการเพจว่าสิ่งที่ทำให้เกิดความมุ่งมั่นที่เป็นไปได้ในตอนแรกก็คือสิ่งที่เคอร์เนลจัดสรรให้กับกระบวนการ userland นั้นไม่ใช่หน่วยความจำทางกายภาพเลยพื้นที่ที่อยู่เสมือน ตัวอย่างเช่นหากกระบวนการสำรอง 10 MB สำหรับบางสิ่งนั่นแสดงว่าเป็นลำดับของที่อยู่ (เสมือน) แต่ที่อยู่เหล่านั้นยังไม่ตรงกับหน่วยความจำกายภาพ เมื่อมีการเข้าถึงที่อยู่ดังกล่าวจะส่งผลให้เกิดความผิดพลาดของหน้าจากนั้นเคอร์เนลพยายามแมปมันลงในหน่วยความจำจริงเพื่อให้สามารถเก็บค่าจริง โดยทั่วไปกระบวนการจะจองพื้นที่เสมือนมากกว่าที่จะเข้าถึงจริงซึ่งช่วยให้เคอร์เนลสามารถใช้ RAM ได้อย่างมีประสิทธิภาพสูงสุด อย่างไรก็ตามหน่วยความจำกายภาพยังคงเป็นทรัพยากรที่มีอยู่อย่าง จำกัด และเมื่อทั้งหมดถูกแมปไปยังพื้นที่ที่อยู่เสมือนพื้นที่ที่อยู่เสมือนบางแห่งจะต้องถูกกำจัดเพื่อให้ RAM ว่างขึ้น
5 คำเตือนครั้งแรก: หากคุณลองทำสิ่งนี้vm.overcommit_memory=0
ตรวจสอบให้แน่ใจว่าคุณได้บันทึกงานของคุณก่อนและปิดแอปพลิเคชันที่สำคัญใด ๆ เนื่องจากระบบจะถูกแช่แข็งเป็นเวลา ~ 90 วินาทีและกระบวนการบางอย่างจะตาย!
ความคิดคือการรันfork bombที่หมดเวลาหลังจาก 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.
ในสภาพแวดล้อมนี้ส้อมระเบิดชนิดนี้อยู่ไม่ไกลมาก โปรดทราบว่าหมายเลขใน "says N forks" ไม่ใช่จำนวนกระบวนการทั้งหมดเป็นจำนวนกระบวนการในห่วงโซ่ / สาขาที่นำไปสู่กระบวนการนั้น
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 กระบวนการล้มเหลวในการจัดสรร 1 GB - แสดงให้เห็นว่าการแก้ปัญหาสำหรับ overcommit_memory = 0 จะได้รับผลกระทบโดยรัฐ มีกระบวนการกี่กระบวนการ? ดูตอนท้ายของ tmp.txt อาจ> 100,000 ตอนนี้อาจจะต้องใช้ 1 GB อย่างไร
> 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
แปด - ซึ่งทำให้รู้สึกอีกครั้งตั้งแต่ตอนที่ฉันมี ~ 3 GB RAM ฟรีและ 6 GB ของการแลกเปลี่ยน
ดูบันทึกระบบของคุณหลังจากที่คุณทำเช่นนี้ คุณควรเห็นคะแนนการรายงานของนักฆ่า OOM (เหนือสิ่งอื่นใด); oom_badness
สันนิษฐานนี้เกี่ยวข้องกับ