यदि एक प्रक्रिया "कम रैम के कारण मार दी जाती है" तो क्या हो सकता है?
यह कभी-कभी कहा जाता है कि डिफ़ॉल्ट रूप से लिनक्स कभी भी एप्लिकेशन कोड से अधिक मेमोरी के अनुरोधों से इनकार नहीं करता है - जैसे malloc()
। 1 यह वास्तव में सच नहीं है; डिफ़ॉल्ट एक अनुमानी का उपयोग करता है जिससे
पता स्थान के स्पष्ट overcommits मना कर दिया जाता है। एक विशिष्ट प्रणाली के लिए उपयोग किया जाता है। यह ओवरलैपिट को स्वैप उपयोग को कम करने की अनुमति देते समय एक गंभीर रूप से जंगली आवंटन सुनिश्चित करता है।
से [linux_src]/Documentation/vm/overcommit-accounting
(सभी उद्धरण 3.11 पेड़ से हैं)। वास्तव में "गंभीर रूप से जंगली आवंटन" के रूप में जो मायने रखता है वह स्पष्ट नहीं किया गया है, इसलिए हमें विवरण निर्धारित करने के लिए स्रोत से गुजरना होगा। हम फुटनोट 2 (नीचे) में प्रयोगात्मक विधि का उपयोग करने की कोशिश कर सकते हैं और हेयुरिस्टिक के कुछ प्रतिबिंब प्राप्त कर सकते हैं - इसके आधार पर, मेरा प्रारंभिक अनुभवजन्य अवलोकन यह है कि आदर्श परिस्थितियों में (== प्रणाली निष्क्रिय है), यदि आप डॉन ' t में कोई स्वैप है, आपको लगभग आधी RAM आवंटित करने की अनुमति होगी, और यदि आपके पास स्वैप है, तो आपको लगभग आधा RAM और आपके सभी स्वैप मिल जाएंगे। यही कारण है कि कम या ज्यादा है प्रक्रिया के अनुसार (लेकिन ध्यान दें कि यह सीमा है , गतिशील और क्योंकि राज्य के परिवर्तन के अधीन, फुटनोट 5 में कुछ टिप्पणियों को देखें)।
आधा आपका रैम प्लस स्वैप स्पष्ट रूप से "कमिटिमिट" फ़ील्ड के लिए डिफ़ॉल्ट है /proc/meminfo
। यहाँ इसका क्या अर्थ है - और ध्यान दें कि इसका वास्तव में चर्चा की गई सीमा से कोई लेना-देना नहीं है [src]/Documentation/filesystems/proc.txt
:
कमिटमेंट: ओवरकमिटी अनुपात ('vm.overcommit_ratio') के आधार पर, यह वर्तमान में सिस्टम पर आवंटित की जाने वाली मेमोरी की कुल राशि है। यह सीमा केवल तभी मनाई जाती है जब सख्त ओवरकॉमिटिंग अकाउंटिंग सक्षम हो (मोड 2 इन 'vm.overcommit_memory/)। कमेटीमिट की गणना निम्न सूत्र से की जाती है: कमिटमिट = ('vm.overcommit_ratio' * फिजिकल रैम) + स्वैप उदाहरण के लिए, एक सिस्टम पर फिजिकल रैम के 1G और 30G के 'vm.overcommit_ratio' के साथ स्वैप का 7G होगा। 7.3G का एक कमिटमेंट।
पहले से उद्धृत ओवरकमिट-अकाउंटिंग डॉक में कहा गया है कि डिफ़ॉल्ट 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 के बारे में थोड़ा और स्पष्ट रूप से है: "लक्ष्य [मारना है] बाद की ऊम विफलताओं से बचने के लिए सबसे अधिक मेमोरी का उपभोग करने वाला कार्य," और निहितार्थ # 4 ( "हम प्रक्रियाओं की न्यूनतम मात्रा ( एक ) को मारना चाहते हैं" ) ) का है ।
यदि आप OOM हत्यारे को कार्रवाई में देखना चाहते हैं, तो फुटनोट 5 देखें।
1 एक भ्रम गाइल्स ने मुझे धन्यवाद दिया, टिप्पणी देखें।
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
। यह पूरी तरह से हानिरहित है, क्योंकि यह प्रक्रिया किसी भी स्थान का उपयोग नहीं करती है जो इसके लिए पूछता है - यानी, यह वास्तव में किसी भी रैम का उपयोग नहीं करता है।
4 जीबी सिस्टम और 6 जीबी स्वैप के साथ 3.11 x86_64 सिस्टम पर, मैं ~ 7400000 केबी पर विफल रहा; संख्या में उतार-चढ़ाव होता है, इसलिए शायद राज्य एक कारक है। यह संयोग के करीब है CommitLimit
में /proc/meminfo
है, लेकिन इस के माध्यम से संशोधित vm.overcommit_ratio
कोई फर्क नहीं है। 64 एमबी स्वैप के साथ 3.6.11 32-बिट एआरएम 448 एमबी प्रणाली पर, हालांकि, मैं ~ 230 एमबी पर विफल रहता हूं। यह दिलचस्प है क्योंकि पहले मामले में यह राशि रैम की मात्रा लगभग दोगुनी है, जबकि दूसरे में यह लगभग 1/4 है - जो कि स्वैप की मात्रा को दृढ़ता से लागू करना एक कारक है। पहली प्रणाली को स्वैप बंद करके इसकी पुष्टि की गई थी, जब विफलता सीमा ~ 1.95 जीबी हो गई, थोड़ा एआरएम बॉक्स के समान अनुपात।
लेकिन क्या यह वास्तव में प्रति प्रक्रिया है? यह प्रतीत होता है। नीचे दिया गया छोटा प्रोग्राम उपयोगकर्ता के लिए मेमोरी का एक हिस्सा निर्धारित करता है, और यदि यह सफल होता है, तो आपको रिटर्न हिट करने के लिए इंतजार करता है - इस तरह से आप एक साथ कई उदाहरणों की कोशिश कर सकते हैं:
#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;
}
हालांकि, सावधान रहें कि यह रैम की मात्रा के बारे में कड़ाई से नहीं है और उपयोग की परवाह किए बिना स्वैप - सिस्टम स्थिति के प्रभावों के बारे में टिप्पणियों के लिए फुटनोट 5 देखें।
3 सिस्टम केCommitLimit
लिए अनुमत पता स्थान की मात्रा को संदर्भित करता है जब vm.overcommit_memory = 2. संभवत: तब, आप जितनी राशि आवंटित कर सकते हैं, वह न्यूनतम होनी चाहिए जो पहले से ही प्रतिबद्ध है, जो कि स्पष्ट रूप से Committed_AS
क्षेत्र है।
एक संभावित दिलचस्प प्रयोग यह प्रदर्शित करता है #include <unistd.h>
कि सदाशिवचिट्ठ के शीर्ष (फुटनोट 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 को विस्तार से देखते हुए आप प्रक्रियाओं को उनके बड़े और बड़े आवंटन को वैकल्पिक रूप से देख सकते हैं (यह आसान है यदि आप आउटपुट में पिड फेंकते हैं), एक तक, जाहिर है, पर्याप्त दावा किया है कि दूसरा विफल रहता है। विजेता तब सब कुछ CommitLimit
माइनस तक हड़पने के लिए स्वतंत्र है Committed_AS
।
4 यह ध्यान देने योग्य बात है, इस बिंदु पर, यदि आप पहले से ही वर्चुअल एड्रेसिंग और डिमांड पेजिंग को नहीं समझते हैं, तो यह कि पहली जगह पर प्रतिबद्धता के बारे में क्या संभव है कि कर्नेल उपयोगकर्ता प्रक्रियाओं को आवंटित करता है, यह शारीरिक मेमोरी नहीं है - यह वर्चुअल एड्रेस स्पेस । उदाहरण के लिए, यदि कोई प्रक्रिया किसी चीज़ के लिए 10 एमबी रखती है, तो उसे (आभासी) पतों के अनुक्रम के रूप में रखा जाता है, लेकिन वे पते अभी तक भौतिक स्मृति के अनुरूप नहीं हैं। जब ऐसा पता एक्सेस किया जाता है, तो इसका परिणाम पृष्ठ दोष होता हैऔर फिर कर्नेल इसे वास्तविक मेमोरी पर मैप करने का प्रयास करता है ताकि यह एक वास्तविक मूल्य स्टोर कर सके। प्रक्रियाएं आमतौर पर बहुत अधिक आभासी स्थान आरक्षित करती हैं, क्योंकि वे वास्तव में उपयोग करते हैं, जो कर्नेल को रैम का सबसे कुशल उपयोग करने की अनुमति देता है। हालाँकि, भौतिक मेमोरी अभी भी एक सीमित संसाधन है और जब सभी को वर्चुअल एड्रेस स्पेस में मैप कर दिया गया है, तो कुछ रैम को खाली करने के लिए कुछ वर्चुअल एड्रेस स्पेस को खत्म करना होगा।
5 पहली चेतावनी : यदि आप इसके साथ प्रयास करते हैं vm.overcommit_memory=0
, तो सुनिश्चित करें कि आप पहले अपना काम बचा लें और किसी भी महत्वपूर्ण एप्लिकेशन को बंद कर दें, क्योंकि सिस्टम ~ 90 सेकंड के लिए फ्रीज हो जाएगा और कुछ प्रक्रिया मर जाएगी!
यह विचार एक कांटा बम चलाने का है जो 90 सेकंड के बाद बाहर निकलता है, कांटे अंतरिक्ष आवंटित करते हैं और उनमें से कुछ रैम को बड़ी मात्रा में डेटा लिखते हैं, जबकि सभी 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.
इस वातावरण में, इस तरह का कांटा बम बहुत दूर नहीं मिलता है। ध्यान दें कि "एन फोर्क्स" में संख्या प्रक्रियाओं की कुल संख्या नहीं है, यह श्रृंखला / शाखा में प्रक्रियाओं की संख्या है जो उस तक जाती है।
अब इसके साथ प्रयास करें vm.overcommit_memory=0
। यदि आप किसी फ़ाइल में स्टेटर को पुनर्निर्देशित करते हैं, तो आप बाद में कुछ कच्चे विश्लेषण कर सकते हैं, जैसे:
> 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 प्रक्रियाओं 1 जीबी का आवंटन करने में विफल रहा है राज्य से प्रभावित। कितनी प्रक्रियाएँ थीं? Tmp.txt के अंत को देखते हुए, शायद> 100,000। अब वास्तव में 1 जीबी का उपयोग कैसे किया जा सकता है?
> 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 जीबी रैम मुफ्त और 6 जीबी स्वैप था।
ऐसा करने के बाद अपने सिस्टम लॉग पर एक नज़र डालें। आपको OOM किलर रिपोर्टिंग स्कोर (अन्य चीजों के बीच) देखना चाहिए; संभवतः इससे संबंधित है oom_badness
।