Минимальный запускаемый пример
Чтобы это имело смысл, вы должны понимать основы пейджинга:https://stackoverflow.com/questions/18431261/how-does-x86-paging-workи, в частности, то, что ОС может выделять виртуальную память через таблицы страниц / свой внутренний бухгалтерский учет памяти (Виртуальная память VSZ )перед этим. фактически имеет резервное хранилище в ОЗУ или на диске (Резидентная память RSS ).
Теперь, чтобы увидеть это в действии, давайте создадим программу, которая:
mmap
main.c
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
typedef struct {
unsigned long size,resident,share,text,lib,data,dt;
} ProcStatm;
/* https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c/7212248#7212248 */
void ProcStat_init(ProcStatm *result) {
const char* statm_path = "/proc/self/statm";
FILE *f = fopen(statm_path, "r");
if(!f) {
perror(statm_path);
abort();
}
if(7 != fscanf(
f,
"%lu %lu %lu %lu %lu %lu %lu",
&(result->size),
&(result->resident),
&(result->share),
&(result->text),
&(result->lib),
&(result->data),
&(result->dt)
)) {
perror(statm_path);
abort();
}
fclose(f);
}
int main(int argc, char **argv) {
ProcStatm proc_statm;
char *base, *p;
char system_cmd[1024];
long page_size;
size_t i, nbytes, print_interval, bytes_since_last_print;
int snprintf_return;
/* Decide how many ints to allocate. */
if (argc < 2) {
nbytes = 0x10000;
} else {
nbytes = strtoull(argv[1], NULL, 0);
}
if (argc < 3) {
print_interval = 0x1000;
} else {
print_interval = strtoull(argv[2], NULL, 0);
}
page_size = sysconf(_SC_PAGESIZE);
/* Allocate the memory. */
base = mmap(
NULL,
nbytes,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1,
0
);
if (base == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
/* Write to all the allocated pages. */
i = 0;
p = base;
bytes_since_last_print = 0;
/* Produce the ps command that lists only our VSZ and RSS. */
snprintf_return = snprintf(
system_cmd,
sizeof(system_cmd),
"ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == \"%ju\") print}'",
(uintmax_t)getpid()
);
assert(snprintf_return >= 0);
assert((size_t)snprintf_return < sizeof(system_cmd));
bytes_since_last_print = print_interval;
do {
/* Modify a byte in the page. */
*p = i;
p += page_size;
bytes_since_last_print += page_size;
/* Print process memory usage every print_interval bytes.
* We count memory using a few techniques from:
* https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c */
if (bytes_since_last_print > print_interval) {
bytes_since_last_print -= print_interval;
printf("extra_memory_committed %lu KiB\n", (i * page_size) / 1024);
ProcStat_init(&proc_statm);
/* Check /proc/self/statm */
printf(
"/proc/self/statm size resident %lu %lu KiB\n",
(proc_statm.size * page_size) / 1024,
(proc_statm.resident * page_size) / 1024
);
/* Check ps. */
puts(system_cmd);
system(system_cmd);
puts("");
}
i++;
} while (p < base + nbytes);
/* Cleanup. */
munmap(base, nbytes);
return EXIT_SUCCESS;
}
Скомпилируйте и запустите:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
sudo dmesg -c
./main.out 0x1000000000 0x200000000
echo $?
sudo dmesg
где:
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
:требуется для Linux, чтобы позволить нам сделать вызов mmap больше, чем физическая оперативная память:https://stackoverflow.com/questions/2798330/maximum-memory-which-malloc-can-allocate/57687432#57687432Выход программы:
extra_memory_committed 0 KiB
/proc/self/statm size resident 67111332 768 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 1648
extra_memory_committed 8388608 KiB
/proc/self/statm size resident 67111332 8390244 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 8390256
extra_memory_committed 16777216 KiB
/proc/self/statm size resident 67111332 16778852 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 16778864
extra_memory_committed 25165824 KiB
/proc/self/statm size resident 67111332 25167460 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 25167472
Killed
Состояние выхода:
137
, что по правилу 128 + номер сигнала означает, что мы получили номер сигнала 9
, который man 7 signal
говорит, что это SIGKILL , который отправляется Linux out -из -убийцы памяти .
Интерпретация выхода:
printf '0x%X\n' 0x40009A4 KiB ~= 64GiB
(ps
значения указаны в КиБ )после mmap. extra_memory_committed 0
, что означает, что мы еще не коснулись ни одной страницы.RSS — это небольшой 1648 KiB
, который был выделен для нормального запуска программы, такой как текстовая область, глобальные переменные и т. д. 8388608 KiB == 8GiB
страниц. В результате RSS увеличился ровно на 8GB до8390256 KiB == 8388608 KiB + 1648 KiB
См. также:Необходимо пояснение по размеру резидентного набора/виртуальному размеру
Журналы убийц OOM
Наши команды dmesg
показали журналы убийц OOM.
Точная интерпретация их была запрошена на:
Самая первая строка журнала была:
[ 7283.479087] mongod invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
Итак, мы видим, что интересно, что именно демон MongoDB, который всегда работает в моем ноутбуке в фоновом режиме, первым вызвал убийцу OOM, предположительно, когда бедняга пытался выделить немного памяти.
Однако убийца ООМ не обязательно убивает того, кто его пробудил.
После вызова ядро печатает таблицу или процессы, включаяoom_score
:
[ 7283.479292] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name
[ 7283.479303] [ 496] 0 496 16126 6 172032 484 0 systemd-journal
[ 7283.479306] [ 505] 0 505 1309 0 45056 52 0 blkmapd
[ 7283.479309] [ 513] 0 513 19757 0 57344 55 0 lvmetad
[ 7283.479312] [ 516] 0 516 4681 1 61440 444 -1000 systemd-udevd
и далее мы видим, что наш собственный маленький main.out
на самом деле был убит при предыдущем вызове:
[ 7283.479871] Out of memory: Kill process 15665 (main.out) score 865 or sacrifice child
[ 7283.479879] Killed process 15665 (main.out) total-vm:67111332kB, anon-rss:92kB, file-rss:4kB, shmem-rss:30080832kB
[ 7283.479951] oom_reaper: reaped process 15665 (main.out), now anon-rss:0kB, file-rss:0kB, shmem-rss:30080832kB
В этом журнале упоминается score 865
, который имел этот процесс, предположительно самый высокий (худший )показатель убийцы OOM, как указано в:Как убийца OOM решает, какой процесс убить первым?
Также интересно, что все видимо произошло так быстро, что до того, как освободившаяся память была засчитана, oom
снова разбудил DeadlineMonitor
процесс:
[ 7283.481043] DeadlineMonitor invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
и на этот раз это убило какой-то процесс Chromium, который обычно потребляет память моих компьютеров:
[ 7283.481773] Out of memory: Kill process 11786 (chromium-browse) score 306 or sacrifice child
[ 7283.481833] Killed process 11786 (chromium-browse) total-vm:1813576kB, anon-rss:208804kB, file-rss:0kB, shmem-rss:8380kB
[ 7283.497847] oom_reaper: reaped process 11786 (chromium-browse), now anon-rss:0kB, file-rss:0kB, shmem-rss:8044kB
Протестировано в Ubuntu 19.04, ядро Linux 5.0.0.
Используйте правило брандмауэра, например, nftables или iptables. (Firewalld также может делать это с «зонами». )Это самый универсальный вариант; и nftables, и iptables поддерживают подстановочные знаки, и оба фактически предотвращают соединения из неправильного интерфейса (, что не имеет отношения к интернет-ботам, но может иметь значение при защите от злонамеренных локальных пользователей на wlan0 ).
Например,в nftables вы бы написали:
table inet filter {
chain input {
[...]
tcp dport 22 iifname "eth*" accept
tcp dport 22 reject
[...]
}
}
В файлах iptables .rules
то же самое:
[...]
-A INPUT -p tcp --dport 22 -i eth+ -j ACCEPT
-A INPUT -p tcp --dport 22 -j REJECT
[...]
Ваша система использует экземпляры sshd для -соединения («стиль inetd» или «активация через сокет» ), а не обычный автономный демон. В этом режиме адреса прослушивания определяются не sshd, а супервизором (супер-сервером ).
При активации сокета systemd запустите systemctl edit --full sshd.socket
, чтобы определить адреса прослушивания, заменив существующие параметры ListenStream= на:
[Socket]
ListenStream=192.168.55.101:22
Кроме того, вы можете оставить глобальный ListenStream=, но привязать сокет только к определенному интерфейсу (только точное имя, подстановочные знаки не разрешены):
[Socket]
ListenStream=0.0.0.0:22
ListenStream=[::]:22
BindToDevice=eth0
После этого перезапустите sshd.socket
, чтобы изменения вступили в силу.
Обратите внимание, что этот режим inetd -обычно не рекомендуется для sshd, так как боты перебора могут привести к срабатыванию ограничения скорости systemd -и помешать вам установить даже законные соединения. Вам следует рассмотреть возможность отключения sshd.socket и включения вместо этого «автономного» sshd.service. (После этого адреса прослушивания будут управляться /etc/ssh/sshd_config
.)