На самом деле я хотел сделать здесь обзор книги "Linux: системное программирование, 2 издание", а получился обзор системных вызовов. Этот абзац в тексте первый, а реально я его добавил в самый последний момент, чтобы было что-то и про книгу тоже. Книга очень крутая и перевод отличный, легко читается, небольшая по объему, без воды, написана простым языком. Описаны все системные вызовы с множеством примеров. Автор объясняет назначение вызова, как его применять, какой получается результат. После того, как познакомишься со всеми сущностями, выстраивается чёткая концепция, когда одни сущности дополняют другие сущности. Из тех редких книг, которая берёт у тебя уже имеющиеся практические разрозненные знания и аккуратно раскладывает их по полочкам, дополняя эти знания. После прочтения книги словно всё становится на свои места и ты уже не можешь жить прежней жизнью. Наступает просветление.

Введение для неподготовленных читателей. Когда ваш компьютер включён, часть его ресурсов, таких, как память и процессор, постоянно загружены выполняющимися системными процессами операционной системы. Эти системные процессы контролируют всё, что запускается пользователем в его пользовательском пространстве. Любая пользовательская исполняемая программа постоянно оперирует с сущностями, которые контролируются и управляются кодом операционной системы. Это открытые файлы, готовые для чтения и записи; таймеры; области памяти, доступные процессу и др. В Linux доступ ко всем этим сущностям предоставляется через системные вызовы.

В Linux есть возможность трассировки в файл всех системных вызовов, порождаемых процессом (и его дочерними процессами тоже) с помощью программы strace. Вот очень короткий фрагмент вывода этой программы:

open(".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
fcntl64(3, F_GETFD) = 0x1 (flags FD_CLOEXEC)
getdents64(3, /* 18 entries */, 4096) = 496
getdents64(3, /* 0 entries */, 4096) = 0
close(3) = 0
fstat64(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f2c000
write(1, "autofs\nbackups\ncache") = 0

и так далее. Бывают мегабайты логов. Вот так просто можно взять и изучить все сущности, с которыми работает исследуемое приложение.

Есть несколько неочевидных (для меня) вещей, которые я смог до конца понять только после прочтения книги.

1. Синтаксис perl при работе с файлами полностью повторяет системные вызовы linux. Стало совсем прозрачным и наглядным понимание, почему у perl файловые операции сделаны так, а не иначе.

2. После прочтения книги стало понятнее, как работает динамическое выделение памяти в linux. Программисты используют malloc, а сама эта библиотечная функция в зависимости из запрошенного размера области памяти выбирает источник - либо в куче (заранее выделенная память в момент создания процесса, можно менять её размер), или с помощью mmap создаёт анонимную область в памяти. Большое количество mmap в логах - неявное использование malloc. Различить, где реально были размещены данные, можно по их адресам.

3. Многопоточность. Видно, что процесс порой порождает десятки потоков и процессов.
Системный вызов clone(). Передаётся адрес функции, которую нужно выполнить в отдельном потоке. Поток завершается, когда достигнута точка выхода этой функции. По сути являются легковесными новыми процессами, затраты на запуск которых невелики. При этом книга настоятельно рекомендует использовать стандартную библиотеку pthread для внедрения данной функциональности.
Системный вызов fork() раздваивает поток - начинают выполняться два совершенно одинаковых потока от точки вызова fork(), единственным различием которого являются разные PID потока. Программный код, если хочет назначить разные точки следования потоков, может это сделать путём анализа PID. Часто форканутый поток вызывает exec().
Системный вызов exec() заменяет текущий процесс программой, переданной в exec() первым параметром. Более того, в linux связка fork(clone)+exec - единственный способ запускать дочерние процессы. Прямого системного вызова "запустить дочерний процесс X" в архитектуре не предусмотрено вообще.

4. select и poll. Системные вызовы, поддерживающие мультиплексированный ввод-вывод, то есть возможность параллельно блокировать несколько файловых дескрипторов и получать уведомления, как только любой из них будет готов к чтению или записи без блокировки. Фактически, методы осуществляют межпроцессное взаимодействие и ожидание наступления множества событий одновременно (через открытые файлы и открытые сокеты). Здесь непонятно написал, поскольку сам точно до конца не разобрался, как применяется этот механизм.

5. Cигналы. Уникальная концепция, есть только в Unix. Это асинхронные события-сообщения, которые приходят процессу или группе процессов извне. Например, нажатие в консоли Ctrl+C (порождает SIGINT) или обрыв передачи данных от процесса к процессу посредством конвейера (порождает SIGPIPE). Сигнал может быть проигнорирован процессом, либо перехвачен и обработан. Часто обработка заключается в вызове процедуры, внепланово завершающий процесс. Сигналы более-менее понимаются интуитивно и без книги. Но есть неочевидные моменты:
- много сигналов совершенно разного предназначения. Хорошо помнить и знать этот список.
- сигналы можно маскировать, то есть временно блокировать доставку сигналов к процессу (кроме SIGKILL и SIGSTOP)
- проверено на печальной практике, что разработчики софта очень любят изменять дефолтное поведение сигналов и делают это не всегда корректно.