訳註: これは作業してる最中のもので、本来、訳作業者以外は見えないものです。 訳作業者以外は見ないでください。意味が反対になったりとか当然にあるから、 内容は利用しないでください。 Chapter3.Processes and threads

Table of Contents

3.1. Process startup
3.2. Traps and system calls
3.3. Processes and threads creation
3.4. Processes and threads termination
3.5. Signal delivery
3.6. Thread scheduling

この章では NetBSD でのプロセスとスレッドについて記述します。これには プロセス startup, traps およびシステムコール、プロセスおよびスレッド生成 および 終了termination 、シグナル配送、およびスレッドスケジューリングが含まれます。

CAUTION! この章は作業中で: タイプミスや技術的誤りに対して 未だ校閲されていません

3.1.Process startup

3.1.1.execve usage

Unix システムでは、新しいプログラムは execve システムを用いて開始し、成功すると execve は currently-executing プログラム を新しいものに置き換えます。これは同一プロセス内で、 全仮想メモリー mapping の再初期化と、メモリーへの新プログラムバイナリーの ロードによって完了します。 calling one[呼び出し元] 以外のプロセスのスレッドが終了すると、 calling[呼び出し元] スレッド CPU context は新しいプログラムの startup の実行のためにリセットされます。

ここに execve プロトタイプがあり:

int execve( path,
argv,
envp);
const char *path;
char *const argv[];
char *const envp[];

path は、新実行形式物への ファイルシステム path 。 argv および envp は、プログラム引数および環境変数を hold する 2つのナル文字で終わる文字列配列です。 execve は 新プロセススタックへの配列のコピーをします。

3.1.2.Overview of in-kernel execve code path

これは、 i386 機種で、ネイティブ 32 bit ELF バイナリーを 実行するときの NetBSD カーネル内の execve 実装のトップダウンモジュール図表で:

  • src/sys/kern/kern_exec.c: sys_execve

    • src/sys/kern/kern_exec.c: execve1

      • src/sys/kern/kern_exec.c: check_exec

        • src/sys/kern/kern_verifiedexec.c: veriexec_verify
        • src/sys/kern/kern_conf.c: *execsw[]->es_makecmds

          • src/sys/kern/exec_elf32.c: exec_elf_makecmds

            • src/sys/kern/exec_elf32.c: exec_check_header
            • src/sys/kern/exec_elf32.c: exec_read_from
            • src/sys/kern/exec_conf.c: *execsw[]->u.elf_probe_func

              • src/sys/kern/exec_elf32.c: netbsd_elf_probe
            • src/sys/kern/exec_elf32.c: elf_load_psection
            • src/sys/kern/exec_elf32.c: elf_load_file
            • src/sys/kern/exec_conf.c: *execsw[]->es_setup_stack

              • src/sys/kern/exec_subr.c: exec_setup_stack
      • *fetch_element

        • src/sys/kern/kern_exec.c: execve_fetch_element
      • *vcp->ev_proc

        • src/sys/kern/exec_subr.c: vmcmd_map_zero
        • src/sys/kern/exec_subr.c: vmcmd_map_pagedvn
        • src/sys/kern/exec_subr.c: vmcmd_map_readvn
        • src/sys/kern/exec_subr.c: vmcmd_readvn
      • src/sys/kern/exec_conf.c: *execsw[]->es_copyargs

        • src/sys/kern/kern_exec.c: copyargs
      • src/sys/kern/kern_clock.c: stopprofclock
      • src/sys/kern/kern_descrip.c: fdcloseexec
      • src/sys/kern/kern_sig.c: execsigs
      • src/sys/kern/kern_ras.c: ras_purgeall
      • src/sys/kern/exec_subr.c: doexechooks
      • src/sys/sys/event.h: KNOTE

        • src/sys/kern/kern_event.c: knote
      • src/sys/kern/exec_conf.c: *execsw[]->es_setregs

        • src/sys/arch/i386/i386/machdep.c: setregs
      • src/sys/kern/kern_exec.c: exec_sigcode_map
      • src/sys/kern/kern_exec.c: *p->p_emul->e_proc_exit (NULL)
      • src/sys/kern/kern_exec.c: *p->p_emul->e_proc_exec (NULL)

execvefetch_element と呼ばれる関数へのポインターと 共に、カーネル空間にプログラム引数および環境変数を loading する責任をもつ execve1 を呼びます。 この抽象化関数の主要な理由は 64 bit システムで 32 bit プロセスからポインターの取得を許すためです。

execve1struct exec_package 型( src/sys/sys/exec.h で定義) の変数を called 関数と様々な情報を共有するために使います。

makecmds は プログラムがロード可能かのチェックに責任を持ち、 後に仮想メモリー空間のセットアップ、および、 プログラムコードおよびデータセクションのロードに使える、 仮想メモリー commands (vmcmd の) のセットを構築する責任を持ちます。 vmcmd のセットは、 exec package の ep_vmcmds フィールドに蓄えられています。 これらの vmcmd セットの利用は、 a commitment point 以前に、プロセス実行の取り消しを許します。

3.1.3.Multiple executable format support with the exec switch

exec switch は src/sys/kern/exec_conf.c で定義される struct execsw 構造体の配列で: execsw[] です。 struct execsw 自身は src/sys/sys/exec.h で定義されています。

exec switch 中の各 entry は、与えられた実行可能形式、および与えられた カーネル ABI のために書かれています。それには、 バイナリーが format に合い、 ABI に適合するかどうかをチェックする test methods を含み、および、もしそうなら、 それをロードし start up する methods を含みます。 execve コードパスで呼ばれる 様々な methods が、ここに見っける事ができますぜ。

Table3.1.struct execsw fields summary

フィールド名 Description
es_hdrsz 実行可能形式ヘッダーのサイズ
es_makecmds プログラムが実行可能かチェックし、そうなら、メモリー空間のセットアップに 要求される vmcmds の作成 (実行可能コードおよび データセクションのロードを含む) する method 。
u.elf_probe_func u.ecoff_probe_func u.macho_probe_func バイナリーが実行可能かどうかチェックする es_makecmds method が使う実行形式 probe method 。 u フィールドは union で、 ELF 、 ECOFF および Mach-O 形式の probe methods が contains
es_emul 異なるカーネル ABI を handling するのに使われる struct emulSection3.2.2, “Multiple kernel ABI support with the emul switch” にて詳細がカバーされてます。
es_prio この exec switch entry の priority level 。このフィールド helps choosing the test order for exec switch entries
es_arglen XXX ?
es_copyargs ユーザー空間の新プログラム引数および 環境関数のコピーに使われる Method
es_setregs 初期プロセス CPU レジスターの セットアップに使われる 機種依存 method
es_coredump プロセスから core を作るのに使われる Method
es_setup_stack 新プロセススタックのセットアップに vmcmd のセットを作るために、 es_makecmds によって呼ばれる Method 。

execve1es_priority を用いて順序付けるために、 各 entry の es_makecmds method が match する まで calls を exec switch entries 上で繰り返します。

es_makecmds は exec package の ep_vmcmds フィールドを 後に、新プロセス仮想メモリー空間のセットアップで使われる vmcmds で 満たします。 vmcmds についての詳細は Section3.1.3.2, “Virtual memory space setup commands (vmcmds)” を御覧ください。

3.1.3.1.Executable format probe

実行可能形式 probe は es_makecmds method と呼ばれます。 その仕事は、実行可能バイナリーが、この exec switch entry で handled できるかを simply にチェックするものです。 バイナリー内の signature (例: ELF note セクション)、バイナリーに 組み込まれたダイナミックリンカーの名前、等をチェックできます。

Some probe 関数 は wildcard を理解し、 es_prio フィールドの助けを借りて 最後の手段として使われます。 これは ネイティブ ELF 32 bit entry の場合です、例えば。

3.1.3.2.Virtual memory space setup commands (vmcmds)

Vmcmds は exec package の ep_vmcmds フィールドの中の struct exec_vmcmd (src/sys/sys/exec.h に定義) の配列に、 execve1 が 実行するか destroy them かを決定する前に stored されます。

struct exec_vmcmd は、 ev_proc フィールドに コマンドを実行する method のポインターを定義します。他のフィールドは method の引数を store するのに使われます。

src/sys/kern/exec_subr.c に4つの方法があり

Table3.2.vmcmd methods

名前 せつめい
vmcmd_map_pagedvn vnode からメモリーを Map 。 demand-paged text および data segments を handling のに適切な。
vmcmd_map_readvn vnode からメモリーを Read 。 non-demand-paged text/data segments を handling のに適切な、 すなわち、不純な objects (a la OMAGIC and NMAGIC).
vmcmd_readvn XXX ?
vmcmd_zero ゼロで満たされたメモリーの領域を Maps

Vmcmd は new_vmcmd を用いて作られ、 kill_vmcmd を用いて destroyed できます。

3.1.3.3.Stack virtual memory space setup

exec switch の es_setup_stack フィールドは スタック space のセットアップをするための vmcmd の生成を担当する method へのポインターを holds 。引数および 環境でスタックを満たすことは es_copyargs method によって、 後に行われます。

ネイティブ ELF バイナリーには、 netbsd32_elf32_copyargs (src/sys/kern/exec_elf32.celf_copyargs method から得られたマクロ) が使われます。 job のその部分のために ELF 特有のではない copyargs ( src/sys/kern/kern_exec.c から) を呼びます。

copyargs は、 引数および環境文字列を、 (exec package 中の) カーネル copy から、ユーザーランド中の 新プロセススタックに copy back しなければなりません。 それから文字列へのポインターの配列を再構成し、そして最後に、 配列へのポインター、および引数の count は スタックの top へコピーされます。新プログラムスタックポインターは、 どんな ANSI プログラムでも期待するように、 引数 count 、続いて、引数の配列ポインターを指すようにセットされます。

Dynamic ELF 実行形式は特殊で: スタック上にコピーされる、 ELF auxiliary テーブル と呼ばれる構造体を必要とします。 そのテーブル は、キーのペアおよび、 ユーザーメモリー中の ELF ヘッダーアドレス、ページサイズ、あるいは ELF 実行形式の entry point と言った様々なものの値の 配列 です。

註として、 dynamic ELF 実行形式を starting 時、 ELF ローダー (インタープリターとして知られる: /usr/libexec/ld.elf_so) は 実行形式をカーネルにロードします。 ELF ローダーはカーネルから started され、そして、 以後の実行形式物それ自身の starting の責任を持ちます。

3.1.3.4.Initial register setup

es_setregs は、初期プロセス CPU レジスターの セットアップに対応する機種依存 method です。 いかなる機種でも、 method は インストラクションポインター、スタックポインターおよび machine state を holding するレジスターをセットする必要があります。 幾つかの ports では更なる作業が必要です (i386 の場合、セグメントレジスター、 および Local Descriptor Table のセットアップ)。

CPU レジスターは struct lwp から利用可能な struct trapframe に蓄えられます。

3.1.3.5.Return to userland

execve がその仕事を終えた後、 新プロセス の実行の準備ができました。 run キューで利用できる状態にあり、適切なときにスケジューラーによって picked up されます。

スケジューラーの観点では、 プロセス実行の開始あるいは再開は同一操作: ユーザーランドへのリターン。 これは プロセス仮想メモリー空間への switching と プロセス CPU レジスターのロードを必要とします。 システム bit off で machine state レジスターをロードする事で、 カーネル特権は落とされます。

XXX 詳細

3.2.Traps and system calls

プロセッサーが例外 (メモリー fault 、ゼロ除算、 システムコールインストラクション...)に出くわしたとき、 trap を実行し: control はカーネルに移り、そして locore.S の幾つかのアセンブリールーチンの後、 CPU は、システムコールのための syscall_plain (i386 では src/sys/arch/i386/i386/syscall.c から) 、あるいは、他の traps のための trap 関数 (i386 では src/sys/arch/i386/i386/trap.c から) に落ちます。

ktrace によってプロセスがトレースされるときのみ使われる syscall_fancy システムコールハンドラー もあります。

3.2.1.Traps

XXX 私を書いて

3.2.2.Multiple kernel ABI support with the emul switch

struct emulsrc/sys/sys/proc.h で定義されています。 それは、システムコールおよび traps を handle する 様々な methods およびパラメーターを定義します。 NetBSD カーネルがサポートする各カーネル ABI は 自前の struct emul を持っています。 例えば、 Linux ABI は src/sys/compat/linux/common/linux_exec.cemul_linux を定義し、そして、 ネイティブ ABI は src/sys/kern/kern_exec.cemul_netbsd を定義しています。

カレント ABI の struct emulexecve で選択された exec switch entry の es_emul フィールドから得ます。 カーネルは、プロセスの struct proc ( src/sys/sys/proc.h で定義) 中に それを指すポインターを holds します。

最も重要な事に、 struct emul は システムコールハンドラー関数、および システムコールテーブルを定義します。

3.2.3.The syscalls.master table

各カーネル ABI にはシステムコールテーブルがあります。テーブル maps システムコール 番号からカーネル内のシステムコールを実装する関数へマップします (例: システムコール番号 2 は fork)。 (ネイティブ syscalls への) 慣例は、 syscall foo を実装するカーネル関数は sys_foo と呼ばれます。 Emulation syscalls には、 Linux emulation には linux_sys_ 接頭語prefix のような、 各自の慣例があります。 ネイティブ システムコール テーブル は src/sys/kern/syscalls.master にあります。

このファイルは C 言語で書かれていません。何らかの変更の後は、同じディレクトリーにある Makefile で処理されなければなりません。 syscalls.master 処理は syscalls.conf の設定で制御され、 それは、数個のファイルを出力し:

Table3.3.Files produced from syscalls.master

ファイル名 Description
syscallargs.h システムコールハンドラー関数から システムコールを実装する関数に data を渡すのに使われる システムコール 引数構造体を定義する。
syscalls.c システムコールの名前を含む文字列の配列
syscall.h 各システムコール名および 番号を定義するプリプロセッサー — libc で使われる
sysent.c システムコール毎に、 引数の数、システムコール 引数 構造体のサイズ、および カーネル内でシステムコールを実装する関数へのポインター の登録を含む配列

namespace 衝突を避けるために、非ネイティブ ABI には タグで接頭語付けられたoutput ファイル名 (例: Linux ABI には linux_) を定義する syscalls.conf があります。

system call argument structures[システムコール引数構造体](短縮して syscallarg)は システムコールを実装する関数に引数を渡すのにいつも使われます。 各 システムコールには、自前の syscallarg 構造体があります。 このカプセル化 layer はここではエンディアンの違いを隠すためです。

システムコールを実装する全関数は同じプロトタイプで:

int syscall( l,
v,
retval);
struct lwp *l;
void * v;
register_t *retval;

l は calling スレッドの struct lwp で、 v は syscallarg 構造体のポインター、そして retval は値を返すためのポインターです。関数はエラーコード (errno(2) を御覧ください) あるいは、エラーが無ければ 0 を返します。 註として、プロトタイプは syscalls.master の “宣言” と同じではありません。 syscalls.master 内の宣言は システムコールのために documented交付された プロトタイプに対応します。 これが、ユーザーランドプログラムで見られるシステムコールが 異なるプロトタイプを持つ理由で、しかし、 sys_... カーネル関数は、 MD syscall ハンドラーおよび MI syscall 実装の間のインターフェースを統一する為に同じプロトタイプ を持つ実装をしなければなりません。 syscalls.master 内では、 宣言がユーザーランドから見える syscall 引数を示し、 また syscallarg 構造体のメンバーを決定し、 構造体は、 syscall 引数をカプセル化し、それぞれのメンバーを持ちます。

上のリストにあるファイルの生成と同時に、関数名で幾つかの代用が行われ: COMPAT_XX とタグ付けされた syscalls は compat_xx_ が接頭語付けられ、 syscallarg 構造体名にも同じ事が行われます。 それで、それらの syscalls を実装する実際のカーネル関数は 同方法にて定義されねばなりません。例え: syscalls.master の行が

97	COMPAT_30	{ int sys_socket(int domain, int type, int protocol); }

なら、実際の syscall 関数はこのプロトタイプを持ち:

int compat_30_sys_socket( l,
v,
retval);
struct lwp *l;
void * v;
register_t *retval;

そして、 v は、 次のように宣言されたstruct compat_30_sys_socket_args へのポインターで:

struct compat_30_sys_socket_args {
        syscallarg(int) domain;
        syscallarg(int) type;
        syscallarg(int) protocol;
};

socket(2) syscall の documented プロトタイプと、 syscalls.master 中の sys_socket の宣言の関係に注意してください。 syscall 引数の型は、 構造体 members を 最小サイズに padded することを保証する syscallarg マクロによって wrapped され、 ふたたび MD および MI コードの間を統一したインターフェースにします。 そーゆーわけで、それらの members は直接アクセスされるべきでなく、 しかし、 syscall arg 構造体へのポインターおよび引数名を取り、 そして引数の値を抽出する SCARG マクロ によって行われるべきです。例として、 below を 御覧ください。

3.2.4.System call implementation in libc

libc のシステムコール実装は、カーネル実装から自動生成されます。例として、 libc の access(2) 関数の実装を考察しましょう。それは、ソースには存在しない access.S ファイルにあり — libc の構築時に自動生成されます。 src/sys/sys/syscall.h および src/lib/libc/arch/MACHINE_ARCH/SYS.h で定義されたマクロを使い: syscall.h ファイルには syscall 名を syscall 番号への map の定義が入っています。 syscall 関数名は sys_ 接頭語を SYS_ で置き換えたものに変更されます。 上に記述したように、 syscall.h ヘッダーファイルは src/sys/kernmake init_sysent.c を running する事によって src/sys/kern/syscalls.master から 自動生成されます。 SYS.h のインクルードで、 syscall.h および、 syscall 名を受け入れ、 SYS_ 接頭語を 自動的に追加し、対応する番号を取り、正しい番号での syscall 自身の実行をするだけの本体の与えられた名前の関数を定義する RSYSCALL マクロを得ます。 (実行法および syscall 番号およびその引数の transfer 法は機種依存ですが、 しかし、これは RSYSCALL マクロに隠されています。)

access(2) の例を続けると、 syscall.h に入っているのは

#define SYS_access      33

それで

RSYSCALL(access)

は 関数 access を定義し、これは 33 番のシステムコールを実行します。このように、 access.S には単にこのようなのが入ってるのが必要で:

#include "SYS.h"
RSYSCALL(access)

これをさらに自動化するためには、このファイルの名前を src/lib/libc/sys/Makefile.incASM 変数に加えるだけで十分で、 これで libc 構築時この内容で自動生成されます。

上のことは カーネル syscalls に正確に一致する libc 関数 については本当です。 関数がマニュアルのセクション 2 にあってもいつもそうでもなく、例えば、 wait(2), wait3(2) および waitpid(2) 関数は ただ一つの syscall, wait4(2) のラッパーとして実装されています。 上の手順のような場合、 wait4 関数およびラッパーが 典型的な C 関数であるかのように参照できる事を許します。

3.2.5.How to add a new system call

access(2) システムコールがまだ無い ふりをして、カーネルに追加したいとしましょう。手順は?

  • src/sys/kern/syscalls.master リストに syscall を追加:

    33      STD             { int sys_access(const char *path, int flags); }

  • src/sys/kern の下で make init_sysent.c を Run します。 これで自動生成されたファイルが更新され: syscallargs.hsyscall.hinit_sysent.c および syscalls.c
  • つぎのプロトタイプであるシステムコール部分のカーネルの実装:

    int sys_access( l,
    v,
    retval);
    struct lwp *l;
    void * v;
    register_t *retval;

    as all other syscalls. 。 syscall 引数 を得るには、 vstruct sys_access_args へのポインターに castキャスト し、そして それらをその構造体から復活させるために SCARG マクロを使い、例えば、 もし uapv を castキャストして得られる struct sys_access_args へのポインターなら、 flags 引数を得るには、こう利用し:

    SCARG(uap, flags)

    struct sys_access_args型 および sys_access 関数 は src/sys/kern/syscalls.master から 自動生成された sys/syscallargs.h で宣言されています。 それらの宣言を得るには

    #include <sys/syscallargs.h>

    を使います。

    sys_access の実際の実装は src/sys/kern/vfs_syscalls.c を見てください。

  • src/sys/sysmake includes を Run 。 自動生成されたインクルードファイル (最も重要なのは syscall.h) を 次のステップで libc 構築が見つける DESTDIR の下の usr/include にコピーします。
  • src/lib/libc/sys/Makefile.incASM 変数に access.S を追加します。

これで全部です。新 syscall をテストするには、単純に libc を再構築し (his point にて access.S は生成されます) そして 新 syscall を含む新カーネルを reboot します。新 syscall を 一般に useful にするために、それのプロトタイプを、ユーザー空間プログラムが 使う適切なヘッダーファイルに加える必要があり — access(2) の場合、これは unistd.h で、 NetBSD ソースは src/include/unistd.h にあります。

3.2.6.Versioning a system call

もしシステムコール ABI (or even API) が変わると、 旧バイナリーが用いる original semantics の 旧 syscall を実装することが必要です。新しい version の syscall は 異なる syscall 番号で、 original のは 旧番号のままにしておきます。 これは versioning と呼ばれます。

versioning と関連した名前変更は複雑です。 original システムコールが foo と呼ばれ(そして sys_foo 関数によって実装され)ていて、 そして x.y リリースの後に変更されていたら、 新しい syscall の名前は __fooxy で、それを実装する関数の名前は sys___fooxy となるでしょう。 original syscall (互換性のため残す)は依然として、 syscalls.master で sys_foo として 宣言されますが、 COMPAT_XY と tag 付けられ、それで関数は compat_xy_sys_foo と名付けられます。 下に記述された手順において、 sys_foo を original 版、 sys___fooxy を新版および、 compat_xy_sys_foo を互換版 と呼びます。

さて、 z.q がリリースされた後 syscall が再び versioned されると、 最新版は __foozq と呼ばれます。 中間の version (formerly the 新版) は互換性のために保持し、それでそれは COMPAT_ZQ とタグ付けし、関数名は sys___fooxy から compat_zq_sys___fooxy に変更します。最古版 compat_xy_sys_foo は second versioning では影響されません。

どうやってシステムコール ABI や API を変更して、互換版を追加するの? 実例を見ましょう: NetBSD 3.0 と 4.0 の間で、 非サポートのアドレス family の場合のエラーコードが EPROTONOSUPPORT から EAFNOSUPPORT に変更された後の socket(2) システムコールの versioning 。

  • syscalls.master の中の 旧版 (sys_socket) を 正しい COMPAT_XY で tag 付けします。 sys_socket の場合は、 COMPAT_30 で、 なぜなら、 NetBSD 3.0 がこのシステムコールが変更される前の 最後のバージョンだからです。
  • 新版を syscalls.master の終わりに 追加します(これは割り当てる 新 syscall 番号に影響します) 。新版を上で記述したように名付けます。 我等が場合、それは sys___socket30 で:

    394	STD		{ int sys___socket30(int domain, int type, int protocol); }

  • socket syscall を実装する関数をここで上の変更に沿うように sys_socket から sys___socket30 に改名する必要があります。 理想的には、このとき versioning で要求される変更がされるとねぇ。 (実際問題として、変更が起こり、そしてそれが起こった後にのみ、 互換性を壊し、そして versioning が必要とされるのだけども。)
  • 互換版の実装には、上記のように compat_xy_sys_... と名付けます。実装は src/sys/compat 以下に属し、 そして、それは新版の変更されたコピーであるべきではなく、 なぜなら、コピーはついに逸脱します。****むしろ、 新版の実装に特有の表現で実装されるべきで、互換性に必要な調整の追加 (それは正確に旧版のふるまいをするということを意味する)****。

    我等が例では、互換版は compat_30_sys_socket と名付けられます。 それは src/sys/compat/common/uipc_syscalls_30.c にあります。

  • カーネル内の旧 syscall 関数への参照を全て探し、 そしてそれらを適切に互換版あるいは新版に pointします。 (カーネル would not link otherwise.) 例えば、 sys_socket への参照が使われる src/sys/compat の下の 様々な emulations のための、沢山の互換 syscalls あるいは syscalls.master テーブル。 参照の変更先を互換版にするか新版にするかの決定は、 emulate するつもりの OS の挙動に依存し変更されるべきです。 例. FreeBSD は旧エラー番号を使いますが、 System V は新しいのを使います。

さて、旧 libc を用いたバイナリーができるべきように、 カーネルはコンパイルされ、 古い静的リンクされた バイナリーは動くべきです。新 syscall を使うものは未だ何もありません。 新版および互換版両方の syscall を含む新 libc を作る必要があり:

  • src/lib/libc/sys/Makefile.inc の、 旧 syscall 名を 新 syscall のもの (我等が例では __socket30) に置き換えます。 libc が再構築されると、新しい関数が入りますが、 この underscore 付きの内部名を使うプログラムはなく、それで、 まだ useful ではありません。 また、旧名を失ってしまいます。
  • 新しくコンパイルされたプログラムが通常名 (我等が例では socket)を参照したとき 新 syscall を使うようにするために、通常名の宣言が宣言された後に __RENAME(newname) statement を 加えます。 socket の場合、これは src/sys/sys/socket.h で:

    int     socket(int, int, int)
    #if !defined(__LIBC12_SOURCE__) && !defined(_STANDALONE)
    __RENAME(__socket30)
    #endif
    

    さて、プログラムがこのヘッダーを用いて再コンパイルされると、 _STANDALONE を定義する standalone ツール (基本ブートローダー)、 および __LIBC12_SOURCE__ を定義する libc compat コードそれ自体のコンパイルを除いて、 socket への参照は __socket30 に置き換えられます。 __RENAMEsocket がソースで使われていたら コンパイラーに __socket30 シンボルを 参照するようにさせます。そのシンボルはリンカーによって 新関数 (新システムコールでの実装) と resolved されます。 旧バイナリーはこれに気づかず、 socket への参照を続け、それは 旧関数 (変更前と同じ API を持つ) へ resolved されるはずです。 次のステップでは、旧関数を再追加します。

  • 新 libc で旧バイナリーが動くようにするには、 旧関数を追加しなければなりません。 src/lib/libc/compat/sys 以下に、 新関数を使った実装をし追加します。註として、カーネル内では 互換 syscall は全く使っていません。それで、 カーネルが COMPAT_30 無しで 構築されていたとしても、旧プログラムは新 libc で動くはずです。 互換 syscall は 旧 libc のためだけにそこにあり、それは、 動的ライブラリーがアップグレードされていなかった、あるいは 静的リンクされたプログラムの内部で使われます。

完了 — 旧バイナリー 旧 libc および新カーネル (静的リンクされたバイナリーを含む)、 旧バイナリー 新 libc および新カーネル、そして、 新バイナリー 新 libc および新カーネルの場合を、カバーしました。

3.2.7.Committing changes to syscall tables

あなたの作業をコミットするとき(either a 新 syscall あるいは新 syscall version with the 互換版 syscalls)、 ファイルの自動生成のためのソース (syscalls.master) を最初にコミットし、 そしてそれから 再生成して自動生成ファイルを コミットすることを覚えていてください。 ソースファイルには RCS Id があって、この方法で RCS Id は現在のソースバージョンを示すようになります。 src/lib/libc/sys/Makefile.inc で生成された アセンブリーファイルは repository では全く保持せず、 それらは libc の構築時毎回再生成されます。

3.2.8.Managing 32 bit system calls on 64 bit systems

64 bit システム上で 32 bit バイナリーを実行するとき、 4 GB 以下のアドレスだけを使うように取り計らう必要があります。 プロセス生成でスタックおよびヒープが allocated た時、 32 bit プロセスで handled される 32 bits ポインター が 64 bit カーネルで扱われるところで、各システムコールにもまた 問題があります。

64 bit バイナリーとしてのカーネル構築には、 32 bit ポインターは 何らかの意味もなさず: ポインターは 64 bit 長だけが可能です。 これが 32 bit ポインターが netbsd32_pointer_t (src/sys/compat/netbsd32/netbsd32.h において) と呼ばれる u_int32_t の別名として 定義される理由です。

copyin および copyout には true 64 bits ポインターが要求されます。それらは NETBSD32PTR64 マクロを通して netbsd32_pointer_t をキャストする事で 得られます。

ほとんどの場合、 32 bit システムコールの実装は、ほとんど ポインターをキャストし、 64 版のシステムコールを call します。 そのような situation の例が src/sys/compat/netbsd32/netbsd32_time.c にあり: netbsd32_timer_delete 。提供された 32 bit システムコール引数構造体ポインターは uap と 呼ばれ、そして 64 bit のは ua と呼ばれ、 それで NETBSD32TO64_UAPNETBSD32TOP_UAPNETBSD32TOX_UAP 、および NETBSD32TOX64_UAP と呼ばれる helper マクロが使えます。 src/sys/compat/netbsd32 のソース に多様な例があります。

3.3.Processes and threads creation

3.3.1.fork, clone, and pthread_create usage

XXX 私を書いて

3.3.2.Overview of fork code path

XXX 私を書いて

3.3.3.Overview of pthread_create code path

XXX 私を書いて

3.4.Processes and threads termination

3.4.1.exit, and pthread_exit usage

XXX 私を書いて

3.4.2.Overview of exit code path

XXX 私を書いて

3.4.3.Overview of pthread_exit code path

XXX 私を書いて

3.5.Signal delivery

3.5.1.Deciding what to do with a signal

XXX 私を書いて

3.5.2.The sendsig function

各カーネル ABI について、 struct emul は、 プロセスユーザー context を代える責任を持つ 機種依存 sendsig 関数を定義し、それで シグナルハンドラーと呼びます。

sendsig は シグナルハンドラー 呼び出しの前に CPU レジスターを入れたスタックフレームを構築します。 CPU レジスターは代えられ[are altered]、それで、 ユーザーランドへの return で、プロセスはシグナルハンドラーを実行し、 スタックポインターを 新スタックフレームへセットします。

もし sigaction 呼び出し時に要求されると、 sendsigstruct siginfo もスタックフレーム に追加します。

最後しかし、とりわけでなしに、 sendsig は シグナルトランポリンと呼ばれるシグナル cleanup で呼び出される 小さなアセンブリーコードをコピーするかもしれません。この詳細は次の節です。 註として、現代の NetBSD ネイティブプログラムはその機能はもはや使わず: それは古いプログラム、 および他の OS のエミュレーションにのみ使われます。

3.5.3.Cleaning up state after signal handler execution

一度シグナルハンドラーが returns すると、カーネルは必ず シグナルハンドラー context を破壊し、 そして前のプロセス state を restore します。 これは 2通り方法で成し遂げる事ができます。

最初の方法は、カーネル-提供のシグナルトランポリンの利用で: シグナルハンドラーはシグナルトランポリンに戻るために、 sendsig はスタックのシグナルトランポリンをコピーし、 スタックおよび/または CPU レジスターを準備[/用意]します。 シグナルトランポリンの仕事は、 sigreturn あるいは、スタックに saved された CPU レジスターを指すポインター を handling する setcontext システムコールを 呼ぶ事です。 これは、シグナルハンドラー呼び出しの前に、 CPU レジスターの値を復帰させ、 そして次回プロセスがユーザーランドに戻ると、 停止したところから実行を再開します。

i386 の The ネイティブシグナルトランポリン sigcode が呼ばれ、それは src/sys/arch/i386/i386/locore.S にあります。 各 emulated ABI には自前のシグナルトランポリンがあり、 通常、 sigreturn システムコール番号以外は ネイティブのものに [can be quite close[非常に近くなります.......]]。

2つ目の方法は libc が提供するシグナルトランポリンを利用する事です。 これが現代の NetBSD ネイティブプログラムの方法です。 sigaction システムコールが呼ばれたとき、 libc stub は libc の setcontext 呼び出しを担当している シグナルトランポリンへのポインターを handle 。

sendsig は、シグナルハンドラーのために return アドレスとしてポインターを用います。この方法は前のものよりも良く、 それは、シグナルトランポリンが stored された実行可能スタック page の 必要を無くするからです。トランポリンは現在 libc のコード部分に storedあります。例えば、 i386 では、シグナルトランポリン は __sigtramp_siginfo_2 と名付けられ、 src/lib/libc/arch/i386/sys/__sigtramp2.S に あります。

3.6.Thread scheduling

3.6.1.Overview

NetBSD 5.0 では新スケジューリング API を導入し、異なる スケジューリングアルゴリズムの実装とコンパイル時の選択を可能にしました。 現在、 2つの異なるスケジューリング アルゴリズム が利用可能で: 伝統的 4.4BSD-based スケジューラー および、より現代式の M2 スケジューラー。

NetBSD は、 POSIX real-time スケジューリング拡張をサポートするため POSIX が要求する 3つのスケジューリングポリシーをサポートし:

  • SCHED_OTHER: Time sharing (TS) 、 NetBSD でのデフォルト

  • SCHED_FIFO: First in, first out

  • SCHED_RR: Round-robin

SCHED_FIFO および SCHED_RR はあらかじめ定義されたスケジューリング ポリシーで、 leaving[のこりの] SCHED_OTHER は実装-特有ポリシー。

現在、 224 の priority levels があり、 ユーザー level には 64 が利用できます。 スケジューリング priorities は 以下の classes に系統立てられ:

Table3.4.Scheduling priorities

Class Range # Levels Description
カーネル (RT) 192..223 32 ソフトウェアー割り込み。
ユーザー (RT) 128..191 64 リアルタイムユーザースレッド (SCHED_FIFO および SCHED_RR ポリシー).
カーネルスレッド 96..127 32 内部カーネルスレッド (kthreads)。 I/O 、 VM および他のカーネルサブシステムで使われる。
カーネル 64..95 32 ユーザープロセス/スレッドのためのカーネル priority 一時的 assigned when entering カーネル-空間 and blocking.
ユーザー (TS) 0..63 64 Time-sharing range, 、ユーザープロセスおよびスレッド (SCHED_RR ポリシー)


SCHED_FIFO ポリシーで running するスレッドは fixed priority で、 すなわち、カーネルはその priority を動的に変更しません。 SCHED_FIFO スレッドが runs は以下まで

  • 完了

  • 自発的に CPU の言いなりになる

  • blocking on an I/O 操作 or other resources (メモリー allocation, locks)

  • preemption by a higher priority real-time スレッド

SCHED_RR は、そんなスレッドに 100ms の デフォルト time-slice があることを除いて SCHED_FIFO に似た動作をします。

SCHED_OTHER ポリシーには、両スケジューラーが現在利用する the same run キュー 実装, employing multi-level feedback キュー CPU およびリソース utilization[利用/役立ち]を反映した スレッドの priority の動的調整によって、 この approach は システムが heavy loads の下でさえ敏感にさせときます。

各 runnable スレッドは、その priority によって、 runqueues の一つに placed されます。 各スレッドは、相当の時間、その time-slice または quantum 、 CPU 上で run が許されます。 一旦、スレッドが time-slice を使い切ると、 その runqueue に戻し置きます。スケジューラー が CPU で run するために新スレッドを検索するとき、 最も高い priority の 最初の スレッド、 non-empty runqueue が選ばれます。

3.6.1.1.The 4.4BSD Scheduler

4.4BSD スケジューラーは、CPU-time を蓄積し、 スレッドの priority を動的に調整します。 CPU 利用[CPU utilization] は システムクロックが ticks する度、および、実行するスレッドが見つかる度に hardclock 中で増えます。 スレッドの recent CPU 利用[CPU utilization] の評価は is stored in l_estcpu, which is adjusted once per second in schedcpu via a digital decay filter.。 スレッドは その CPU 利用[utilization] に 4 ticks が貯まる度に、 schedclock が プロセスの スケジューリング priority の再計算の為に resetpriority を呼び出します。

3.6.1.2.The M2 scheduler

M2 スケジューラーは Unix System V Release 4 および Solaris に類似した 伝統的 time-sharing approach を使用します。

3.6.2.References

common スケジューラー API は src/sys/kern/kern_synch.c ファイルに 実装されています。追加の情報は csf(9) にあります。 一般 run-queues は src/sys/kern/kern_runq.c に 実装されています。 4.4BSD スケジューラー についての詳細な情報は [McKusick] にあります。 SVR4 スケジューラーの解説は [Goodheart] で提供されています。