ОС / Linux
Процессы Linux, файловые дескрипторы, IPC, каналы, права доступа, сигналы и менеджеры пакетов.
34 вопросов
JuniorТеорияОчень частоКакой системный вызов создаёт новый поток и чем он отличается от fork?
Какой системный вызов создаёт новый поток и чем он отличается от fork?
pthread_create() (POSIX) создаёт новый поток внутри текущего процесса: он разделяет адресное пространство, файловые дескрипторы и кучу. fork() создаёт целый новый процесс с собственной копией памяти. CreateThread — это аналог из Windows, не POSIX.
Типичные ошибки
- ✗Считать, что
fork()создаёт поток — он создаёт полноценный процесс со скопированным адресным пространством - ✗Думать, что потоки из
pthread_create()получают приватную память, как форкнутый процесс - ✗Называть
CreateThreadпереносимым POSIX-кодом — это только для Windows
Уточняющие вопросы
- →Что в итоге вызывает
pthread_create()на Linux под капотом? - →Как взаимодействуют
fork()и потоки — что опасно делать междуforkиexec?
JuniorТеорияОчень частоЧто такое файловые дескрипторы? Опишите стандартные FD.
Что такое файловые дескрипторы? Опишите стандартные FD.
FD — небольшое неотрицательное целое, индексирующее таблицу открытых файлов процесса. Стандартные: 0=stdin, 1=stdout, 2=stderr. Запись в ядре хранит смещение, флаги (O_RDONLY, O_NONBLOCK), ссылку на inode. open() возвращает FD; close() освобождает; FD переживают fork(). dup2(old, new) перенаправляет. Лимит: ulimit -n.
Типичные ошибки
- ✗Не закрывать файловые дескрипторы в дочерних процессах после fork — если дочерний не вызывает exec, он наследует все FD родителя; закрывайте их с
O_CLOEXECили явнымclose()передexec() - ✗Утечки FD в долгоработающих процессах — в конечном счёте достигается лимит и
open()возвращаетEMFILE; всегда закрывайте FD в путях ошибок - ✗Считать, что FD 0/1/2 всегда являются терминалом — демон, запущенный systemd, может иметь их перенаправленными в
/dev/null; всегда проверяйте
Уточняющие вопросы
- →Как
epollиспользует файловые дескрипторы и в чём разница между level-triggered и edge-triggered режимами? - →Что происходит с файловым дескриптором после
exec(), еслиO_CLOEXECне установлен?
JuniorТеорияОчень частоСходства и различия между процессами и потоками.
Сходства и различия между процессами и потоками.
Оба — единицы планирования ОС. Процесс имеет своё адресное пространство, FD и кучу (изолирован); поток делит память и открытые файлы с собратьями внутри процесса. Потоки дешевле, но менее изолированы.
Типичные ошибки
- ✗Думать, что
forkнемедленно копирует память — используется copy-on-write; страницы общие до записи - ✗Не присоединять или отсоединять потоки — деструктор
std::threadдля работающего потока вызываетstd::terminate - ✗Относиться к межпроцессной коммуникации как к простому вызову функции — сериализация и задержка нетривиальны
Уточняющие вопросы
- →Каковы преимущества использования нескольких процессов вместо потоков для изоляции отказов?
- →Как
std::jthread(C++20) улучшаетstd::threadс точки зрения RAII?
JuniorТеорияОчень частоЧто такое PID? Как ОС управляет процессами?
Что такое PID? Как ОС управляет процессами?
PID — уникальное целое, присваиваемое ядром; init/systemd имеет PID 1. Ядро ведёт таблицу (task_struct в Linux): PID, PPID, состояние (running/sleeping/zombie/stopped), карту VM, FD, обработчики сигналов, регистры CPU, приоритет. fork() создаёт copy-on-write потомка; exec() заменяет образ; wait() пожинает zombie. ps, top, /proc/[pid]/ показывают состояние.
Типичные ошибки
- ✗Забывать вызывать
wait()послеfork()— дочерний процесс становится зомби (всё ещё в таблице процессов) до тех пор, пока родитель его не пожнёт; утечка zombie-процессов - ✗Считать, что PID последовательные — PID оборачиваются после достижения максимума (по умолчанию 32768 на Linux); не сохраняйте PID и не предполагайте, что тот же процесс жив позже
- ✗Отправлять SIGKILL зомби — zombie-процессы уже мертвы и не могут получать сигналы; только
wait()от родителя удаляет их
Уточняющие вопросы
- →В чём разница между процессом и потоком на уровне ядра (системный вызов
clone())? - →Что показывает
/proc/[pid]/mapsи как это полезно для отладки?
MiddleТеорияОчень частоКак работают fork и exec и почему они часто используются вместе?
Как работают fork и exec и почему они часто используются вместе?
fork() создаёт точную копию процесса — та же память (через copy-on-write), те же fd. exec*() заменяет образ процесса новой программой (сохраняя таблицу fd по умолчанию). Чтобы запустить новую программу: fork, в потомке exec; родитель остаётся и wait'ит. Шаблон fork-then-exec — из Unix, гибче (и сложнее) чем CreateProcess в Windows.
Типичные ошибки
- ✗Звать
execбез проверки ошибок — при сбое получаем двойной процесс - ✗Забывать, что fork в многопоточной программе копирует только вызывающий поток
- ✗Аллоцировать между fork и exec в многопоточной — возможный deadlock
Уточняющие вопросы
- →Почему
posix_spawnрекомендуется вместо fork+exec для производительности? - →Что такое
vforkи почему он опасен?
MiddleТеорияОчень частоЧто такое системный вызов и чем он отличается от обычного вызова функции?
Что такое системный вызов и чем он отличается от обычного вызова функции?
Syscall переключает CPU из user в kernel-mode для того, что процесс не может сам (открыть файл, выделить память, создать поток). Механизм: спец. инструкция (syscall на x86_64, svc на ARM), аргументы в регистрах, ядро обрабатывает. Стоит сотни циклов против десятков у функции — поэтому батчинг (writev, io_uring) выигрывает. strace трассирует.
Типичные ошибки
- ✗Считать syscall'ы дешёвыми и делать один на каждый байт
- ✗Забывать, что syscall'ы могут быть прерваны сигналом (EINTR)
- ✗Считать glibc-обёртки самим syscall'ом — обёртки добавляют буферизацию, errno
Уточняющие вопросы
- →Как
vDSOускоряет часть syscall'ов? - →Что меняет
io_uringдля высокопроизводительного I/O?
JuniorТеорияЧастоЧто такое UID и как Linux управляет правами доступа?
Что такое UID и как Linux управляет правами доступа?
UID/GID идентифицируют владельца процесса/файла. Процесс имеет реальный, эффективный (для проверки прав) и сохранённый UID (восстановление через seteuid). Права — три rwx-группы: владелец, группа, остальные (9 бит, rwxr-xr-- = 754). Спецбиты: setuid, setgid, sticky (на каталоге: удаляет только владелец). UID 0 = root: обходит проверки.
Типичные ошибки
- ✗Использовать setuid-бинарники для повышения привилегий без тщательной валидации — setuid-программы являются векторами атак (напр., уязвимости
sudo); аудируйте их тщательно - ✗Путать права доступа к файлу с ACL — стандартные Unix-права поддерживают одного пользователя+группу на файл; ACL (getfacl/setfacl) позволяют правила per-user/group
- ✗Забывать про umask — новые файлы создаются с правами = mode & ~umask; umask 022 по умолчанию убирает запись для группы и остальных
Уточняющие вопросы
- →Что такое Linux capabilities и как они заменяют модель root 'всё или ничего'?
- →Чем
sudoотличается отsuс точки зрения управления UID?
MiddleТеорияЧастоЧто такое ОС? Ключевые компоненты ОС общего назначения (Linux).
Что такое ОС? Ключевые компоненты ОС общего назначения (Linux).
Компоненты: ядро (планировщик CFS, MMU/paging, драйверы I/O); процессы (fork/exec/wait, сигналы, namespaces); память (виртуальная память, mmap, OOM killer); ФС (VFS над ext4/XFS/btrfs); сеть (TCP/IP, сокеты, netfilter); IPC (pipes, shared memory, futex); syscalls — граница user/kernel через syscall.
Типичные ошибки
- ✗Путать ядро с ОС — ядро является основным компонентом; ОС включает ядро + инструменты пользовательского пространства (glibc, оболочку, систему инициализации и т.д.)
- ✗Думать, что виртуальная память всегда больше физической — виртуальное адресное пространство большое (128 ТБ для user space на x86-64), но каждое отображение должно быть обеспечено физическими страницами или swap
- ✗Недооценивать стоимость переключения контекста — сохранение/восстановление регистров, сброс TLB (если ASID не поддерживается), задержки конвейера: стоит 1–10 мкс; проектируйте с целью минимизации переключений
Уточняющие вопросы
- →В чём разница между монолитным ядром (Linux) и микроядром (L4, seL4)?
- →Чем
mmapотличается отmallocс точки зрения взаимодействия с ОС?
MiddleТеорияЧастоЧто такое context switch и что ОС сохраняет?
Что такое context switch и что ОС сохраняет?
Context switch — CPU прекращает один поток и запускает другой. Ядро сохраняет регистры исходящего, stack pointer, instruction pointer и (для process-to-process) page tables; восстанавливает входящий. Стоимость: ~1-10мкс плюс косвенная порча TLB/кеша.
Типичные ошибки
- ✗Создавать сотни потоков, ожидая линейного ускорения — context switching доминирует
- ✗Измерять латентность одного syscall без учёта хвостовых эффектов context switch
- ✗Путать voluntary (yield, blocking I/O) и involuntary (preemption) переключения
Уточняющие вопросы
- →Что такое TLB shootdown и когда он происходит?
- →Что такое
perf schedи что он показывает?
MiddleТеорияЧастоВидны ли родителю записи потомка в унаследованную память после fork()?
Видны ли родителю записи потомка в унаследованную память после fork()?
Нет. fork() даёт потомку собственное приватное адресное пространство; страницы сначала разделяются как copy-on-write, но первая запись с любой стороны создаёт приватную копию. Родитель и потомок видят только свои записи — для разделения нужен явный mmap(MAP_SHARED) или другой IPC.
Типичные ошибки
- ✗Думать, что copy-on-write означает разделение памяти родителем и потомком после записи
- ✗Считать, что
fork()физически копирует все страницы сразу — он разделяет их лениво через CoW - ✗Полагать, что выделения в куче остаются общими после
fork()и изменения доходят до родителя
Уточняющие вопросы
- →Как copy-on-write взаимодействует с
fork(), за которым сразу идётexec()? - →Что меняет
mmapсMAP_SHAREDпротивMAP_PRIVATEдля памяти, разделяемой черезfork()?
MiddleТеорияЧастоЕсли процесс открыл файл и вызвал fork(), может ли потомок читать этот файл?
Если процесс открыл файл и вызвал fork(), может ли потомок читать этот файл?
Да. fork() дублирует таблицу файловых дескрипторов родителя, поэтому потомок получает свои номера fd, указывающие на те же самые open file descriptions. Потомок может читать файл, а родитель и потомок делят одно смещение — чтение в любом из них сдвигает его для обоих.
Типичные ошибки
- ✗Думать, что потомок обязан повторно вызвать
open(), потому что дескрипторы не переживаютfork() - ✗Считать, что родитель и потомок получают независимые смещения — они делят одно, пока кто-то не закроет fd
- ✗Путать
O_CLOEXEC(влияет наexec, не наfork) с наследованием дескрипторов черезfork()
Уточняющие вопросы
- →В чём разница между file descriptor, open file description и inode?
- →Как
O_CLOEXECменяет наследование дескрипторов, когда послеforkидётexec?
MiddleТеорияЧастоКакой вызов обслуживает множество соединений в одном потоке переносимо между Unix-системами?
Какой вызов обслуживает множество соединений в одном потоке переносимо между Unix-системами?
poll() — он входит в POSIX, поэтому работает одинаково на Linux, BSD и macOS, и в отличие от select() не имеет ограничения FD_SETSIZE на номера дескрипторов. epoll (Linux) и kqueue (BSD/macOS) лучше масштабируются на огромных наборах дескрипторов, но непереносимы, поэтому переносимый выбор — poll().
Типичные ошибки
- ✗Считать
epollпереносимым, потому что он по умолчанию в популярных Linux-серверах - ✗Думать, что у
poll()есть то же ограничениеFD_SETSIZE, что и уselect() - ✗Хвататься напрямую за
epoll/kqueueвместо абстракции вродеlibevent, когда важна переносимость
Уточняющие вопросы
- →Почему
poll()всё равно плохо масштабируется в сравнении сepollна десятках тысяч дескрипторов? - →Что выбирает переносимая абстракция вроде
libeventилиlibuvна каждой платформе?
MiddleТеорияЧастоКак передавать данные между процессами? Механизмы IPC.
Как передавать данные между процессами? Механизмы IPC.
Pipes (родитель/потомок), named pipes/FIFO, сокеты (между машинами), разделяемая память (самая быстрая, нужна синхронизация), очереди сообщений, сигналы (мало данных) и файлы.
Типичные ошибки
- ✗Использовать разделяемую память без синхронизации — конкурентные записи без мьютекса/семафора вызывают повреждение
- ✗Не обрабатывать broken pipe — запись в pipe с закрытым концом чтения отправляет SIGPIPE, крашащий писателя если не замаскирован
- ✗Использовать SysV IPC (msgget/shmget) вместо POSIX-аналогов — SysV сложнее очищать при аварийном завершении
Уточняющие вопросы
- →Что такое
AF_UNIX(Unix domain socket) и чем он отличается от TCP-сокета с точки зрения производительности? - →Как использовать
mmapсMAP_SHAREDдля разделяемой памяти между процессами?
MiddleТеорияЧастоКак перевести файловый дескриптор в неблокирующий режим и зачем это нужно?
Как перевести файловый дескриптор в неблокирующий режим и зачем это нужно?
Вызвать fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK). После этого read() или write(), который иначе заблокировался бы — например, запись в pipe, который никто не вычитывает — вернёт -1 с errno, равным EAGAIN/EWOULDBLOCK, вместо зависания. Это позволяет одному потоку обслуживать множество дескрипторов через poll(), ни разу не застревая.
Типичные ошибки
- ✗Делать голый
F_SETFL, O_NONBLOCK, затирая существующие флаги, вместо OR сF_GETFL - ✗Считать
EAGAINфатальной ошибкой, а не сигналом «повторить позже» - ✗Полагать, что неблокирующий
write()всегда записывает всё — он может вернуть меньше запрошенного
Уточняющие вопросы
- →Если
poll()сообщил, что дескриптор читаем, может ли неблокирующийread()всё равно вернутьEAGAIN? - →Почему неблокирующий режим общий для всех дескрипторов, продублированных от одного открытого файла?
MiddleТеорияЧастоКак обычно Linux scheduler решает, какой поток запускать?
Как обычно Linux scheduler решает, какой поток запускать?
По умолчанию в Linux CFS: каждая runnable-задача копит виртуальное время; scheduler выбирает минимальный vruntime, взвешенный по nice. Real-time политики (SCHED_FIFO, SCHED_RR) вытесняют CFS по приоритету. EEVDF (новые ядра) заменяет CFS другой моделью справедливости. CPU pinning (taskset, sched_setaffinity) ограничивает поток множеством CPU — для cache locality.
Типичные ошибки
- ✗Использовать SCHED_FIFO без ограничения работы — голодание остального
- ✗Считать
niceпроцентом CPU — это соотношение между конкурирующими задачами - ✗Пинить каждый поток в надежде на ускорение — часто хуже при неравномерной нагрузке
Уточняющие вопросы
- →Что такое context switch и сколько он стоит?
- →Как
cgroupsv2 cpu-controller встраивается над scheduler?
MiddleТеорияЧастоЧто происходит, когда сигнал приходит, а процесс не установил для него обработчик?
Что происходит, когда сигнал приходит, а процесс не установил для него обработчик?
Срабатывает действие по умолчанию (default disposition) — оно есть у каждого сигнала. По умолчанию это может быть Term (завершить, напр. SIGUSR1, SIGTERM), Core (завершить + дамп ядра, напр. SIGSEGV), Ignore (напр. SIGCHLD) или Stop (напр. SIGSTOP).
Типичные ошибки
- ✗Считать, что необработанный сигнал просто игнорируется — большинство действий по умолчанию завершают процесс
- ✗Думать, что действие по умолчанию у каждого сигнала — завершение: у
SIGCHLDиSIGURGоно Ignore - ✗Полагать, что
SIGKILLилиSIGSTOPможно перехватить или сменить их действие — это невозможно
Уточняющие вопросы
- →Какие два сигнала нельзя перехватить, заблокировать или проигнорировать и почему?
- →Какие ограничения действуют на код внутри обработчика сигнала (async-signal-safety)?
MiddleТеорияЧастоЧто такое POSIX-сигналы и какие функции async-signal-safe?
Что такое POSIX-сигналы и какие функции async-signal-safe?
Сигналы — асинхронные уведомления: SIGINT, SIGTERM, SIGKILL (неперехватываемый), SIGSEGV, SIGCHLD. Обработчики ставят через sigaction. Внутри можно только async-signal-safe функции (POSIX: write, _exit, kill); printf, malloc, STL небезопасны. Паттерн: volatile sig_atomic_t flag в обработчике, обработка в основном цикле.
Типичные ошибки
- ✗Вызывать
printfиз обработчика — UB, если прерывает другой printf - ✗Использовать mutex в обработчике — deadlock, если прерывает держателя
- ✗Пытаться поймать SIGKILL или SIGSTOP — нельзя по дизайну
Уточняющие вопросы
- →Как
signalfdпозволяет обрабатывать сигналы как fd? - →Почему
pthread_sigmaskважен в многопоточных программах?
MiddleТеорияЧастоЧто такое виртуальная память и как ОС отображает её на физическую RAM?
Что такое виртуальная память и как ОС отображает её на физическую RAM?
Каждый процесс получает собственное виртуальное адресное пространство; ОС и MMU переводят виртуальные страницы (4KB) в физические через page tables. Страницы бывают unmapped, shared, copy-on-write, swap на диск или mmap'нутыми.
Типичные ошибки
- ✗Путать committed и resident память при разборе OOM
- ✗Считать, что
mallocсразу резервирует RAM — overcommit это опровергает - ✗Использовать огромные массивы на стеке, не зная про guard pages
Уточняющие вопросы
- →Чем отличаются RSS, VSZ и PSS в
top? - →Чем
mmapотличается отreadпо перфу?
JuniorТеорияИногдаЧто такое pipe и named pipe? Как они используются для IPC?
Что такое pipe и named pipe? Как они используются для IPC?
Pipe — однонаправленный байтовый поток в ядре между родственными процессами. pipe(fds) создаёт fds[0] (чтение), fds[1] (запись); шелл | использует pipe. FIFO (mkfifo) имеет путь в ФС — несвязанные процессы открывают по имени. Блок при полном (писатель) или пустом (читатель) буфере. Альтернативы: Unix сокеты, shared memory, очереди сообщений.
Типичные ошибки
- ✗Не закрывать неиспользуемые концы pipe — если родитель не закрывает
fds[0],read()дочернего процесса никогда не вернёт EOF, потому что ядро видит открытый конец записи - ✗Использовать pipe для двунаправленной коммуникации — pipe строго однонаправлен; используйте два pipe или
socketpair()для двунаправленного потока данных - ✗Считать, что буфер pipe бесконечен — по умолчанию 64 КБ на Linux; большие записи блокируются до тех пор, пока читатель не потребляет данные
Уточняющие вопросы
- →Как
SCM_RIGHTSпозволяет передавать файловые дескрипторы через Unix domain socket? - →В чём разница между pipe и Unix domain socket?
JuniorТеорияИногдаЗачем использовать select() вместо sleep() для паузы по таймеру?
Зачем использовать select() вместо sleep() для паузы по таймеру?
select() принимает таймаут struct timeval с микросекундной точностью, поэтому может приостановиться на доли секунды. Что важнее, он одновременно ждёт готовности файловых дескрипторов, завершая паузу раньше, если дескриптор стал готов. Обычный sleep() принимает только целые секунды и не реагирует ни на что больше.
Типичные ошибки
- ✗Думать, что
sleep()принимает дробный аргумент вроде0.5 - ✗Считать, что
select()только ждёт дескрипторы и не может работать как чистый таймер - ✗Забывать, что
select()на Linux изменяетtimeval, поэтому его нужно сбрасывать перед повторным использованием в цикле
Уточняющие вопросы
- →Как сделать паузу меньше секунды, не задействуя дескрипторы?
- →Что вернёт
select(), если таймаут истёк и ни один дескриптор не готов?
JuniorТеорияИногдаЧем отличаются stat() и lstat(), когда им передают символическую ссылку?
Чем отличаются stat() и lstat(), когда им передают символическую ссылку?
stat() идёт по символической ссылке: он сообщает о целевом файле — его настоящем размере и типе вроде S_IFREG. lstat() не идёт по ней: он сообщает о самой ссылке, с типом S_IFLNK и размером, равным длине строки пути, которую ссылка хранит.
Типичные ошибки
- ✗Считать, что
stat()для символической ссылки описывает саму ссылку — он описывает цель, на которую она указывает - ✗Ожидать, что размер из
lstat()— это размер цели; на деле это длина в байтах хранимой строки пути - ✗Обходить дерево каталогов через
stat()и пропускать символические ссылки или зацикливаться вместо использованияlstat()
Уточняющие вопросы
- →При обходе дерева каталогов почему
lstat()— более безопасный выбор по умолчанию? - →Как
O_NOFOLLOWдаёт ту же гарантию «не идти по ссылке» дляopen()?
JuniorТеорияИногдаПочему создание потока дешевле создания процесса на уровне ОС?
Почему создание потока дешевле создания процесса на уровне ОС?
У процесса своё адресное пространство, таблица FD, PID — создание дублирует page tables (даже с CoW), аллоцирует PCB, таблицу FD. Поток разделяет адресное пространство и FD — ядро аллоцирует только TCB, kernel stack, TLS. В Linux оба — task_struct; потоки делят больше состояния с группой. Создание потока ~10× быстрее fork.
Типичные ошибки
- ✗Считать создание потока бесплатным — это всё ещё микросекунды
- ✗Создавать поток на каждый запрос без pool — стоимость накапливается
- ✗Путать kernel threads с user-space кооперативными (fibers, coroutines)
Уточняющие вопросы
- →Почему
pthread_createиспользуетcloneвнутри? - →Что такое thread pool и когда он помогает?
MiddleТеорияИногдаКак создать файл только в том случае, если он ещё не существует?
Как создать файл только в том случае, если он ещё не существует?
Открыть его через open(path, O_CREAT|O_EXCL, mode). Ядро проверяет отсутствие файла и создаёт его одной атомарной операцией; если файл уже есть, вызов завершается ошибкой EEXIST. Отдельная проверка stat()/access(), а затем open() — это TOCTOU-гонка.
Типичные ошибки
- ✗Делать проверку существования через
stat()/access()передopen()— создаёт TOCTOU-гонку, которую может выиграть параллельный процесс - ✗Считать, что один
O_CREATоткажется открывать существующий файл — безO_EXCLон просто откроет имеющийся - ✗Ожидать, что
O_EXCLусечёт или перезапишет файл — вместо этого вызов завершается сEEXIST
Уточняющие вопросы
- →Почему
O_EXCLисторически ненадёжен на NFS и что это смягчает? - →Как вместо этого создать гарантированно уникальный временный файл?
MiddleТеорияИногдаЗачем нужны реентерабельные _r-варианты вроде readdir_r и strtok_r?
Зачем нужны реентерабельные _r-варианты вроде readdir_r и strtok_r?
Функции вроде readdir и strtok хранят статическое состояние на уровне процесса, поэтому одновременные вызовы из двух потоков портят друг друга. _r-варианты принимают буфер от вызывающего и потому реентерабельны. Важно: в современной glibc readdir потокобезопасен в пределах одного DIR*, поэтому readdir_r теперь объявлен устаревшим.
Типичные ошибки
- ✗Считать, что любая функция libc по умолчанию потокобезопасна
- ✗Вызывать
strtokдля двух строк вперемежку из разных потоков, ожидая независимого состояния - ✗Предпочитать
readdir_rв новом коде, не зная, что он устарел, а современный ответ —readdirв пределах одногоDIR*
Уточняющие вопросы
- →В чём разница между реентерабельной и потокобезопасной функцией?
- →Почему
readdir_rобъявили устаревшим, а не просто исправили?
MiddleТеорияИногдаЧем RSS, VSZ и PSS отличаются как метрики памяти процесса?
Чем RSS, VSZ и PSS отличаются как метрики памяти процесса?
VSZ (Virtual Set Size): общий виртуальный адрес-спан процесса — включает unused/lazy/file-mapped. RSS (Resident Set Size): физические страницы в RAM этого процесса; shared страницы считаются полностью каждому. PSS (Proportional Set Size): как RSS, но shared страницы делятся между разделяющими процессами — единственная метрика, сумма которой по процессам даёт общую RAM.
Типичные ошибки
- ✗Суммировать RSS по процессам — переоценивает shared-библиотеки
- ✗Считать рост VSZ признаком утечки — может расти без расхода RAM (
mmap PROT_NONE) - ✗Игнорировать
AnonymousvsFile-backedв smaps
Уточняющие вопросы
- →Что такое
oom_score_adjи как он влияет на порядок kill? - →Как
vm.overcommit_memoryменяет картину?
SeniorТеорияИногдаЧто такое cgroups в Linux и для чего они нужны?
Что такое cgroups в Linux и для чего они нужны?
Control groups (cgroups) ограничивают и учитывают ресурсы группы процессов: CPU, память, I/O, PID, сеть. Это ресурсная часть контейнеров (Docker, k8s, systemd). cgroups v2 унифицирует иерархию (одно дерево, несколько контроллеров). Применения: ограничить runaway-процесс, честное распределение между сервисами, OOM scoring per-group, точные метрики.
Типичные ошибки
- ✗Ставить только memory limit без swap limit — может упасть в swap
- ✗Смешивать v1 и v2 иерархии — v2 только unified
- ✗Забывать, что cgroup limits влияют на выбор OOM-цели
Уточняющие вопросы
- →Чем cgroup v1 отличается от v2?
- →Как
systemdиспользует cgroups для изоляции сервисов?
SeniorТеорияИногдаРазница между потоками уровня ядра и потоками уровня пользователя.
Разница между потоками уровня ядра и потоками уровня пользователя.
Потоки ядра (1:1) планирует ОС — настоящий параллелизм, но переключение — микросекунды. Пользовательские (M:N) планирует рантайм — дёшево и быстро, но блокирующий syscall стопорит лежащий снизу OS-поток.
Типичные ошибки
- ✗Думать, что пользовательские потоки исключают накладные расходы ОС — каждый блокирующий syscall всё равно входит в ядро; выигрыш только когда рантайм перехватывает и async-диспатчит их
- ✗Путать корутины с потоками — корутины кооперативные (явный yield) и работают на одном или нескольких OS-потоках; автоматического параллелизма нет
- ✗Забывать, что потоки 1:1 ограничены стеком ОС (обычно 8 МБ каждый); создание тысяч требует уменьшения стека или перехода к корутинам/async
Уточняющие вопросы
- →Как планировщик горутин Go (M:N) обрабатывает блокирующие syscall с сетевым I/O?
- →Что такое модель executor в C++ async/корутинах и как она связана с пулами потоков?
SeniorТеорияИногдаЧто делает dynamic loader и как работает LD_PRELOAD?
Что делает dynamic loader и как работает LD_PRELOAD?
При exec динамического ELF ядро грузит loader (ld-linux.so); loader маппит программу, затем DT_NEEDED-библиотеки (поиск: LD_LIBRARY_PATH, /etc/ld.so.cache, RPATH, RUNPATH, default), резолвит символы, запускает _init/ctors. LD_PRELOAD=/path/lib.so форсит библиотеку первой — её символы перехватывают последующие; для дебага, sandbox, замены malloc.
Типичные ошибки
- ✗Использовать
LD_LIBRARY_PATHв проде — неожиданные override - ✗Путать RPATH и RUNPATH — RPATH до LD_LIBRARY_PATH, RUNPATH после
- ✗Пытаться
LD_PRELOADдля setuid-бинарей — отключено по безопасности
Уточняющие вопросы
- →Что такое
dlopenи чем отличается от авто-загружаемых? - →Как работает
lddи почему опасно запускать его на ненадёжном бинаре?
SeniorПроизводительностьИногдаКогда mmap лучше read для файлового I/O и каковы компромиссы?
Когда mmap лучше read для файлового I/O и каковы компромиссы?
mmap отображает файл в адресное пространство; чтения становятся разыменованиями с подгрузкой по требованию. Выигрывает для рандомного доступа в больших файлах и zero-copy IPC. Проигрывает read для малых файлов, коротких сканов, сетевых ФС (SIGBUS при ошибках).
Типичные ошибки
- ✗Использовать mmap на сетевой ФС и получать SIGBUS / зависание
- ✗Забывать
madvise(MADV_RANDOM)/MADV_SEQUENTIALдля хинтов кешу - ✗Не обрабатывать SIGBUS при I/O ошибках — segfault при чтении
Уточняющие вопросы
- →Что такое
MAP_POPULATEи когда он помогает? - →Как mmap'нутая память взаимодействует с fork и copy-on-write?
SeniorТеорияИногдаЧто такое Linux namespaces и как они дают контейнеры?
Что такое Linux namespaces и как они дают контейнеры?
Namespaces изолируют у процесса view системных ресурсов. Виды: PID (своё дерево процессов), NET (свой сетевой стек), MNT (свои mount), UTS (hostname), IPC (System V/posix IPC), USER (маппинг uid/gid), CGROUP, TIME. Контейнер — процесс с набором namespaces (изоляция) плюс cgroups (лимиты ресурсов) плюс выбранный rootfs. Создаются через unshare syscall или флаги clone.
Типичные ошибки
- ✗Путать namespaces (изоляция view) и cgroups (лимиты) — разные механизмы
- ✗Считать контейнеры VM — они делят host-ядро
- ✗Запускать unprivileged-контейнеры без USER-namespace mapping
Уточняющие вопросы
- →Как USER namespace позволяет rootless-контейнеры?
- →Что делает
nsenter?
SeniorТеорияИногдаКак синхронизировать разные процессы?
Как синхронизировать разные процессы?
Используйте примитивы ОС вне процесса: именованные семафоры (sem_open), file locks (flock/fcntl), pthread_mutex с PTHREAD_PROCESS_SHARED в shared memory, futex или очереди сообщений.
Типичные ошибки
- ✗Размещать
std::mutexв разделяемой памяти — он использует адреса внутри процесса; используйтеpthread_mutex_tс атрибутомPTHREAD_PROCESS_SHARED - ✗Не обрабатывать очистку при аварийном завершении процесса — если процесс падает, удерживая именованный семафор, он остаётся заблокированным; предпочтительны robust-мьютексы (
PTHREAD_MUTEX_ROBUST) или watchdog-восстановление - ✗Забывать
msyncилиstd::atomic_thread_fenceпри записи в разделяемую память — переупорядочивание CPU и компилятора не ограничено границами процессов
Уточняющие вопросы
- →В чём разница между именованным семафором POSIX и семафором SysV?
- →Как реализовать lock-free кольцевой буфер в разделяемой памяти между процессами?
SeniorТеорияИногдаПроцесс A читает файл, когда процесс B вызывает unlink для него. Что произойдёт?
Процесс A читает файл, когда процесс B вызывает unlink для него. Что произойдёт?
Процесс A продолжает читать как ни в чём не бывало. unlink удаляет лишь запись в каталоге (имя); inode и его блоки данных живут, пока на них ссылается хоть один открытый fd. Файл просто больше нельзя открыть по имени. Место на диске освобождается, когда закрывается последний fd.
Типичные ошибки
- ✗Считать, что
unlinkудаляет файл немедленно, ломая читателей, у которых ещё открыт fd - ✗Путать
unlink(убирает одну ссылку в каталоге) с уничтожением inode (исчезли и последняя ссылка, и последний fd) - ✗Ожидать, что место на диске освободится в момент возврата
unlink— оно освобождается только после финальногоclose
Уточняющие вопросы
- →Как это поведение делает безопасным
tmpfile()и приём «unlink, затем использование»? - →Почему
dfиduмогут расходиться и какlsofнаходит удалённый, но открытый файл?