Роберт Лав - Разработка ядра Linux
Реализация потоков в ядре Linux
Многопоточность — это популярная сегодня программная абстракция. Она обеспечивает выполнение нескольких потоков в совместно используемом адресном пространстве памяти. Потоки также могут совместно использовать открытые файлы и другие ресурсы. Многопоточность используется для параллельного программирования (concurrent programming), что на многопроцессорных системах обеспечивает истинный параллелизм.
Реализация потоков в операционной системе Linux уникальна. Для ядра Linux не существует отдельной концепции потоков. В ядре Linux потоки реализованы так же, как и обычные процессы. В ОС Linux нет никакой особенной семантики для планирования выполнения потоков или каких-либо особенных структур данных для представления потоков. Поток— это просто процесс, который использует некоторые ресурсы совместно с другими процессами. Каждый поток имеет структуру task_struct и представляется для ядра обычным процессом (который совместно использует ресурсы, такие как адресное пространство, с другими процессами).
В этом смысле Linux отличается от других операционных систем, таких как Microsoft Windows или Sun Solaris, которые имеют явные средства поддержки потоков в ядре (в этих системах иногда потоки называются процессами с быстрым переключением контекста, lightweight process). Название "процесс с быстрым переключением контекста" показывает разницу между философией Linux и других операционных систем. Для остальных операционных систем потоки— это абстракция, которая обеспечивает облегченные, более быстрые для исполнения сущности, чем обычные тяжелые процессы. Для операционной системы Linux потоки — это просто способ совместного использования ресурсов несколькими процессами (которые и так имеют достаточно малое время переключения контекста)[18].
Допустим, у нас есть процесс, состоящий из четырех потоков. В операционных системах с явной поддержкой потоков должен существовать дескриптор процесса, который далее указывает на четыре потока. Дескриптор процесса описывает совместно используемые ресурсы, такие как адресное пространство и открытые файлы. Потоки описываются ресурсами, которые принадлежат только им. В ОС Linux, наоборот, существует просто четыре процесса и, соответственно, четыре обычные структуры task_struct. Четыре процесса построены так, чтобы совместно использовать определенные ресурсы.
Потоки создаются так же, как и обычные задания, за исключением того, что в системный вызов clone() передаются флаги с указанием, какие ресурсы должны использоваться совместно:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
Результат выполнения показанного кода будет таким же, как и при выполнении обычного вызова fork(), за исключением того, что адресное пространство, ресурсы файловой системы, дескрипторы файлов и обработчики сигналов останутся общими. Другими словами, новая задача, так же как и родительский процесс, — обычные потоки. В отличие от этого, обычный вызов fork() может быть реализован следующим образом:
clone(SIGCHLD, 0);
а вызов vfork() в таком виде:
clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0);
Флаги, которые передаются в системный вызов clone() , помогают указать особенности поведения нового процесса и детализировать, какие ресурсы должны быть общими для родительского и порожденного процессов. В табл. 3.1 приведены флаги системного вызова clone() и их эффект.
Таблица 3.1. Флаги системного вызова clone()
Флаг Описание CLONE_FILES Родительский и порожденный процессы совместно используют открытые файлы CLONE_FS Родительский и порожденный процессы совместно используют информацию о файловой системе CLONE_IDLETASK Установить значение PID в нуль (используется только для холостых (idle) задач) CLONE_NEWNS Создать новое пространство имен для порожденной задачи CLONE_PARENT Родительский процесс вызывающего процесса становится родительским и для порожденного CLONE_PTRACE Продолжить трассировку и для порожденного процесса CLONE_SETTID Возвратить значение идентификатора TID в пространство пользователя CLONE_SETTLS Для порожденного процесса создать новую область локальных данных потока (thread local storage, TLS) CLONE_SIGHAND У порожденного и родительского процессов будут общие обработчики сигналов CLONE_SYSVSEM У родительского и порожденного процессов будет общая семантика обработки флага SEM_UNDO для семафоров System V CLONE_THREAD Родительский и порожденный процессы будут принадлежать одной группе потоков CLONE_VFORK Использовать vfork(): родительский процесс будет находиться а приостановленном состоянии, пока порожденный процесс не возобновит его работу CLONE_UNTRACED Запретить родительскому процессу использование флага CLONE_PTRACE для порожденного процесса CLONE_STOP Запустить процесс в состоянии TASK_STOPPED CLONE_CHILD_CLEARTID Очистить идентификатор TID для порожденного процесса CLONE_CHILD_SETTID Установить идентификатор TID для порожденного процесса CLONE_PARENT_SETTID Установить идентификатор TID для родительского процесса CLONE_VM У порожденного и родительского процессов будет общее адресное пространствоПотоки в пространстве ядра
Часто в ядре полезно выполнить некоторые операции в фоновом режиме. В ядре такая возможность реализована с помощью потоков пространства ядра (kernel thread) — обычных процессов, которые выполняются исключительно в пространстве ядра. Наиболее существенным отличием между потоками пространства ядра и обычными процессами является то, что потоки в пространстве ядра не имеют адресного пространства (значение указателя mm для них равно NULL). Эти потоки работают только в пространстве ядра, и их контекст не переключается в пространство пользователя. Тем не менее потоки в пространстве ядра планируются и вытесняются так же, как и обычные процессы.
В ядре Linux потоки пространства ядра выполняют определенные задания, наиболее часто используемые, — это pdfush и ksoftirq. Эти потоки создаются при загрузке системы другими потоками пространства ядра. В действительности поток в пространстве ядра может быть создан только другим потоком, работающим в пространстве ядра. Интерфейс для запуска нового потока в пространстве ядра из уже существующего потока следующий:
int kernel_thread(int (*fn)(void*), void* arg, unsigned long flags);
Новая задача создается с помощью обычного системного вызова clone() с соответствующими значениями флагов, указанными в параметре flags. При возврате из системного вызова родительский поток режима ядра завершается и возвращает указатель на структуру task_struct порожденного процесса. Порожденный процесс выполняет функцию, адрес которой указан в параметре fn, в качестве аргумента этой функции передается параметр arg. Для указания обычных флагов потоков пространства ядра существует флаг CLONE_KERNEL, который объединяет в себе флаги CLONE_FS, CLONE_FILES и CLONE_SIGHAND, так как большинство потоков пространства ядра должны указывать эти флаги в параметре flags.
Чаще всего поток пространства ядра продолжает выполнять свою функцию вечно (или, по крайней мере, до перегрузки системы, но когда она произойдет в случае ОС Linux- неизвестно). Функция потока обычно содержит замкнутый цикл, в котором поток пространства ядра по необходимости возобновляет выполнение, исполняет свои обязанности и снова переходит в приостановленное состояние.