Tôi không phải là nhà phát triển hạt nhân nhưng tôi đã dành nhiều năm để triết lý về vấn đề này bởi vì tôi đã gặp phải vấn đề này rất nhiều lần. Tôi thực sự đã đưa ra một phép ẩn dụ cho toàn bộ tình huống vì vậy hãy để tôi nói với bạn điều đó. Tôi sẽ cho rằng trong câu chuyện của mình rằng những thứ như "hoán đổi" không tồn tại. Hoán đổi không có ý nghĩa nhiều với RAM 32 GB dù sao đi nữa.
Hãy tưởng tượng một khu phố của bạn, nơi nước được kết nối với mỗi tòa nhà thông qua các đường ống và thị trấn cần quản lý công suất. Giả sử rằng bạn chỉ sản xuất 100 đơn vị nước mỗi giây (và tất cả công suất không sử dụng sẽ bị lãng phí vì bạn không có bể chứa). Mỗi nhà (nhà = một ứng dụng nhỏ, thiết bị đầu cuối, tiện ích đồng hồ, v.v.) yêu cầu 1 đơn vị nước mỗi giây. Điều này là tốt và tốt vì dân số của bạn là 90, vì vậy mọi người đều có đủ nước.
Bây giờ thị trưởng (= bạn) quyết định rằng bạn muốn mở một nhà hàng lớn (= trình duyệt). Nhà hàng này sẽ chứa nhiều đầu bếp (= tab trình duyệt). Mỗi đầu bếp cần 1 đơn vị nước mỗi giây. Bạn bắt đầu với 10 đầu bếp, vì vậy tổng lượng nước tiêu thụ cho cả khu phố là 100 đơn vị nước vẫn còn tốt.
Bây giờ công cụ thú vị bắt đầu: bạn thuê một đầu bếp khác vào nhà hàng của bạn, điều này làm cho tổng nhu cầu nước 101 mà rõ ràng là bạn không có. Bạn cần phải làm một cái gì đó.
Quản lý nước (= kernel) có 3 tùy chọn.
1. Tùy chọn đầu tiên chỉ là ngắt kết nối dịch vụ cho những ngôi nhà không sử dụng nước gần đây. Điều này là tốt nhưng nếu nhà bị ngắt kết nối muốn sử dụng nước một lần nữa, họ sẽ cần phải trải qua quá trình đăng ký dài. Ban quản lý có thể ngắt kết nối nhiều ngôi nhà để giải phóng nhiều nguồn nước hơn. Trên thực tế, họ sẽ ngắt kết nối tất cả các ngôi nhà gần đây không sử dụng nước, do đó luôn có sẵn một lượng nước miễn phí.
Mặc dù thị trấn của bạn tiếp tục hoạt động, nhưng nhược điểm là sự tiến bộ bị đình trệ. Hầu hết thời gian của bạn dành cho việc chờ đợi vào quản lý nước để phục hồi dịch vụ của bạn.
Đây là những gì kernel làm với các trang được hỗ trợ tập tin. Nếu bạn chạy một tệp thực thi lớn (như chrome), tệp của nó sẽ được sao chép bộ nhớ. Khi bộ nhớ còn thấp hoặc nếu có những phần không được truy cập gần đây, kernel sẽ có thể loại bỏ những phần đó vì nó có thể tải lại chúng từ đĩa. Nếu điều này được thực hiện quá mức, điều này sẽ khiến máy tính của bạn tạm dừng vì mọi thứ sẽ chỉ chờ vào đĩa IO. Lưu ý rằng kernel cũng sẽ bỏ rất nhiều trang được sử dụng gần đây nhất khi bạn bắt đầu thực hiện nhiều IO. Đây là lý do tại sao phải mất nhiều thời gian để chuyển sang ứng dụng nền sau khi bạn sao chép một số tệp lớn như hình ảnh DVD.
Đây là hành vi khó chịu nhất đối với tôi vì tôi ghét những kẻ hèn nhát và bạn không có quyền kiểm soát nào đối với nó. Nó sẽ là tốt đẹp để có thể tắt nó. Tôi đang nghĩ về một cái gì đó dọc theo dòng
sed -i 's/may_unmap = 1/may_unmap = (vm_swappiness >= 0)/' mm/vmscan.c
và sau đó bạn có thể đặt vm_swappiness thành -1 để tắt tính năng này. Điều này hoạt động khá tốt trong các thử nghiệm nhỏ của tôi nhưng than ôi tôi không phải là nhà phát triển hạt nhân nên tôi đã không gửi nó cho bất kỳ ai (và rõ ràng là sửa đổi nhỏ ở trên chưa hoàn tất).
2.Ban quản lý có thể từ chối yêu cầu của người đầu bếp mới về nước. Điều này ban đầu nghe có vẻ là một ý tưởng tốt. Tuy nhiên có hai nhược điểm. Đầu tiên, có những công ty yêu cầu rất nhiều thuê bao nước mặc dù họ không sử dụng chúng. Một lý do có thể để làm điều này là để tránh tất cả các chi phí nói chuyện với quản lý nước bất cứ khi nào họ cần thêm nước. Việc sử dụng nước của họ tăng lên và giảm xuống tùy thuộc vào thời gian trong ngày. Ví dụ, trong trường hợp của nhà hàng, công ty cần nhiều nước hơn vào buổi trưa so với nửa đêm. Vì vậy, họ yêu cầu tất cả nước có thể họ có thể sử dụng nhưng điều đó làm lãng phí việc phân bổ nước trong nửa đêm. Vấn đề là không phải tất cả các công ty đều có thể thấy trước mức sử dụng tối đa của họ một cách chính xác nên họ yêu cầu nhiều hơn nữa với hy vọng họ sẽ không bao giờ phải lo lắng về việc yêu cầu thêm.
Đây là những gì máy ảo của Java thực hiện: nó phân bổ một loạt bộ nhớ khi khởi động và sau đó hoạt động từ đó. Theo mặc định, kernel sẽ chỉ phân bổ bộ nhớ khi ứng dụng Java của bạn thực sự bắt đầu sử dụng nó. Tuy nhiên, nếu bạn vô hiệu hóa quá mức, kernel sẽ nghiêm túc đặt phòng. Nó sẽ chỉ cho phép phân bổ thành công nếu nó thực sự có tài nguyên cho nó.
Tuy nhiên, có một vấn đề khác nghiêm trọng hơn với phương pháp này. Giả sử một công ty bắt đầu yêu cầu một đơn vị nước mỗi ngày (thay vì theo các bước 10). Cuối cùng, bạn sẽ đạt đến trạng thái có 0 đơn vị miễn phí. Bây giờ công ty này sẽ không thể phân bổ nhiều hơn. Điều đó tốt, dù sao cũng quan tâm đến các công ty lớn. Nhưng vấn đề là những ngôi nhà nhỏ cũng sẽ không thể yêu cầu nhiều nước hơn! Bạn sẽ không thể xây dựng phòng tắm công cộng nhỏ để đối phó với lượng khách du lịch đột ngột. Bạn sẽ không thể cung cấp nước khẩn cấp cho đám cháy trong khu rừng gần đó.
Về mặt máy tính: Trong các tình huống bộ nhớ rất thấp mà không có vấn đề gì quá mức, bạn sẽ không thể mở một xterm mới, bạn sẽ không thể ssh vào máy của mình, bạn sẽ không thể mở một tab mới để tìm kiếm có thể sửa lỗi. Nói cách khác, vô hiệu hóa overcommit cũng làm cho máy tính để bàn của bạn trở nên vô dụng khi thiếu bộ nhớ.
3. Bây giờ đây là một cách thú vị để xử lý vấn đề khi một công ty bắt đầu sử dụng quá nhiều nước. Việc quản lý nước thổi nó lên! Theo nghĩa đen: nó đi đến trang web của nhà hàng, ném thuốc nổ vào đó và đợi cho đến khi nó nổ tung. Điều này sẽ giảm ngay lập tức nhu cầu nước của thị trấn xuống rất nhiều để những người mới có thể chuyển đến, bạn có thể tạo phòng tắm công cộng, v.v. Bạn, với tư cách là thị trưởng, có thể xây dựng lại nhà hàng với hy vọng rằng lần này sẽ cần ít nước hơn. Ví dụ, bạn sẽ bảo mọi người không vào nhà hàng nếu có quá nhiều người bên trong (ví dụ: bạn sẽ mở ít tab trình duyệt hơn).
Đây thực sự là những gì kernel làm khi hết tất cả các tùy chọn và nó cần bộ nhớ: nó gọi là kẻ giết người OOM. Nó chọn một ứng dụng lớn (dựa trên nhiều phương pháp phỏng đoán) và giết chết nó, giải phóng một loạt bộ nhớ nhưng vẫn duy trì một máy tính để bàn đáp ứng. Trên thực tế, kernel Android thực hiện điều này thậm chí còn mạnh mẽ hơn: nó giết chết ứng dụng ít được sử dụng gần đây nhất khi bộ nhớ còn thấp (so với kernel stock chỉ làm như một phương sách cuối cùng). Đây được gọi là Viking Killer trong Android.
Tôi nghĩ rằng đây là một trong những giải pháp đơn giản nhất cho vấn đề: không phải là bạn có nhiều lựa chọn hơn thế này, vậy tại sao không vượt qua nó sớm hơn, phải không? Vấn đề là nhân đôi khi thực hiện khá nhiều công việc để tránh gọi kẻ giết OOM. Đó là lý do tại sao bạn thấy rằng máy tính để bàn của bạn rất chậm và kernel không làm gì với nó. Nhưng may mắn thay, có một lựa chọn để tự mình gọi kẻ giết người OOM! Trước tiên, hãy đảm bảo khóa sysrq ma thuật được bật (ví dụ echo 1 | sudo tee
/proc/sys/kernel/sysrq
) sau đó bất cứ khi nào bạn cảm thấy hạt nhân sắp hết bộ nhớ, chỉ cần nhấn Alt + SysRQ, Alt + f.
OK vậy tất cả đều tốt nhưng bạn muốn dùng thử? Tình huống bộ nhớ thấp rất đơn giản để tái tạo. Tôi có một ứng dụng rất đơn giản cho việc đó. Bạn sẽ cần phải chạy nó hai lần. Lần chạy đầu tiên sẽ xác định bạn có bao nhiêu RAM miễn phí, lần chạy thứ hai sẽ tạo ra tình trạng bộ nhớ thấp. Lưu ý rằng phương pháp này giả định rằng bạn đã vô hiệu hóa trao đổi (ví dụ: a sudo swapoff -a
). Mã và cách sử dụng như sau:
// gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char** argv)
{
int limit = 123456789;
if (argc >= 2) {
limit = atoi(argv[1]);
}
setbuf(stdout, NULL);
for (int i = 1; i <= limit; i++) {
memset(malloc(1 << 20), 1, 1 << 20);
printf("\rAllocated %5d MiB.", i);
}
sleep(10000);
return 0;
}
Và đây là cách bạn sử dụng nó:
$ gcc -std=c99 -Wall -Wextra -Werror -g -o eatmem eatmem.c
$ ./eatmem
Allocated 31118 MiB.Killed
$ ./eatmem 31110
Allocated 31110 MiB.Killed
Yêu cầu đầu tiên phát hiện ra rằng chúng ta có 31.118 MiB RAM miễn phí. Vì vậy, tôi đã nói với ứng dụng phân bổ 31.110 RAM MiB để kernel không giết nó mà ăn gần hết bộ nhớ của tôi. Hệ thống của tôi đóng băng: ngay cả con trỏ chuột cũng không nhúc nhích. Tôi đã nhấn Alt + SysRQ, Alt + f và nó đã giết quá trình eatmem của tôi và hệ thống đã được khôi phục.
Mặc dù chúng tôi đã đề cập đến các lựa chọn của mình trong tình huống bộ nhớ thấp, cách tiếp cận tốt nhất (giống như bất kỳ tình huống nguy hiểm nào khác) là tránh nó ngay từ đầu. Có rất nhiều cách để làm điều này. Một cách phổ biến mà tôi đã thấy là đặt các ứng dụng hoạt động sai (như trình duyệt) vào các thùng chứa khác với các phần còn lại của hệ thống. Trong trường hợp đó, trình duyệt sẽ không thể ảnh hưởng đến máy tính để bàn của bạn. Nhưng bản thân việc phòng ngừa nằm ngoài phạm vi câu hỏi nên tôi sẽ không viết về nó.
TL; DR: Mặc dù hiện tại không có cách nào để tránh hoàn toàn việc phân trang, bạn có thể giảm thiểu toàn bộ hệ thống bằng cách vô hiệu hóa quá mức. Nhưng hệ thống của bạn sẽ vẫn không sử dụng được trong tình huống bộ nhớ thấp nhưng theo một cách khác. Bất kể ở trên, trong tình huống bộ nhớ thấp, nhấn Alt + SysRQ, Alt + f để giết một quá trình lớn trong lựa chọn của kernel. Hệ thống của bạn sẽ khôi phục khả năng phản hồi sau vài giây. Điều này giả định rằng bạn đã bật khóa sysrq ma thuật (nó không phải mặc định).