Разбор задач 3 и 4: fork, потоки, init, VFS и ссылки

⏱ 40 мин · 🧩 заданий: 5

Задачи 3 и 4 — теоретические. Ниже — ответы, которые можно почти дословно писать на экзамене. Подробности — в уроках модулей «Процессы и потоки» и «Файловые системы».

3(a). Действия ядра при fork

родитель: fork() ──▶ ядро 1. проверить лимиты 2. task_struct + стек ядра + новый PID 3. files, fs, sighand, cred: копия, refcount++ 4. mm: копия VMA и таблиц страниц, COW 5. контекст потомка: вернуть 0 6. потомок → очередь готовых родитель ◀── PID потомка потомок ◀── 0 (когда его выберет планировщик)
Образцовый ответ
  1. Процесс выполняет системный вызов, процессор переходит в режим ядра. Ядро проверяет ограничения: число процессов пользователя (RLIMIT_NPROC), наличие памяти. При неудаче fork возвращает −1 (EAGAIN, ENOMEM).
  2. Выделяет дескриптор процесса (task_struct в Linux, запись таблицы процессов в классическом UNIX) и стек ядра. Копирует в него поля родителя, назначает новый PID, записывает родителя (PPID). Обнуляет учёт времени и ожидающие сигналы. Группа и сеанс наследуются.
  3. Копирует таблицу файловых дескрипторов: дескрипторы потомка указывают на те же записи таблицы открытых файлов (общая позиция в файле), счётчики ссылок увеличиваются. Копирует диспозиции сигналов и маску, текущий и корневой каталоги, umask, учётные данные.
  4. Адресное пространство: копирует описания областей (VMA) и таблицы страниц. Сами страницы не копирует: записываемые страницы в обоих процессах помечаются «только для чтения» (copy-on-write), у кадров растёт счётчик ссылок. Разделяемые отображения (MAP_SHARED) остаются общими. При первой записи происходит страничный отказ, и ядро копирует только эту страницу.
  5. Готовит сохранённый контекст потомка: копия регистров родителя, но регистр результата равен 0. Потомок продолжит выполнение с того же места — возврата из fork.
  6. Помещает потомка в очередь готовых процессов. Родителю возвращает PID потомка. Кто из них выполнится первым, не определено.

3(b). Процесс vs поток

Образцовый ответ

Процесс — единица владения ресурсами, поток — единица планирования. В Linux оба создаются вызовом clone(), разница — в том, что разделяется:

  • fork() ≈ clone(SIGCHLD): новый процесс получает копии адресного пространства (COW), таблицы дескрипторов, обработчиков сигналов. Новый PID.
  • pthread_create() ≈ clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | ...): новый поток разделяет адресное пространство, файлы, обработчики сигналов. Тот же PID (TGID), свой TID. Своими у потока остаются только стек (выделяется в общем адресном пространстве), регистры, маска сигналов, errno и TLS.

Следствия: создать поток дешевле, потому что не нужно копировать таблицы страниц и дескрипторы. Переключение между потоками одного процесса не меняет cr3 и не сбрасывает TLB. Потоки общаются через общую память без IPC, но нужна синхронизация. Ошибка в одном потоке (SIGSEGV) завершает весь процесс. Процессы изолированы друг от друга.

3(c). init и идентификатор сеанса

Образцовый ответ
  • Создаёт сеансы. Каждый процесс из /etc/inittab, прежде всего getty на терминалах, init запускает так: fork → в потомке setsid() → exec. Процесс становится лидером нового сеанса и группы (PID = SID = PGID). Терминал, который открывает getty, становится управляющим терминалом сеанса. Затем login и shell работают в том же сеансе.
  • Управляет по группе и сеансу. При смене уровня выполнения или остановке записи init посылает сигнал не одному процессу, а всей группе: kill(-pid, SIGTERM), затем kill(-pid, SIGKILL). Так завершаются все процессы пользовательского сеанса на этом терминале.
  • Учитывает сеансы в utmp. Записи содержат PID, терминал, тип и поле ut_session. Когда лидер сеанса завершается, init получает SIGCHLD, вызывает wait, помечает запись DEAD_PROCESS и для respawn запускает новый getty в новом сеансе.
  • Оставшиеся процессы сеанса после смерти лидера становятся сиротами. init их усыновляет и собирает их статус.

4(a). Зачем нужна VFS

Образцовый ответ

VFS — слой ядра между системными вызовами и конкретными файловыми системами. Он нужен для:

  1. Единого интерфейса: open, read, write, stat, mmap работают одинаково для ext4, XFS, FAT, NFS, procfs, sysfs, tmpfs, каналов и сокетов. Программе не важно, где файл.
  2. Единого дерева каталогов: разные ФС монтируются в общее пространство имён. VFS переходит через точки монтирования при разборе пути.
  3. Общего кода: разбор путей, кеш элементов каталога (dentry) и inode, кеш страниц, проверка прав, таблица открытых файлов, блокировки. Всё это пишется один раз, а не в каждой ФС.
  4. Расширяемости: новая ФС — это модуль, который заполняет таблицы операций (super_operations, inode_operations, file_operations) и регистрируется (register_filesystem). VFS вызывает функции через указатели, не зная, какая это ФС. По сути это объектно-ориентированный полиморфизм на C.

4(b). Поля виртуального индексного дескриптора

Образцовый ответ

Виртуальный inode (struct inode в Linux, vnode в System V/BSD) — представление файла в памяти ядра, общее для всех ФС. Поля делятся на три группы:

  1. Атрибуты файла (копия с диска): номер i_ino, тип и права i_mode, владелец i_uid/i_gid, размер i_size/i_blocks, времена i_atime/i_mtime/i_ctime, число жёстких ссылок i_nlink, номер устройства i_rdev для файлов устройств.
  2. Связи: суперблок своей ФС i_sb (в vnode — v_vfsp), таблица операций i_op/i_fop (v_op), кеш страниц файла i_mapping, список имён i_dentry, данные для устройств и каналов, указатель на данные конкретной ФС (v_data, i_private или окружающая структура ext4_inode_info). В vnode есть ещё v_vfsmountedhere — ФС, смонтированная на этот каталог.
  3. Состояние в памяти: счётчик использования i_count (v_count), флаги i_state (грязный, новый, освобождается), блокировки, ссылки в хеш-таблице и списке LRU.

Главное: указатель на таблицу операций делает inode «виртуальным».

4(c). Жёсткая ссылка на другое устройство

Образцовый ответ

Нельзя. Жёсткая ссылка — это ещё один элемент каталога (имя → номер inode). Элемент каталога не хранит идентификатор устройства, а номер inode уникален только внутри своей ФС: на другом разделе под тем же номером другой файл. Кроме того, счётчик ссылок i_nlink хранится в inode исходной ФС, а другая ФС не может его менять. Поэтому ядро в link() проверяет, что исходный файл и каталог нового имени на одной смонтированной ФС, иначе возвращает EXDEV («Invalid cross-device link»). Это касается и одного устройства, смонтированного дважды (bind mount через разные точки).

Выход — символическая ссылка: она хранит путь, а не номер inode, поэтому может указывать куда угодно, даже на несуществующий файл.

Главное в задачах 3–4

Отвечайте структурно — пунктами, со словами-маркерами: task_struct, PID, COW, таблица дескрипторов, очередь готовых, 0 / PID (3a); clone и флаги, общее адресное пространство, свой стек (3b); setsid, kill(-pgid), utmp, respawn (3c); единый интерфейс, монтирование, таблицы операций (4a); атрибуты, связи, состояние (4b); номер inode уникален в ФС, EXDEV, symlink (4c).

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Расставить по порядку

3(a). Расставьте действия ядра при fork() по порядку:

  1. Скопировать адресное пространство: дескрипторы областей и таблицы страниц; записываемые страницы обоих процессов пометить «только чтение» для copy-on-write
  2. Скопировать или разделить ресурсы: таблицу файловых дескрипторов (с увеличением счётчиков открытых файлов), обработчики сигналов, текущий каталог, учётные данные
  3. Поставить потомка в очередь готовых процессов; родителю вернуть PID потомка
  4. Вход в ядро через системный вызов; проверка лимитов (RLIMIT_NPROC, свободная память)
  5. Выделить дескриптор процесса (task_struct) и стек ядра, назначить новый PID
  6. Подготовить контекст потомка так, чтобы он вернулся из fork с результатом 0
Задание 2 Заполнить таблицу

3(b). Что разделяется, а что у каждого своё?

Общее у потоков одного процессаСвоё у каждого потокаСвоё у процессов после fork (копия)
Адресное пространство (код, данные, куча)
Стек и регистры
Таблица файловых дескрипторов
Обработчики сигналов
Маска блокированных сигналов
PID (getpid)
Отметьте ячейку — «+», пустая — «−».
Задание 3 Один вариант ответа

3(c). Как init использует идентификатор сеанса?

Задание 4 Несколько вариантов

4(a). Зачем ядру виртуальная файловая система?

Задание 5 Один вариант ответа

4(c). Можно ли сделать жёсткую ссылку на файл на другом блочном устройстве?

Войдите, чтобы отмечать пройденные уроки.