BashTex | Linux
Kanalga Telegram’da o‘tish
Авторский канал для тех, кто хочет глубже погрузиться в мир Linux. Подойдет для разработчиков, системных администраторов и DevOps Реклама: @dad_admin
Ko'proq ko'rsatish2 515
Obunachilar
+324 soatlar
+127 kun
-330 kun
Ma'lumot yuklanmoqda...
O'xshash kanallar
Ma'lumot yo'q
Muammo bormi? Iltimos, sahifani yangilang yoki bizning qo'llab-quvvatlash boshqaruvchimizga murojaat qiling>.
Taglar buluti
Kirish va chiqish esdaliklari
---
---
---
---
---
---
Obunachilarni jalb qilish
Oktabr '26Okt '26
Oktabr '26
+18
0 kanalda
Sentabr '26
+7
0 kanalda
Get PRO
Avgust '26
+54
11 kanalda
Get PRO
Iyul '26
+16
1 kanalda
Get PRO
Iyun '26
+38
5 kanalda
Get PRO
May '26
+8
0 kanalda
Get PRO
Aprel '26
+14
0 kanalda
Get PRO
Mart '26
+22
0 kanalda
Get PRO
Fevral '26
+18
0 kanalda
Get PRO
Yanvar '26
+85
5 kanalda
Get PRO
Dekabr '25
+122
19 kanalda
Get PRO
Noyabr '25
+93
2 kanalda
Get PRO
Oktabr '25
+107
7 kanalda
Get PRO
Sentabr '25
+197
20 kanalda
Get PRO
Avgust '25
+16
1 kanalda
Get PRO
Iyul '25
+130
13 kanalda
Get PRO
Iyun '25
+57
4 kanalda
Get PRO
May '25
+70
1 kanalda
Get PRO
Aprel '25
+728
23 kanalda
Get PRO
Mart '25
+129
6 kanalda
Get PRO
Fevral '25
+235
9 kanalda
Get PRO
Yanvar '25
+497
19 kanalda
Get PRO
Dekabr '24
+134
8 kanalda
Get PRO
Noyabr '24
+819
17 kanalda
| Sana | Obunachilarni jalb qilish | Esdaliklar | Kanallar | |
| 07 Oktabr | 0 | |||
| 06 Oktabr | +4 | |||
| 05 Oktabr | +14 | |||
| 04 Oktabr | 0 | |||
| 03 Oktabr | 0 | |||
| 02 Oktabr | 0 | |||
| 01 Oktabr | 0 |
Kanal postlari
Ephemeral ports - почему заканчиваются исходящие TCP-порты
Когда приложение устанавливает исходящее TCP-соединение, порт выбирается автоматически. Это ephemeral port, временный исходный порт клиента.
Например:
10.0.0.10:49152 → 142.250.74.14:443Если приложение создаёт тысячи соединений, диапазон таких портов может стать узким местом. ▪️Посмотреть доступный диапазон В Linux его задаёт:
cat /proc/sys/net/ipv4/ip_local_port_range
Например:
32768 60999Это около 28 тысяч портов для исходящих соединений с одного локального IP. ▪️Но количество соединений не всегда ограничено этим числом TCP-соединение определяется не одним портом, а набором:
source IP source port destination IP destination port protocolТо есть:
10.0.0.10:50000 → 1.1.1.1:443 10.0.0.10:50001 → 1.1.1.1:443это разные соединения. Но тот же локальный порт теоретически может использоваться снова, если меняется destination tuple. Поэтому несколько destination IP/портов значительно расширяют пространство комбинаций. ▪️Как увидеть исходящие соединения
ss -tan state establishedА чтобы посмотреть локальные порты:
ss -tan | awk 'NR>1 {print $4}' | sort | uniq -c | sort -nr | head
Если приложение создаёт огромное количество короткоживущих TCP-соединений, дополнительно стоит посмотреть TIME-WAIT:
ss -tan state time-wait | wc -l
▪️Почему TIME_WAIT особенно неприятен
После закрытия TCP-соединения локальный порт не всегда сразу можно использовать для нового соединения.
Это нужно для защиты TCP от старых сегментов предыдущего соединения.
При большом количестве коротких запросов можно получить ситуацию:
requests ↓ тысячи TCP connections ↓ close() ↓ TIME_WAIT ↓ исчерпание доступных комбинацийВ итоге новое
connect() начинает получать ошибки вроде:
Cannot assign requested address▪️Практический сценарий Проблема часто появляется у прокси, API-клиентов, NAT-шлюзов и сервисов, которые вместо переиспользования соединений постоянно создают новые. Поэтому сначала стоит проверить:
ss -s
cat /proc/sys/net/ipv4/ip_local_port_range
ss -tan state time-wait | wc -l
А уже потом решать проблему: расширять диапазон, использовать connection pooling или уменьшать количество коротких TCP-соединений.
Важно: увеличение ip_local_port_range не создаёт новые IP-адреса и не отменяет остальные ограничения TCP.
BashTex 📱 #bash #utils| 2 | name_to_handle_at() - как получить файловый объект без обычного пути
В Linux обычно обращаются к файлу через путь:
/etc/hosts
Но ядро умеет работать с файловым объектом иначе. Системный вызов name_to_handle_at() позволяет получить специальный file handle, связанный с объектом filesystem.
Это используется низкоуровневыми инструментами, которым нужно сохранить ссылку на объект, а не его текстовый путь.
▪️Получаем handle
Сам системный вызов выглядит примерно так:
int name_to_handle_at(
int dirfd,
const char *path,
struct file_handle *handle,
int *mount_id,
int flags
);
Например, программа может передать:
/etc/hosts
и получить структуру с handle и идентификатором mount.
Важно: это не файловый дескриптор. Handle не позволяет просто сделать read().
▪️Зачем тогда он нужен
Основная идея - получить устойчивое представление объекта внутри filesystem, которое затем можно использовать для повторного обращения к нему.
Для этого существует парный системный вызов:
open_by_handle_at()
Схема получается такой:
path
↓
name_to_handle_at()
↓
file handle + mount ID
↓
open_by_handle_at()
↓
file descriptor
То есть сначала filesystem выдаёт идентификатор объекта, а позже по нему можно снова получить FD.
▪️Почему это отличается от обычного пути
Представим:
/var/data/report
Путь зависит от directory entries. Файл могут переименовать:
mv /var/data/report /var/archive/report
Сам inode при этом остаётся тем же объектом.
File handle предназначен именно для работы с объектом filesystem, а не с конкретным именем в каталоге.
▪️Но есть важное ограничение
Handle не является универсальным идентификатором любого файла на всех filesystem.
Его формат зависит от filesystem, а поддержка механизма должна быть реализована самой filesystem.
Кроме того, open_by_handle_at() требует соответствующих привилегий. Обычное приложение не получает возможность произвольно открывать объекты по filesystem handles.
▪️Где это реально встречается
Механизм особенно интересен для низкоуровневых инструментов резервного копирования, мониторинга и работы с filesystem, где обычный путь может быть неудобен или недостаточно надёжен.
И главное: name_to_handle_at() не «обходит filesystem по inode».
Он просит конкретную filesystem предоставить handle для объекта, а затем этот handle может быть использован через соответствующий kernel API.
BashTex 📱 #bash #utils | 227 |
| 3 | madvise() - как процесс подсказывает ядру, как он собирается использовать память
Когда процесс работает с большой областью памяти, ядро не всегда знает, как именно приложение будет обращаться к этим страницам.
Linux предоставляет madvise() - системный вызов, через который процесс может сообщить ядру предполагаемый характер использования памяти.
Это особенно интересно для mmap() и больших memory-mapped файлов.
▪️Последовательный доступ
Если программа собирается читать память последовательно:
madvise(addr, length, MADV_SEQUENTIAL);
ядро получает подсказку, что страницы будут использоваться примерно по порядку.
Это позволяет иначе управлять page cache и упреждающим чтением.
Например, обработчик большого файла может пройти по нему от начала до конца вместо случайных обращений.
▪️Случайный доступ
Для случайного доступа есть:
madvise(addr, length, MADV_RANDOM);
Если приложение читает страницы в произвольном порядке, агрессивное read-ahead может оказаться бесполезным.
Подсказка позволяет ядру учитывать такой сценарий.
▪️Можно сообщить, что страницы больше не нужны
Особенно интересен:
madvise(addr, length, MADV_DONTNEED);
Процесс говорит ядру, что содержимое этой области ему сейчас не требуется.
Для анонимной памяти это может позволить освободить физические страницы. Для файлового отображения поведение связано с отображёнными страницами и page cache.
Важно: это не то же самое, что free().
Виртуальный адрес всё ещё может оставаться отображённым, но физические страницы могут быть освобождены и восстановлены при следующем обращении.
▪️Есть и подсказка WILLNEED
madvise(addr, length, MADV_WILLNEED);
Она сообщает ядру, что страницы, вероятно, скоро понадобятся.
Это может помочь подготовить данные заранее, например перед обработкой большого memory-mapped файла.
Но madvise() именно подсказывает, а не заставляет ядро выполнить конкретную стратегию.
▪️Практический сценарий
Представим программу, которая через mmap() обрабатывает несколько гигабайт файла строго последовательно.
Вместо случайного поведения с page cache она может сделать:
void *p = mmap(NULL, size,
PROT_READ,
MAP_PRIVATE,
fd, 0);
madvise(p, size, MADV_SEQUENTIAL);
Теперь приложение явно сообщает ядру характер будущего доступа.
А когда большой участок больше не нужен:
madvise(p, size, MADV_DONTNEED);
Это может уменьшить давление на память, не требуя немедленно уничтожать само отображение.
▪️Важный нюанс
madvise() не является универсальной кнопкой «ускорить память».
Эффект зависит от конкретного флага, типа mapping, версии ядра и сценария доступа.
И особенно важно не путать MADV_DONTNEED с гарантированным физическим освобождением каждой страницы: это рекомендация ядру о том, что содержимое региона можно считать ненужным.
То есть madvise() - это интерфейс, через который userspace сообщает kernel memory manager: «я примерно знаю, что собираюсь делать с этой памятью».
BashTex 📱 #bash #utils | 331 |
| 4 | flock vs fcntl - почему две блокировки одного файла могут вести себя совершенно по-разному
В Linux есть несколько механизмов блокировки файлов. Наиболее часто встречаются flock() и fcntl().
На первый взгляд они делают одно и то же: не дают нескольким процессам одновременно работать с одним файлом.
Но внутри ядра это разные механизмы, поэтому блокировки могут вообще не замечать друг друга.
▪️flock блокирует сам файл
Простейший вариант:
flock /tmp/app.lock -c 'echo "working"; sleep 10'
Пока первый процесс держит блокировку, второй:
flock -n /tmp/app.lock -c 'echo "working"'
получит ошибку и сразу завершится из-за -n.
Часто этот механизм используют для защиты cron-задач:
flock -n /run/myjob.lock /usr/local/bin/myjob
▪️fcntl работает через диапазоны файла
Здесь можно блокировать не только весь файл, но и конкретный диапазон байтов.
На уровне C:
struct flock lock = {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 100
};
fcntl(fd, F_SETLK, &lock);
Можно заблокировать первые 100 байт, а другой процесс при этом сможет работать с другим диапазоном.
▪️Главная ловушка
Процесс A:
flock /tmp/test.lock
Процесс B использует fcntl() для того же файла.
Они не обязаны конфликтовать.
Причина в том, что flock и традиционные POSIX advisory locks через fcntl исторически используют разные пространства блокировок.
То есть наличие:
flock → locked
не означает автоматически:
fcntl → blocked
И наоборот.
▪️Обе блокировки advisory
Ни flock, ни fcntl сами по себе не запрещают процессу открыть и изменить файл.
Если приложение вообще не проверяет блокировку, оно может спокойно сделать:
echo "data" >> /tmp/file
Поэтому механизм работает только тогда, когда все участники системы договариваются его соблюдать.
▪️Есть ещё одна важная разница
Для flock блокировка связана с открытым file description. При fork() дочерний процесс наследует соответствующую блокировку.
У fcntl семантика другая: POSIX locks связаны с процессом и имеют собственные правила поведения при close().
Из-за этого без понимания модели владения FD легко получить ситуацию, когда один close() неожиданно освобождает блокировку.
▪️Практический вывод
Если нужно просто гарантировать, что cron или несколько экземпляров shell-скрипта не выполняются одновременно:
flock -n /run/myjob.lock /usr/local/bin/myjob
обычно достаточно.
Если приложение должно блокировать отдельные диапазоны файла или ему нужна POSIX-модель блокировок, используют fcntl().
И главное: нельзя считать flock и fcntl взаимозаменяемыми только потому, что оба называются file locking.
BashTex 📱 #bash #utils | 325 |
| 5 | DEBUG trap - как Bash выполняет код перед каждой командой
В Bash есть специальный DEBUG trap, который позволяет выполнить собственный код непосредственно перед выполнением команды.
Это удобно не только для отладки. Через него можно посмотреть, какие команды реально выполняет скрипт, с какими аргументами и в каком контексте.
▪️Самый простой пример
trap 'echo "CMD: $BASH_COMMAND"' DEBUG
echo "hello"
mkdir /tmp/test
rm -rf /tmp/test
Перед каждой командой Bash вызовет trap:
CMD: echo "hello"
CMD: mkdir /tmp/test
CMD: rm -rf /tmp/test
$BASH_COMMAND содержит команду, которую Bash собирается выполнить.
▪️Можно добавить PID и функцию
trap 'printf "[%s] %s: %s\n" "$$" "${FUNCNAME[1]:-main}" "$BASH_COMMAND"' DEBUG
Теперь при выполнении функций можно получить что-то вроде:
[4217] main: prepare
[4217] deploy: mkdir -p /srv/app
[4217] deploy: cp app.conf /srv/app/
Это уже превращается в простой трассировщик Bash-скрипта.
▪️Но DEBUG не означает буквально «перед каждой строкой»
Trap срабатывает перед выполнением простых команд, for, case, некоторых условных конструкций и других элементов shell execution.
Поведение также зависит от того, включено ли наследование trap внутри функций и subshell.
Например:
trap 'echo "DEBUG: $BASH_COMMAND"' DEBUG
foo() {
echo "inside"
}
foo
Чтобы DEBUG распространялся внутрь функций, часто используют:
set -T
или:
shopt -s functrace
▪️У trap есть интересный побочный эффект
Сам код внутри DEBUG тоже выполняется Bash, поэтому слишком сложный обработчик может сам стать источником неожиданных эффектов.
Для диагностики лучше держать его простым:
trap 'printf "%s\n" "$BASH_COMMAND" >> /tmp/bash-debug.log' DEBUG
А после диагностики обязательно убрать:
trap - DEBUG
▪️Практический сценарий
Есть большой deployment-скрипт, который запускает десятки функций и команд. Лог показывает только:
deployment failed
Вместо того чтобы расставлять echo по всему скрипту, временно включаем:
trap 'printf "[%s] %s\n" "$SECONDS" "$BASH_COMMAND"' DEBUG
и получаем последовательность реально выполнявшихся команд.
Важно: DEBUG предназначен прежде всего для трассировки. Для полноценного production-аудита он неудобен: trap может влиять на поведение скрипта и генерировать очень много вывода.
BashTex 📱 #bash #utils | 278 |
| 6 | tee + process substitution: один поток и несколько получателей
Иногда нужно одновременно: увидеть вывод команды в терминале, записать его в файл и передать на обработку другой программе Если делать это по очереди - потеряется поток данных. Здесь помогает связка tee + process substitution.
▪️ Что делает tee. tee дублирует поток:
command | tee file.log
вывод остается в терминале и одновременно пишется в file.log
▪️ Несколько получателей. tee может писать сразу в несколько файлов:
command | tee out1.log out2.log
Но иногда нужно не просто файл, а другую команду.
▪️ Process substitution. Bash позволяет подставить вывод команды как файл:
>(command)
Это называется process substitution.
▪️ Комбинируем
command | tee >(grep ERROR > errors.log)
command генерирует поток
tee дублирует его
Одна копия идет в терминал, а другая в grep. Если найден ERROR, запись попадет в errors.log.
▪️ Более реальный пример. Допустим, идет сбор логов:
journalctl -f | tee >(grep ERROR >> errors.log)
Теперь: полный поток остается в терминале, ошибки автоматически сохраняются
▪️ Можно делать несколько обработчиков
journalctl -f | tee \
>(grep ERROR >> errors.log) \
>(grep WARN >> warn.log)
Один поток и сразу несколько фильтров.
BashTex 📱 #bash #utils | 339 |
| 7 | Архитектура большого bash-скрипта
Пока скрипт на 30 строк - все терпимо. На 300 строк начинается хаос. На 1000 - уже никто не понимает, где init, где логика, где cleanup. Чтобы bash не превратился в лапшу, ему нужна архитектура.
📂 Нормальная структура проекта
project/
├── main.sh
├── lib/
│ ├── log.sh
│ ├── config.sh
│ ├── checks.sh
│ └── deploy.sh
├── conf/
│ └── app.conf
└── tmp/
Где:
main.sh - точка входа
lib/ - функции по темам
conf/ - конфиги
tmp/ - временные файлы
▪️ Деление на модули
Не надо держать все в одном файле.
Лучше так:
source "$(dirname "$0")/lib/log.sh"
source "$(dirname "$0")/lib/checks.sh"
Примеры модулей:
log.sh - логгер
config.sh - загрузка переменных
checks.sh - проверки окружения
actions.sh - основная логика
▪️ Naming: единый стиль
Худший вариант:
doStuff()
x()
RunAll()
Лучше:
log_info()
check_dependencies()
deploy_app()
cleanup_tmp()
Для приватных функций можно префикс:
_internal_parse_config()
▪️ Поток исполнения. В начале файла:
main() {
load_config
check_dependencies
run_tasks
}
main "$@"
Это делает скрипт читаемым как программу, а не как свалку команд.
BashTex 📱 #bash #scripts | 353 |
| 8 | BashTex 📱 #мем | 520 |
| 9 | Почему Permission denied: как найти каталог, на котором ломаются права
Иногда файл имеет правильные права, пользователь состоит в нужной группе, но Permission denied всё равно появляется.
Причина может быть выше по пути: чтобы открыть /var/www/site/config.php, процесс должен иметь право x на каждый каталог в этом пути.
Разберем, как быстро найти место, где ломается доступ.
1️⃣namei показывает права на весь путь
namei -l /var/www/site/config.php
В выводе будут отдельно показаны:
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-x--- root www-data www
drwx------ root root site
-rw-r----- root www-data config.php
Сразу видно, на каком уровне пользователь может упереться в права.
2️⃣Почему важен x у каталога
Для каталога x означает не «запуск», а возможность проходить через него и обращаться к объектам внутри.
Например:
chmod 644 /var/www/site
Файлы внутри могут быть доступны для чтения, но войти в каталог без x уже нельзя.
Проверить можно:
sudo -u www-data cat /var/www/site/config.php
3️⃣Проверяем конкретного пользователя
Если доступ должен быть у www-data:
sudo -u www-data namei -l /var/www/site/config.php
А группы:
id www-data
Это помогает понять, действительно ли процесс запускается с теми группами, которые вы ожидаете.
4️⃣ACL тоже могут менять картину
Обычных ls -l иногда недостаточно. Проверить ACL:
getfacl /var/www/site/config.php
Там могут быть дополнительные разрешения для конкретного пользователя или группы.
5️⃣Почему ls -l файл не всегда помогает
Команда:
ls -l /var/www/site/config.php
показывает права самого файла.
Но если проблема находится в /var/www/site, информация о файле сама по себе этого не объяснит.
Именно поэтому namei -l удобен при странных Permission denied: он показывает всю цепочку каталогов, через которую процесс должен пройти до нужного файла.
BashTex 📱 #bash #systemd | 525 |
| 10 | veth - как Linux соединяет два network namespace
Network namespace изолирует сетевой стек процесса. У каждого namespace могут быть свои интерфейсы, маршруты и таблица соседей.
Но как соединить два таких изолированных сетевых мира?
Для этого в Linux есть veth pair - виртуальный Ethernet-кабель с двумя концами.
▪️Создаём два namespace
ip netns add ns1
ip netns add ns2
Теперь создаём пару:
ip link add veth1 type veth peer name veth2
Получили:
veth1 <──────────> veth2
То, что отправлено в один конец, появляется на другом.
▪️Разносим концы по namespace
ip link set veth1 netns ns1
ip link set veth2 netns ns2
Теперь:
namespace ns1 namespace ns2
veth1 <──────────> veth2
В каждом namespace находится только свой конец виртуального кабеля.
▪️Назначаем адреса
ip -n ns1 addr add 10.10.0.1/24 dev veth1
ip -n ns2 addr add 10.10.0.2/24 dev veth2
ip -n ns1 link set veth1 up
ip -n ns2 link set veth2 up
Теперь можно проверить связь:
ip netns exec ns1 ping 10.10.0.2
Пакет проходит примерно так:
process
↓
network stack ns1
↓
veth1
↓
veth2
↓
network stack ns2
↓
process
Никакого физического интерфейса между namespace здесь нет.
▪️Почему это используется в контейнерах
Контейнер получает собственный network namespace.
Один конец veth помещается внутрь контейнера:
container
eth0
│
│ veth
│
host
Второй конец остаётся на host и обычно подключается к Linux bridge.
Например:
container eth0
│
veth
│
bridge
├── veth
├── veth
└── host
Так несколько изолированных namespace получают доступ к одной L2-сети.
▪️Проверяем, где находится интерфейс
На host:
ip link
В namespace:
ip -n ns1 link
Можно увидеть, что veth1 и veth2 физически не существуют как один интерфейс — это два связанных виртуальных endpoint’а.
▪️Важный нюанс
veth сам по себе не маршрутизирует трафик.
Он просто соединяет два сетевых пространства на уровне Ethernet.
Если нужно выйти из namespace в интернет, поверх veth обычно добавляют bridge, routing, NAT или другой сетевой механизм.
То есть veth - это буквально виртуальный кабель:
namespace A
│
veth A
│
└──────── veth B
│
namespace B
А уже дальше Linux решает, куда отправлять пакеты.
BashTex 📱 #bash #systemd | 433 |
| 11 | nohup vs session vs terminal - что реально происходит после закрытия SSH
Запустили процесс по SSH:
./backup.sh &
Закрыли SSH-сессию - а потом обнаружили, что процесс исчез.
Почему?
Проблема не в самом SSH. Нужно понимать связь между терминалом, session, shell и сигналами.
▪️Что происходит при SSH-подключении
Условно:
sshd
↓
shell
↓
terminal / PTY
↓
команды
Shell создаёт процессы в своей session и process group, а SSH предоставляет им псевдотерминал.
Когда соединение закрывается, терминал исчезает. Это может привести к отправке SIGHUP процессам, связанным с терминалом.
Именно здесь обычный background-процесс может неожиданно завершиться.
▪️& не делает процесс независимым
./backup.sh &
& лишь запускает команду в фоне для текущего shell.
Это не означает, что процесс переживёт закрытие SSH.
Проверить дерево:
ps -o pid,ppid,sid,pgid,tty,stat,cmd
Здесь особенно интересны:
PID PPID SID PGID TTY
Можно увидеть, к какой session и terminal относится процесс.
▪️Что делает nohup
nohup ./backup.sh >backup.log 2>&1 &
nohup не создаёт магическую «вечную» копию процесса.
Он в первую очередь меняет обработку SIGHUP и перенаправляет стандартные потоки, если они всё ещё связаны с терминалом.
Проверить:
ps -o pid,ppid,sid,pgid,tty,cmd -p <PID>
Процесс всё ещё может находиться в той же session.
Но получение SIGHUP уже не должно завершить его обычным способом.
▪️А что такое session
Session - это уровень выше process group.
У неё есть лидер - обычно shell, запущенный после входа по SSH.
Посмотреть:
ps -o pid,ppid,sid,pgid,tty,cmd
Например:
PID PPID SID PGID TTY
1000 900 1000 1000 pts/0
1050 1000 1000 1050 pts/0
Оба процесса находятся в одной session, хотя имеют разные process groups.
▪️Почему setsid - это уже другое
Можно создать новую session:
setsid ./backup.sh
Теперь процесс не находится в старой session SSH.
Это уже принципиально отличается от простого:
./backup.sh &
Но для полноценного фонового запуска всё равно нужно учитывать stdin/stdout/stderr и дальнейшее поведение приложения.
▪️Практический вариант
Если нужен простой запуск долгой команды:
nohup ./backup.sh </dev/null >backup.log 2>&1 &
Если нужна полноценная интерактивная среда, которая переживает отключение SSH:
tmux
В tmux процесс продолжает работать внутри отдельной terminal/session среды, а вы можете подключиться к ней позже.
▪️Важный нюанс
nohup, setsid и tmux решают разные задачи.
& → background job
nohup → защита от SIGHUP + перенаправление потоков
setsid → новая session
tmux → отдельная управляемая terminal session
Поэтому вопрос «почему процесс умер после выхода из SSH?» лучше начинать не с nohup, а с:
ps -o pid,ppid,sid,pgid,tty,stat,cmd
Сначала смотрим, к какой session, process group и terminal он вообще был привязан.
BashTex 📱 #bash #systemd | 360 |
| 12 | Minor и major page fault: почему page fault не всегда означает проблему с диском
Когда процесс обращается к виртуальной памяти, нужная страница не всегда уже находится в памяти именно в том состоянии, которое ожидает процесс.
Тогда возникает page fault - обращение к ядру для обработки этого случая.
Но сам факт page fault ещё не означает, что система полезла на диск.
▪️Смотрим статистику процесса
ps -o pid,comm,min_flt,maj_flt -p 1234
Или:
pidstat -r -p 1234 1
Там можно увидеть количество minor и major faults.
▪️minor fault
При minor page fault данные уже находятся в RAM, но процессу нужно, например, создать или настроить соответствующее отображение страницы.
Дисковый I/O для загрузки этой страницы не требуется.
Поэтому большое количество minor faults само по себе не означает медленный диск.
▪️major fault
При major fault для продолжения работы процесса требуется получить данные из более медленного источника, например с диска.
Условно:
minor
процесс → kernel → RAM → продолжение
major
процесс → kernel → storage → RAM → продолжение
Именно major faults интересны при поиске проблем с памятью и I/O.
▪️Смотрим процесс в реальном времени
pidstat -r -p 1234 1
Можно увидеть примерно:
PID minflt/s majflt/s
1234 15230.00 0.00
Здесь процесс создаёт много minor faults, но major faults нет.
Это совершенно другая ситуация, чем:
PID minflt/s majflt/s
1234 1200.00 85.00
Во втором случае часть обращений действительно приводит к ожиданию загрузки данных.
▪️Практический сценарий
Приложение внезапно начинает тормозить.
top показывает:
CPU: 20%
RAM: 60%
Но:
pidstat -r -p 1234 1
показывает заметный majflt/s.
Тогда стоит дополнительно посмотреть I/O:
iostat -xz 1
Возможно, процесс регулярно ждёт чтения данных со storage.
▪️Важный нюанс
Не стоит использовать количество page faults как самостоятельную метрику производительности.
Высокий minflt/s может быть совершенно нормальным для приложения с определённым характером работы с памятью.
Гораздо интереснее смотреть на major faults вместе с latency и I/O activity.
То есть:
page fault
↓
minor или major?
↓
если major → есть ли реальный I/O?
↓
где процесс проводит время?
Сам по себе page fault - это не ошибка. Это механизм, который Linux постоянно использует при работе с виртуальной памятью.
BashTex 📱 #bash #systemd | 371 |
| 13 | BashTex 📱 #мем | 654 |
| 14 | inotify vs polling: почему постоянный find может быть плохим мониторингом
Иногда нужно отследить появление нового файла в каталоге.
Самый простой вариант:
while true; do
find /var/incoming -type f
sleep 1
done
Работает. Но каждые несколько секунд мы заново обходим каталог, даже если за это время ничего не произошло.
Для событийного мониторинга в Linux есть inotify.
▪️Следим за изменениями без постоянного обхода
Например, с inotifywait:
inotifywait -m /var/incoming
При создании файла можно получить:
/var/incoming/ CREATE report.csv
А с -e можно ограничить интересующие события:
inotifywait -m \
-e create -e moved_to \
/var/incoming
Теперь процесс ждёт события вместо постоянного запуска find.
▪️Можно сразу обрабатывать новые файлы
inotifywait -m -e create -e moved_to --format '%w%f' \
/var/incoming |
while read -r file; do
process "$file"
done
Когда файл появляется или перемещается в каталог, вызывается обработчик.
▪️Почему polling хуже
При polling:
find → sleep → find → sleep → find
мы постоянно проверяем состояние файловой системы, даже когда ничего не меняется.
При inotify:
wait → event → обработка → wait
процесс большую часть времени просто ожидает событие.
▪️Но есть важный нюанс
inotify не является очередью событий для бесконечного количества изменений.
У ядра есть ограничение на очередь событий:
cat /proc/sys/fs/inotify/max_queued_events
Если приложение не успевает читать события, очередь может переполниться.
Тогда появляется:
IN_Q_OVERFLOW
И приложение уже не может считать, что знает обо всех произошедших изменениях.
Поэтому для критичных систем иногда нужен дополнительный механизм сверки состояния.
▪️Практический сценарий
Есть каталог, куда другой сервис складывает файлы:
/var/incoming/
Вместо постоянного:
find /var/incoming ...
можно реагировать непосредственно на CREATE или MOVED_TO.
Но если файл сначала создаётся и затем постепенно записывается, одного события создания недостаточно.
Нужно учитывать момент, когда файл полностью готов к обработке.
Именно поэтому в production важно следить не только за самим событием, но и за способом передачи файла.
BashTex 📱 #bash #systemd | 562 |
| 15 | Sparse files: почему файл на 100 ГБ может занимать несколько мегабайт
Размер файла и реально занятое место на диске не всегда совпадают.
Можно создать файл, который логически имеет размер 100 ГБ, но физически занимает всего несколько мегабайт.
Для этого используются sparse files.
▪️Создаём sparse-файл
truncate -s 100G test.img
Теперь:
ls -lh test.img
покажет:
-rw-r--r-- 1 user user 100G test.img
Но:
du -h test.img
может показать всего несколько килобайт.
ls смотрит на логический размер, а du - на реально выделенные filesystem blocks.
▪️Что произошло
Файл содержит большой диапазон нулей, для которого файловая система не обязана физически выделять блоки.
Условно:
логический файл:
[данные][ огромная область нулей ][данные]
↓ ↓ ↓
блоки hole блоки
Эта незаполненная область называется hole.
▪️Можно посмотреть оба размера
stat test.img
Обратите внимание на:
Size:
Blocks:
Size — логический размер файла.
Blocks — сколько filesystem blocks реально выделено.
▪️Почему это важно
Sparse files часто используются для образов виртуальных машин и контейнеров:
VM disk image → 100 GB
actual allocated space → 12 GB
Пока виртуальная машина не записала данные в свободные области, физическое место не требуется.
Но если эти области постепенно заполняются, реальное потребление диска начинает расти.
▪️Как проверить sparse-файл
du -h test.img
du --apparent-size -h test.img
Второй вариант показывает размер так, будто sparse-областей нет.
Например:
8.0K test.img
100G test.img
▪️Практический сценарий
Есть сервер виртуализации, и администратор видит:
ls -lh /var/lib/images/*
Образы занимают:
500G
Но:
du -sh /var/lib/images
показывает:
180G
Это не обязательно ошибка подсчёта.
Часть образов может быть sparse.
▪️Важный нюанс
Копирование sparse-файла не всегда сохраняет holes.
Например, некоторые способы копирования могут превратить разреженный файл в обычный и реально записать нулевые блоки.
Проверить результат можно снова через:
du -h file
du --apparent-size -h file
Поэтому при работе с большими VM-образами важно различать логический размер файла и реально выделенное дисковое пространство.
BashTex 📱 #bash #systemd | 551 |
| 16 | du vs df: почему Linux показывает разный размер диска
Иногда ситуация выглядит странно:
df -h /
показывает, что занято 80 GB.
А:
du -sh /
находит только 55 GB.
Кажется, что Linux «потерял» 25 GB. На самом деле du и df измеряют разные вещи.
▪️Что считает df
df смотрит на файловую систему и её блоки:
df -h /
Он показывает, сколько места файловая система считает занятым и свободным.
▪️Что считает du
du обходит дерево каталогов и суммирует пространство, связанное с найденными файлами:
du -xhd1 / 2>/dev/null
-x здесь важен: он не позволяет уйти на другие файловые системы.
▪️Одна из главных причин расхождения
Процесс может держать открытым файл, который уже удалили.
Например:
rm /var/log/app.log
Имя файла исчезло из каталога, но процесс всё ещё держит его открытым.
Для du такого файла уже не существует.
А файловая система продолжает считать его блоки занятыми.
Проверить:
lsof +L1
Можно увидеть что-то вроде:
app 1234 ... /var/log/app.log (deleted)
Пока процесс не закроет этот дескриптор, место может не освободиться.
▪️Есть и другие причины
Например, du без -x может пересекать mount points и давать неожиданный результат.
Или часть пространства файловой системы зарезервирована и не доступна обычным пользователям.
Посмотреть файловую систему подробнее:
findmnt /
df -h /
df -i /
▪️Практический сценарий
На сервере внезапно заканчивается место:
df -h /
Показывает:
/dev/sda2 100G 95G 5G 95% /
Но:
du -xsh /
показывает только 70G.
Первое, что стоит проверить:
lsof +L1
Если там огромный (deleted) лог, причина найдена.
▪️Важный нюанс
df отвечает примерно на вопрос:
«Сколько блоков файловой системы сейчас занято?»
А du:
«Сколько места занимают доступные мне файлы в этом дереве?»
Поэтому при расхождении df и du не стоит сразу запускать rm -rf по большим каталогам.
Сначала нужно понять, куда именно делось пространство.
BashTex 📱 #bash #systemd | 477 |
| 17 | file vs расширение - как Linux определяет тип файла на самом деле
В Linux расширение файла само по себе ничего не определяет.
Для системы:
backup.tar.gz
backup.jpg
backup.txt
это просто имена файлов. Можно спокойно сделать:
mv image.jpg image.txt
и содержимое файла от этого никак не изменится.
▪️Откуда тогда file знает тип
Утилита file смотрит не на расширение, а на содержимое файла.
file image.txt
file archive.bin
file unknown
Например:
unknown: PNG image data, 1920 x 1080, 8-bit/color RGB
Даже если файл называется:
photo.txt
file всё равно может определить его как PNG.
▪️Главный источник - magic bytes
Многие форматы имеют характерную сигнатуру в начале файла.
Например, PNG начинается с:
89 50 4e 47 0d 0a 1a 0a
Проверить первые байты можно:
xxd -l 16 photo.png
А file сопоставляет такие сигнатуры с базой magic:
file --version
и использует правила из базы magic.
▪️Можно обмануть расширение
cp image.png document.txt
file document.txt
Результат всё равно будет примерно таким:
document.txt: PNG image data
Потому что имя изменилось, а байты остались прежними.
▪️Но не всё определяется только первыми байтами
file может анализировать не только сигнатуру.
Для ELF:
file /bin/ls
может показать архитектуру, разрядность и тип исполняемого файла.
Для текстовых файлов утилита анализирует содержимое и может определить кодировку, тип текста и другие признаки.
▪️Практический сценарий
Скачали файл с подозрительным расширением:
download.exe
Не стоит сразу доверять имени.
Сначала:
file download.exe
А затем, если это бинарник:
readelf -h download.exe
или:
xxd -l 32 download.exe
Так можно понять, что реально лежит внутри файла.
▪️Важный нюанс
file не является универсальным детектором содержимого.
Если формат не имеет узнаваемой сигнатуры или несколько форматов используют похожую структуру, результат может быть приблизительным.
И ещё важнее: file определяет тип по содержимому, но не говорит, что файл безопасен.
Файл может называться document.pdf, определяться как PDF и при этом содержать вредоносную нагрузку.
В Linux расширение - часть имени.
А реальный формат гораздо чаще определяется тем, какие байты находятся внутри файла.
BashTex 📱 #bash #systemd | 456 |
| 18 | BashTex 📱 #мем | 700 |
| 19 | systemd-delta: как найти, чем на самом деле переопределён unit
Иногда вы открываете unit-файл и видите одну конфигурацию, а systemd ведёт себя иначе.
Причина часто в drop-in override, созданном через systemctl edit.
Например, основной unit:
/usr/lib/systemd/system/nginx.service
а дополнительная настройка лежит здесь:
/etc/systemd/system/nginx.service.d/override.conf
▪️Посмотреть переопределения
systemd-delta
Команда показывает отличия между vendor-конфигурацией и локальными изменениями.
Можно проверить конкретный unit:
systemd-delta nginx.service
Если сервис был переопределён, вы увидите соответствующий drop-in.
▪️Почему это важно
Допустим, в основном unit указано:
[Service]
Restart=no
А где-то в /etc/systemd/system/nginx.service.d/override.conf:
[Service]
Restart=always
Открыв только /usr/lib/systemd/system/nginx.service, вы будете смотреть на неполную картину.
systemd при запуске учитывает оба слоя.
▪️Посмотреть итоговую конфигурацию
Для проверки того, что systemd реально использует:
systemctl show nginx.service
Например:
systemctl show nginx.service -p Restart -p RestartUSec
А список связанных drop-in можно увидеть так:
systemctl cat nginx.service
Здесь systemd покажет основной unit и применённые drop-in-файлы.
▪️Практический сценарий
Сервис неожиданно перезапускается:
systemctl status nginx
В основном unit Restart= не настроен.
Вместо того чтобы сразу менять конфигурацию, проверяем:
systemd-delta nginx.service
И обнаруживаем старый override:
/etc/systemd/system/nginx.service.d/override.conf
Именно он мог изменить поведение сервиса.
▪️Важный нюанс
systemd-delta особенно полезен после обновлений дистрибутива.
Пакет может заменить vendor unit в /usr/lib/systemd/system/, но локальный override в /etc/systemd/system/ продолжит действовать.
Поэтому при странном поведении unit полезно проверить не только сам файл, но и что поверх него было добавлено или изменено.
BashTex 📱 #bash #systemd | 587 |
| 20 | PSI: /proc/pressure/* - как понять, чего реально не хватает системе
load average показывает, что в системе есть очередь на выполнение или I/O. Но он плохо отвечает на другой вопрос: насколько пользователи и процессы реально страдают от этого дефицита?
Для этого в Linux есть PSI - Pressure Stall Information:
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io
Например:
full avg10=0.00 avg60=0.01 avg300=0.00 total=...
▪️some - хотя бы часть задач была вынуждена ждать ресурс.
▪️full - все задачи соответствующей группы одновременно стояли из-за этого ресурса.
avg10, avg60, avg300 - процент времени за последние 10, 60 и 300 секунд, когда происходило такое ожидание.
Например:
some avg10=18.5
full avg10=7.2
Это уже гораздо интереснее, чем просто увидеть высокий RAM usage.
Если memory some растёт - процессы регулярно упираются в нехватку памяти.
Если начинает расти memory full - система уже попадает в периоды, когда все учитываемые задачи одновременно не могут нормально продвигаться из-за memory pressure.
То же самое для I/O:
cat /proc/pressure/io
Если I/O PSI высокий, а CPU почти не испытывает pressure, проблема может быть не в загрузке процессора, а в storage latency.
А CPU PSI позволяет увидеть ситуацию, когда CPU формально загружен не на 100%, но runnable-задачи всё равно регулярно ждут свою очередь.
Проверить всё сразу:
for f in /proc/pressure/*; do
echo "=== $f ==="
cat "$f"
done
PSI полезен именно как метрика задержки из-за конкуренции за ресурсы, а не просто их потребления.
Поэтому вместо вопроса «CPU/RAM/Disk загружены?» иногда правильнее спросить:
«Какой ресурс заставляет процессы ждать?»
BashTex 📱 #perfomance #linux | 597 |
