Разбор задач 3 и 4: fork, потоки, init, VFS и ссылки
Задачи 3 и 4 — теоретические. Ниже — ответы, которые можно почти дословно писать на экзамене. Подробности — в уроках модулей «Процессы и потоки» и «Файловые системы».
3(a). Действия ядра при fork
Образцовый ответ
- Процесс выполняет системный вызов, процессор переходит в режим ядра. Ядро проверяет ограничения: число процессов пользователя (
RLIMIT_NPROC), наличие памяти. При неудачеforkвозвращает −1 (EAGAIN,ENOMEM). - Выделяет дескриптор процесса (
task_structв Linux, запись таблицы процессов в классическом UNIX) и стек ядра. Копирует в него поля родителя, назначает новый PID, записывает родителя (PPID). Обнуляет учёт времени и ожидающие сигналы. Группа и сеанс наследуются. - Копирует таблицу файловых дескрипторов: дескрипторы потомка указывают на те же записи таблицы открытых файлов (общая позиция в файле), счётчики ссылок увеличиваются. Копирует диспозиции сигналов и маску, текущий и корневой каталоги, umask, учётные данные.
- Адресное пространство: копирует описания областей (VMA) и таблицы страниц. Сами страницы не копирует: записываемые страницы в обоих процессах помечаются «только для чтения» (copy-on-write), у кадров растёт счётчик ссылок. Разделяемые отображения (
MAP_SHARED) остаются общими. При первой записи происходит страничный отказ, и ядро копирует только эту страницу. - Готовит сохранённый контекст потомка: копия регистров родителя, но регистр результата равен 0. Потомок продолжит выполнение с того же места — возврата из
fork. - Помещает потомка в очередь готовых процессов. Родителю возвращает 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 — слой ядра между системными вызовами и конкретными файловыми системами. Он нужен для:
- Единого интерфейса:
open,read,write,stat,mmapработают одинаково для ext4, XFS, FAT, NFS, procfs, sysfs, tmpfs, каналов и сокетов. Программе не важно, где файл. - Единого дерева каталогов: разные ФС монтируются в общее пространство имён. VFS переходит через точки монтирования при разборе пути.
- Общего кода: разбор путей, кеш элементов каталога (dentry) и inode, кеш страниц, проверка прав, таблица открытых файлов, блокировки. Всё это пишется один раз, а не в каждой ФС.
- Расширяемости: новая ФС — это модуль, который заполняет таблицы операций (
super_operations,inode_operations,file_operations) и регистрируется (register_filesystem). VFS вызывает функции через указатели, не зная, какая это ФС. По сути это объектно-ориентированный полиморфизм на C.
4(b). Поля виртуального индексного дескриптора
Образцовый ответ
Виртуальный inode (struct inode в Linux, vnode в System V/BSD) — представление файла в памяти ядра, общее для всех ФС. Поля делятся на три группы:
- Атрибуты файла (копия с диска): номер
i_ino, тип и праваi_mode, владелецi_uid/i_gid, размерi_size/i_blocks, временаi_atime/i_mtime/i_ctime, число жёстких ссылокi_nlink, номер устройстваi_rdevдля файлов устройств. - Связи: суперблок своей ФС
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— ФС, смонтированная на этот каталог. - Состояние в памяти: счётчик использования
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
3(a). Расставьте действия ядра при fork() по порядку:
- Скопировать адресное пространство: дескрипторы областей и таблицы страниц; записываемые страницы обоих процессов пометить «только чтение» для copy-on-write
- Скопировать или разделить ресурсы: таблицу файловых дескрипторов (с увеличением счётчиков открытых файлов), обработчики сигналов, текущий каталог, учётные данные
- Поставить потомка в очередь готовых процессов; родителю вернуть PID потомка
- Вход в ядро через системный вызов; проверка лимитов (RLIMIT_NPROC, свободная память)
- Выделить дескриптор процесса (task_struct) и стек ядра, назначить новый PID
- Подготовить контекст потомка так, чтобы он вернулся из fork с результатом 0
3(b). Что разделяется, а что у каждого своё?
| Общее у потоков одного процесса | Своё у каждого потока | Своё у процессов после fork (копия) | |
|---|---|---|---|
| Адресное пространство (код, данные, куча) | |||
| Стек и регистры | |||
| Таблица файловых дескрипторов | |||
| Обработчики сигналов | |||
| Маска блокированных сигналов | |||
| PID (getpid) |
3(c). Как init использует идентификатор сеанса?
4(a). Зачем ядру виртуальная файловая система?
4(c). Можно ли сделать жёсткую ссылку на файл на другом блочном устройстве?
Войдите, чтобы отмечать пройденные уроки.