Получение указателя на предыдущее задание работает аналогично.
list_entry(task->tasks.prev, struct task_struct, tasks);
Дна указанных выше выражения доступны также в виде макросов next_task(task)
(получить следующую задачу), prev_task(task)
(получить предыдущую задачу). Наконец, макрос for_each_process(task)
позволяет выполнить цикл по всему списку задач. На каждом шаге цикла переменная task
указывает на следующую задачу из списка:
struct task_struct *task;
for_each_process(task) {
/* просто печатается имя команды и идентификатор PID
для каждой задачи */
printk("%s[%d]\n", task->comm, task->pid);
}
Следует заметить, что организация цикла по всем задачам системы, в которой выполняется много процессов, может быть достаточно дорогостоящей операцией. Для применения такого кода должны быть веские причины (и отсутствовать другие альтернативы).
Создание нового процесса
В операционной системе Unix создание процессов происходит уникальным образом. В большинстве операционных систем для создания процессов используется метод fork
и exec
[15].
В начале с помощью функции fork
создается порожденный процесс, который является копией текущего задания. Порожденный процесс отличается от родительского только значением идентификатора PID
(который является уникальным в системе), значением параметра PPID
(идентификатор PID
родительского процесса, который устанавливается в значение PID
порождающего процесса), некоторыми ресурсами, такими как ожидающие на обработку сигналы (которые не наследуются), а также статистикой использования ресурсов. Вторая функция — exec
— загружает исполняемый файл в адресное пространство процесса и начинает исполнять его. Комбинация функций fork
и exec
аналогична той одной функции создания процесса, которую предоставляет большинство операционных систем.
Копирование при записи
Традиционно при выполнении функции fork
делался дубликат всех ресурсов родительского процесса и передавался порожденному. Такой подход достаточно наивный и неэффективный. В операционной системе Linux вызов fork
реализован с использованием механизма exec
сразу после вызова fork
, то эти страницы никогда и не копируются. Единственные накладные расходы, которые вносит вызов функции fork
, — это копирование таблиц страниц родительского процесса и создание дескриптора порожденного процесса. Данная оптимизация предотвращает ненужное копирование большого количества данных (размер адресного пространства часто может быть более 10 Мбайт), так как процесс после разветвления в большинстве случаев сразу же начинает выполнять новый исполняемый образ. Эта оптимизация очень важна, потому чти идеология операционной системы Unix предусматривает быстрое выполнение процессов.
fork
В операционной системе Linux функция fork
реализована через системный вызов clone
. Этот системный вызов может принимать в качестве аргументов набор флагов, определяющих, какие ресурсы должны быть общими (если вообще должны) у родительского и порожденного процессов. Далее в разделе "Реализация потоков в ядре Linux" об этих флагах рассказано более подробно. Библиотечные вызовы fork
, vfork
и __clone
вызывают системную функцию clone
с соответствующими флагами. В свою очередь системный вызов clone
вызывает функцию ядра do_fork
.