Завершение процесса
Завершение процесса
Как это ни грустно, но любой процесс в конечном итоге должен завершиться. Когда процесс завершается, ядро должно освободить ресурсы, занятые процессом, и оповестить процесс, который является родительским для завершившегося, о том, что его порожденный процесс, к сожалению, "умер".
Обычно уничтожение процесса происходит тогда, когда процесс вызывает системный вызов exit() явно или неявно при выходе из главной функции программы (компилятор языка С помещает вызов функции exit() после возврата из функции main()). Процесс также может быть завершен непроизвольно. Это происходит, когда процесс получает сигнал или возникает исключительная ситуация, которую процесс не может обработать или проигнорировать. Независимо от того, каким образом процесс завершается, основную массу работы выполняет функция do_exit(), а именно указанные далее операции.
• Устанавливается флаг PF_EXITING в поле flags структуры task struct.
• Вызывается функция del_timer_sync(), чтобы удалить все таймеры ядра. После выхода из этой функции гарантируется, что нет никаких ожидающих таймеров и никакой обработчик таймера не выполняется.
• Если включена возможность учета системных ресурсов, занятых процессами (BSD process accounting), то вызывается функция acct_process() для записи информации об учете ресурсов, которые использовались процессом.
• Вызывается функция __exit_mm() для освобождения структуры mm_struct, занятой процессом. Если эта структура не используется больше ни одним процессом (другими словами, не является разделяемой), то она освобождается совсем.
• Вызывается функция exit_sem(). Если процесс находится в очереди ожидания на освобождение семафора подсистемы IPC, то в этой функции процесс удаляется из этой очереди.
• Вызываются функции __exit_files(), __exit_fs(), exit_namespace() и exit_signals() для уменьшения счетчика ссылок на объекты, которые отвечают файловым дескрипторам, данным по файловой системе, пространству имен и обработчикам сигналов соответственно. Если счетчик ссылок какого- либо объекта достигает значения, равного нулю, то соответствующий объект больше не используется никаким процессом и удаляется.
• Устанавливается код завершения задания, который хранится в поле exit_code структуры task struct. Значение этого кода передается как аргумент функции exit() или задается тем механизмом ядра, из-за которого процесс завершается.
• Вызывается функция exit_notify(), которая отправляет сигналы родительскому процессу завершающегося задания и назначает новый родительский процесс (reparent) для всех порожденных завершающимся заданием процессов, этим процессом становится или какой-либо один поток из группы потоков завершающегося процесса, или процесс init. Состояние завершающегося процесса устанавливается в значение TASK_ZOMBIE.
• Вызывается функция schedule() для переключения на новый процесс (см. главу 4, "Планирование выполнения процессов"). Поскольку процесс в состоянии TASK_ZOMBIE никогда не планируется на выполнение, этот код является последним, который выполняется завершающимся процессом.
Исходный код функции do_exit() описан в файле kernel/exit.c.
К этому моменту освобождены все объекты, занятые задачей (если они используются только этой задачей). Задача больше не может выполняться (действительно, у нее больше нет адресного пространства, в котором она может выполняться), а кроме того, состояние задачи — TASK_ZOMBIE Единственные области памяти, которые теперь занимает процесс, — это стек режима ядра и слябовый объект, соответственно содержащие структуры thread_info и task_struct.
Задание завершено настолько, насколько остается возможность передать необходимую информацию родительскому процессу.