LoginSignup
3

More than 5 years have passed since last update.

カーネルのシグナル生成と配信の実装を読んだ

Posted at

Linuxのシグナルの実装について調べた。

カーネルバージョン

v4.5.0

シグナルの仕組み

普通のシグナルと、リアルタイムシグナルの2種類がある。
普通のシグナルは同じ種類のシグナルを2回送っても1度しか配信されないことがあるが、リアルタイムシグナルはキューにつながって必ず生成した回数分届く(メモリが足りていれば)

シグナルは生成と配信という2段階を踏んで届く。
生成はシグナル発信側プロセスが受信側のプロセスのtask_struct->pendingに対して保留中のキューをつなげる操作のことで、
配信は受信側のプロセスのschedule()の延長でdo_signalを呼び出す操作のことである。

条件分岐が複雑になるので、以下は普通のシグナルの場合だけをみる。

シグナル生成

__send_signal()で生成される。

普通のシグナルがすでに保留されているかどうかはlegacy_queue()で確認する。
引数のsignalsが現在保留されているシグナルのビットマップである。

static inline int legacy_queue(struct sigpending *signals, int sig)
{
    return (sig < SIGRTMIN) && sigismember(&signals->signal, sig);
}

まだ保留されていない場合、次の処理でシグナルがキューに繋がれその情報をinfoに格納する。
SI_USERはシグナルの送信者がユーザプロセスの場合、SI_KERNELはカーネルから。
キューに繋げられたら、sigaddset()で保留済みのビットを立てる。

    q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,
        override_rlimit);
    if (q) {
        list_add_tail(&q->list, &pending->list);
        switch ((unsigned long) info) {
        case (unsigned long) SEND_SIG_NOINFO:
            q->info.si_signo = sig;
            q->info.si_errno = 0;
            q->info.si_code = SI_USER;
            q->info.si_pid = task_tgid_nr_ns(current,
                            task_active_pid_ns(t));
            q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
            break;
        case (unsigned long) SEND_SIG_PRIV:
            q->info.si_signo = sig;
            q->info.si_errno = 0;
            q->info.si_code = SI_KERNEL;
            q->info.si_pid = 0;
            q->info.si_uid = 0;
            break;
        default:
            copy_siginfo(&q->info, info);
            if (from_ancestor_ns)
                q->info.si_pid = 0;
            break;
        }

        userns_fixup_signal_uid(&q->info, t);

    } else if (!is_si_special(info)) {

...

out_set:
    signalfd_notify(t, sig);
    sigaddset(&pending->signal, sig);
    complete_signal(sig, t, group);
ret:
    trace_signal_generate(sig, info, t, group, result);
    return ret;

シグナルの配信

システムコールからユーザモードに戻るとき、do_signal()が呼び出されget_signal()で保留中のシグナルハンドラを処理する。ハンドラが設定されている場合は、handle_signal()で実行される。ユーザモードへ戻るときのレジスタ情報などを差し替えるのだろうと思う。詳細は次回記事参照。

void do_signal(struct pt_regs *regs)
{
    struct ksignal ksig;

    if (get_signal(&ksig)) {
        /* Whee! Actually deliver the signal.  */
        handle_signal(&ksig, regs);
        return;
    }

...

プロセスごとのシグナルの状態と設定の確認

各プロセスごとのシグナルの保留状態、ブロックなどの設定は/proc/[pid]/statusから確認できる。
SigPndからSigCgtはシグナル番号のビットマップになっている。
例えばSigIgnは2進数にすると、100000000000001000000000000なので、13と27のシグナルがブロックされている。

juntaki@lab% cat /proc/1342/status
Name:   emacs
State:  S (sleeping)
Tgid:   1342
Ngid:   0

...

Threads:        2 #スレッド数
SigQ:   1/15922 #キューの長さと上限
SigPnd: 0000000000000000 保留中:task->pending.signal
ShdPnd: 0000000000000000 task->signal->shared_pending.signal
SigBlk: 0000000000000000 ブロックのマスク:task->blocked
SigIgn: 0000000004001000 ハンドラがSIG_IGN:task->sighand->action->sa.sa_handler
SigCgt: 00000001db816eff シグナルハンドラが指定されている

...

まとめ

シグナルは生成と配信の2段階で実現されている。
生成は比較的簡単な構造体の操作だけだが、配信はプロセス切り替えなどのアーキテクチャ依存の部分があるため、やや複雑だった。
次回は配信の箇所をもう少し読みたい。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3