Linux 3.2 の kill_orphaned_pgrp 関数について詳しく見ていきます。
1. 使用例を考える
Shell を使ったことがある方であれば、次のシナリオが理解できるでしょう:
sudo-su-bash@Sudo-su-BashdeMacBook-Air ~ % sleep 100
^Z
zsh: suspended sleep 100
sudo-su-bash@Sudo-su-BashdeMacBook-Air ~ % nc -l 8080
^Z
zsh: suspended nc -l 8080
sudo-su-bash@Sudo-su-BashdeMacBook-Air ~ % jobs
[1] - suspended sleep 100
[2] + suspended nc -l 8080
sudo-su-bash@Sudo-su-BashdeMacBook-Air ~ % fg %1
[1] - continued sleep 100
^Z
zsh: suspended sleep 100
sudo-su-bash@Sudo-su-BashdeMacBook-Air ~ % bg %1
[1] - continued sleep 100
sudo-su-bash@Sudo-su-BashdeMacBook-Air ~ % exit
ここで、`Ctrl+Z` を押すことでフロントエンドプロセスを一時停止し、`bg` コマンドでバックグラウンドに移行できます。しかし、`exit` を実行するとシェルが終了し、バックグラウンドで実行されていたプロセス(`sleep` と `nc`)が取り残されます。この問題に対処するために使用されるのがこの関数です。
2. 関数の概要
この関数は、**孤立したプロセスグループを検出し、そのグループ内のすべての Stopped 状態のプロセスに対して SIGHUP や SIGCONT シグナルを送信する**ことを目的としています。
static void orphaned_group_handler(struct task_struct *current_task, struct task_struct *parent_task) {
struct pid *group_id = get_process_group(current_task);
struct task_struct *ignored_task = current_task;
if (!parent_task) {
parent_task = current_task->real_parent;
} else {
ignored_task = NULL;
}
if (get_process_group(parent_task) != group_id &&
get_session(parent_task) == get_session(current_task) &&
check_orphaned_group(group_id, ignored_task) &&
has_stopped_tasks(group_id)) {
send_signal_to_group(SIGTERM, group_id);
send_signal_to_group(SIGCONT, group_id);
}
}
3. 孤立プロセスグループの判定
以下は孤立プロセスグループであるかどうかを判定する関数です。
// 指定されたプロセスグループが孤立しているかチェック
static int check_orphaned_group(struct pid *group_id, struct task_struct *ignored_task) {
struct task_struct *task;
iterate_tasks_in_group(group_id, task) {
if ((task == ignored_task) || // 忽略対象のプロセス
(task->state == TASK_ZOMBIE && is_single_thread(task)) || // シングルスレッドで状態が ZOMBIE
is_system_init(task->parent)) { // 親プロセスがシステムの init
continue;
}
if (get_process_group(task->parent) != group_id &&
get_session(task->parent) == get_session(task)) {
return 0; // 他のセッション内に親が存在しない場合
}
}
return 1; // 孤立している
}
この関数は、以下の条件を満たす場合にプロセスグループを孤立とみなします:
- 親プロセスが init ではない。
- 親子プロセスが異なるグループに属しており、同じセッションに存在する。
- プロセスグループ内の少なくとも1つのプロセスが動作中またはマルチスレッドである。
4. 条件分岐の詳細
次に、実際にシグナルを送信する部分を見てみましょう。
if (get_process_group(parent_task) != group_id &&
get_session(parent_task) == get_session(current_task) &&
check_orphaned_group(group_id, ignored_task) &&
has_stopped_tasks(group_id)) {
send_signal_to_group(SIGTERM, group_id);
send_signal_to_group(SIGCONT, group_id);
}
ここでの条件は、パフォーマンスを向上させるためのものです。もし最初の条件を省略すると、`check_orphaned_group` 関数が全プロセスを走査することになり、大きな負荷がかかります。
5. 特殊ケースの考慮
最後に、いくつかの特殊ケースについて考えてみます。
struct pid *group_id = get_process_group(target_task);
struct task_struct *ignored_task = target_task;
if (!parent_task) {
parent_task = target_task->real_parent;
} else {
ignored_task = NULL;
}
`parent_task` が NULL の場合は、現在のプロセスの実際の親プロセスが代入されます。また、`ignored_task` は特定のプロセスを無視する役割を持ちます。