Bir işlem "düşük RAM nedeniyle öldürülürse" ne olabilir?
Bazen linux'un varsayılan olarak uygulama kodundan daha fazla bellek isteğini hiçbir zaman reddetmediği söylenir - ör malloc()
. 1 Bu aslında doğru değil; varsayılan, bir sezgisel tarama kullanır.
Açıkça adres alanı fazlalıkları reddedilir. Tipik bir sistem için kullanılır. Takas kullanımını azaltmak için fazla mesai yapılmasına izin verirken ciddi bir şekilde ayırmanın başarısız olmasını sağlar.
Kimden [linux_src]/Documentation/vm/overcommit-accounting
(tüm alıntılar 3.11 ağacından alınmıştır). "Ciddi derecede vahşi bir tahsis" olarak kabul edilen şey tam olarak açıklığa kavuşturulamamıştır, bu yüzden ayrıntıları belirlemek için kaynaktan geçmemiz gerekir. Ayrıca, sezgisel bir bakış açısı elde etmek için dipnot 2'deki (aşağıda) deneysel yöntemi kullanabiliriz - buna dayanarak, ilk deneysel gözlemim, ideal koşullar altında (== sistem boştaysa), Herhangi bir takasınız yoksa, RAM'inizin yaklaşık yarısını ayırmanıza izin verilir ve takasınız varsa, RAM'inizin yaklaşık yarısını artı tüm takaslarınızı alırsınız. Yani daha fazla veya daha az işlem başına (ancak bu sınır not olduğunu , dinamik ve çünkü devletin değişikliğine tabi dipnot 5'de bazı gözlemler bakınız).
RAM artı takasınızın yarısı, "CommandLimit" alanı için açıkça varsayılan değerdir /proc/meminfo
. İşte anlamı - ve sadece tartışılan sınırdan (hiçbirinden) hiçbir ilgisi olmadığını unutmayın [src]/Documentation/filesystems/proc.txt
:
Komite Sınırı: Fazla taahhüt oranına ('vm.overcommit_ratio') dayanarak, bu sistemde tahsis edilebilecek toplam bellek miktarıdır . Bu sınıra yalnızca katı fazla taahhüt muhasebesi etkinse ('vm.overcommit_memory' deki mod 2) uyulur. CommandLimit aşağıdaki formülle hesaplanır: CommandLimit = ('vm.overcommit_ratio' * Fiziksel RAM) + Takas Örneğin, 1G fiziksel RAM ve 7G takas içeren 30'lu 'vm.overcommit_ratio' ile bir sistemde 7.3G'lık bir Taahhüt Sınırı.
Daha önce alıntılanan aşırı taahhüt muhasebesi dokümanı varsayılanın vm.overcommit_ratio
50 olduğunu belirtir . Böylece, eğer sysctl vm.overcommit_memory=2
vm.covercommit_ratio'yu (ile sysctl
) ayarlayabilir ve sonuçlarını görebilirsiniz. 3 Varsayılan mod, CommitLimit
zorlanmadığında ve yalnızca "adres alanının aşikar aşırılıkları reddedilir", modudur vm.overcommit_memory=0
.
Varsayılan strateji, "ciddi vahşi tahsisi" önleyen sezgisel işlem başına bir sınıra sahip olsa da, sistemi ciddi bir şekilde vahşi hale getirmek için tahsis açısından akıllı bir şekilde serbest bırakır. 4 Bu, bir noktada belleğinin bitebileceği ve OOM katili aracılığıyla bazı işlemlere iflas beyan etmesi gerektiği anlamına gelir .
OOM katili ne öldürüyor? Mutlaka suçlu olmadığı zaman bellek isteyen süreç olması gerekmez, çünkü bu gerçekten suçlu süreç değildir ve daha da önemlisi, sistemi içinde bulunduğu sorundan en hızlı şekilde çıkaracak olan süreç değildir.
Buradan , muhtemelen 2.6.x kaynağından bahsedilir :
/*
* 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)
*/
Bu iyi bir mantık gibi görünüyor. Ancak, adli tıp olmadan, # 5 (# 1 gereksiz) zor bir satış uygulaması akıllıca gibi görünüyor ve # 3 gereksiz # 2. Bu nedenle, # 2/3 ve # 4'e bölünmüş olarak düşünmek mantıklı olabilir.
Yakın zamanda bir kaynak (3.11) geçirdim ve bu yorumun geçici olarak değiştiğini fark ettim:
/**
* 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.
*/
Bu biraz daha açık bir şekilde # 2 hakkında: "Amaç, daha sonraki oom arızalarından kaçınmak için en fazla belleği tüketen görevi [öldürmektir” ve ima # 4 ( "minimum işlem miktarını ( bir ) öldürmek istiyoruz ) ) .
OOM katilini çalışırken görmek için bkz. Dipnot 5.
1 Neyse ki Gilles beni kurtaran bir yanılgı, bkz. Yorumlar.
2 Daha fazla bilgi için bir isteğin ne zaman başarısız olacağını belirlemek için giderek daha büyük bellek yığınları isteyen basit bir C biti:
#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'yi bilmiyorsanız, bunu derleyip gcc virtlimitcheck.c -o virtlimitcheck
çalıştırabilirsiniz ./virtlimitcheck
. Süreç istediği alanın hiçbirini kullanmadığından tamamen zararsızdır, yani hiçbir zaman gerçekten RAM kullanmaz.
4 GB sistem ve 6 GB takas içeren 3.11 x86_64 sisteminde ~ 7400000 kB'de başarısız oldum; sayı dalgalanıyor, belki devlet bir faktör. Bu tesadüfen yakın olmaktır CommitLimit
in /proc/meminfo
, ancak bu aracılığıyla değiştirerek vm.overcommit_ratio
herhangi bir fark yapmaz. 64 MB takas içeren bir 3.6.11 32-bit ARM 448 MB sisteminde, ~ 230 MB'da başarısızım. Bu ilginçtir, çünkü ilk durumda miktar RAM miktarının neredeyse iki katıdır, ikincisinde ise takas miktarını güçlü bir şekilde ima eden bir faktördür. Bu, başarısızlık eşiği ~ 1.95 GB'a düştüğünde, küçük ARM kutusuna çok benzer bir oran olan ilk sistemde takas kapatılarak doğrulandı.
Ama bu gerçekten süreç başına mı? Öyle görünüyor. Aşağıdaki kısa program, kullanıcı tanımlı bir bellek yığını ister ve başarılı olursa, geri dönmenizi bekler - bu şekilde birden fazla eşzamanlı örneği deneyebilirsiniz:
#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;
}
Bununla birlikte, kullanımdan bağımsız olarak RAM ve takas miktarıyla ilgili olmadığına dikkat edin - sistem durumunun etkileri hakkındaki gözlemler için 5. dipnota bakın.
3 , vm.overcommit_memory = 2 olduğunda sistemCommitLimit
için izin verilen adres alanı miktarını ifade eder. 2 .Committed_AS
Bunu gösteren potansiyel olarak ilginç bir deney #include <unistd.h>
, virtlimitcheck.c'nin (bkz. Dipnot 2) üstüne ve döngüden fork()
hemen önce eklenmelidir while()
. Bazı yorucu senkronizasyon olmadan burada açıklandığı gibi çalışacağı garanti edilmez, ancak YMMV'nin iyi bir şansı vardır:
> 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.
Bu mantıklıdır - tmp.txt'yi ayrıntılı olarak incelemek, süreçlerin daha büyük ve daha büyük ayırmalarını değiştirdiğini görebilirsiniz (pid'i çıktıya atarsanız daha kolaydır), açıkça, diğerinin başarısız olduğunu iddia edene kadar. Kazanan daha sonra her şeyi CommitLimit
eksi olarak tutabiliyor Committed_AS
.
4 Bu noktada, sanal adresleme ve talep çağrılamayı zaten anlamadıysanız, ilk etapta taahhüdü mümkün kılan şeyin, çekirdeğin kullanıcı alanı süreçlerine ayırdığı şeyin fiziksel bellek olmadığıdır. sanal adres alanı . Örneğin, bir işlem bir şey için 10 MB ayırırsa, bu (sanal) adresler dizisi olarak düzenlenir, ancak bu adresler henüz fiziksel belleğe karşılık gelmez. Böyle bir adrese erişildiğinde, bu bir sayfa hatasına neden olurve sonra çekirdek gerçek bir değer depolayabilmesi için gerçek belleğe eşlemeye çalışır. İşlemler genellikle gerçekte eriştiklerinden çok daha fazla sanal alan ayırır, bu da çekirdeğin RAM'i en verimli şekilde kullanmasını sağlar. Bununla birlikte, fiziksel bellek hala sınırlı bir kaynaktır ve tümü sanal adres alanına eşlendiğinde, bazı RAM'leri boşaltmak için bazı sanal adres alanlarının ortadan kaldırılması gerekir.
5 Önce bir uyarı : Bunu denerseniz vm.overcommit_memory=0
, önce çalışmanızı kaydettiğinizden ve kritik uygulamaları kapattığınızdan emin olun, çünkü sistem ~ 90 saniye boyunca donar ve bazı işlemler ölür!
Fikir, çatalların yer ayırdığı ve bazıları RAM'e büyük miktarlarda veri yazdığı için 90 saniye sonra zaman aşımına uğrayan bir çatal bombası çalıştırmaktır .
#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);
}
}
}
Bunu derleyin gcc forkbomb.c -o forkbomb
. İlk olarak, deneyin sysctl vm.overcommit_memory=2
- muhtemelen şöyle bir şey elde edersiniz:
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.
Bu ortamda, bu tür çatal bombası çok uzaklara gitmiyor. "N çatalını söylüyor" daki sayının toplam işlem sayısı olmadığını, zincir / dalda buna giden işlemlerin sayısı olduğunu unutmayın.
Şimdi deneyin vm.overcommit_memory=0
. Stderr'ı bir dosyaya yönlendirirseniz, daha sonra bazı ham analizler yapabilirsiniz, örneğin:
> 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 buluşsal gösteren - sadece 15 süreçleri 1 GB ayıramadı olan devlet tarafından etkilenir. Kaç süreç vardı? Tmp.txt dosyasının sonuna bakıldığında, muhtemelen> 100.000. Şimdi gerçekte 1 GB'ı nasıl kullanabilir?
> 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
Sekiz - bu yine mantıklı, çünkü o zamanlar ~ 3 GB RAM boş ve 6 GB takas vardı.
Bunu yaptıktan sonra sistem günlüklerinize bir göz atın. OOM katil skorlarını görmelisiniz (diğer şeylerin yanı sıra); muhtemelen bu ile ilgilidir oom_badness
.