レンタルサーバ + Webシステム開発 = E-business

■レンタルサーバご利用参考資料
サーバご利用の参考にJF Project によるJF (Japanese FAQ)を掲載しています。

Linux JF(Japanese FAQ)Project.
JF は, Linux に関する解説文書・FAQ などを作成・収集・配布するプロジェクトです.

グリーンネット・トップページへ戻る


一覧に戻る
  Linux Kernel 2.4 Internals
  Tigran Aivazian tigran@veritas.com
  7 August 2002 (29 av 6001)
  訳: Hiroshi Miura miura@da-cha.org
  22 May 2003

  この文書はLinux 2.4カーネルの手引きです。原文の最新版は、以下のURLから
  ダウンロードできます。 
  また、訳文の最新版は、  にて配布されます。この
  ガイドは、現在では、LDP (Linux Documentation Project)の一部となってお
  り、  から様々なフォーマットでダ
  ウンロードできます。また、最新版は
  からオンラインで読むこ
  とができます。 This documentation is free software; you can redis-
  tribute it and/or modify it under the terms of the GNU General Public
  License as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.  著者
  は、VERITASソフトウエア社で、シニアLinuxカーネルエンジニアとして働いて
  います。そしてこの本は、VERITAS内部で、このタイトルで行っている短期間
  のトレーニングコース/講義の補助のために書き下したものです。謝辞: Juan
  J. Quintela (quintela@fi.udc.es), Francis Galiegue (fg@mandrake-
  soft.com), Hakjun Mun (juniorm@orgio.net), Matt Kraai
  (kraai@alumni.carnegiemellon.edu), Nicholas Dronen (ndronen@frii.com),
  Samuel S Chessman (chessman@tux.org), Nadeem Hasan (nhasan@nadmm.com),
  Michael Svetlik (m.svetlik@ssi-schaefer-peem.com) のみなさまには、多く
  の訂正と、ご意見をいただきました。 Linux ページキャッシュの章
  は、Christoph Hellwig (hch@caldera.de) によって書かれたものです。 IPC
  機構の章は、Russell Weight (weightr@us.ibm.com) と Mingming Cao
  (mcao@us.ibm.com) により書かれたものです。 IPC機構の章は、 Russell
  Weight (weightr@us.ibm.com) and Mingming Cao (mcao@us.ibm.com) より書
  かれたものです。本訳は、三浦広志(miura@da-cha.org)が著者の許可を得て訳
  出したものです。訳出についての指摘は、三浦までお願いします。訳出にあ
  たっては、小林@四日市さん(zap03216@nifty.ne.jp)、小幡昇さ
  ん(noboru@ylug.org)から貴重なご指摘と訂正をいただきました。ありがとう
  ございます。
  ______________________________________________________________________

  目次

  1. ブート
     1.1 Linuxカーネルイメージの作成
     1.2 ブート: 概論
     1.3 ブート: BIOS POST
     1.4 ブート: ブートセクタと setup
     1.5 LILOをブートローダとして使う
     1.6 高いレベルの初期化
     1.7 x86でのSMPブート
     1.8 初期化データおよびコードの解放
     1.9 カーネルコマンドラインを処理する

  2. プロセスと割り込み管理
     2.1 タスク構造体とプロセステーブル
     2.2 タスクとカーネルスレッドの生成と終端
     2.3 Linuxスケジューラ
     2.4 Linuxでのリンクリストの実装
     2.5 ウエイトキュー
     2.6 カーネルタイマ
     2.7 ボトムハーフ
     2.8 タスクキュー
     2.9 タスクレット
     2.10 ソフト割り込み
     2.11 システムコールがi386ではどのように実装されているか?
     2.12 アトミックな操作
     2.13 スピンロック、読み書きスピンロック、ビッグリーダースピンロック
     2.14 セマフォと読み書きセマフォ
     2.15 モジュール読み込みのカーネルサポート

  3. 仮想ファイルシステム (VFS)
     3.1 Inode キャッシュとDcache との相互作用
     3.2 ファイルシステム登録/登録解除
     3.3 ファイルデスクリプタ管理
     3.4 ファイル構造体管理
     3.5 スーパーブロックとマウントポイント管理
     3.6 仮想ファイルシステムの例: pipefs
     3.7 ディスクファイルシステムの例:BFS
     3.8 実行ドメインとバイナリフォーマット

  4. Linux ページキャッシュ
  5. IPC機構
     5.1 セマフォ
        5.1.1 セマフォシステムコールのインターフェース
           5.1.1.1 sys_semget()
           5.1.1.2 sys_semctl()
           5.1.1.3 sys_semop()
              5.1.1.3.1 非ブロックセマフォ操作
              5.1.1.3.2 セマフォ操作の失敗
              5.1.1.3.3 ブロッキングセマフォ操作
        5.1.2 セマフォ独自にサポートする構造体
           5.1.2.1 struct sem_array
           5.1.2.2 struct sem
           5.1.2.3 struct seminfo
           5.1.2.4 struct semid64_ds
           5.1.2.5 struct sem_queue
           5.1.2.6 struct sembuf
           5.1.2.7 struct sem_undo
        5.1.3 セマフォサポート関数
           5.1.3.1 newary()
           5.1.3.2 freeary()
           5.1.3.3 semctl_down()
              5.1.3.3.1 IPC_RMID
              5.1.3.3.2 IPC_SET
           5.1.3.4 semctl_nolock()
              5.1.3.4.1 IPC_INFO と SEM_INFO
              5.1.3.4.2 SEM_STAT
           5.1.3.5 semctl_main()
              5.1.3.5.1 GETALL
              5.1.3.5.2 SETALL
              5.1.3.5.3 IPC_STAT
              5.1.3.5.4 GETVAL
              5.1.3.5.5 GETPID
              5.1.3.5.6 GETNCNT
              5.1.3.5.7 GETZCNT
              5.1.3.5.8 SETVAL
           5.1.3.6 count_semncnt()
           5.1.3.7 count_semzcnt()
           5.1.3.8 update_queue()
           5.1.3.9 try_atomic_semop()
           5.1.3.10 sem_revalidate()
           5.1.3.11 freeundos()
           5.1.3.12 alloc_undo()
           5.1.3.13 sem_exit()
     5.2 メッセージキュー
        5.2.1 メッセージシステムコールインターフェース
           5.2.1.1 sys_msgget()
           5.2.1.2 sys_msgctl()
              5.2.1.2.1 IPC_INFO (または MSG_INFO)
              5.2.1.2.2 IPC_STAT (または MSG_STAT)
              5.2.1.2.3 IPC_SET
              5.2.1.2.4 IPC_RMID
           5.2.1.3 sys_msgsnd()
           5.2.1.4 sys_msgrcv()
        5.2.2 メッセージ独自の構造体
           5.2.2.1 struct msg_queue
           5.2.2.2 struct msg_msg
           5.2.2.3 struct msg_msgseg
           5.2.2.4 struct msg_sender
           5.2.2.5 struct msg_receiver
           5.2.2.6 struct msqid64_ds
           5.2.2.7 struct msqid_ds
           5.2.2.8 msg_setbuf
        5.2.3 メッセージサポート関数
           5.2.3.1 newque()
           5.2.3.2 freeque()
           5.2.3.3 ss_wakeup()
           5.2.3.4 ss_add()
           5.2.3.5 ss_del()
           5.2.3.6 expunge_all()
           5.2.3.7 load_msg()
           5.2.3.8 store_msg()
           5.2.3.9 free_msg()
           5.2.3.10 convert_mode()
           5.2.3.11 testmsg()
           5.2.3.12 pipelined_send()
           5.2.3.13 copy_msqid_to_user()
           5.2.3.14 copy_msqid_from_user()
     5.3 共有メモリ
        5.3.1 共有メモリシステムコールインターフェース
           5.3.1.1 sys_shmget()
           5.3.1.2 sys_shmctl()
              5.3.1.2.1 IPC_INFO
              5.3.1.2.2 SHM_INFO
              5.3.1.2.3 SHM_STAT, IPC_STAT
              5.3.1.2.4 SHM_LOCK, SHM_UNLOCK
              5.3.1.2.5 IPC_RMID
              5.3.1.2.6 IPC_SET
           5.3.1.3 sys_shmat()
           5.3.1.4 sys_shmdt()
        5.3.2 共有メモリサポート構造体
           5.3.2.1 struct shminfo64
           5.3.2.2 struct shm_info
           5.3.2.3 struct shmid_kernel
           5.3.2.4 struct shmid64_ds
           5.3.2.5 struct shmem_inode_info
        5.3.3 共有メモリサポート関数
           5.3.3.1 newseg()
           5.3.3.2 shm_get_stat()
           5.3.3.3 shmem_lock()
           5.3.3.4 shm_destroy()
           5.3.3.5 shm_inc()
           5.3.3.6 shm_close()
           5.3.3.7 shmem_file_setup()
     5.4 Linux IPC プリミティブ
        5.4.1 セマフォ、メッセージおよび共有メモリで使用する汎用 Linux IPCプリミティブ
           5.4.1.1 ipc_alloc()
           5.4.1.2 ipc_addid()
           5.4.1.3 ipc_rmid()
           5.4.1.4 ipc_buildid()
           5.4.1.5 ipc_checkid()
           5.4.1.6 grow_ary()
           5.4.1.7 ipc_findkey()
           5.4.1.8 ipcperms()
           5.4.1.9 ipc_lock()
           5.4.1.10 ipc_unlock()
           5.4.1.11 ipc_lockall()
           5.4.1.12 ipc_unlockall()
           5.4.1.13 ipc_get()
           5.4.1.14 ipc_parse_version()
        5.4.2 セマフォ、メッセージおよび共有メモリで使われる汎用 IPC 構造体
           5.4.2.1 struct kern_ipc_perm
           5.4.2.2 struct ipc_ids
           5.4.2.3 struct ipc_id

  ______________________________________________________________________

  1.  ブート

  1.1.  Linuxカーネルイメージの作成

  この章では、Linuxカーネルをコンパイルをする時に取られるステップと各ス
  テージの生成物について解説します。ビルド工程はアーキテクチャにより異な
  りますが、ここではLinux/x86カーネルのビルドについてだけ考えることにし
  ます。

  ユーザが、'make zImage'あるいは'make bzimage'とタイプすると、その結果
  の起動イメージは、それぞれarch/i386/boot/zImage と
  arch/i386/boot/bzImage になります。ここでは、どのようにイメージが作ら
  れるかを見ていきましょう。

  1. C とアセンブリのソースファイルは、ELF 再配置可能オブジェクト形式
     (.o) へとコンパイルされる。中には論理的なグループとして ar(1) を
     使ってアーカイブ形式 (.a) にされるものもある。

  2. ld(1) を使って、上記の .o と .a は 'vmlinux' ファイルへとリンクされ
     る。'vmlinux'ファイルは、静的にリンクされたストリップ前の ELF
     32-bit LSB 80386 実行形式ファイルである。

  3. System.map は、'nm vmlinux' から作成される。関連がないシンボルや些
     細なシンボルは、fマップファイルから除外される。

  4. arch/i386/boot ディレクトリに移る。

  5. ブートセクタのアセンブラコード bootsect.S は、ターゲットが bzImage
     か zImage かによって -D__BIG_KERNEL__  をつけるか、あるいはつけない
     かされて、プリプロセッサが処理する。そして、各々の場合で
     bbootsect.s かあるいは bootsect.s が生成される。
  6. bbootsect.s はアセンブルされ、その後、bbootsect という名前の'rawバ
     イナリ'形式へと変換される。(あるいは、bootsect.s がアセンブルされ
     zImage 向けの bootsect へrawバイナリ変換される)

  7. セットアップ用のコード setup.S (setup.Sは、video.Sを含む)は、
     bzImageのときはbsetup.sへ、zImageの時はsetup.sへプリプロセッサの処
     理結果が出力される。ブートセクタのコードと同じ方法で処理される
     が、bzImageの場合に、-D__BIG_KERNEL__タグが付与されるところに違いが
     ある。生成物は、bsetupという名前の'rawバイナリ'形式へと変換される。

  8. arch/i386/boot/compressed というディレクトリへとうつり、
     /usr/src/linux/vmlinux をrawバイナリ形式の、$tmppiggy (テンポラリ
     ファイル名)へと変換する。またこの際に、ELF フォーマットでの .note
     セクションと .comment セクションが削除される。

  9. gzip -9 < $tmppiggy > $tmppiggy.gz

  10.
     $tmppiggy.gz を ELF 再配置可能形式の (ld -r) piggy.o へリンクする。

  11.
     head.S と misc.c からなる圧縮ルーチンをコンパイルする(まだ
     arch/i386/boot/compressed ディレクトリ)。そして ELF オブジェクト形
     式の head.o と、misc.o を生成する。

  12.
     head.o と misc.o、そして piggy.o をリンクし、bvmlinuxとする(あるい
     は zImageの場合は、vmlinux にする。 /usr/src/linux/vmlinux と間違わ
     ないこと)。ここで、vmlinux では-Ttext 0x1000 だが、bvmlinux の場合
     -Ttext 0x100000 というところに違いがある。bzImage の圧縮ローダは
     high エリアにロードされる。

  13.
     .note と .comment の2つの ELF セクションを削除して bvmlinux は 'raw
     バイナリ'形式の bvmlinux.outへと変換される。

  14.
     ディレクトリ arch/i386/boot へ戻り、tools/build にあるプログラムを
     使って、 bbootsect + bsetup + 圧縮された/bvmlinux.out を bzImage へ
     とつなぎ合わせる(zImage の場合は、各々の b を取ったものをつなぎ合わ
     せる)。これは、さらに setup_sects や、root_dev のような重要な変数
     を、ブートセクタの最後のところへと書き込む。

  ブートセクタの大きさは、常に512バイトです。setupのサイズは、4セクタよ
  り大きくなければなりませんが、最大でも約12Kに制限されています。これは
  次のようなルールで計算されます。

  0x4000 bytes >= 512 + setup のセクタ数 * 512 + ブートセクタ/setupを実
  行するときのスタックの領域

  後で、この制限がどこからきているのか学ぶことにしましょう。

  bzImage のサイズの上限は、現時点では LILO からのブートで約2.5M となっ
  ています。そして、たとえば、フロッピーディスクや、CD-ROM (El-Torito エ
  ミュレーションモードにおいて)といった、raw イメージのブートでは 0xFFFF
  パラグラフ分(0xFFFF0 = 1048560 バイト)になります。

  ここで tools/build は、カーネルイメージのブートセクタやカーネルイメー
  ジ、setup の下限サイズを検証します。しかし、setup の上限については、
  チェックしません。したがって、setup.S の最後に余分な大きな".space"を付
  加するだけで、簡単に壊れたカーネルが作成できてしまいます。

  1.2.  ブート: 概論

  ブートプロセスの詳細部分はアーキテクチャに依存しています。そこで IBM
  PC/IA32 のアーキテクチャへ注目することにします。古いデザインですし、ま
  た過去への互換性への問題から、PC のファームウエアは、オペレーティング
  システムをブートするときには、古い方式で起動してきます。このプロセス
  は、つぎの 6 つの論理的なステップへと分割できます。

  1. BIOS はブートデバイスを選ぶ。

  2. BIOS はブートデバイスのブートセクタをメモリーへと読み込む。

  3. ブートセクタは、setup と解凍ルーチンそして、圧縮されたカーネルを読
     み込む。

  4. カーネルは、プロテクトモードで解凍される。

  5. ローレベルの初期化が asm コードで行われる。

  6. ハイレベルの C での初期化が行われる。

  1.3.  ブート: BIOS POST

  1. 電源が入り、クロックジェネレータが開始する。また、バス上の
     #POWERGOOD 信号がアサートされる。

  2. CPU #RESET 信号がアサートされる (この時 CPU は、8086 互換のリアル
     モードである)。

  3. %ds=%es=%fs=%gs=%ss=0, %cs=0xFFFF0000, %eip = 0x0000FFF0 (ROM BIOS
     POST コード)。

  4. 割り込みが禁止された状態で、すべての POST チェックが行われる。

  5. IVT (割り込みベクトルテーブル) がアドレス 0 へ初期化される。

  6. BIOS の ブートストラップローダ関数が、int 0x19 により呼び出される。
     このとき、%dl は、ブートデバイスの「ドライブ番号」である。この関数
     は、 トラック 0 セクタ 1 番を物理アドレスの 0x7C00 (0x07C0:0000) へ
     と読み込む。

  1.4.  ブート: ブートセクタと setup

  Linuxカーネルをブートするために使われるブートセクタは、

  o  Linux ブートセクタ (arch/i386/boot/bootsect.S),

  o  LILO (あるいは他のブートローダ) ブートセクタ、あるいは

  o  ブートセクタはない(loadlinなど)

     のいずれかになります。ここで、Linuxブートセクタの詳細を見てみます。
     最初の数行では、セグメント値に使う便宜上のマクロが初期化されていま
     す。

  ______________________________________________________________________
  29 SETUPSECS = 4                /* default nr of setup-sectors */
  30 BOOTSEG   = 0x07C0           /* original address of boot-sector */
  31 INITSEG   = DEF_INITSEG      /* we move boot here - out of the way */
  32 SETUPSEG  = DEF_SETUPSEG     /* setup starts here */
  33 SYSSEG    = DEF_SYSSEG       /* system loaded at 0x10000 (65536) */
  34 SYSSIZE   = DEF_SYSSIZE      /* system size: # of 16-byte clicks */
  ______________________________________________________________________

  (左の数字は bootsect.S ファイルの行番号である) DEF_INITSEG,
  DEF_SETUPSEG, DEF_SYSSEG および DEF_SYSSIZE の値は、
  include/asm/boot.h で定義されており、

       ______________________________________________________________________
       /* Don't touch these, unless you really know what you're doing. */
       #define DEF_INITSEG     0x9000
       #define DEF_SYSSEG      0x1000
       #define DEF_SETUPSEG    0x9020
       #define DEF_SYSSIZE     0x7F00
       ______________________________________________________________________

  となっています。さて、実際のbootsect.Sのコードを見ていきましょう。

       ______________________________________________________________________
           54          movw    $BOOTSEG, %ax
           55          movw    %ax, %ds
           56          movw    $INITSEG, %ax
           57          movw    %ax, %es
           58          movw    $256, %cx
           59          subw    %si, %si
           60          subw    %di, %di
           61          cld
           62          rep
           63          movsw
           64          ljmp    $INITSEG, $go

           65  # bde - changed 0xff00 to 0x4000 to use debugger at 0x6400 up (bde).  We
           66  # wouldn't have to worry about this if we checked the top of memory.  Also
           67  # my BIOS can be configured to put the wini drive tables in high memory
           68  # instead of in the vector table.  The old stack might have clobbered the
           69  # drive table.

           70  go:     movw    $0x4000-12, %di         # 0x4000 is an arbitrary value >=
           71                                          # length of bootsect + length of
           72                                          # setup + room for stack;
           73                                          # 12 is disk parm size.
           74          movw    %ax, %ds                # ax and es already contain INITSEG
           75          movw    %ax, %ss
           76          movw    %di, %sp                # put stack at INITSEG:0x4000-12.
       ______________________________________________________________________

  54-63行は、アドレス 0x7C00 から 0x90000 へブートセクタコードを移動して
  います。これは、次のような手順で実行されています。

  1. %ds:%si へ $BOOTSEG:0 (0x7C0:0 = 0x7C00) を設定する。

  2. %es:%di へ $INITSEG:0 (0x9000:0 = 0x90000)を設定する。

  3. %cx へ16ビットのワード値を代入する (256 ワード = 512 バイト = 1 セ
     クタ)。

  4. 自動的にアドレスを加算するよう(cld)に、EFLAGS の DF (direction) フ
     ラグをクリアする。

  5. 先に進み、 512 bytes (rep movsw)コピーする。

  このコードがrep movsdを使わないのは、特別な理由があります。(ヒント
  .code16) 64行目では、新しく作られたブートセクタのコピーのラベルgo:へ
  ジャンプする。つまり、セグメント 0x9000です。これと続く3つの命令(64-76
  行)では、$INITSEG:0x4000-0xCへスタックを設定しています。つまり、%ss =
  $INITSEG (0x9000) と %sp = 0x3FF4 (0x4000-0xC) です。ここに前出のsetup
  のサイズ制限がどこからきているかの理由があります (Linux カーネルイメー
  ジの作成 参照)。

  77-103行では、一つめのディスクパラメータテーブルを上書きして、マルチセ
  クタ読み込みができるようにします。

       ______________________________________________________________________
           77  # Many BIOS's default disk parameter tables will not recognise
           78  # multi-sector reads beyond the maximum sector number specified
           79  # in the default diskette parameter tables - this may mean 7
           80  # sectors in some cases.
           81  #
           82  # Since single sector reads are slow and out of the question,
           83  # we must take care of this by creating new parameter tables
           84  # (for the first disk) in RAM.  We will set the maximum sector
           85  # count to 36 - the most we will encounter on an ED 2.88.
           86  #
           87  # High doesn't hurt.  Low does.
           88  #
           89  # Segments are as follows: ds = es = ss = cs - INITSEG, fs = 0,
           90  # and gs is unused.

           91          movw    %cx, %fs                # set fs to 0
           92          movw    $0x78, %bx              # fs:bx is parameter table address
           93          pushw   %ds
           94          ldsw    %fs:(%bx), %si          # ds:si is source
           95          movb    $6, %cl                 # copy 12 bytes
           96          pushw   %di                     # di = 0x4000-12.
           97          rep                             # don't need cld -> done on line 66
           98          movsw
           99          popw    %di
          100          popw    %ds
          101          movb    $36, 0x4(%di)           # patch sector count
          102          movw    %di, %fs:(%bx)
          103          movw    %es, %fs:2(%bx)
       ______________________________________________________________________

  BIOS のサービスの int 0x13 ファンクション0 (reset FDC) を使って、フ
  ロッピーディスクコントローラをリセットします。そして、setup セクタが
  bootsector のすぐ後へ読み込まれます。つまり 物理アドレスの0x90200
  ($INITSEG:0x200) です。そして、再度BIOSサービスのint 0x13 ファンクショ
  ン 2(read sector(s)) を呼び出します。この辺りは、107-124行に記述されて
  います。

       ______________________________________________________________________
          107  load_setup:
          108          xorb    %ah, %ah                # reset FDC
          109          xorb    %dl, %dl
          110          int     $0x13
          111          xorw    %dx, %dx                # drive 0, head 0
          112          movb    $0x02, %cl              # sector 2, track 0
          113          movw    $0x0200, %bx            # address = 512, in INITSEG
          114          movb    $0x02, %ah              # service 2, "read sector(s)"
          115          movb    setup_sects, %al        # (assume all on head 0, track 0)
          116          int     $0x13                   # read it
          117          jnc     ok_load_setup           # ok - continue

          118          pushw   %ax                     # dump error code
          119          call    print_nl
          120          movw    %sp, %bp
          121          call    print_hex
          122          popw    %ax
          123          jmp     load_setup

          124  ok_load_setup:
       ______________________________________________________________________

  もし、なにかの原因(フロッピーが劣化しているとか、使用中にディスケット
  を抜き去ったとか)で読み込みが失敗したら、エラーコードを表示しながら、
  無限に再試行されます。再試行が成功していない状態から抜け出すには、パソ
  コンを再起動するほかありません。しかし、通常このようなことは起りません
  (もしなにかが間違えているとしたら、単におかしくなったのです)。も
  し、setupのコードのsetup_sectセクタの読み込みがうまく行くと、ラベル
  ok_load_setup: へジャンプします。その後、圧縮されたカーネルイメージを
  物理アドレス 0x10000 へと読み込みます。これは、低位のメモリ領域
  (0-64K) にあるファームウエアのデータ領域を保護するために行われていま
  す。カーネルが読み込まれると、$SETUPSEG:0 (arch/i386/boot/setup.S)に
  ジャンプします。ファームのデータがもう要らなくなれば (例えば、もうBIOS
  をコールしないなど)、(圧縮された)すべてのカーネルのイメージを
  0x10000 から 0x1000 (当然、物理アドレス)へ移します。その結果、この領域
  は上書きされます。これは、setup.Sで実行されます。このコードでは、プロ
  テクトモードへの移行に必要なことを行い、圧縮カーネルの先頭である
  0x1000 へとジャンプするようになっています。つまり、arch/i386/boot/com-
  pressed/{head.S,misc.c} です。さらに、スタックを設定し、decom-
  press_kernel() を呼び出します。このルーチンではカーネルをアドレス
  0x100000 へ展開します。そして、その展開されたカーネルへジャンプしま
  す。

  ここで古いブートローダ(古いLILO)では、setupの最初の4セクタしか読み込む
  ことができません。そのため、もし必要であれば自分自身の残りを読み込むよ
  うなsetupのコードが存在しています。もちろん、setup のコードは様々なタ
  イプ/バージョンのローダとzImage/bzImageの組合わせを取り扱わなければな
  りません。そのため、非常に複雑です。

  ここで、"bzImage" として知られている大きなカーネルのロードを行うブート
  セクタコードでの対処方法を見てみましょう。 setupのセクタは、通
  常0x90200にロードされますが、この時のカーネルは、一度に64Kの固まりで読
  み込まれるような特別な補助ルーチンを使います。このルーチンは、データを
  低位アドレスから、高位アドレスへ移動するBIOSコールを呼び出します。この
  補助ルーチンは、setup.Sのbootsect_kludgeラベルから参照されます。そし
  てbootsect_helperとしてsetup.Sで定義されています。 setup.S
  のbootsect_klughラベルは、setupセグメント値と、その中のbootsect_helper
  コードのオフセットからなっています。そのため、ブートセクタでは、ジャン
  プするのにlcall命令を使うことができます。(つまりセグメント内ジャンプで
  す) setup.Sにこれがあるのは、単にbootsect.Sには、もう余分なスペースが
  無いからなのです。(これは厳密には正しくはありません -- bootsect.Sに
  は、約4バイトと少なくとも1バイトの余裕があります。しかし、明らかに十分
  とはいえません)。このルーチンは高位のメモリへ移動するのに、int
  0x15(ax=0x8700)のBIOSサービスを使い、%esを常に0x10000を示すようリセッ
  トします。これは、bootsect.Sのコード内で、ディスクからデータをコピーす
  るときに低位メモリが足らなくならないことを保証します。

  1.5.  LILOをブートローダとして使う

  Linuxブートセクタを裸で使わず、特別なブートローダ(LILO)を使うことで、
  利点が生まれます。

  1. 複数の Linux カーネルから選択したり、さらには複数の OS も選択できる
     ようになります。

  2. カーネルにコマンドラインパラメタを渡すことができるようになります (
     裸の bootsector+setup にこの機能を付加できる BCP というパッチがあり
     ます)。

  3. よりサイズの大きな bzImage カーネルをロードすることができます。1M
     までのところが 2.5M まで可能になります。

  LILO の古いバージョン(v17以前)では、bzImage カーネルをロードすることが
  できません。新しいバージョンでは(ここ数年前より最近で
  は)、bootsect+setup と同じように、BIOS サービスにある低位メモリから高
  位メモリへデータを移動するテクニックを使っています。人によっては(特に
  Peter Anvinは)、zImage サポートは削除すべきだと主張しています。それで
  も残されている主な理由は、(Alan Cox によると) zImage のロードは可能だ
  が、bzImage カーネルのブートができないような壊れた BIOS が明らかに存在
  しているためだということです。

  LILO は最後に、setup.Sへジャンプし、通常どおりの処理を続けます。

  1.6.  高いレベルの初期化

  「高いレベルの初期化」では、ブートに直接関連しないものについて考えま
  す。しかし、これを行うコードの一部は、展開されたカーネルの先頭にあ
  るarch/i386/kernel/head.Sと呼ばれるアセンブラで書かれています。そこで
  は、以下のような処理が行われます。

  1. セグメント値を初期化する (%ds = %es = %fs = %gs = __KERNEL_DS =
     0x18)。

  2. ページテーブルを初期化する。

  3. %cr0のPGビットをセットし、ページングを有効にする。

  4. BSSをゼロで埋める (SMPでは、1つめのCPUだけがこの処理を行う)。

  5. ブートパラメータの最初の2kをコピーする (カーネルコマンドライン)。

  6. CPUタイプをEFLAGSと、可能であれば386以上を確認できるcpuidでチェック
     する。

  7. 1つめのCPUはstart_kernel()を呼び出し、他のCPUは、ready=1であれば単
     に esp/eip をリロードするのみで戻ってこない関数の
     arch/i386/kernel/smpboot.c:initialize_secondary() を呼び出す。

  init/main.c:start_kernel()はCで書かれており、以下のような処理を行いま
  す。

  1. (初期化の間には1つのCPUだけが動作することが必要なため)グローバルの
     カーネルロックを取得する。

  2. アーキテクチャ独自のセットアップを実行する(メモリレイアウトの解析、
     ブートコマンドラインの再度のコピーなど)。

  3. Linux カーネルのバージョン、ビルドに使用したコンパイラなどからなる
     「バナー」をメッセージ用のカーネルリングバッファへ書き込む。この
     メッセージは、init/version.c で定義されている linux_bannar 変数から
     取得する。このメッセージは、cat /proc/version コマンドを実行したと
     きと同じメッセージになっている。

  4. trap を初期化する。

  5. irq を初期化する。

  6. スケジューラの使うデータを初期化する。

  7. 時刻を保持するデータを初期化する。

  8. ソフト割り込みサブシステムを初期化する。

  9. ブートコマンドラインオプションを解析する。

  10.
     コンソールを初期化する。

  11.
     もしモジュールサポートがカーネルに組み込まれていたら、動的モジュー
     ル読み込み機構を初期化する。

  12.
     もし、"profile=" コマンドラインがあれば、プロファイルバッファを初期
     化する。

  13.
     kmem_cache_init() を実行し、ほとんどのスラブアロケータを初期化す
     る。

  14.
     割り込みを有効にする。

  15.
     使用している CPU の BogoMips を計算する。

  16.
     max_mapnr、totalram_pages と high_memoryを計算するmem_init()を呼び
     出し、"Memory: ..." の行を表示する。

  17.
     kmem_cache_sizes_init() を実行し、スラブアロケータの初期化が完了す
     る。

  18.
     procfs が使うデータ構造体を初期化する。

  19.
     fork_init()を呼び出し、uid_cache を作成して、利用可能なメモリ量に基
     づきmax_threadsを初期化し、  init_task が max_threads/2 になるよう
     RLIMIT_NPROC を設定する。

  20.
     VFS、VM、バッファキャッシュなどで必要な種々のスラブキャッシュを作成
     する。

  21.
     System V IPC サポートがカーネルに含まれていたら、IPC サブシステムを
     初期化する。System V shm の場合は、shmfsファイルシステムの(カーネル
     の)内部インスタンスをマウントすることに注意すること。
  22.
     quota サポートがカーネルに含まれていたら、quota 用のスラブキャッ
     シュを作成し初期化する。

  23.
     アーキテクチャ特有の「バグのチェック」を行う。そして現時点で可能な
     限り、プロセッサやバス、その他の対処を有効にする。各種のアーキテク
     チャを比較すると、「IA64 にはバグはなく」、「IA32 はかなりバグがあ
     る」。この良い例として「f00fバグ」がある。このバグは、カーネルが
     686 より前の CPU 向けにコンパイルされたときだけチェックされ、チェッ
     ク結果に従い対処を行なう。

  24.
     スケジューラが「次の機会」に起動される事を示すフラグをセットする。
     そして、init カーネルスレッドを作成する。このカーネルスレッドは、も
     し、"init=" ブートパラメータが与えられていた場合は、
     execute_command を実行する。もし指定がなけれ
     ば、/sbin/init、/etc/init、/bin/init、/bin/shの順にファイルを探し、
     実行しようとする。もし全て失敗したら、"init="パラメータを使うよう
     「忠告」してパニック状態になる。

  25.
     アイドルループに入る。これは pid=0 のアイドルスレッドとなる。

  ここで重要なことは、init() カーネルスレッドが do_basic_setup() を呼び
  出していることです。この関数はさらに、__initcall や module_init() マク
  ロによって登録された関数のリストを読み出して実行する do_initcalls() を
  呼び出します。これらの関数は、各々が相互に依存していないか、Makefileで
  リンクの順序を入れ替えることで、依存関係を手動で修正してあります。これ
  はすなわち、ツリーの中のディレクトリの位置とMakefileの構成によって、初
  期化関数の実行順序が入れ替えられるということを意味しています。ときに
  は、二つのサブディレクトリAとBがあった場合、BがAの中の初期化関数に依存
  しているような場合に、この順序が重要になります。もし、Aが静的にカーネ
  ルにリンクされ、Bはモジュールであった場合は、Bの実行タイミングは、Aが
  必要な環境を整えた後であることが保証されます。もし、Aがモジュールであ
  り、Bも当然モジュールである場合にも問題はありません。しかし、AとBが両
  方静的にカーネルにリンクされる場合はどうでしょう? 2つの実行順序は、
  カーネルイメージの.initcall.init ELF セクションにおける位置の差に依存
  しているのです。 Rogier Wolffは階層的な「優先度」構造を提案し、それに
  よってリンカがどの(相互的な)順序でモジュールをリンクするかが分かるよう
  にしました。しかし、いまのところ、これをカーネルへ受け入れられるような
  効果的にエレガントな方法で実装したパッチは存在していません。したがっ
  て、リンクの順序を正しくしないといけないのです。もし、上記の例で、AとB
  がともに静的にコンパイルされたとき良好に動作したなら、常に動作します
  が、そのためにはおなじMakefileに順序よくリストしなければなりません。も
  しうまく働かないようなら、オブジェクトファイルのリスト順を変えることに
  なります。注意する価値のあるもう一つの事柄として、"init="ブートコマン
  ドラインを渡すことにより、「別のinitプログラム」を実行するLinuxの機能
  があります。これは、/sbin/init を誤って上書きしたときの回復や、初期
  化(rc)スクリプトや /etc/initttab を、一回に一つずつ手で実行することで
  デバッグするのに有益です。

  1.7.  x86でのSMPブート

  SMPにおいて、BP は start_kernel()へ進み、そしてsmp_init()と特
  にsrc/i386/kernel/smpboot.c:smp_boot_cpus()に進むまで、通常のブートセ
  クタ、setupなどのブートシーケンスを進んでいきます。 smp_boot_cpus()
  は、(NR_CPUSまで)ループで各apicidごとに実行され、その中でdo_boot_cpu()
  が呼ばれます。 do_boot_cpu()はターゲットのCPU用のアイドルタスクを生
  成(i.e. fork_by_hand)します。そして、Intel MP仕様で定義されている既定
  の位置 (0x467/0x469) へと、trampoline.SにあるトランポリンコードのEIPを
  書き込みます。そして、このAPが trampoline.Sのコードを実行するように、
  ターゲット CPU の STARTUP IPI を生成します。

  ブートした CPU は、低位メモリにある各 CPU のトランポリンコードのコピー
  を作成します。APコードはマジックナンバーを自身のコードに埋め込むこと
  で、BPによりそのAPがトランポリンコードを実行してよいかの判断を行わせま
  す。Intel MP仕様によって規定されているため、トランポリンコードは低位メ
  モリに置かれる必要があります。

  トランポリンコードは単純に %bx レジスタを 1 にします。そして、プロテク
  トモードに移り、arch/i386/kernel/head.Sのメインエントリーポイントであ
  るstartup_32 へとジャンプします。

  さて、APはhead.Sの実行を開始し、自身がBPではないことに気が付きます。す
  ると、BSSをクリアするコードの実行をスキップして、initalize_secondary()
  へと進みます。そして、この CPU はアイドルタスクへと単に進みます。 --
  init_tasks[cpu]は、BPがdo_boot_cpu(cpu) を実行したときにすでに初期化さ
  れていたことを思い起こしましょう。

  ここで、init_taskは共有できますが、各アイドルスレッドで各々 TSS を持た
  なければならないことに注意しましょう。これは、init_tss[NR_CPUS]が配列
  になっている理由になっています。

  1.8.  初期化データおよびコードの解放

  オペレーティングシステムが自身を初期化するとき、そのためのほとんどの
  コードとデータ構造体は二度と使われることはありません。ほとんどのオペ
  レーティングシステム(BSD, FreeBSD etc...)では、この不必要な情報を破棄
  することができません。すなわち、貴重な物理カーネルメモリを浪費している
  ことになるのです。彼らの使う言い訳(McKusickの4.4BSD本を参照)は、「関連
  のコードが各種のサブシステムに広がっており、これらを解放するのは現実的
  ではない。」ということです。Linux はもちろん、このような言い訳をしませ
  ん。なぜなら、Linux では、「もしなにかが原理的に可能であれば、それはす
  でに実装されているか、誰かが作業している」からです。

  そして、前の章で述べたように、Linux カーネルはELF バイナリとしてのみコ
  ンパイルできることから、私たちにもそれが可能なことが(あるいは、その根
  拠の一つ)が分かっています。以下のように使われる2つのマクロを Linux が
  提供しており、初期化コード/データを廃棄することができるようになってい
  ます。

  o  __init - 初期化コード用

  o  __initdata - 初期化データ用

  これらはinclude/linux/init.hに定義されるgccの属性指示子("gcc magic"と
  しても知られる)を評価します。

       ______________________________________________________________________
       #ifndef MODULE
       #define __init        __attribute__ ((__section__ (".text.init")))
       #define __initdata    __attribute__ ((__section__ (".data.init")))
       #else
       #define __init
       #define __initdata
       #endif
       ______________________________________________________________________

  これが意味することは、もしコードがカーネルに静的にコンパイルされている
  なら(つまり MODULEが定義されていなければ)、特殊な ELF セクション
  のtext.initにこれらのコードが配置されるということです。そしてその配置
  は、arch/i386/vmlinux.ldsのリンカマップに定義されるのです。逆に(つまり
  モジュールであれば)マクロはなにもしないということです。

  ブート時に、アドレス__init_beginから__init_endの間の全てのページを解放
  する、アーキテクチャ特有の関数free_initmem()を、"init"カーネルスレッ
  ド(関数init/main.c:init())が呼び出すのです。

  (私のワークステーションのような)一般的なシステムでは、この結果、約260K
  のメモリの解放になります。

  module_init()によって登録された関数は、.initcall.initに配置され、静的
  にリンクされていたときには同様に解放されます。Linux 開発の現在の方向性
  では、将来問題のサブシステムが必要に応じてモジュール化できるように、(
  当初モジュールの必要性のない)サブシステムの場合にも、デザインの初期段
  階から、init/exit エントリーポイントが提供されるようにしていま
  す。fs/pipe.cのpipefsが、このよい例です。たとえ、bdflush (fs/buffer.c
  参照)のように、あるサブシステムが決してモジュールになることがなく、そ
  の時点でその関数を呼ぶことが重要でないとしても、それでも初期化関数とし
  てmodule_init()マクロを使うことはよいことです。

  さらに同じような使い方をする__exitと__exitdataという名前の2つのマクロ
  があります。しかしこれらは、モジュールサポートにより直接的につながるた
  め、後のセクションにて説明します。

  1.9.  カーネルコマンドラインを処理する

  ここで、ブート時にカーネルに渡されたコマンドラインに何が起こるかを考え
  てみましょう。

  1. LILO(ないしはBCP) は BIOS のキーボードサービスを使って、コマンドラ
     インを受け取る。そして、物理メモリの既定の位置に、そこに有効なコマ
     ンドラインがあることを示す印といっしょに格納する。

  2. arch/i386/kernel/head.S は自身の最初の 2k をゼロページの外へとコ
     ピーする。

  3. (start_kernel() から呼ばれた setup_arch()から呼び出される)
     arch/i386/kernel/setup.c:parse_mem_cmdline()は、ゼロページから 256
     バイトを /proc/cmdline で表示されるのと同じ  saved_command_line へ
     とコピーする。これと同じルーチンは、"mem=" オプションがもしあれば処
     理し、VM パラメータを適切に調整する。

  4. 話を(start_kernel()から呼ばれる) parse_options() のコマンドラインに
     戻すと、この関数は「カーネル内部」パラメータ(現時点では、"init="と
     init の環境変数、引数)も処理し、各ワードをchecksetup()へと渡す。

  5. checksetup() は ELF セクション.setup.initのコードについて、そこの各
     関数を起動し、コマンドラインがマッチしていた時は、コマンドラインの
     ワードを渡していく。ここで、__setup()で登録された関数からの返り値が
     0 のときは、同じ"variable=value" を一つ以上の関数へ渡せ、そ
     の"value"が一方の関数では無効で、他方では有効であるということを表し
     ている。 Jeff Garzikは、「こういうことをするハッカーはおしりペンペ
     ンだ :) 」とコメントしている。なぜか?  これは明らかに ID の順序に特
     有で、つまりある順序でリンクされたカーネルは関数Aを関数Bの前に起動
     するが、そうでない場合は、逆の順序になり結果が順序に依存してしまう
     からだ。

  さて、ブートコマンドラインを処理するコードはどのようになっているので
  しょうか。include/linux/init.hで定義される __setup() マクロを使いま
  す。

  ______________________________________________________________________

  /*
   * Used for kernel command line parameter setup
   */
  struct kernel_param {
          const char *str;
          int (*setup_func)(char *);
  };

  extern struct kernel_param __setup_start, __setup_end;

  #ifndef MODULE
  #define __setup(str, fn) \
     static char __setup_str_##fn[] __initdata = str; \
     static struct kernel_param __setup_##fn __initsetup = \
     { __setup_str_##fn, fn }

  #else
  #define __setup(str,func) /* nothing */
  endif
  ______________________________________________________________________

  次に、実際のコードでの典型的な使い方は以下のようになります (実際のドラ
  イバの BusLogic HBA drivers/scsi/BusLogic.cから引用)。

       ______________________________________________________________________
       static int __init
       BusLogic_Setup(char *str)
       {
               int ints[3];

               (void)get_options(str, ARRAY_SIZE(ints), ints);

               if (ints[0] != 0) {
                       BusLogic_Error("BusLogic: Obsolete Command Line Entry "
                                       "Format Ignored\n", NULL);
                       return 0;
               }
               if (str == NULL || *str == '\0')
                       return 0;
               return BusLogic_ParseDriverOptions(str);
       }

       __setup("BusLogic=", BusLogic_Setup);
       ______________________________________________________________________

  ここで __setup()はモジュールに対しては何もしません。そしてモジュールで
  あれ静的にリンクされているのであれ、ブートコマンドラインを処理したい
  コードでは、モジュールの初期化ルーチンにおいて、自身の解析関数を持って
  いてそれを起動しなければならないからです。これは、静的にコンパイルされ
  る時などだけでなく、モジュールとしてコンパイルされる時にも、パラメータ
  を処理するコードを書くことができるという事でもあります。

  2.  プロセスと割り込み管理

  2.1.  タスク構造体とプロセステーブル

  Linuxでは、全てのプロセスは struct tack_struct 構造体が動的に割り当て
  られます。Linux 上で動かせる最大のプロセス数の制限は、存在する物理メモ
  リ容量で決まります (kernel/fork.c:fork_init()参照)。その値は以下のとお
  りです。つまり、

       ______________________________________________________________________
               /*
                * The default maximum number of threads is set to a safe
                * value: the thread structures can take up at most half
                * of memory.
                */
               max_threads = mempages / (THREAD_SIZE/PAGE_SIZE) / 2;
       ______________________________________________________________________

  になります。

  IA32 アーキテクチャでは、基本的に num_physpages/4 を意味しています。例
  えば、512M のマシンがあれば、32k 個のスレッドを生成できるという訳で
  す。これは古い (2.2 やそれ以前の) カーネルの 4k 弱の制限に比べ相当な改
  善になっています。そのうえ、実行時にカーネルを調整できる
  KERN_MAX_THREADS sysctl(2) や、単純に procfs インターフェースを使って
  変更することもできます。

       ______________________________________________________________________
       # cat /proc/sys/kernel/threads-max
       32764
       # echo 100000 > /proc/sys/kernel/threads-max
       # cat /proc/sys/kernel/threads-max
       100000
       # gdb -q vmlinux /proc/kcore
       Core was generated by `BOOT_IMAGE=240ac18 ro root=306 video=matrox:vesa:0x118'.
       #0  0x0 in ?? ()
       (gdb) p max_threads
       $1 = 100000
       ______________________________________________________________________

  Linux システムでは、プロセス群は2種類の異なる方法でリンクされた
  struct task_struct 構造体の集合として表すことができます。

  1. pid によりハッシュされたハッシュテーブルと、

  2. p->next_task と p->prev_task ポインタを用いた円環状のダブルリンクリ
     スト。

  ハッシュテーブルは、pidhash[] と呼ばれ、include/linux/sched.h で定義さ
  れています。

       ______________________________________________________________________
       /* PID hashing. (shouldnt this be dynamic?) */
       #define PIDHASH_SZ (4096 >> 2)
       extern struct task_struct *pidhash[PIDHASH_SZ];

       #define pid_hashfn(x)   ((((x) >> 8) ^ (x)) & (PIDHASH_SZ - 1))
       ______________________________________________________________________

  タスクはpid値のハッシュにされ、上記のハッシュ関数はその(0からPID_MAX-1
  の)領域に要素が均一に分散されるようになっています。ハッシュテーブル
  は、include/linux/sched.h の中にある find_task_pid() を使って、与えら
  れた pid のタスクをすぐに見つけられるよう使われています。

       ______________________________________________________________________
       static inline struct task_struct *find_task_by_pid(int pid)
       {
               struct task_struct *p, **htable = &pidhash[pid_hashfn(pid)];

               for(p = *htable; p && p->pid != pid; p = p->pidhash_next)
                       ;

               return p;
       }
       ______________________________________________________________________

  各ハッシュリストの(つまり同じ値にハッシュされた)タスク
  は、p->pidhash_next/pidhash_pprevによってリンクされ、hash_pid()
  とunhash_pid()がハッシュテーブルにあるプロセスを挿入したり削除したりす
  るのに使われます。これらの操作は、WRITEのために取得するtasklist_lockと
  いう読み書きスピンロックの保護のもと行われます。

  p->next_task/prev_taskが使うリング状の双方向リンクリストは、システムの
  すべてのタスクに容易に到達できるように管理されます。これ
  は、include/linux/sched.hで定義されるfor_each_task()マクロによって実現
  されています。

       ______________________________________________________________________
       #define for_each_task(p) \
               for (p = &init_task ; (p = p->next_task) != &init_task ; )
       ______________________________________________________________________

  for_each_task()のユーザはREAD の tasklist_lock を取得する必要がありま
  す。ここで、for_each_task()がリストの始点(と終点)を表すのにinit_taskを
  使用しています。これは、アイドルタスク(pid 0)が終了することがないこと
  から、安全な方法です。

  プロセスハッシュテーブルまたはプロセステーブルリンクの変更時には、特に
  fork()とexit、ptrace() ですが、WRITEのためにtasklist_lockを取得しなけ
  ればなりません。もっと興味深いことには、書き込みを行う時は、ローカ
  ルCPUの割り込みも無効にしなければならないのです。これには明白な理由が
  あります。send_sigio()関数はタスクリストをたどって、READのため
  にtasklist_lockを取得し、割り込みコンテキストで kill_fasync()から呼び
  出されるからです。これが、読み込みを行うときには不要でも、書き込みを行
  うときには必ず割り込みを無効にしなければならない理由です。

  さて、task_struct構造体がどのようにして相互にリンクしているか見ていき
  ましょう。 task_structのメンバーを子細にみてみます。これらは相互に結合
  している UNIX の'struct proc'と 'struct user' と弱い関連があります。

  UNIX の他のバージョンでは、タスク状態の情報は2つに分けて格納されます。
  一つは常にメモリ内に無ければならない('proc structure'とよばれ、プロセ
  ス状態、スケジューリング情報などを含んでいる)ものです。もう一方は、プ
  ロセスが走行するときにだけ必要になる('u area'と呼ばれ、ファイルディス
  クリプタテーブル、ディスククオタ情報などの)情報です。このような醜い実
  装がなされたのは、単にメモリが非常に貴重な資源であったという事情があり
  ました。現代のOSでは、(もちろん今学んでいる Linux だけでなく、他の例え
  ば FreeBSD では、Linuxのこの方向性をさらに進歩させたものになっている)
  このように分離する必要はなく、それゆえ常にメモリ上にあるデータ構造体に
  よりプロセス状態の管理を行うようになっています。

  task_struct構造体は、 include/linux/sched.hで宣言され、そのサイズは現
  在 1680 byte です。

  状態フィールドは次のように宣言されています。

       ______________________________________________________________________
       volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */

       #define TASK_RUNNING            0
       #define TASK_INTERRUPTIBLE      1
       #define TASK_UNINTERRUPTIBLE    2
       #define TASK_ZOMBIE             4
       #define TASK_STOPPED            8
       #define TASK_EXCLUSIVE          32
       ______________________________________________________________________

  なぜ TASK_EXCLUSIVE が 16ではなく32と定義されているのでしょうか。実
  は16は TASK_SWAPPING により使われており、(2.3.xのどこか
  で)TASK_SWAPPINGのリファレンスを削除するときに TASK_EXCLUSIVEをシフト
  するのを忘れてしまったからなのです。 p->stateのvolatile修飾子は、(割り
  込みハンドラから)非同期で変更され得ることを意味しています。

  1. TASK_RUNNING:タスクが runキューにある「であろう」ことを意味す
     る。runキューにないかもしれないタスクへTASK_RUNNING印をつけておく理
     由は、runqueueに置く操作がアトミックでないからである。 runキューを
     調べるには読み込みのために runqueue_lock 読み書きスピンロックを取得
     する必要がある。もしそうすると、その後に runqueue 上の各タスクが
     TASK_RUNNING 状態にあるかどうか見るだろう。しかし、上記の理由から
     は、逆は真にならない。同様に, ドライバが自身を(走行しているプロセス
     のコンテキストに関わらず)TASK_INTERRUPTIBLE (もしくは
     TASK_UNINTERRUPTIBLE) にマークできる。 そしてその後、 schedule() を
     呼び出す。これは (runqueue に残されるケースである保留されたシグナル
     がなければ) runqueueからそれを削除する。

  2. TASK_INTERRUPTIBLE: タスクは休止しているが、シグナルやタイマの終了
     で起こされることがある。

  3. TASK_UNINTERRUPTIBLE: TASK_INTERRUPTIBLEと同じだが、起こされない点
     が異なっている。

  4. TASK_ZONBIE: タスクは終了しているが、その状態が(通常かアダプション
     による)親プロセスによって(wait()により)回収されていない。

  5. TASK_STOPPED: ジョブ制御シグナルかptrace(2)により、タスクが停止して
     いる。

  6. TASK_EXCLUSIVE: これは独立した状態ではなく、TASK_INTERRUPTIBLE や
     TASK_UNINTERRUPTIBLE と OR される。他の多くのタスクといっしょにウエ
     イトキューで休止しているとき、全ての待機タスクを起こすこと
     で"thundering herd"問題を起こす代りに、自分だけ起きることを意味して
     いる。

  タスクフラグは相互排他的ではないプロセス状態についての情報からなってい
  ます。

       ______________________________________________________________________
       unsigned long flags;    /* per process flags, defined below */
       /*
        * Per process flags
        */
       #define PF_ALIGNWARN    0x00000001      /* Print alignment warning msgs */
                                               /* Not implemented yet, only for 486*/
       #define PF_STARTING     0x00000002      /* being created */
       #define PF_EXITING      0x00000004      /* getting shut down */
       #define PF_FORKNOEXEC   0x00000040      /* forked but didn't exec */
       #define PF_SUPERPRIV    0x00000100      /* used super-user privileges */
       #define PF_DUMPCORE     0x00000200      /* dumped core */
       #define PF_SIGNALED     0x00000400      /* killed by a signal */
       #define PF_MEMALLOC     0x00000800      /* Allocating memory */
       #define PF_VFORK        0x00001000      /* Wake up parent in mm_release */
       #define PF_USEDFPU      0x00100000      /* task used FPU this quantum (SMP) */
       ______________________________________________________________________

  p->has_cpuや p->processor, p->counter, p->priority, p->policy そして
  p->rt_priorityフィールドはスケジューラと関連があるため、後ほど見ていき
  ます。

  p->mm と p->active_mm フィールドはそれぞれ、プロセスの mm_struct 構造
  体で表されるアドレス空間と、プロセスがリアルなアドレス空間を持たな
  い(e.g. カーネルスレッド)時の、アクティブなアドレス空間を示していま
  す。これはタスクがスケジューラにより中止するときのアドレス空間スイッチ
  におけるTLBフラッシュを最小限にしてくれます。そして、もし(p->mmをもた
  ない)カーネルスレッドがスケジュールインするときは、このnext->active_mm
  はスケジュールアウトされたタスクのprev->active_mmに設定されます。この
  タスクは、もしprev->mm != NULL であったときのprev->mmに等しくなりま
  す。 CLONE_VMフラグがclone(2)システムコールに渡されたときや、vfork(2)
  システムコールによるときは、アドレス空間はスレッドで共有されます。

  p->exec_domain と p->personality フィールドはタスクのパーソナリティに
  関連しています。つまり、あるシステムコールが他のUNIXの"personality"を
  エミュレートするために振る舞う方法になっています。

  p->fsフィールドはファイルシステム情報からなり、Linuxにおいては、三つの
  情報要素を意味します。

  1. ルートディレクトリのdentryとマウントポイント

  2. 代替ルートディレクトリのdentryとマウントポイント

  3. 現在のワーキングディレクトリのdentryとマウントポイント

  この構造体はリファレンスカウントも保持します。これは、CLONE_FSフラグ
  がclone(2)システムコールに渡されたときに、クローンのタスク間で共有でき
  るようにするためです。

  p->filesフィールドは、ファイルディスクリプタテーブルからなっています。
  これもCLONE_FILESがclone(2)システムコールで指定されたときにタスク間で
  共有されます。

  p->sigフィールドは、シグナルハンドラからなっています。そし
  てCLONE_SIGHANDによりクローンのタスク間で共有されます。

  2.2.  タスクとカーネルスレッドの生成と終端

  オペレーティングシステムの他の書籍では、「プロセス」をそれぞれ違うよう
  に定義しています。「実行時のプログラムのインスタンス」に始まり、
  「clone(2)やfork(2)システムコールにより生成されるもの」まで様々で
  す。Linuxにおいては、三つのプロセスの種類があります。

  o  アイドルスレッド

  o  カーネルスレッド

  o  ユーザタスク

  アイドルスレッドはコンパイル時に一つめのCPUのために作られます。そして
  「手動」でアーキテクチャ特有のarch/i386/kernel/smpboot.c
  のfork_by_hand()により各CPUごとに作られます。この関数では、fork(2)シス
  テムコールが(アーキテクチャによっては)手で展開されています。アイドルタ
  スクは一つのinit_task構造体を共有しますが、TSS構造体は個別にCPUごとの
  配列init_tssとして持っています。アイドルタスクはすべて pid=0 となりま
  すが、他のタスクは pidを共有できません。これはつまり CLONE_PIDフラグ
  をclone(2)へと渡すということです。

  カーネルスレッドは、カーネルモードで clone(2) システムコールを呼び出す
  kernel_thread()関数を使って生成されます。カーネルスレッドは通常ユーザ
  アドレス空間を持ちません(つまりp->mm = NULL)。これは、これらのスレッド
  が、(たとえばdaemonize()関数を通して)明白にexit_mm()を行うからです。
  カーネルスレッドは、いつでもカーネルアドレス空間へ直接アクセスできま
  す。そして、小さな値のpid番号を割り当てられます。(x86のとき) プロセッ
  サリング0 で走行しているときは、カーネルスレッドがすべてのI/Oの権利を
  もち、スケジューラに対してプリエンプティブではないということを意味して
  います。

  ユーザタスクは、clone(2)ないしは、fork(2)システムコールにより作られま
  す。これらのシステムコールは、内部的にkernel/fork.c:do_fork()を呼び出
  しています。

  ここで、ユーザプロセスがfork(2)システムコールを呼び出したときに、何が
  起こっているかを解説しましょう。fork(2)がユーザスタックやレジスタを渡
  す方法が異なるという意味でアーキテクチャ依存だとしても、根底にある実際
  の仕事を行う関数の do_fork() は可搬性があり、ソースコード
  のkernel/fork.cに納められています。

  以下のステップで実行されます。

  1. ローカル変数のretvalが-ENOMEMにセットされる。これはfork(2)が新しい
     タスク構造体の割り当てに失敗したときにセットされるerrnoの値になって
     いる。

  2. もし CLONE_PID が clone_flags にセットされていた場合、呼び出し元が
     アイドルスレッド(ブート時のみ)でない限り、エラー(-EPERM)を返す。通
     常のユーザスレッドがCLONE_PIDをclone(2)に渡すことはできないし、成功
     を期待することもできない。
  3. current->vfork_sem が初期化される(これは子によって後にクリアされ
     る)。例えば、他のプログラムを exec() したり、exit(2) したときのよう
     に、これは sys_vfork() (clone_flags = CLONE_VFORK|CLONE_VM|SIGCHLD
     に関連する vfork(2) システムコール) により、子プロセス
     がmm_release()を実行するまで、親を休止させるために使われる。

  4. 新しいタスク構造体がアーキテクチャ依存のalloc_task_struct()マクロを
     使って割り当てられる。x86 上では、GFP_KERNEL 優先度の gfp になって
     いる。これが fork(2) システムコールが休止する最大の理由になってい
     る。もし割り当てが失敗すれば、-ENOMEMを返す。

  5. 現在のプロセスのタスク構造体の全ての値は、構造体の代入 *p =
     *current によって、新しいものへコピーされる。おそらくこれは、memcpy
     に置き換えられるべきものだ。その後で、子に引き継がれないフィールド
     は、正しい値に設定される。

  6. 残りのコードがリエントラント可能ではないことから、大きなカーネル
     ロックが取得される。

  7. もし、親がユーザ資源をもっていた場合 (UIDの考え方において、Linux
     は、事実としてではなく、その質問をするだけの十分な柔軟性をもってい
     る) 、ユーザがRLIMIT_NPROCソフトリミットを越えているかどうかを確認
     する。もし越えているなら失敗し-EAGAINを返す。もし越えていなければ、
     与えられたuidのプロセスのカウンタp->user->countを1増加させる。

  8. もし、実行されるシステム全体のタスク数が、調整可能な値である
     max_threads を越えていた場合、失敗して-EAGAINを返す。

  9. もしバイナリがモジュール実行領域に関連していた場合、関連モジュール
     のリファレンスカウンタを増やす。

  10.
     もし、実行されるバイナリがモジュール化されたバイナリフォーマットに
     関連していた場合、関連モジュールのリファレンスカウンタを1増加させ
     る。

  11.
     子が「実行されていない」(p->pid_exec = 0)とマークされる。

  12.
     子が「スワップ不可」(p->swappable = 0)とマークされる。

  13.
     子が、「割り込み不可休止」状態、つまりp->state =
     TASK_UNINTERRUPTIBLEに入る(TODO: なぜこれが行われるのか?わたしには
     不要に思われる─これを削除するには、Linus が必要ないと認める必要が
     ある)。

  14.
     子のp->flagsが clone_flags の値にしたがって設定される。通常
     のfork(2)の時は、これはp->flags = PF_FORKNOEXECになる。

  15.
     子のpid p->pid はkernel/fork.c:get_pid()にある fast アルゴリズムを
     使用して、設定される。(TODO: lastpid_lockスピンロックはリダンダント
     にできる。do_fork()から大きなカーネルロックのもと常にget_pid()が呼
     ばれget_pid()のフラグ引数を削除すればリダンダントにできる。パッチは
     Alan に2000年6月20日に送った。その後をフォローすること)

  16.
     do_fork()の残りのコードは、子のタスク構造体の残りを初期化する。一番
     最後に、子のタスク構造体はpidhashハッシュ表にハッシュされ、子は起こ
     される(TODO: wake_up_process(p)はp->state = TASK_RUNNINGを設定し、
     プロセスを runq に加える。したがって、do_fork()の最初のほう
     でp->stateをTASK_RUNNINGに設定する必要はおそらくない)。興味深いの
     は、fork(2)にとってSIGCHLDを意味する、p->exit_signalがclone_flags &
     CSIGNALに設定することと、p->pdeath_signalを0に設定することである。
     pdeath_signalは、(親が死ぬなどして)プロセスがオリジナルの親を「忘れ
     た」ときに使われ、prctl(2)システムコールのPR_GET/SET_PDEATHSIGに
     よって設定/取得することができる。(pdeath_signalの値は、prctl(2)が
     ユーザ空間のポインタアーギュメントを経由して戻されるということが少
     し不思議だと主張されるかもしれない―ごめんなさい。Andries Brouwer
     がマニュアルページを更新したあとだったので、修正が間に合わなかった
     ;)

     このようにタスクが生成されます。そして、タスクが終端するには、いく
     つか方法があります。

  1. exit(2)システムコールを発行する。

  2. デフォルトで死ぬ性質のシグナルが届く。

  3. 特定の例外によって、強制的に死ぬ。

  4. func == 1のbdflush(2)を呼ぶ (これは Linux 独特であり、'update'行
     が/etc/inittabに残っているような古いディストリビューションとの互換
     性のためである。現在ではupdateの仕事は、カーネルスレッドのkupdateが
     行っている)。

  Linux でシステムコールを実装している関数は、sys_のプレフィックスを持っ
  ています。しかし、これらの関数は、たいてい引数のチェックやアーキテク
  チャ独特の情報の渡し方についてのみ関心をもっており、実際の仕事は、do_
  のついた関数が行います。そのため、sys_exit()においては、do_exit()を実
  際の仕事を行うために呼び出します。しかしながら、カーネルの他の部分で時
  折、実処理をしているdo_exit()を呼び出さなければならない場面
  で、sys_exit()を呼び出しているところが見受けられます。

  do_exit()関数は、kernel/exit.cにあります。do_exit()についてのポイント
  としては、

  o  グローバルカーネルロックを使用する(ロックするがロック解除しない)。

  o  schedule()を最後に呼び出すが、戻ることはない。

  o  タスク状態をTASK_ZOMBIEに設定する。

  o  もし0でなければ、current->pdeath_signalにより、すべての子に通知す
     る。

  o  親にcurrent->exit_signalによって通知する。これは通常SIGCHLDになる。

  o  forkによって割り当てられた資源を解放する。オープンしたファイルな
     ど。

  o  lazy FPUスイッチングを使うアーキテクチャの場合(ia64, mips, mips64)
     (TODO: sparc, sparc64の'flags'引数を削除する) FPUオーナーシップ(も
     しカレントが所有していたら)「なし」を渡すためにハードウエアが要求
     することを行う。

  2.3.  Linuxスケジューラ

  スケジューラの仕事は、カレントの CPU へのアクセスを複数のプロセス間で
  仲裁することにあります。スケジューラは、「メインのカーネルファイ
  ル」kernel/sched.cに実装されています。関連のへッダファイルであ
  るinclude/linux/sched.hは、(明示的にせよ間接的にせよ)事実上全てのカー
  ネルソースファイルから読み込まれます。
  スケジューラに関連するタスク構造体のフィールドには、次のものがありま
  す。

  o  p->need_resched: このフィールドは、schedule()が「次の機会」に起動さ
     れるべき時にセットされます。

  o  p->counter: このスケジューリングスライスでの実行の残りのクロック
     チック数でタイマによって減少される。この値が0以下になったとき
     は、p->need_resched が設定される。これは自身が変更できるため、プロ
     セスの「動的優先度」と呼ばれることもある。

  o  p->priority: プロセスの静的な優先度であり、よく使われるシステムコー
     ルのnice(2)や、POSIX.1bのsched_setparam(2)、4.4BSD/SVR4
     のsetpriority(2)によって変更される。

  o  p->rt_priority: リアルタイム優先度。

  o  p->policy: スケジューリングポリシーであり、タスクが所属するスケ
     ジューリングのクラスを表す。sched_setscheduler(2)システムコールを使
     うことで、タスクのスケジューリングのクラスを変更することができる。
     取りうる値には、SCHED_OTHER(伝統的なUNIXのプロセ
     ス)、SCHED_FIFO(POSIX.1b FIFO リアルタイムプロセス)とSCHED_RR(POSIX
     ラウンドロビンリアルタイムプロセス)がある。これらは、SCHED_YIELD
     とORを取ることができ、たとえばsched_yield(2)システムコールを呼び出
     すことによって、プロセスが CPU を譲ることにしたことを示すことができ
     る。FIFOリアルタイムプロセスは a) I/Oがブロックされるか、b) 明示的
     に CPU を解放するか、c) 他の高いp->rt_priority値を持つリアルタイム
     プロセスから割り込まれるか、いずれかがあるまで実行される。

  schedule()関数は、見掛け上、非常に複雑ですが、スケジューラのアルゴリズ
  ムは単純です。関数が複雑なのは、3つのスケジューリングアルゴリズムを一
  つに実装しているのと同時に、SMP のデリケートな仕様を実装しているからで
  す。

  明らかに「無駄な」gotoがschedule()にありますが、これはi386で一番最適化
  されたコードを生成するためです。もちろん、スケジューラは2.4で完全に(
  カーネルの他の部分同様)書き直されました。そのため、以下の議論は2.2やそ
  れ以前のカーネルには当てはまりません。

  では詳細に関数を見ていきましょう。

  1. もしcurrent->active_mm == NULLなら、なにかがおかしい。カレントのプ
     ロセスは、カーネルスレッド(current->mm == NULL )であったとしても、
     正しいp->active_mmを常に持っていなければならない。

  2. もしtq_schedulerタスクキューに実行するものがあれば、いまそれを処理
     する。タスクキューは関数の実行を遅らせてスケジュールを行うカーネル
     の仕組みを提供するものである。その詳細は他の章で説明する。

  3. ローカル変数のprevとthis_cpuを、それぞれ現在のタスクと現在の CPU に
     初期化する。

  4. schedule()が(バグにより)割り込みハンドラから起動されていないか
     チェックする。もしそうならカーネルパニックになる。

  5. グローバルカーネルロックを解放する。

  6. もしソフト割り込み機構経由で行うことがあれば、ここで実行する。

  7. ローカルポインタstruct  schedule_data *sched_dataを初期化し、CPU毎
     の(キャッシュラインピンポンを避けるためキャッシュラインにアラインさ
     れる)スケジューリングデータ領域を指すようにする。これ
     は、last_scheduleのTSC値や最後にスケジュールされたタスク構造体への
     ポインタからなっている。(TODO: sched_dataは SMP の時のみ使われる
     が、なぜinit_idle()は UPであっても同様に初期化を行うのか?)

  8. runqueue_lockスピンロックを取得する。私たちがspin_lock_irq()を使う
     のは、schedule()の中で、割り込み可になっていることを保証するためで
     ある。したがって、runqueue_lockを解除するときに
     は、(spin_lock_irqsave/restoreの一種の) saving/restoring eflagsを使
     うのではなく、単に再度有効にするだけでよい。

  9. タスク状態機械: もしタスクがTASK_RUNNING状態にあれば、そのままにさ
     れる。もしTASK_INTERRUPTIBLE状態であり、シグナルが保留されていれ
     ば、TASK_RUNNING状態へ移行させる。それ以外の場合は、runqueueから削
     除する。

  10.
     (もっともスケジュールされやすい候補の)nextがこの CPU でのアイドルタ
     スクにされる。しかし、この候補のグッドネスは、他のものがこれより良
     くなるように、とても低い値(-1000)となる。

  11.
     もしprev(カレント)のタスクがTASK_RUNNING状態であれば、カレントの
     グッドネスをそのグッドネスと同じに設定する。アイドルタスクよりスケ
     ジュールされやすくなるよう印をつける。

  12.
     さて、runqueueを調べ、このCPUでスケジュールされることが可能な各プロ
     セスのグッドネスをカレントの値と比較する。そして、もっとも高いグッ
     ドネスを持つプロセスが勝つ。ここで、この「この CPU でスケジュールさ
     れることが可能な」という考え方を明確にすると、UP では、runqueue の
     各プロセスをスケジュール可能である。SMP においては、すでに他の CPU
     で走行可能になっていないプロセスだけがスケジュールできる。グッドネ
     スはgoodness()という関数により計算される。リアルタイムプロセスの
     グッドネスでは非常に高く(1000 + p->rt_priority)することで処理する。
     1000 より大きいことで、SCHED_OTHERではないプロセスが勝つことを保証
     している。そして他のより大きなp->rt_priorityを持つリアルタイムプロ
     セスだけが争うことができる。

     プロセスのタイムスライス(p->counter)が終了したときは、goodness 関数
     は 0 を返す。リアルタイムではないプロセスでは、グッドネスの初期値は
     p->counter に設定されるため、すでにしばらくの間 CPU を占有したプロ
     セスは、 CPU を得ることが少なくなる。つまり対話的プロセスは、CPUの
     割り当てをたくさんとってしまうプロセスより優先される。アーキテク
     チャ固有の定数PROC_CHANGE_PENALTYは「CPU との密接度」を実装しようと
     したもの(つまり、同じ CPU のプロセスが優先される)で、これはカレント
     のactive_mmをさしているプロセスや、(ユーザ)アドレス空間を持たない(
     カーネルスレッドのような)プロセスに若干の優先度を与える。

  13.
     もし、グッドネスの現在の値が 0 のときは、プロセスのリストの全
     て(runqueueにあるものだけではなく)が検査され、その動的な優先度が単
     純なアルゴリズムにより再計算される。

  ______________________________________________________________________

  recalculate:
          {
                  struct task_struct *p;
                  spin_unlock_irq(&runqueue_lock);
                  read_lock(&tasklist_lock);
                  for_each_task(p)
                          p->counter = (p->counter >> 1) + p->priority;
                  read_unlock(&tasklist_lock);
                  spin_lock_irq(&runqueue_lock);
          }
  ______________________________________________________________________

  再計算を行う前にrunqueue_lockを解除することに注目しよう。全てのプロセ
  スの配列を処理するため、時間を必要とするからだ。CPU が再計算をさせられ
  ているその間に、schedule()が他の CPU において呼ばれ、その CPU にとって
  十分にグッドネスが良いプロセスを選ぶことができる。そう、これは明らかに
  ちょっと矛盾している。私たちが(この CPU 上で)もっとも良いグッドネスを
  持つプロセスを選んでいる間に、他の CPU で実行されている schedudle()
  が、動的優先度を再計算することができているからだ。

  14.
     この時点で、nextがスケジュールされるタスクを指しており、そのた
     めnext->has_cpuを 1 にnext->processorをthis_cpuに初期化していること
     が確実である。runqueue_lockはそうしたうえで解除できる。

  15.
     もし同じタスクに戻ってきたなら(next == prev)、単純にグローバルカー
     ネルロックを再取得し返る。つまり、全てのハードウエアレベル(レジス
     タ、スタックなど)と VM 関連(スイッチページディレクトリ、active_mmの
     再計算など)のものはスキップされる。

  16.
     マクロswitch_to()はアーキテクチャ特有である。i386では、これは a)FPU
     ハンドリング、b) LDT ハンドリング、c) セグメントレジスタのリロー
     ド、d)TSSのハンドリング、e) デバッグレジスタのリロードに関係があ
     る。

  2.4.  Linuxでのリンクリストの実装

  ウエイトキューの実装に行く前に、Linux標準のダブルリンクリストの実装に
  熟知しなければなりません。ウエイトキュー(Linuxの他のものと同様)は、こ
  の実装を良く使いますし、include/linux/list.h がもっとも関連しているこ
  とから、ジャーゴンで「list.h の実装」とも呼ばれます。

  ここでの基礎的なデータ構造体は、struct list_headです。

  ______________________________________________________________________
  struct list_head {
          struct list_head *next, *prev;
  };

  #define LIST_HEAD_INIT(name) { &(name), &(name) }

  #define LIST_HEAD(name) \
          struct list_head name = LIST_HEAD_INIT(name)

  #define INIT_LIST_HEAD(ptr) do { \
          (ptr)->next = (ptr); (ptr)->prev = (ptr); \
  } while (0)

  #define list_entry(ptr, type, member) \
          ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

  #define list_for_each(pos, head) \
          for (pos = (head)->next; pos != (head); pos = pos->next)
  ______________________________________________________________________

  最初の 3 つのマクロはnextとprevポインタを自身を指すようにして、空リス
  トを初期化するものです。例えば、LIST_HEAD_INIT() は、宣言時に構造体の
  要素を初期化するために使われ、2つ目はスタティック変数の初期化を行う宣
  言で、3つ目は関数の中で使われると言ったように、どれがどこで使われるか
  は、 C の構文の制約からも明らかです。

  マクロlist_entry()はそれぞれのリスト要素へのアクセスを提供します。たと
  えば、 (fs/file_table.c:fs_may_remount_ro()からの例で):

       ______________________________________________________________________
       struct super_block {
          ...
          struct list_head s_files;
          ...
       } *sb = &some_super_block;

       struct file {
          ...
          struct list_head f_list;
          ...
       } *file;

       struct list_head *p;

       for (p = sb->s_files.next; p != &sb->s_files; p = p->next) {
            struct file *file = list_entry(p, struct file, f_list);
            do something to 'file'
       }
       ______________________________________________________________________

  list_for_each()マクロの使い方の良い例が、ちょうど注目しているスケ
  ジューラのなかの、もっとも高いグッドネスを持つプロセスを探してrunqueue
  を見る部分にあります。

  ______________________________________________________________________
  static LIST_HEAD(runqueue_head);
  struct list_head *tmp;
  struct task_struct *p;

  list_for_each(tmp, &runqueue_head) {
      p = list_entry(tmp, struct task_struct, run_list);
      if (can_schedule(p)) {
          int weight = goodness(p, this_cpu, prev->active_mm);
          if (weight > c)
              c = weight, next = p;
      }
  }
  ______________________________________________________________________

  ここで、p->run_listは、task_struct構造体の中のstruct list_head
  run_listとして宣言されています。そして、リストのアンカーを提供します。
  要素をリストから削除し、あるいは追加(リストの先頭にせよ、末尾にせよ)す
  る場合には、list_del()/list_add()/list_add_tail()マクロによって実行し
  ます。以下の例は、runqueueへタスクを追加および削除する例です。

       ______________________________________________________________________
       static inline void del_from_runqueue(struct task_struct * p)
       {
               nr_running--;
               list_del(&p->run_list);
               p->run_list.next = NULL;
       }

       static inline void add_to_runqueue(struct task_struct * p)
       {
               list_add(&p->run_list, &runqueue_head);
               nr_running++;
       }

       static inline void move_last_runqueue(struct task_struct * p)
       {
               list_del(&p->run_list);
               list_add_tail(&p->run_list, &runqueue_head);
       }

       static inline void move_first_runqueue(struct task_struct * p)
       {
               list_del(&p->run_list);
               list_add(&p->run_list, &runqueue_head);
       }
       ______________________________________________________________________

  2.5.  ウエイトキュー

  プロセスがカーネルに依頼したことが現在不可能であるが、後に可能となる事
  であったなら、プロセスは休止状態へと入り、要求が満たされる条件が整いそ
  うなときに起こされます。この時使われるカーネルのメカニズムの一つが「ウ
  エイトキュー」です。

  Linux の実装では、TASK_EXCLUSIVEフラグを使う起動セマンティクスを許して
  います。ウエイトキューによって、well-knownキューを使って単純に
  sleep_on/sleep_on_timeout/interruptible_sleep_on/interruptible_sleep_on_timeout
  を使うこともできますし、自身のウエイトキューを定義し、それに自身を追
  加/削除するために add/remove_wait_queue を使用して、必要時に起きるた
  めwake_up/wake_up_interruptible を使用することもできます。

  最初のウエイトキューの使い方の例として
  は、(mm/page_alloc.c:__alloc_pages()の) ページアロケータ と
  (mm/vmscan.c:kswap()の) kswapd カーネルデーモンの間におけ
  る、mm/vmscan.c で定義されるウエイトキューkswapd_waitによる相互作用が
  あります。kswapdデーモンはこのキュー上で休止し、ページアロケータがペー
  ジを解放してやる必要がでるまで起きることはありません。

  自発的なウエイトキューの用例には、 read(2)システムコールでデータを要求
  するユーザプロセスと、データを提供するため、割り込みコンテキストで走行
  するカーネルの間の相互作用があります。割り込みハンドラは以下のように
  なっています (drivers/char/rtc_interrupt()を簡略化)。

       ______________________________________________________________________
       static DECLARE_WAIT_QUEUE_HEAD(rtc_wait);

       void rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
       {
               spin_lock(&rtc_lock);
               rtc_irq_data = CMOS_READ(RTC_INTR_FLAGS);
               spin_unlock(&rtc_lock);
               wake_up_interruptible(&rtc_wait);
       }
       ______________________________________________________________________

  ここで割り込みハンドラは、デバイスに特有のI/O ポートを読み取
  る(CMOS_READ() マクロは複数の outb/inbになります)ことでデータを取得
  し、rtc_wait ウエイトキューで休止中のものを起動します。

  ______________________________________________________________________
  ssize_t rtc_read(struct file file, char *buf, size_t count, loff_t *ppos)
  {
          DECLARE_WAITQUEUE(wait, current);
          unsigned long data;
          ssize_t retval;

          add_wait_queue(&rtc_wait, &wait);
          current->state = TASK_INTERRUPTIBLE;
          do {
                  spin_lock_irq(&rtc_lock);
                  data = rtc_irq_data;
                  rtc_irq_data = 0;
                  spin_unlock_irq(&rtc_lock);

                  if (data != 0)
                          break;

                  if (file->f_flags & O_NONBLOCK) {
                          retval = -EAGAIN;
                          goto out;
                  }
                  if (signal_pending(current)) {
                          retval = -ERESTARTSYS;
                          goto out;
                  }
                  schedule();
          } while(1);
          retval = put_user(data, (unsigned long *)buf);
          if (!retval)
                  retval = sizeof(unsigned long);

  out:
          current->state = TASK_RUNNING;
          remove_wait_queue(&rtc_wait, &wait);
          return retval;
  }
  ______________________________________________________________________

  rtc_read() で起っている事というのは、

  1. ウエイトキューの要素をカレントプロセスのコンテキストを指すよう宣言
     する。

  2. この要素を rtc_wait ウエイトキューに追加する。

  3. 現在のコンテキストに、次回休止したあとに再スケジュールされないこと
     を意味する TASK_INTERRUPTIBLEのマークづけをする。

  4. データが存在しないかどうかチェックする。もし存在するなら、処理を中
     断してデータをユーザバッファにコピーし、自身をTASK_RUNNINGとマーク
     して、自身をウエイトキューから削除して、返る。

  5. データがなければ、ユーザがノンブロッキング I/O を指定したかどうか
     チェックし、もしそうなら、EAGAIN(これは、EWOULDBLOCKと同じ)エラーで
     終了する。

  6. ここでまた、シグナルが保留されていないかをチェックする。もし保留さ
     れていれば、「より上のレイヤー」に、必要があればシステムコールを再
     スタートさせるよう知らせる。「必要があれば」とは、sigaction(2)シス
     テムコールで指定されるシグナルの性質の詳細を意図している。

  7. そして、「switch out」つまり、割り込みハンドラに起こされるまで休止
     状態になる。もし自身をTASK_INTERRUPTIBLEにマークしないと、スケ
     ジューラはデータが有効になるより早くスケジュールするため、不必要な
     処理の原因になる。

  また、ウエイトキューを用いることで、poll(2)システムコールの実装がむし
  ろ容易になっていることも指摘しておきましょう。

       ______________________________________________________________________
       static unsigned int rtc_poll(struct file *file, poll_table *wait)
       {
               unsigned long l;

               poll_wait(file, &rtc_wait, wait);

               spin_lock_irq(&rtc_lock);
               l = rtc_irq_data;
               spin_unlock_irq(&rtc_lock);

               if (l != 0)
                       return POLLIN | POLLRDNORM;
               return 0;
       }
       ______________________________________________________________________

  全ての仕事は、デバイス非依存関数のpoll_wait()によって行われます。これ
  は、必要なウエイトキューの操作を行います。我々がしなければならないこと
  は、デバイス特有の割り込みハンドラによって起こすべきウエイトキューを指
  示することだけです。

  2.6.  カーネルタイマ

  さて、次はカーネルタイマに注目しましょう。カーネルタイマとは、将来の特
  定の時刻に、特定の関数(「タイマーハンドラ」と呼ばれる)の実行を行わせる
  ためにあるものです。主なデータ構造体は、struct timer_listであ
  り、include/linux/timer.hで定義されています。

       ______________________________________________________________________
       struct timer_list {
               struct list_head list;
               unsigned long expires;
               unsigned long data;
               void (*function)(unsigned long);
               volatile int running;
       };
       ______________________________________________________________________

  listフィールドは内部のリストへとリンクするためのもので、timerlist_lock
  スピンロックにより保護されています。expiresフィールドは、functionハン
  ドラがdataをパラメータとして渡され起動されるときのjiffiesの値で
  す。runningフィールドは、タイマハンドラが現在他の CPU で動作しているか
  テストするために SMP において用いられます。

  関数add_timer()と、del_timer()は、与えられたタイマのリストへの追加なら
  びに削除を行います。タイマの時間が終了すると、自動的に削除されます。タ
  イマが使われる前に、必ずinit_timer()関数によって初期化しなければなりま
  せん。そして、追加前にfunctionとexpiresフィールドを必ず設定しなければ
  なりません。

  2.7.  ボトムハーフ

  割り込みハンドラが実行する仕事を、すぐに行う(例えば、割り込みへの反
  応、状態の更新などの)仕事と、割り込みが有効(データの後処理)になってか
  ら、後で処理すれば良いもの(データの後処理の実施や、データを待っている
  プロセスを起こすなど)との2つに分けるのが理にかなっていることが、時々あ
  ります。

  ボトムハーフは、カーネルタスクの実行を延期する最も古い機構で、Linux
  1.xのときからあります。Linux 2.0になって、次の章で取り扱う「タスク
  キュー」という新しい機構が導入されました。

  ボトムハーフはglobal_bh_lockスピンロックによってシリアライズされます。
  つまり一度にいずれかのCPUで実行できるのは、ただ一つのボトムハーフのみ
  であるということです。ハンドラを実行しようとするとき、global_bh_lockが
  有効でなければ、ボトムハーフは実行の印をつけられ(つまりスケジュールさ
  れ)ます。そして、global_bh_lockでビジーループすることを避けるため処理
  は継続されます。

  現在、全部で32のボトムハーフだけが登録されています。ボトムハーフを操作
  するために必要となる関数は、以下のようになります(すべてモジュール化さ
  れています)。

  o  void init_bh(int nr, void (*routine)(void)): routineで指示されるボ
     トムハーフハンドラをスロットnrへ導入する。スロット
     はinclude/linux/interrupt.hに、XXXX_BHの形で(例えば、TIMER_BH
     やTQUEUE_BHのように)列挙されていなければならない。通常、サブシステ
     ムの初期化ルーチン(モジュールの場合にはinit_module())は、この関数を
     使って必要なボトムハーフを導入する。

  o  void remove_bh(int nr): init_bf()の逆を実行する。つまり、スロットnr
     に導入されたボトムハーフを削除する。ここではエラーチェックは行われ
     ない。そのため、例えば、remove_bh(32)はシステムがpanicまたはoopsに
     なる。通常、サブシステムのクリーンアップルーチンは(モジュールの場
     合、cleanup_module())、他のサブシステムが後で再利用できるよう、この
     関数をスロットを解放するために使用する。(TODO: システムの全ての登録
     されたボトムハーフが、/proc/bottom_halvesにリストアップされたらどう
     だろう? これは、blobal_bh_lockが明示的に読み書きされなければならな
     いということを意味する)

  o  void mark_bh(int nr): スロットnrのボトムハーフに実行の印をつける。
     通常、割り込みハンドラはそのボトムハーフ(この名前がついた由来!)に
     「安全な時刻」に実行するよう印をつける。

  ボトムハーフは、グローバルロックされたタスクレットです。そこで「ボトム
  ハンドラはいつ実行されるのか?」という質問は、本当は「タスクレットはい
  つ実行されるのか?」というのが本当なのです。答えは、a) 各schedule()と
  b)entry.Sの各割り込み/システムコールのリターンパスの二カ所です。(TODO:
  そのため、schedule()の場合は本当に遅いのです。もう一つの非常に遅い遅い
  割り込みを追加したようなものです。なぜ、schedule()からhandle_softirqラ
  ベルを完全に取り除かなかったのか?)

  2.8.  タスクキュー

  タスクキューは古いボトムハーフのダイナミックな拡張として考えられます。
  実際のところ、ソースコードでは、「新しい」ボトムハーフとして参照されて
  いるところもあります。より具体的には、前章で取り上げた古いボトムハーフ
  には、次に示すような制限があります。

  1. 決められた数(32)しか存在できません。

  2. 各ボトムハーフは一つのハンドラ関数としか関連づけられません。

  3. ボトムハーフは、ブロック出来ないように、持っているスピンロックが消
     費されます。

  そこで、タスクキューにより任意の数の関数をつなぎ、後で次々と処理ができ
  るようにします。 DECLARE_TASK_QUEUE マクロを使って新しいタスクキューを
  作成すると、queue_task()関数を使って、そのキューへとタスクをためること
  ができます。そして、タスクキューはrun_task_queueによって処理されます。
  自身のタスクキューを作成する代りに(そして、手動で消費する代り
  に)、Linux にあらかじめ準備された、良く知られた個所で消費されるタスク
  キューを使うことができます。

  1. tq_timer: タイマのタスクキュー。各タイマ割り込み毎と tty デバイスを
     解放するとき(半オープン状態のターミナルデバイスをクローズないしは解
     放する時)に実行する。タイマハンドラが割り込みコンテキストで実行され
     たときは、tq_timerタスクも割り込みコンテキスト内で実行されるため、
     ブロックできない。

  2. tq_scheduler: スケジューラによって(tq_timer 同様 tty を閉じるとき
     も)消費されるスケジューラタスクキュー。スケジューラが再スケジュール
     されるプロセスのコンテキストで実行されるため、tq_scheduler タスクは
     やりたいことが何でもできる。つまり、ブロックしてプロセスのコンテキ
     ストのデータを使用する(やりたいかどうかは別として)などである。

  3. tq_immediate: これは本当はボトムハーフIMMEDIATE_BHであり、そのため
     ドライバはqueue_task(task, &tq_immediate) と、そして割り込みコンテ
     キストで消費されるmark_bh(IMMEDIATE_BH)を行う。

  4. tq_disk: 実際の要求を開始するため、低レベルブロックデバイスのアクセ
     ス(とRAID)により使われる。このタスクキューはモジュールにエキスポー
     トされるが、設計上の特定の目的を除いて使われない。

  ドライバが自身のタスクキューを使わないかぎり、以下の場合を除いて、
  キューを処理するためにrun_tasks_queue()を呼ぶ必要はありません。

  タスクキューtq_timer/tq_scheduler を消費するのが通常の場所だけでなく外
  の場所(一例としては、ttyデバイスのクローズ)でも行われる理由は、ドライ
  バがキューにタスクをスケジュールしたとき、そのタスクが意味を持つのはデ
  バイスの特定のインスタンスが有効であるとき(通常はアプリケーションがク
  ローズするまで)なのを考えると自ずと明らかでしょう。そのため、ドライバ
  はrun_task_queueを呼んで、自身や(他のドライバが)キューにいれたタスクを
  フラッシュする必要があります。これは、後で実行することができるようにす
  ることが無意味だからです。つまり、関連するデータ構造は他のインスタンス
  により解放されたり再利用されるということです。これが、それぞれタイマ割
  り込みやschedule()の代りにtq_timerとtq_schedulerにrun_task_queue()があ
  る理由になっています。

  2.9.  タスクレット

  作成中。将来のリビジョンで記述。

  2.10.  ソフト割り込み

  作成中。将来のリビジョンで記述。

  2.11.  システムコールがi386ではどのように実装されているか?

  o  lcall7/lcall27 コールゲート

  o  int 0x80 ソフトウエア割り込み

  他のUNIX系OS(Solaris, Unixware 7 など)のバイナリではlcall7メカニズムを
  使っていますが、ネイティブな Linux プログラムは int 0x80 を使っていま
  す。'lcall7'の名前は、歴史的な過ちです。これは、lcall27(例えば
  Solaris/x86)も使用しているが、ハンドラ関数はlcall7_funcと呼ばれている
  からです。

  システムがブートするとき、IDTを設定する関数
  arch/i386/kernel/traps.c:trap_init() が呼ばれ、(type 15, dpl 3 の)ベク
  タ 0x80 を、arch/i386/kernel/entry.Sのシステムコールエントリアドレスを
  示すように設定します。

  ユーザ空間のアプリケーションがシステムコールを行う時は、引数をレジスタ
  に入れて、'int 0x80' 命令を実行します。これは、カーネルモードにトラッ
  プされ、プロセッサはentry.Sのシステムコールエントリポイントへとジャン
  プします。これは、次のようなことを行います。

  1. レジスタを保存する。

  2. %ds と %es を KERNEL_DS に設定する。そのため、参照する全てのデータ(
     と外部のセグメント)はカーネルアドレス空間になる。

  3. もし %eax の値がNR_syscalls(現在 256) より大きかった場合に
     は、ENOSYS エラーで失敗する。

  4. もしタスクがptraced されていた(tsk->ptrace & PF_TRADESYS )時は、特
     別な処理を行う。これは strace (SVR4でいうtruss(1))のようなプログラ
     ムやデバッガをサポートするためである。

  5. sys_call_table+4*(%eax の syscall_number)を呼ぶ。このテーブルは同じ
     ファイル(arch/i386/kernel/entry.S)で初期化されており、各々のシステ
     ムコールハンドラを指している。Linux においては、ハンドラには通常、
     例えば sys_openやsys_exitのように sys_というプレフィックスがついて
     いる。これらの C システムコールハンドラはSAVE_ALLが格納したスタック
     から引数を見つけ出す。

  6. ``system call return path''に入る。int 0x80 だけでなく、lcall7,
     lcall27でも使うため、別のラベルになっている。これは、(ボトムハーフ
     の)タスクレットの取り扱いや、schedule()が必要かどう
     か(tsk->need_resched != 0) を確認したり、シグナルが保留されていない
     か確認したり、そしてそれらのシグナルを処理したりすることに関連して
     いる。

  Linux は システムコールに 6 つまでの引数をサポートしています。これらは
  %ebx, %ecx, %edx, %esi, %edi (および一時的に使われる
  %edp。asm-i386/unistd.hの_syscall6()を参照)に入れて渡されます。システ
  ムコール番号は %eax で渡されます。

  2.12.  アトミックな操作

  2つのタイプのアトミックな操作があります。ビットマップとatomic_t です。
  ビットマップは、ある大きな集合から「割り当て」や「解放」の単位で、各単
  位がある番号で指定される、例えばフリー inodeやフリーブロックのようなコ
  ンセプトを維持するのにとても便利です。これらは単純なロックに広く使われ
  ています。例えばデバイスのオープンを排他的に行えるようにするために使わ
  れます。この例は、arch/i386/kernel/microcode.cに見ることができます。

       ______________________________________________________________________
       /*
        *  Bits in microcode_status. (31 bits of room for future expansion)
        */
       #define MICROCODE_IS_OPEN       0       /* set if device is in use */

       static unsigned long microcode_status;
       ______________________________________________________________________

  BSSを Linux で明示的にゼロクリーンするように microcode_status を0に初
  期化する必要はありません。

       ______________________________________________________________________
       /*
        * We enforce only one user at a time here with open/close.
        */
       static int microcode_open(struct inode *inode, struct file *file)
       {
               if (!capable(CAP_SYS_RAWIO))
                       return -EPERM;

               /* one at a time, please */
               if (test_and_set_bit(MICROCODE_IS_OPEN, µcode_status))
                       return -EBUSY;

               MOD_INC_USE_COUNT;
               return 0;
       }
       ______________________________________________________________________

  ビットマップの操作は、

  o  void set_bit(int nr, volatile void *addr): addrで示されるビットマッ
     プのnrのビットをセットする。

  o  void clear_bit(int nr, volatile void *addr): addrで示されるビット
     マップのnrビットをクリアする。

  o  void change_bit(int nr, volati.e void *addr): addrで示されるビット
     マップのnrビットを反転(クリアされていればセット、セットされていれば
     クリア)する。

  o  int test_and_set_bit(int nr, volatile void *addr): アトミックにnr
     ビットをセットし、古いビットの値を返す。

  o  int test_and_clear_bit(int nr, volatile void *addr): アトミックにnr
     ビットをクリアし、古いビットの値を返す。

  o  int test_and_change_bit(int nr, volatile void *addr): アトミック
     にnrビットを反転し、古いビットの値を返す。

  これらの操作は、LOCK_PREFIX マクロを使っています。このマクロは SMP
  カーネルにおいてバスのロック操作を行う接頭辞として評価されますが、UP
  カーネルでは何もしません。これによって、SMP 環境でのアトミックなアクセ
  スを保証しています。

  ときにはビット操作が不便な場面もありますが、その場合は算術演算を使う必
  要があります。加算、減算、インクリメント、デクリメントなどです。(inode
  のような)参照カウンタが典型的な例です。この機能はatmic_tデータタイプと
  以下の操作で実現されています

  o  atomic_read(&v): atomic_t変数tの値を読み込む。

  o  atomic_set(&v, i): atomic_t 変数 v を整数値 i に設定する。

  o  void atomic_add(int i, volatile atomic_t *v): 整数値i をv で示され
     るアトミック変数の値に加算する。

  o  void atomic_sub(int i, volatile atomic_t *v): 整数値i をv で示され
     るアトミック変数の値から減算する。

  o  int atomic_sub_and_test(int i, volatile atomic_t *v): 整数値i をv
     で示されるアトミック変数の値から減算し、その結果が0のときは 1 を返
     し、それ以外では 0 を返す。

  o  void atomic_inc(volatile atomic_t *v): 値を 1 インクリメントする。

  o  void atomic_dec(volatile atomic_t *v): 値を 1 デクリメントする。

  o  int atomic_dec_and_test(volatile atomic_t *v): 値をデクリメントす
     る。そして、結果が0なら1を返し、それ以外なら0を返す。

  o  int atomic_inc_and_test(volatile atomic_t *v): 値をインクリメントす
     る。そして、結果が0なら1を返し、それ以外なら0を返す。

  o  int atomic_add_negative(int i, volatile atomic_t *v): i をvの値に加
     算する。そして、結果が負数ならば1を返し、結果が0以上であれば0を返
     す。この操作はセマフォの実装に用いられている。

  2.13.  スピンロック、読み書きスピンロック、ビッグリーダースピンロック

  初期の Linux (前世紀 90 年代の初頭)から、開発者は、異なる型のコンテキ
  ストの間や、複数の CPU における同じ型のコンテキストであるが違ったイン
  スタンス間での共有データのアクセスに関する古典的問題に直面していまし
  た。

  1995 年 11 月 15 日に、SMP サポート(オリジナルのパッチは 同年 10 月の
  1.3.37 に対するものでした)が Linux 1.3.42 で追加されました。

  コードのクリティカル領域がプロセスコンテキストと割り込みコンテキストの
  両方で実行されることも考えられるので、UPでcli/sti命令を使って保護する
  方法は、

  ______________________________________________________________________
  unsigned long flags;

  save_flags(flags);
  cli();
  /* critical code */
  restore_flags(flags);
  ______________________________________________________________________

  のようになります。

  これが UP でOKだとしても、 SMP では明らかに意味がありません。同じコー
  ドシーケンスが他の CPU で同時に実行されるかも知れないからです。そし
  て、cli()は個々の CPU 上での割り込みコンテキストの競合からの保護しか提
  供しないため、異なった CPU で実行されるコンテキスト間の競合からの保護
  には全くならないのです。これでスピンロックが有用になります。

  3 つのタイプのスピンロックがあります。バニラ(基本)、読み書き、ビッグ
  リーダー スピンロックです。読み書きスピンロックは「読む者が多くて書く
  者が少ない」よくある傾向の場合に用いられます。この例としては、登録され
  たファイルシステムのリスト(fs/super.c参照)へのアクセスがあります。リス
  トは、file_systems_lock読み書きスピンロックにより保護され、ファイルシ
  ステムの登録/解除の際にだけ排他的なアクセスを行います。しかし、全ての
  プロセスは/proc/filesystemsを読めますし、sysfs(2)システムコールを使っ
  て、ファイルシステムの読み取り専用アクセスを行うことができるのです。こ
  のことが、読み書きスピンロックを使う際に気をつかう点になっています。読
  み書きスピンロックによって、同時に複数の読み取りができるか、単一の書き
  込みがあって、他の読み取りができない状況かになります。とはいうものの、
  書き込み者がロックを取得しようとしている間、新しい読み取り者がロックを
  取得できないなら、つまり複数の読み取り者による書き込み者の飢餓問題につ
  いて、Linux が正しく処理しているなら、これは良いことだといえます。書き
  取り者がロックを取得しようとしている間は、読み取り者がブロックされなけ
  ればならないという事を意味しています。

  これは現在のところ問題ではなく、修正しなければならないことかどうかも明
  らかではありません。逆にいうと、読み取り者は常に非常に短い時間だけの
  ロックを取得していれば、書き込み者が比較的長い時間間隔でロックを取得し
  ている間に本当に枯渇してしまうことがあるのか? ということです。

  ビッグリーダスピンロックは、読み書きスピンロックに、非常に軽い読込みア
  クセスへ強く最適化をかけたもので、書き込みアクセスにはペナルティを課し
  ています。ビッグリーダスピンロックは限られた範囲、現在は2個所だけで使
  われています。一つは sparc64 (global irq) でのみ使われ、もう一つはネッ
  トワークスタックです。アクセスパターンがこの2つの型のどちらにも当ては
  まらない場合では、基本スピンロックを使っています。どの種類のスピンロッ
  クを保持している場合でもブロックすることはできません。

  プレーン、_irq()、_bh()と、スピンロックには3種類あります。

  1. プレーンspin_lock()/spin_unlock(): 割り込みが常に不可にされるか、割
     り込みコンテキストと競合しないことが分かっている(例えば割り込みハン
     ドラ内部で)なら、これを使うことができる。現在の CPU の割り込み状態
     には触れることがない。

  2. spin_lock_irq()/spin_unlock_irq(): もし割り込みが常に有効ならこの
     バージョンのスピンロックを使うことができる。これは単純に現在の CPU
     の割り込みを(ロック時に)無効にし、(アンロック時に)再度有効にする。
     たとえば、rtc_interrupt()がspin_lock(&rtc_lock)(割り込みは割り込み
     ハンドラの中では常に無効)を使うため、rtc_read()
     はspin_lock_irq(&rtc_lock)(read中割り込みは常に有効)を使ってい
     る。rtc_read()がspin_lock_irq()を使い、より汎用的
     なspin_lock_irqsave()を使わないのは、全てのシステムコール割り込みが
     常に有効になっているからである。

  3. spin_lock_irqsave()/spin_unlock_irqrestore(): 割り込み状況が分から
     ないときに使う、最強の形式。割り込みが問題になる場合だけでなく、割
     り込みが全く不明な場合にも使われる。つまり、割り込みハンドラがクリ
     ティカルコードを実行しない場合には、これを使うポイントはないという
     ことだ。

  もし割り込みハンドラが競合するならば、素のspin_lock()を使えません。こ
  れを使ったときに、割り込みが同じCPU上で発生すると、永遠にロックを待ち
  つづけるためです。割り込まれているロックを取得したハンドラは、割り込み
  ハンドラから処理が返ってくるまで、処理を継続できないためです。

  ユーザプロセスのコンテキストと割り込みハンドラとで共有するデータ構造体
  にアクセスするときには、スピンロックがもっとも良く使われています。

       ______________________________________________________________________
       spinlock_t my_lock = SPIN_LOCK_UNLOCKED;

       my_ioctl()
       {
               spin_lock_irq(&my_lock);
               /* critical section */
               spin_unlock_irq(&my_lock);
       }

       my_irq_handler()
       {
               spin_lock(&lock);
               /* critical section */
               spin_unlock(&lock);
       }
       ______________________________________________________________________

  この例については、いくつか注記があります。

  1. 典型的なドライバがとる方法の(属性値と返り値が明確に省略されてい
     る)ioctl() によるプロセスのコンテキストでは、必ずspin_lock_irq()を
     使わなければならない。これは、デバイスのioctl()呼び出しを実行してい
     る間は、割り込みが常に有効になっているためである。

  2. (属性値は明確に省略されている)my_irq_handler()によって表される割り
     込みコンテキストでは、plain spin_lock() form を使うことができる。こ
     れは、割り込みハンドラ内部では、割り込みは無効にされているためであ
     る。

  2.14.  セマフォと読み書きセマフォ

  時折、共有データ構造体にアクセスするときに、操作の実行をブロックしなけ
  ればならないことがあります。たとえばデータをユーザ空間へコピーする場合
  などです。Linux におけるこのような場合に使用できるロックの基本機構とし
  てセマフォと呼ばれるものがあります。セマフォには2つのタイプがあり、基
  本セマフォと読み書きセマフォと呼んでいます。セマフォの初期値により、交
  互実行(初期値1)にも、より複雑なアクセスのタイプを提供するためにも使う
  ことができます。

  読み書きセマフォと基本のセマフォとの違いは、読み書きスピンロックと基本
  のスピンロックの相違と同様のものになっています。一方が同時に複数の読み
  手と一つの書き手を許すのに対して、もう一方は書き込みのある間は読み出し
  がロックされるものになります。つまり、書き手が全ての読み手をブロック
  し、書き手が待ち状態にある間、新しい読み手はブロックします。

  同様に、素のdown()/up()の代りにdown/up_interruptible()を使うこと
  と、down_interruptible()からの返り値をチェックすることで、基本セマフォ
  は割り込みが可能になります。もし操作が割り込まれたならば、返り値が0以
  外になります。

  クリティカルコードが他のサブシステムやモジュールから登録される未知の関
  数から呼び出される状況では、典型的には交互実行にセマフォを使うことにな
  ります。つまり、呼び出し側が先験的に関数がブロックされるかどうか知るこ
  とができない場合です。

  セマフォの簡単な例は、gethostname(2)/sethostname(2)システムコールを実
  装したkernel/sys.cにあります。

       ______________________________________________________________________
       asmlinkage long sys_sethostname(char *name, int len)
       {
               int errno;

               if (!capable(CAP_SYS_ADMIN))
                       return -EPERM;
               if (len < 0 || len > __NEW_UTS_LEN)
                       return -EINVAL;
               down_write(&uts_sem);
               errno = -EFAULT;
               if (!copy_from_user(system_utsname.nodename, name, len)) {
                       system_utsname.nodename[len] = 0;
                       errno = 0;
               }
               up_write(&uts_sem);
               return errno;
       }

       asmlinkage long sys_gethostname(char *name, int len)
       {
               int i, errno;

               if (len < 0)
                       return -EINVAL;
               down_read(&uts_sem);
               i = 1 + strlen(system_utsname.nodename);
               if (i > len)
                       i = len;
               errno = 0;
               if (copy_to_user(name, system_utsname.nodename, i))
                       errno = -EFAULT;
               up_read(&uts_sem);
               return errno;
       }
       ______________________________________________________________________

  この例のポイントは以下の通りです。

  1. 関数は、copy_from_user()/copy_to_user()で、データをユーザスペースか
     ら、もしくはユーザスペースへコピーするときにブロックされることがあ
     る。すなわち、ここではいかなるタイプのスピンロックもつかうことはで
     きない。

  2. ベーシックではなく読み書きのセマフォが選ばれているのは
     gethostname(2)要求が同時にたくさん発生し得るが、それらは交互に実行
     する必要はないためである。

  Linux のセマフォならびに読み書きセマフォの実装は非常に精巧にできていま
  すが、まだ実装されていない考え得る可能なシナリオも存在しています。例え
  ば、割り込み可能な読み書きセマフォのコンセプトなどです。このようなエキ
  ゾチックな種類のプリミティブが必要になる状況は、実世界でない状況なの
  で、このようなコンセプトは明らかにありえません。

  2.15.  モジュール読み込みのカーネルサポート

  マイクロカーネルデザインに基づくオペレーティングシステムの提供する"ア
  ドバンテージ"といった最近の宣伝にも関わらず、Linux はモノリシックなオ
  ペレーティングシステムです。実際、(Linus Torvalds 彼自身の言葉による
  と)

       ... message passing as the fundamental operation of the OS is just an
       exercise in computer science masturbation. It may feel good, but you
       don't actually get anything DONE.

  だから、Linux は ずっとモノリシックデザインをベースとしており、すべて
  のサブシステムは同じ特権モードで動き、同じアドレス空間を共有しているの
  です。カーネル内の通信は通常の C 関数呼び出しで行われます。

  しかし、たとえマイクロカーネルが行うようにカーネルの機能をそれぞれの
  「プロセス」へと分けることは明らかに悪い考えですが、オンデマンドで動的
  に読み込めるカーネルモジュールに分けることは、ある条件下(例えば、マシ
  ンのメモリが少ない場合や、互いに排他的な複数の ISA 自動判別デバイスド
  ライバを持つカーネルをインストールした場合)では、いい考えです。ローダ
  ブルモジュールのサポートを組み込むかどうかの判断は、コンパイル時に行わ
  れ、CONFIG_MODULES オプションによって指定されます。 request_module()
  機構によるモジュールの自動組み込みのサポートは、別のコンパイルオプショ
  ン (CONFIG_KMOD) になっています。以下の機能は Linux のロード可能モ
  ジュールとして実装されています。

  1. キャラクタならびにブロックデバイスドライバ。misc デバイスドライバを
     含む。

  2. ターミナルライン関係。

  3. /procと devfs における仮想(一般の)ファイル (例え
     ば/dev/cpu/microcodeやdev/misc/micrococe)。

  4. バイナリファイルフォーマット (例えば ELF, aoutなど)。

  5. 実行ドメイン (例えば, Linux, UnixWare7, Solarisなど)。

  6. ファイルシステム。

  7. System V IPC。

  一部には Linux におけるモジュールとして実装できていないものもありま
  す。(おそらくそれらはモジュールについて考慮されていないためと思われま
  す)

  1. スケジューリングアルゴリズム

  2. VMポリシー

  3. バッファキャッシュ、ページキャッシュやその他のキャッシュ

  Linux にはロード可能モジュールを援助するいろいろなシステムコールがあり
  ます。

  1. caddr_t create_module(cost char *name, size_t size): vmalloc()を用
     いてsizeバイトを割り当て、モジュールの構造体をその先頭に割り付け
     る。新しいモジュールは module_list が先頭にあるリストへとリンクされ
     る。CAP_SYS_MODULEのプロセスのみが、このシステムコールを呼び出すこ
     とができ、他のプロセスはEPERMエラーが返る。

  2. long init_module(const char *name, struct module *image): 再配置さ
     れたモジュールイメージを読み込み、モジュールの初期化ルーチンが起動
     される。CAP_SYS_MODULEが設定されたプロセスのみがこのシステムコール
     を呼び出すことができ、それ以外の場合はEPERMエラーが返る。

  3. long delete_module(const char *name): モジュールをアンロードしよう
     とする。name == NULLならば、全ての使用されていないモジュールを取り
     外そうとする。

  4. long query_module(const char *name, int which, void *buf, size_t
     bufsize, size_t *ret): モジュールの(ないしは全てのモジュールの)情報
     を返す。

  ユーザが使えるコマンドインターフェースは次の通りです。

  o  insmod: 一つのモジュールを挿入する。

  o  modprobe: モジュールをその依存する全てのモジュールとともに挿入す
     る。

  o  rmmod: モジュールを取り外します。

  o  modinfo : モジュールについての情報を表示する。例えば、作者、内容、
     モジュールの受け取れるパラメータ など。

  insmodやmodprobeを使ってモジュールを手動で読み込むことができるのは当然
  ですが、特定の機能が必要になったときに、カーネルが自動的にモジュールを
  読む機能もあります。この機能のカーネルインターフェースは
  request_module(name)という関数で、モジュールへエキスポートされていま
  す。そして、モジュールは他のモジュールを同様に読み込むことができるので
  す。 request_module(name)は内部的にカーネルスレッドを作成し、標準
  のexec_usermodehelper()カーネルインターフェース(これもまたモジュールに
  エキスポートされています)を使って、ユーザ空間のコマンドのmodprobe -s
  -k module_nameを実行させます。関数は成功すれば0を返します
  が、request_module()の返り値をチェックすることに通常は意味はありませ
  ん。そのかわり、プログラミングでは、

       ______________________________________________________________________
       if (check_some_feature() == NULL)
           request_module(module);
       if (check_some_feature() == NULL)
           return -ENODEV;
       ______________________________________________________________________

  のようにするのが通例です。

  例えばこれは、fs/block_dev.c:get_blkfops()により、メジャー番号がNであ
  るようなブロックデバイスをオープンしようとしたときに、モジュー
  ルblock-major-Nを読み込むために実行されます。明らかに、block-major-Nと
  いったモジュールは存在していません(Linux 開発者は、モジュールにはそれ
  なりの名前を選ぶものです)。でも、/etc/modules.confファイルにより、ある
  モジュール名に割り当ててあるのです。しかし、たいていの良く知られたメ
  ジャー番号(とそのモジュール)については、modprobe/insmodコマンド
  は、/etc/modules.confに明示的なalias 文がなくとも、実際のモジュールが
  なにかを分かっています。

  モジュールの読み込の良い例が、mount(2)システムコールの内部にあります。
  mount(2)システムコールはファイルシステムタイプを文字列として受け取
  り、fs/super.c:do_mount()はfs/super.c:get_fs_type()へそれを渡します。

       ______________________________________________________________________
       static struct file_system_type *get_fs_type(const char *name)
       {
               struct file_system_type *fs;

               read_lock(&file_systems_lock);
               fs = *(find_filesystem(name));
               if (fs && !try_inc_mod_count(fs->owner))
                       fs = NULL;
               read_unlock(&file_systems_lock);
               if (!fs && (request_module(name) == 0)) {
                       read_lock(&file_systems_lock);
                       fs = *(find_filesystem(name));
                       if (fs && !try_inc_mod_count(fs->owner))
                               fs = NULL;
                       read_unlock(&file_systems_lock);
               }
               return fs;
       }
       ______________________________________________________________________

  この関数では、いくつか注意する点があります。

  1. 最初に、与えられた名前のファイルシステムをすでに登録されているうち
     から見つけようとする。(登録されているファイルシステムのリストを更新
     しないため)読み込みのために取得する file_system_lock の保護のもと、
     これは実行される。

  2. もしファイルシステムが見つかったら、新しいリファレンスを取得しよう
     とする。そして、モジュールのホールドカウントを 1 増やそうとする。こ
     れは静的にリンクされたファイルシステムでは常に 1 を返す。モジュール
     としてロードしたファイルシステムでも、現在削除されているところでな
     い限り 1 を返す。もし try_inc_mod_count() が 0 を返す場合は失敗した
     と考える。つまり、モジュールはあるが削除されているところで、それは
     元々なかったのと同じことである。

  3. (request_module())次は、ブロックする操作を行いたいた
     め、file_system_lockを落とす。したがってこの時にはスピンロックを保
     持できない。実際のところ、この特別な場合には、たと
     えrequest_module()がノンブロックであることが保証されていて、モ
     ジュール読み込みが同じコンテキストでアトミックに実行されたとして
     も、file_system_lockを落とさなければならない。これは、モジュールの
     初期化ルーチンでは、書き込みのために同じfile_systems_lockを呼び出
     すregister_filesystem()を呼び出すことがあるからである。

  4. もしロードが成功したら、file_systems_lockスピンロックを取得し、新し
     く登録されたファイルシステムをリストへ加えようとする。これは少し悪
     い考えである。というのは、原理的にはmodprobe コマンドのバグになり得
     るからだ。 request_module()が失敗したが、新しいファイルシステムが登
     録されてしまって、しかもget_fs_type()が見つからない場合に、要求され
     たモジュールのロードが成功した後にコアダンプしてしまうことがあるた
     めだ。

  5. もしファイルシステムが見つかり、そのリファレンスを取得できたら復帰
     する。そうでなければ、NULL を返す。

  モジュールがカーネルにロードされているとき、カーネルの EXPORT_SYMBOL()
  マクロを使っている部分や現在ロードされている他のモジュールから、パブ
  リックにエキスポートされたシンボルを参照することができます。。もしモ
  ジュールが他のモジュールからのシンボルを使うときは、ブート時のdepmod
  -aコマンドの実行により行われる依存関係の再計算の間は(例えば新しいカー
  ネルをインストールした後など)、そのモジュールは保留されることになりま
  す。

  通常、使用するカーネルインターフェースのバージョンと、モジュール一式の
  バージョンは一致しなければなりません。これは Linux では、特別なカーネ
  ルインターフェースのバージョン機構を一般には持っていないため、単純な
  「カーネルバージョン」を意味しています。しかし、「モジュールバージョ
  ン」ないしはCONFIG_MODVERSIONS と呼ばれる限定された機能をもっていま
  す。これによって、新しいカーネルに移行したときにモジュールの再コンパイ
  ルを避けることができます。カーネルのシンボルテーブルを、内部のアクセス
  とモジュールのアクセスを別々に扱うことで、これを実現しています。シンボ
  ルテーブルのパブリック(つまりエキスポートされた)要素は、Cの修飾子によ
  り32ビットチェックサムがつけられます。そして、ロード中にモジュールが使
  うシンボルを解決するために、ローダはチェックサムを含めてシンボルの表記
  名を比較します。もし一致しなければロードを拒否するようになっています。
  これは、カーネルとモジュールを、モジュールバージョンを有効にしてコンパ
  イルしたときのみ起ります。もし、どちらかがオリジナルのシンボル名を使っ
  ていた場合、ローダは単にモジュールによってカーネルバージョンの修飾のつ
  いたものと、カーネルによりエキスポートされているものを比較して、違って
  いればロードを拒否します。

  3.  仮想ファイルシステム (VFS)

  3.1.  Inode キャッシュとDcache との相互作用

  複数のファイルシステムをサポートするため、 Linux は VFS (Virtual
  Filesystem Switch) という特別なカーネルインターフェースレベルを持って
  います。これは SVR4 から派生した OS に見られる vnode/vfs インター
  フェースに良く似ています (もともとは、BSD と Sun に由来の実装からきて
  います)。

  Linux の inode キャッシュは、一つの 977 行からなるファイル fs/inode.c
  に実装されています。特筆すべき事に、このファイルは、この 5-7 年の間ほ
  とんど変更がありませんでした。一番最後に更新されたのは、そう 1.3.42 の
  時なのです。

  Linux の inode キャッシュの構造は以下の通りです。

  1. 各 inode がスーパーブロックポインタと 32bit の inode 番号の値によっ
     てハッシュされるグローバルハッシュテーブル inode_hashtable。 スー
     パーブロックの無い inode (inode->i_sb == NULL) は、代り
     にanon_hash_chainで始まるダブルリンクリストへとつながれる。匿名
     inodeの例として、fs/inode.c:get_empty_inode()を呼ぶこと
     で、net/socket.c:sock_alloc()によって作られるソケットがある。

  2. i_count>0 かつ i_nlink>0 となる inode からなるグローバルタイプの
     in_use_list(inode_in_use)。inode は、get_empty_inode() と
     get_new_inode() により新たに割り当てられ、inode_in_use リストへつな
     がれる。

  3. i_count = 0であるような グローバルタイプの未使用リスト
     (inode_unused)。

  4. i_count>0、i_nlink>0そしてi_state & I_DIRTY であるような inode によ
     るスーパーブロック毎の汚れたリスト(sb->s_dirty)。inodeが dirtyの印
     がつけられたら、それはやはりハッシュになっているsb->s_dirtyリストに
     追加される。スーパーブロック毎に inode の dirty リストを管理するこ
     とで、inode の同期をすばやく行うことができるようになる。

  5. 厳密な意味の inode キャッシュ -- inode_cachepを呼び出す SLAB キャッ
     シュ。inode オブジェクトが割り当てられたり、解放されたりするとき
     は、この SLAB キャッシュから取得されたり、返されたりする。

  それぞれのタイプリストは、inode->i_listから指し示され、ハッシュテーブ
  ルは、inode->i_hashから結び付けられています。各 inode は それぞれハッ
  シュテーブルと1種類のみのタイプリスト(in_use, unused, または dirty)に
  入ります。

  このすべてのリストは一つのスピンロックinode_lockにより保護されていま
  す。

  inode キャッシュサブシステムは、inode_init()関数
  が、init/main.c:start_kernel()から呼び出された時に初期化されます。この
  関数は、__initとマークされており、これはその後、このコードが解放されて
  しまうことを意味しています。この関数は一つの引数 -- システムの物理ペー
  ジ数を渡します。このことから、inode キャッシュは利用可能なメモリ量に依
  存して決められていることがわかります。つまり、もし十分な量のメモリがあ
  れば、より大きなハッシュテーブルを作成するということです。

  inode キャッシュに関するの唯一の統計的情報は、利用されていないinode数
  です。これは、inodes_stat.nr_unusedに格納され、ファイ
  ル/proc/sys/fs/inode-nrやproc/sys/fs/inode-stateを通してユーザプログラ
  ムから見ることができます。

  そのため、生きたカーネルで実行する gdb からリストを取出すことができま
  す。

       ______________________________________________________________________
       (gdb) printf "%d\n", (unsigned long)(&((struct inode *)0)->i_list)
       8
       (gdb) p inode_unused
       $34 = 0xdfa992a8
       (gdb) p (struct list_head)inode_unused
       $35 = {next = 0xdfa992a8, prev = 0xdfcdd5a8}
       (gdb) p ((struct list_head)inode_unused).prev
       $36 = (struct list_head *) 0xdfcdd5a8
       (gdb) p (((struct list_head)inode_unused).prev)->prev
       $37 = (struct list_head *) 0xdfb5a2e8
       (gdb) set $i = (struct inode *)0xdfb5a2e0
       (gdb) p $i->i_ino
       $38 = 0x3bec7
       (gdb) p $i->i_count
       $39 = {counter = 0x0}
       ______________________________________________________________________

  ここで、include/linux/list.h の list_entry()マクロの定義にしたがっ
  てstruct inodeのアドレス (0xdfb5a3e0) を得るために、アドレス
  0xdfb5a2e8 から8を引いていることに注意しましょう。

  inode キャッシュがどのように働くか理解するために、ext2 ファイルシステ
  ムの通常のファイルで、オープン、クローズされるときの inode の一生を見
  ていきましょう。

       ______________________________________________________________________
       fd = open("file", O_RDONLY);
       close(fd);
       ______________________________________________________________________

  open(2) システムコールは fs/open.c:sys_open() 関数に定義されていて、実
  体は fs/open.c:file_open() 関数が行っています。この関数は2つの部分に分
  けられ、

  1. open_namei():  dentryとvfsmount構造体からなるnameiデータ構造体を埋
     める。

  2. dentry_open(): dentryやvfsmountを与える。この関数は、新しいstruct
     fileを割り当て、相互にリンクする。また、inode が(dentry->d_inodeか
     ら inode を与える) open_namei() で読み込まれる時 inode->i_fop に
     セットされているファイルシステムに特有の f_op->open() メソッドを呼
     び出す。

  open_namei() 関数は、ファイルシステム毎のinode_operations->lookup() メ
  ソッドを起動する real_lookup()をさらに呼び出す path_walk() によって、
  dentry キャッシュと相互作用を起こします。このメソッドの役割は、親ディ
  レクトリで名前の一致するエントリを探すことで、その inode を取得するた
  め iget(sb, ino) を呼び出しています。この関数は inode キャッシュへアク
  セスします。 inode が読み込まれたら、 d_add(dentry, inode) により
  dentry のインスタンスが作成されます。ここで注意するのは、上記の間で、
  オンディスク inode 番号というコンセプトを持つUNIX スタイルのファイルシ
  ステムのために、そのエンディアンを現在の CPU フォーマットにマップする
  のがルックアップメソッドの仕事だということです。たとえば、ファイルシス
  テムに特有な raw ディレクトリエントリの inode 番号がリトルエンディアン
  の32ビットフォーマットであれば、

       ______________________________________________________________________
       unsigned long ino = le32_to_cpu(de->inode);
       inode = iget(sb, ino);
       d_add(dentry, inode);
       ______________________________________________________________________

  というコードを実行します。

  このように、ファイルを開くときには、実体がiget4(sb, ino, NULL, NULL)
  である iget(sb, ino) をたたきます。これは、

  1. inode_lockの保護のもと、ハッシュテーブルの中から、スーパブロックと
     inode 番号が一致する inode を見つけようとする。もし、inode が見つか
     れば、その参照カウンタ (i_count) を増分する。もし、増加の前の値が 0
     であったらば、inode は dirty ではないので、どのタイプのリス
     ト(iode->i_list)からであれ削除する。これは、現在 on (それはもちろ
     んinode_unusedリストでなければならない)であり、inode_in_useタイプの
     リストに挿入される。最後に、inodes_stat.nr_unused が減少される。

  2. もしinodeが現在ロックされていたら、ロックが解除されるまで待つ。つま
     り、iget4()はロックされていない inode を返すことが保証される。

  3. もし inode がハッシュテーブルに見つからなければ、この inode に初め
     て出会ったということになる。よって、get_new_inode()を呼び出し、ハッ
     シュテーブルの挿入すべき位置をポインタで渡す。

  4. get_new_inode() は新しい inode をinode_cachep SLAB キャッシュから割
     り当てる。しかし、この操作は( GFP_KERNEL割り当てで)ブロックできるた
     め、ハッシュテーブルを守る inode_lock spinlock をドロップしないとい
     けない。spinlock をドロップしてしまったため、後で再びハッシュテーブ
     ルで inode を検索してみなくてはならない。再度それが見つかれば、ハッ
     シュテーブルで見つけたものを復帰し、(__iget によって参照数を増やし
     た後に)、新しく割り当てられた inode を破棄する。ハッシュテーブルで
     まだ見つからなければ、今さっき割り当てた新しい inode が、使われるべ
     きものとなる。したがって、それは要求された値に初期化される。そし
     て、ファイルシステムに特有の sb->s_op->read_inode() メソッドは、残
     りの inode を使うために起動される。ということで、inode キャッシュ
     バックのコードから、ファイルシステムコードへ見に行かなければならな
     い。ここで、ファイルシステムに特有の lookup() メソッドが iget()を起
     動したために、 inode キャッシュのコードを読んでいることを思い出して
     ほしい。 s_op->read_inode() メソッドがディスクよりinodeを読み込む
     間、inode はロックされる。(i_state = I_LOCK()) read_inode() メソッ
     ドが復帰した後にロック解除される。そして、inode ロックを待っている
     全てが起こされる。

     さて、このファイルディスクリプタをcloseしたときに、なにが起こるかを
     みていきます。close(2) システムコールは、do_close(fd, 1) を呼び出す
     fs/open.c:sys_close() 関数として実装されています。do_close(fd, 1)
     は、プロセスのファイルディスクリプタテーブルの記述子を剥ぎ取り(NULL
     で置き換える)、ほとんどの作業を行う filp_close() 関数を呼び出しま
     す。 fput()ではおもしろいことが起こります。これがファイルの最後のリ
     ファレンスであるならば、__fput()を呼びだす fs/file_table.c:_fput()
     が呼び出され、dcache と相互作用を起こします。(しかも inode cache と
     も作用します -- dcache は inode キャッシュのマスターであった!)
     fs/dcache.c:dput() は、 我々を iput(inode)を経由してinode キャッ
     シュへ引き戻してくれるdentry_iput()を実行します。したがっ
     て、fs/inode.c:iput(inode)は次のように理解することができます。

  1. もし渡されたパラメータが NULL ならば、本当に何もせず戻る。

  2. もし、fs特有の sb->s_op->put_inode() メソッドがあれば、(ブロックで
     きるため)スピンロックを取得することなくすぐに起動される。

  3. inode_lockスピンロックが取得され i_count が1つ減少される。もしこれ
     がこの inode への最後のリファレンスで*なけれ*ば、単純にリファレン
     スが多すぎないかどうかを確認する。これは、i_count が割り当てられた
     32bit 以内に納めるためである。もし、リファレンスが多すぎる場合は警
     告を表示して返る。ここで、inode_lock スピンロックを取得しているとき
     に printk() を呼び出すが、これは大丈夫である。というのは、printk()
     はブロックされることはなく、したがっていついかなるコンテキスト(割り
     込みハンドラでさえ)からでも呼び出すことができるためだ。

  4. もし最後のアクティブなリファレンスであった場合には、必要な処理があ
     る。

  最後の inode リファレンスでの iput() が行う処理は、やや複雑なもので
  す。そのため、リストでもその最後に分けてあります。

  1. もし i_nlink == 0 (例: ファイルがオープンしているときに unlink され
     た)場合は、 inode はハッシュテーブルとtype list から削除される。も
     し、この inode のページキャッシュになにかデータページが保持されてい
     たら、truncate_all_inode_pages(&inode->i_data) により削除される。そ
     して、ファイルシステム特有の s_op->delete_inode()メソッドが呼び出さ
     れる。これは通常は、ディスク上の inode を削除する。も
     し、s_op->delete_inode()メソッドがファイルシステムにより登録されて
     いない(例: ramfs)場合には、clear_inode(inode)を呼び出し、これはもし
     登録されていればs_op->clear_inode()を呼び出す。また、inode が特定の
     ブロックデバイスに関連していれば、デバイスのリファレンスカウント
     をbdput(inode->i_bdev)で落とす。

  2. もし i_nlink != 0 なら、同じハッシュバケットの他の inode があるか
     チェックする。そして、なければ inode は dirty ではないため、その
     type list から削除できる。 そして、inode_unusedリストへ追加
     し、inodes_stat.nr_unusedを増分する。もし、これが匿名 inode であっ
     た(NetApp .snapshot)なら、タイプリストから削除し、完全にクリア/破壊
     する。

  3.2.  ファイルシステム登録/登録解除

  Linuxカーネルは、新しいファイルシステムを最小限の努力で書くことができ
  る機構を提供しています。その歴史的な理由とは次のようなものです。

  1. 人々がLinux以外のオペレーティングシステムを使っている世界で、古いソ
     フトウエアへの投資を保護するために、Linuxは大変多くの異なったファイ
     ルファイルシステムのサポートにより相互運用性を提供しなければならな
     かった。そのため、多くのファイルシステムはそれ自身の存在はそれほど
     重要ではないが、現存するLinux以外のオペレーティングシステムがとの互
     換性をとるためだけの目的で存在する。

  2. ファイルシステムを記述するインターフェースはとてもシンプルなもので
     なければならなかった。現存するプロプラエタリなファイルシステムを、
     読込みのみ可能なファイルシステムを書くことによってリバースエンジニ
     アリングできるようにするために、ファイルシステムを記述するインター
     フェースはとてもシンプルなものでなければならなかった。したがっ
     て、LinuxのVFSは読込み専用ファイルシステムの実装が非常に容易になっ
     ている。ファイルシステムのサポートを書く仕事のの95%は、書き込みのフ
     ルサポートを追加して完成させることに費やされるのである。具体的な例
     としては、私がLinux用に読込み専用のBFSファイルシステムを書くのに約
     10時間でできたのに対して、完全な書き込みサポートを追加して完成さ
     せるのに数週間を要したことを挙げられる(しかも、現在でも完璧主義者か
     ら、"これはcompactificationサポートを持たないため"完成していないと
     主張されるのだ)。

  3. VFS インターフェースはエキスポートされており、したがってすべて
     のLinuxファイルシステムはモジュールとして実装できる。

  ここで、Linuxでファイルシステムの実装に必要なステップを考えてみましょ
  う。ファイルシステムを実装するコードは、動的にロードされるモジュールで
  も、静的にカーネルにリンクされるようにも構成することができます。そし
  て、 Linuxの元で行われる方法は非常に明白です。必要なことは、struct
  file_system_type構造体に情報を埋めて、これをVFSへregister_filesystem()
  関数によって登録することだけなのです。fs/bfs/inode.cの例だと、以下のよ
  うになります。

  ______________________________________________________________________
  #include 
  #include 

  static struct super_block *bfs_read_super(struct super_block *, void *, int);

  static DECLARE_FSTYPE_DEV(bfs_fs_type, "bfs", bfs_read_super);

  static int __init init_bfs_fs(void)
  {
          return register_filesystem(&bfs_fs_type);
  }

  static void __exit exit_bfs_fs(void)
  {
          unregister_filesystem(&bfs_fs_type);
  }

  module_init(init_bfs_fs)
  module_exit(exit_bfs_fs)
  ______________________________________________________________________

  module_init()/module_exit()マクロは、BFSがモジュールとしてコンパイルさ
  れたときに、関数init_bfs_fs()とexit_bfs_fs()がそれぞれ、init_module()
  とcleanup_module()になるようにします。

  struct file_system_typeは、include/linux/fs.hで定義されます。

       ______________________________________________________________________
       struct file_system_type {
               const char *name;
               int fs_flags;
               struct super_block *(*read_super) (struct super_block *, void *, int);
               struct module *owner;
               struct vfsmount *kern_mnt; /* For kernel mount, if it's FS_SINGLE fs */
               struct file_system_type * next;
       };
       ______________________________________________________________________

  それぞれのメンバは、次のように説明されます。

  o  name: 人が読める名前で、filesystemsprocファイルへ現われます。そし
     て、ファイルシステムを名前から見つけるときのキーとして使われます。
     これと同じ名前は、mount(2)へ指定するファイルシステムのタイプとして
     も使われ、ファイルシステムごとに一意でなければなりません。

  o  fs_flags: 1つないしは(ORされた)複数のフラグ: FS_REQUIRES_DEVは、ブ
     ロックデバイス上にだけマウント可能なファイルシステムという意
     味。FS_SINGLEは、唯1つのスーパーブロックをもつファイルシステ
     ム、FS_NOMOUNTはユーザスペースからマウントできない、つまりmount(2)
     システムコールでマウントできないファイルシステム。しかしこれは、内
     部的にはkern_mount()インターフェースを使えばマウントできる (例
     pipefs)。

  o  read_super: マウント操作中にスーパブロックを読込む関数へのポイン
     タ。この関数は必須である。このポインタが渡されなかったなら、マウン
     ト操作(ユーザ空間にせよカーネルからにせよ)がFS_SINGLEの場合を除いて
     常に失敗し、 fs_type->kern_mnt->mnt_sbを(fs_type->kern_mnt = NULL)
     のように参照しようとして、get_sb_sinle()でOops エラーが発生する。

  o  owner: このファイルシステムを実装しているモジュールへのポインタ。も
     しファイルシステムがカーネルに静的にリンクされていれば、この値
     はNULLとなる。これを手動で設定する必要はなく、マクロ THIS_MODULE に
     よって自動で適切に設定される。

  o  kern_mnt: FS_SINGLEファイルシステムのみ利用。これはkern_mount()が設
     定する。(TODO: kern_mount()はFS_SINGLEが設定されていないファイルシ
     ステムのマウントを拒否すべきである)。

  o  next: file_systemsによる片方向リストの先頭へのリンク (fs/super.cを
     参照)。リストは、file_systems_lock読み書きスピンロックにより保護さ
     れており、register/unregister_filesystem()関数は、リストからエント
     リを取り外したり、入れたりすることで変更を行う。

  read_super()関数の仕事は、スーパーブロックのフィールドを埋めることで
  す。 inodeの場所を割り当て、マウントされたファイルシステムのインスタン
  スに関連づけられたfs固有の情報を初期化します。

  1. バッファキャッシュbread()関数を使って、スーパーブロックをsb->s_dev
     項で指示されるデバイスから読み取る。

  2. スーパブロックが正しいマジックナンバーからなっているか確認し、全体
     的に正常であるかを「見」る。

  3. sb->s_opをstruct super_block_operations構造体を指すように初期化す
     る。この構造体は、「inodeを読み取る」とか、「inodeを削除する」など
     のような操作を実装しているファイルシステム特有の関数からなる。

  4. d_alloc_root()を用いてルートinodeとルートdentryを割り当てる。

  5. もしファイルシステムが読み取り専用でマウントされていなけれ
     ば、sb->s_dirtを1に設定し、スーパーブロックからなるバッファをdirty
     とマークする(TODO: なぜこれが必要?BFSでは、MINIXがそうしていたので
     やっているが...)

  3.3.  ファイルデスクリプタ管理

  Linuxにおいては、ユーザファイルデスクリプタとカーネルのinode構造体の間
  に間接的なレベルが存在しています。プロセスがopen(2)システムコール呼び
  出すとき、カーネルは、このファイルを次のI/O操作で使用する小さい正の整
  数を返します。この整数は、struct fileへのポインタの配列のインデックス
  になっています。各ファイル構造体は、file->f_dentryを通して、dentryを指
  しています。そして各dentryはdentry->d_inodeを通じてinodeを指していま
  す。

  各タスクは、include/linux/sched.hで定義されているstruct files_structを
  指しているtsk->filesフィールドを持っています。

  ______________________________________________________________________
  /*
   * Open file table structure
   */
  struct files_struct {
          atomic_t count;
          rwlock_t file_lock;
          int max_fds;
          int max_fdset;
          int next_fd;
          struct file ** fd;      /* current fd array */
          fd_set *close_on_exec;
          fd_set *open_fds;
          fd_set close_on_exec_init;
          fd_set open_fds_init;
          struct file * fd_array[NR_OPEN_DEFAULT];
  };
  ______________________________________________________________________

  file->countは参照カウンタであり、(通常、fget()から呼び出され
  る)get_file()で増分され、fput()とput_filp()で減少されます。fput()
  とput_filp()の違いは、fput()が一般のファイルに通常必要な、ファイルロッ
  クの開放やdentryの開放などの追加の処理を行うのに対し、put_filp()は単に
  ファイルテーブル構造体を操作するに過ぎないところにあります。つまり、カ
  ウンタの減少はfile_lockスピンロックの保護のもと、anon_listからファイル
  を削除し、それをfree_listへ追加するということです。

  もし子のスレッドがクローンフラグ引数にCLONE_FILESを設定してclone()シス
  テムコールを呼んで作られたものであれば、tsk->filesは親と子の間で共有す
  ることができます。これは、(do_fork()によって呼ばれ
  る)kernel/fork.c:copy_files()において見ることができ、これは、も
  しCLONE_FILESが、伝統的な従来の古典的UNIXのfork(2)における通常のファイ
  ルデスクリプタテーブルのコピーのかわりに設定されていれば、単
  にfile->countを増分させるだけです。

  ファイルが開かれるとき、それに割り当てられるファイル構造体
  は、current->files->fd[fd]スロットへ組み込まれます。そしてビットマッ
  プcurrent->files->open_fdsのfdビットがセットされます。これらのことは全
  て、current->files->file_lock読み書きスピンロックの書き込み保護のもと
  で行われます。もし、デスクリプタがcloseされれば、
  current->files->open_fdsのfdビットはクリアされ、このプロセスがファイル
  をつぎにオープン使用としたときに、最初の未使用のデスクリプタを見つける
  ためのヒントとして、current->files->next_fdはfdと同じになるよう設定さ
  れます。

  3.4.  ファイル構造体管理

  ファイル構造体は include/linux/fs.hで定義されています。

  ______________________________________________________________________
  struct fown_struct {
          int pid;                /* pid or -pgrp where SIGIO should be sent */
          uid_t uid, euid;        /* uid/euid of process setting the owner */
          int signum;             /* posix.1b rt signal to be delivered on IO */
  };

  struct file {
          struct list_head        f_list;
          struct dentry           *f_dentry;
          struct vfsmount         *f_vfsmnt;
          struct file_operations  *f_op;
          atomic_t                f_count;
          unsigned int            f_flags;
          mode_t                  f_mode;
          loff_t                  f_pos;
          unsigned long           f_reada, f_ramax, f_raend, f_ralen, f_rawin;
          struct fown_struct      f_owner;
          unsigned int            f_uid, f_gid;
          int                     f_error;

          unsigned long           f_version;

          /* needed for tty driver, and maybe others */
          void                    *private_data;
  };
  ______________________________________________________________________

  では struct file のメンバを見ていきましょう。

  1. f_list: このメンバは次のリストのうち一つ(そしてただひとつ)のファイ
     ル構造体をリンクする。a) このファイルシステムの全てのオープンしてい
     るファイルのsb->s_filesリスト、もし関連するinodeが匿名でなけれ
     ば、(filp_open()からよばれる)dentry_open()は、このリストにファイル
     をリンクする。b) fs/file_table.c:free_list, 未使用のファイル構造体
     からなる。c) fs/file_table.c:anon_list, get_empty_filp()によって新
     しいファイル構造体が作られたときにこのリストにおかれる。すべてのリ
     ストはfiles_lockスピンロックによって保護される。

  2. f_dentry: このファイルに関連づけられるdentry。dentry
     は、open_namei()がnameidataを探すとき(あるいは、これを呼び出
     すpath_walk()が実行されたとき)に作られる。しかし、実際
     のfile->f_dentryメンバはdentry_open()によって、このようにして見つ
     かったdentryにセットされる。

  3. f_vfsmnt: そのファイルのvfsmountファイルシステム構造体を指す。これ
     は、dentry_open()によって設定されるが、open_namei()(ないしは、これ
     を呼び出す path_init()) により nameidata の一部として見ることができ
     る。

  4. f_op: このファイルで起動されるさまざまなメソッドからな
     るfile_operationsを指す。これは、nameidataの検索中にファイルシステ
     ム特有のs_op->read_inode()メソッドによって作られた、inode->i_fopか
     らコピーされる。このセクションの後の方で、file_operationsメソッドは
     詳細に見ていくことにしよう。

  5. f_count: get_file/put_filp/fputにより操作されるリファレンスカウン
     タ。

  6. f_flags: dentry_open()によって(filp_open()が若干の変更を加えて)コ
     ピーされるopen(2)システムコールからのO_XXXフラ
     グ。O_CREAT、O_EXCL、O_NOCTTY、O_TRUNCをクリアした後はこれらのフラ
     グを長い間保存しておく意味はない。なぜなら、F_SETFL や F_GETFL
     fcntl(2) システムコールで変更されることも、問い合わせられることもな
     いからである。

  7. f_mode: ユーザ空間のフラグとモードの組み合わせ。dentry_open()によっ
     て設定される。変換のポイントは、読み書きのアクセスを別のビットへ格
     納することで、(f_mode & FMODE_WRITE)や(f_mode & FMODE_READ)のような
     チェックを容易にすることができる。

  8. f_pos: 次回のファイルの読み書きにおける現在のファイル位置。i386で
     は、long long型、つまり64ビット値になる。

  9. f_reada, f_ramax, f_raend, f_ralen, frawin: readaheadをサポートす
     る。 -- 複雑すぎて人間には議論できない ;)

  10.
     f_owner: 非同期I/O通知をSIGIO機構を通じて受け取るファイルI/Oの所有
     者。(fs/fcntl.c:kill_fasync()参照)

  11.
     f_uid, f_gid: ファイル構造体がget_empty_filp()によって作られたとき
     にファイルを開いたプロセスのユーザIDとグループIDに設定される。ファ
     イルがソケットのときは、ipv4ネットフィルタが使う。

  12.
     f_error: 書き込みエラーを返すためにNFSクライアントが利用する。これ
     はfs/nfs/file.cで設定され、mm/filemap.c:generic_file_write()で
     チェックされる。

  13.
     f_version: キャッシュを取り消すためのバージョン管理メカニズム。(グ
     ローバル変数のeventを使って)f_posが変ったときにいつも増分される。

  14.
     private_data: ファイルシステムが使うことができる(例えば、coda はこ
     こに 証明書を格納する) 非公開のファイル毎のデータ。ないしは、デバイ
     スドライバが使う。(devfsにある)デバイスドライバは、このメンバ
     を、file->f_dentry->d_inode->i_rdevに符号化される、古典的なマイナー
     番号のかわりに、複数のインスタンス間を区別するのに使える。

  さて、ファイルについて起動できるメソッドからできている file_operations
  構造体を見てみましょう。inode->i_fopからコピーされることや、それ
  がs_op->read_inode()メソッドによって設定されることなどを覚えているで
  しょうか。これは、include/linux/fs.hで定義されています。

  ______________________________________________________________________
  struct file_operations {
          struct module *owner;
          loff_t (*llseek) (struct file *, loff_t, int);
          ssize_t (*read) (struct file *, char *, size_t, loff_t *);
          ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
          int (*readdir) (struct file *, void *, filldir_t);
          unsigned int (*poll) (struct file *, struct poll_table_struct *);
          int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
          int (*mmap) (struct file *, struct vm_area_struct *);
          int (*open) (struct inode *, struct file *);
          int (*flush) (struct file *);
          int (*release) (struct inode *, struct file *);
          int (*fsync) (struct file *, struct dentry *, int datasync);
          int (*fasync) (int, struct file *, int);
          int (*lock) (struct file *, int, struct file_lock *);
          ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
          ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
  };
  ______________________________________________________________________

  1. owner: 論点となっているサブシステムを所有しているモジュールを指す。
     ドライバのみがこれをTHIS_MODULEに設定する必要があり、モジュールのカ
     ウントは、ドライバがopen/release時に制御する必要があるのに対し
     て、mount/umountのときに制御されるため、ファイルシステムは幸運なこ
     とに無視できる。

  2. llseek: lseek(2)システムコールを実装している。通常、これは省略さ
     れ、fs/read_wirte.c:default_llseek()が使われる。これは正しい方法で
     ある。(TODO: default_llseekを使うために現在これにNULLを設定しなけれ
     ばならなくなっている。このときは、llseek()でif()を保存している)。

  3. read: read(2)システムコールの実装。ここでは、ファイルシステム
     は、mm/flemap.c:generic_file_read()を通常のファイル
     に、fs/read_write.c:generic_read_dir()(単純に-EISDIRを返す)をディレ
     クトリに使用できる。

  4. write: write(2)システムコールの実装。ここでは、ファイルシステム
     は、mm/filemap.c:generic_file_write()を通常のファイルに使用でき、
     ディレクトリは無視できる。

  5. readdir: ファイルシステムが使う。通常のファイルは無視され、ディレク
     トリにはreaddir(2) と getdents(2) システムコールが実装される。

  6. poll: poll(2) と select(2) システムコールが実装される。

  7. ioctl: ドライバやファイルシステム特有の ioctl を実装している。一般
     のファイルに対する FIBMAPや FIGETBSZ、 FIONREAD のようなioctl は、
     高レベルで実装されておりf_op->ioctl() メソッドを読むことがない。

  8. mmap: mmap(2) システムコールを実装している。ファイルシステムは
     generic_file_mmapを一般のファイルに対してここで使うことができ、ディ
     レクトリに対しては無視される。

  9. open: open(2) したときにdentry_open() が呼ぶ。ファイルシステムは滅
     多に使わない。例えば、coda はオープンしたときにファイルをローカルに
     キャッシュしようとする。

  10.
     flush: ファイルの各close(2)ごとに呼ばれる。必ずしも最後に呼ばれる必
     要はない。(以下のrelease()メソッド参照) これを使うファイルシステム
     というのは、唯一NFSのクライアントで、全てのdirtyページを書き出す。
     ここで、close(2)システムコールから、ユーザ空間に送り返されるエラー
     を返すことがある。

  11.
     release: ファイルの最後のclose(2)で呼ばれる。つまり、file->f_count
     が0であるときだ。整数を返すと定義されているにも関わらず、VFSでは返
     り値は無視される。(fs/file_table.c:__fput()を参照)

  12.
     fsync: fsync(2)/fdatasync(2)システムコールに直接対応する。最後の引
     数でfsyncになるかfdatasyncになるかを指定する。VFS はその際ほとんど
     何も仕事をしないが、ファイルディスクリプタを file 構造体に対応させ
     ること (file = fget(fd))、および inode->i_sem セマフォを操作するこ
     とは行う。Ext2 ファイルシステムは最後の引数を無視するので、fsync(2)
     と fdatasync(2) のシステムコールは全く同じ働きをする。

  13.
     fasync: このメソッドは、file->f_flags & FASYNC が真であるときに使わ
     れる。

  14.
     lock : ファイルシステムに固有なPOSIXのfcntl(2)のファイル領域ロック
     機構。ここでの唯一のバグは、fs 非依存(posix_lock_file())なものが呼
     ばれる前に呼ばれたとき、もし成功したが通常のPOSIXロックコードが失敗
     したら fs 依存レベルでは、ロック解除できなくなるということである。

  15.
     readv: readv(2)システムコールの実装。

  16.
     writev: writev(2)システムコールの実装。

  3.5.  スーパーブロックとマウントポイント管理

  Linux においては、マウントされたファイルシステムについての情報は2つに
  分かれた構造体 super_blockとvfsmount で保持されています。これは Linux
  は同じファイルシステム(ブロックデバイス)を複数のマウントポイントへマウ
  ントする、つまり同じsuper_block が複数のvfsmount構造体へ関連づけられる
  という意味ですが、これが許されていることが、2つに分かれて管理される理
  由になっています。

  include/linux/fs.h で定義される struct super_block を最初にみていきま
  す。

  ______________________________________________________________________
  struct super_block {
          struct list_head        s_list;         /* Keep this first */
          kdev_t                  s_dev;
          unsigned long           s_blocksize;
          unsigned char           s_blocksize_bits;
          unsigned char           s_lock;
          unsigned char           s_dirt;
          struct file_system_type *s_type;
          struct super_operations *s_op;
          struct dquot_operations *dq_op;
          unsigned long           s_flags;
          unsigned long           s_magic;
          struct dentry           *s_root;
          wait_queue_head_t       s_wait;

          struct list_head        s_dirty;        /* dirty inodes */
          struct list_head        s_files;

          struct block_device     *s_bdev;
          struct list_head        s_mounts;       /* vfsmount(s) of this one */
          struct quota_mount_options s_dquot;     /* Diskquota specific options */

         union {
                  struct minix_sb_info    minix_sb;
                  struct ext2_sb_info     ext2_sb;
                  ..... all filesystems that need sb-private info ...
                  void                    *generic_sbp;
          } u;
         /*
           * The next field is for VFS *only*. No filesystems have any business
           * even looking at it. You had been warned.
           */
          struct semaphore s_vfs_rename_sem;      /* Kludge */

          /* The next field is used by knfsd when converting a (inode number based)
           * file handle into a dentry. As it builds a path in the dcache tree from
           * the bottom up, there may for a time be a subpath of dentrys which is not
           * connected to the main tree.  This semaphore ensure that there is only ever
           * one such free path per filesystem.  Note that unconnected files (or other
           * non-directories) are allowed, but not unconnected diretories.
           */
          struct semaphore s_nfsd_free_path_sem;
  };
  ______________________________________________________________________

  super_block 構造体には様々なメンバがあります。

  1. s_list: 全てのアクティブなスーパーブロックのダブルリンクリスト。こ
     こで、「全てのマウントされたファイルシステム」と言わないのは、Linux
     では、一つのスーパブロックに関連づけられたマウントされたファイルシ
     ステムの複数のインスタンスを持てるためである。

  2. s_dev: マウントされるのにブロックを必要とするファイルシステム、つま
     り FS_REQUIRES_DEV なファイルシステムにとっては、このメンバはそのブ
     ロックデバイスの i_dev である。その他の (匿名ファイルシステムと呼ば
     れる) ファイルシステムに対しては、これは MKDEV(UNNAMED_MAJOR, i) で
     表される整数である。ただし i は、unnamed_dev_in_use ビット配列の中
     で最初の OFF ビットを指す、1 から 255 の範囲の値であ
     る。fs/super.c:get_unnamed_dev()/put_unnamed_dev() を参照のこと。匿
     名ファイルシステムが s_dev メンバを利用すべきでないということは、こ
     れまで何度も提案されてきた。
  3. s_blocksize, s_blocksize_bits: ブロックサイズとlog2(ブロックサイ
     ズ)。

  4. s_lock: スーパーブロックが、lock_super()/unlock_super() によって現
     在ロックされているかどうかを示す。

  5. s_dirt: スーパーブロックが変更されたときに設定され、ディスクへ書き
     戻されたときはいつもクリアされる。

  6. s_type: 関連するファイルシステムの struct file_system_type を指
     す。fs 特有のread_super()が成功したときに VFS
     fs/super.c:read_super() が設定し、失敗したとき NULL にリセットする
     ため、ファイルシステムの read_super() メソッドは設定する必要がな
     い。

  7. s_op: fs 特有のread/write inodeなどのメソッドからなる
     super_operations 構造体へのポインタ。s_op を正しく初期化するのは、
     ファイルシステムの read_super() メソッドの仕事である。

  8. dq_op: ディスククオタ操作。

  9. s_flags: スーパーブロックフラグ。

  10.
     s_magic: ファイルシステムのマジック番号。minixファイルシステムで
     は、それ自身の複数バージョンを区別するために使われる。

  11.
     s_root: ファイルシステムのルートのdentry。ルートの inode をディスク
     から読み、d_alloc_root()を呼んでdentryを割り当てて実体化するために
     渡すのは、read_super() の仕事である。

  12.
     s_wait: スーパーブロックがロック解除されるのを待つプロセスの待ち
     キュー。

  13.
     s_dirty: 全てのdirty な inode のリスト。もし inode が汚れ
     て(inode->i_state & I_DIRTY)いれば、スーパーブロック特有
     のinode->i_list経由でリンクされた dirty リストにおかれる。

  14.
     s_files: このスーパーブロックでオープンしているファイル全てのリス
     ト。ファイルシステムを read-only で再マウントできるかを判断するとき
     に有用。sb->s_files リストをみて、もしファイルが書き込みオープンさ
     れている(file->f_mode & FMODE_WRITE)か、unlinkが保留されてい
     る(inode->i_nlink == 0)ファイルがあれば、再マウントを拒否す
     るfs/file_table.c:fs_may_remount_ro()を参照すること。

  15.
     s_bdev: FS_REQUIRES_DEV のために、これは ファイルシステムがマウント
     されているデバイスを表す block_device 構造体を指している。

  16.
     s_mounts: 全ての vfsmount 構造体のリスト。マウントされたこのスー
     パーブロックの各インスタンスの???

  17.
     s_dquot: ディスククオタ用。

  スーパーブロックの操作は、include/linux/fs.h で定義されている
  super_operations 構造体に記述されている。

  ______________________________________________________________________
  struct super_operations {
          void (*read_inode) (struct inode *);
          void (*write_inode) (struct inode *, int);
          void (*put_inode) (struct inode *);
          void (*delete_inode) (struct inode *);
          void (*put_super) (struct super_block *);
          void (*write_super) (struct super_block *);
          int (*statfs) (struct super_block *, struct statfs *);
          int (*remount_fs) (struct super_block *, int *, char *);
          void (*clear_inode) (struct inode *);
          void (*umount_begin) (struct super_block *);
  };
  ______________________________________________________________________

  1. read_inode: inodeをファイルシステムから読み込む。iget4()(したがっ
     て、iget())を通して、fs/inode.c:get_new_inode() からのみ呼ばれる。
     もしファイルシステムがiget()を使いたいならば、read_inode()は実装さ
     れないといけない。そうでなければ、get_new_inode()はパニックになる。
     inodeが読まれている間はロックされる(inode->i_state = I_LOCK)。関数
     から復帰するとき、全てのinode->i_waitの待ちは起こされる。ファイルシ
     ステムのread_inode()メソッドの仕事は、読み込まれる inode からなる
     ディスクブロックを割り当て、バッファーキャッシュ bread()関数を使っ
     て読み込み,inode 構造体の様々なフィールドが初期化される.例え
     ばinode->i_op とinode->i_fop では、VFSレベルが、inode や関連する
     ファイルでなんの操作が行われるかを知っている read_inode()を実装しな
     いファイルシステムは、ramfsとpipefsである。たとえば、ramfsは、必要
     になったときに全てのinode の操作が呼ぶ、自身のinode生成関
     数ramfs_get_inode()を持っている。

  2. write_inode: inode をディスクに書き戻す。read_inode()と似て、ディス
     クに同様なブロックを割り当て、mark_buffer_dirty(bh)を呼ぶことで、
     バッファキャッシュと作用する。このメソッドは、inodeが、それぞれや
     ファイルシステム全体の同期の一部なりで同期される必要があると
     き、dirty な inode (mark_inode_dirty()によって dirty と印をつけられ
     ている)に対して呼ばれる。

  3. put_inode: リファレンスカウンタが減少させられるときにいつも呼ばれ
     る。

  4. delete_inode: inode->i_countとinode->i_nlinkが0になるときは常に呼ば
     れる。ファイルシステムは、オンディスクのinodeのコピーを削除し、VFS
     でclear_inode()を呼び出して、「完全に根絶する」。

     -->

  5. put_super: umount(2)システムコールの最終段階で呼ばれ、ファイルシス
     テムに対して、このインスタンスについてファイルシステムが保持してい
     るいかなる内部の情報を解放しなければならないことを通知する。典型的
     にはこれは、スーパーブロックがあるブロックをbrelse()し、フリーブ
     ロックやinodeなどのために割り当てられたビットマップを全てkfree()す
     る。

  6. write_super: スーパーブロックをディスクに書き戻す必要があるときに呼
     ばれる。これは、スーパブロック(通常sb-private領域にある)
     とmark_buffer_dirty(bh)があるブロックを見つけなければならない。これ
     はまた、sb->s_dirtフラグをクリアする。

  7. statfs: fstatfs(2)/statfs(2)システムコールの実装。引数として渡され
     るstruct statsへのポインタは、ユーザポインタではなくカーネルポイン
     タであるため、ユーザ空間へのI/Oが不必要であることに注意すること。も
     し実装されていなければ、statfs(2)はENOSYSで失敗する。

  8. remount_fs: ファイルシステムを再マウントするときには必ず呼ばれる。

  9. clear_inode: VFSレベルclear_inode()から呼ばれる。プライベートデータ
     をinode構造体へ(generic_ipメンバを経由して)割り当てるファイルシステ
     ムは、ここで解放されなければならない。

  10.
     umount_begin: なにもファイルシステムをビジーにしないことを確かめる
     のに全力を尽くすことができるように、あらかじめファイルシステムへ通
     知するため、umountを強制する間に呼ばれる。現在、NFSでのみ使われる。
     これは、一般的なVFSレベルの強制umountサポートとの関係はない。

  それでは、on-disk (FS_REQUIRES_DEV)ファイルシステムをマウントするとき
  に起ることを見ていきましょう。 mount(2)システムコールの実装
  は、fs/super.c:sys_mount() にあり、これは実際の処理を行う do_mount()
  関数にオプションとファイルシステムタイプやデバイス名をコピーする単なる
  ラッパーになっています。

  1. ファイルシステムのドライバは必要になったときにロードされ、モジュー
     ルの参照カウントが増分される。ここで、マウントの最中にファイルシス
     テムのモジュールについての参照カウントについては、2つ増分されること
     に注意が必要である。do_mount()がget_fs_type()を呼ぶときに1
     回、read_super()が成功したならば、get_sb_dev()がget_filesystem()を
     呼ぶときに1回である。1つめの増分は、read_super()の内部において、モ
     ジュールがアンロードされるのを避けるためであり、2つめの増分は、マウ
     ントされたインスタンスによってモジュールが使用中であることを示すた
     めのものである。明らかに、各マウント後の全体でのカウントは1つだけ増
     えることから、do_mount()は戻る前にカウンタを減少させるのである。

  2. 我々の場合、fs_type->fs_flags & FS_REQUIRES_DEVが真であるなら、ブ
     ロックデバイスへのリファレンスを取得し、ファイルシステム
     のread_super()メソッドと連携してスーパーブロックを埋め
     るget_sb_bdev()を呼ぶことで、スーパーブロックが初期化される。全てが
     うまく行けば、super_block構造体は初期化され、ファイルシステムモ
     ジュールへのextra参照と、下にあるブロックデバイスへの参照を得ること
     になる。

  3. 新しいvfsmount構造体が割り当てられ、sb->s_mountsリストとグローバル
     なvfsmntlistリストへリンクされる。vfsmountのメンバのmnt_instancesは
     全ての同じスーパーブロックにマウントされているインスタンスを見つけ
     られるようにする。 mnt_listメンバは、システム全体の全てのスーパーブ
     ロックに対して、全てのインスタンスを見つけることができるようにす
     る。mnt_sbメンバはこのスーパーブロックを指し、mnt_rootはsb->s_root
     dentry への新しい参照を持つ。

  3.6.  仮想ファイルシステムの例: pipefs

  マウントにブロックデバイスが必要ない簡単な Linux ファイルシステムの例
  として、fs/pipe.cからpipefsを取り上げましょう。ファイルシステムの前提
  部分はかなり簡単で、ちょっとした説明で十分です。

  ______________________________________________________________________
  static DECLARE_FSTYPE(pipe_fs_type, "pipefs", pipefs_read_super,
          FS_NOMOUNT|FS_SINGLE);

  static int __init init_pipe_fs(void)
  {
          int err = register_filesystem(&pipe_fs_type);
          if (!err) {
                  pipe_mnt = kern_mount(&pipe_fs_type);
                  err = PTR_ERR(pipe_mnt);
                  if (!IS_ERR(pipe_mnt))
                          err = 0;
          }
          return err;
  }

  static void __exit exit_pipe_fs(void)
  {
          unregister_filesystem(&pipe_fs_type);
          kern_umount(pipe_mnt);
  }

  module_init(init_pipe_fs)
  module_exit(exit_pipe_fs)
  ______________________________________________________________________

  このファイルシステムは、ユーザ空間からマウントできず、システム全体で一
  つのスーパーブロックのみ持てることを意味するFS_NOMOUNT|FS_SINGLE型に
  なっています。 FS_SINGLEであるファイルは同様に、register_filesystem()
  を使って登録成功したあとに、kern_mount()を使ってマウントされなければな
  らないことを意味しています。これは、まさに、init_pipe_fs()で行われるこ
  とです。この関数の唯一のバグは、もしkern_mount()が失敗した場合(例え
  ば、kmalloc()がadd_vfsmnt()中で失敗したときなど)に、ファイルシステムは
  登録された状態で残りますが、モジュールの初期化は失敗になることです。こ
  れは、cat /proc/filesystemsの Oops エラーの原因になります。(丁度 Linus
  に、これを知らせるパッチを送った。pipefs はモジュールとしてコンパイル
  できないためこれは真のバグではないが、将来モジュールになるときを考えて
  書かれるべきだと思う)。

  register_filesystem()の結果として、pipe_fs_typeがfle_systemsリストにリ
  ンクされることになります。つまり/proc/filesystemsを読み出し
  て、FS_REQUIRES_DEVが設定されていないことを示す「nodev」フラグつきで
  「pipefs」が登録されているのを見つけられることになりま
  す。/proc/filesystemsファイルは、新しいFS_フラグを全てサポートするよ
  う、まさに機能強化されるべきです(そして、私はそのパッチを作りました)。
  しかし、これを使う全てのユーザのアプリケーションが動かなくなるため、今
  だ実行することができません。 Linux カーネルインターフェースが随時変更
  されている(単に良くするために)にも関わらず、ユーザ空間での互換性に及ぶ
  となると、Linux はとても保守的なオペレーティングシステムとなり、長い期
  間再コンパイルをしないで多くのアプリケーションが使えるようにするので
  す。

  kern_mount()の結果は次のようになります。

  1. 新たな無名の(anonymous)デバイス番号が割り当てら
     れ、unnamed_dev_in_useビットマップのビットが設定される。もし割り当
     てるビットがなければ、kern_mount()はEMFILEで失敗する。

  2. get_empty_super()によって、新たなスーパーブロック構造体が割り当てら
     れる。get_empty_super()関数は、super_blockによって先頭を示される
     スーパーブロックのリストを見て、s->s_dev == 0となる空のエントリーを
     探す。
  3. ファイルシステム特有のpipe_fs_type->read_super()メソッド(つま
     りpipefs_read_super()だが)が起動され、ルートのinodeとルートのdentry
     となる sb->s_rootを割り当て、sb->s_opを&pipefs_opsになるよう設定す
     る。

  4. そして kern_mount() が 新しいvfsmount構造体を割り当て、vfsmntlist
     とsb->s_mounts へリンクするadd_vfsmnt(NULL, sb->s_root, "none") を
     呼ぶ。

  5. pipe_fs_type->kern_mntは新しいvfsmount構造体に設定されて戻
     る。kern_mount()返り値がvfsmount構造体になっているのは、FS_SINGLEの
     ファイルシステムでさえ複数回マウントできることから、mnt->mnt_sb
     はkern_mount()の複数回の呼び出しからの戻りであったとしても同じもの
     を指して戻るためである。

  これでファイルシステムは登録され、私たちが使えるようにカーネル内にマウ
  ントされました。pipefsファイルシステムのエントリポイントは、pipe(2)シ
  ステムコールとなり、これはアーキテクチャ依存のsys_pipe()へ実装されてい
  ますが、実際にはアーキテクチャ非依存のfs/pipe.c:do_pipe()関数で処理さ
  れます。これからdo_pipe()を見ていこうと思います。pipefs との相互作用
  は、do_pipe()がget_pipe_inode()を新しい pipefs の inode を割り当てるた
  めに呼び出したときに起こります。この inode のために、inode->i_sb
  は、pipefs のスーパーブロックpipe_mnt->mnt_sbへ設定されます。そして
  ファイルの操作i_fopはrdwr_pipe_fopsに設定されて、(inode->i_pipeに保持
  される)読み書きする者の数は 1 に設定されます。fs-private 共用体へ保存
  する代りに、別の inode メンバ i_pipe がある理由は、pipe と FIFO が同じ
  コードを共有しているためと、同じ共用体の中の他のアクセスパスを使う複数
  の FIFO が他のファイルシステム上に存在できるようにあうるためです。これ
  はとても悪い C の使い方であり、純粋に運だけで動きます。そのため、そ
  う、2.2.x カーネルは、運だけで動いているのです。inode のフィールドをす
  こし調整すればすぐに処理を停止してしまうことになるでしょう。

  pipe(2)システムコール毎に、pipe_mntマウントインスタンスの参照カウンタ
  は増分されます。

  Linux においては パイプは対称(双方向あるいはSTREAM pipe)ではなく、つ
  まりファイルの両側で、read_pipe_fopsとwrite_pipe_fopsそれぞれの
  file->f_op 操作は持てません。

  3.7.  ディスクファイルシステムの例:BFS

  簡単なディスク上の Linux ファイルシステムの例としてBFSを考えてみましょ
  う。BFSモジュールのプリアンブルはfs/bfs/inode.cにあり、

       ______________________________________________________________________
       static DECLARE_FSTYPE_DEV(bfs_fs_type, "bfs", bfs_read_super);

       static int __init init_bfs_fs(void)
       {
               return register_filesystem(&bfs_fs_type);
       }

       static void __exit exit_bfs_fs(void)
       {
               unregister_filesystem(&bfs_fs_type);
       }

       module_init(init_bfs_fs)
       module_exit(exit_bfs_fs)
       ______________________________________________________________________

  となっています。

  特別なfstype 定義マクロDECLARE_FSTYPE_DEV()が使われて、fs_type->flags
  を FS_REQUIRES_DEVに設定され、BFS がマウントする実際のブロックデバイス
  が必要であることを示しています。

  モジュールの初期化関数はファイルシステムと(BFSがモジュールとして設定さ
  れたときのみ存在する)登録解除を行う解除関数をVFSに登録します。

  ファイルシステムの登録を行うことで、マウントを進めることができます。マ
  ウントではfs/bfs/inode.c:bfs_read_super() に実装されている
  fs_type->read_super()メソッドが起動されます。この関数は次のような処理
  を行います。

  1. set_blocksize(s->s_dev, BFS_BSIZE): ブロック型デバイス層とバッファ
     キャッシュを通して相互作用しようとしていることから、二つ三つの初期
     化を行う必要がある。すなわち、ブロックサイズを設定して、さらに
     s->s_blocksize と s->s_blocksize_bits メンバを通じて VFS へ通知する
     のだ。

  2. bh = bread(dev, 0, BFS_BSIZE): デバイスのブロック0を読み、s->s_dev
     を通じて渡す。このブロックはファイルシステムのスーパーブロックであ
     る。

  3. スーパーブロックはBFS_MAGIC番号に対して正当性を確認し、sbプライベー
     トメンバs->su_sbhへ格納する(実際はs->u.bfs_sb.si_sbhである)。

  4. そして、 inode ビットマップをkmalloc(GFP_KERNEL) を使って割り当
     て、1に設定される最初の2つを除く全てのビットを0に設定する。これ
     は、inode 0と1をずっと割り当てないことを示すためである。inode 1は
     ルートであり、関連するビットはいずれ数行後に1に設定される。というの
     は、ファイルシステムはマウントされるときには必ず妥当なルート inode
     を持ってるべきものだからだ。

  5. そしてs->s_opを初期化する。これはs_op->read_inode()が起動される結果
     としてiget()を通じて、この時点から inode キャッシュを呼び出すことが
     できる。これは、(inode->i_inoとinode->i_devによる)特定のinode から
     なるブロックを見つけて読み込む。もしルート inode の取得が失敗した
     ら、inode ビットマップを解放し、バッファーキャッシュの背後にある
     スーパーブロックバッファーを解放して、NULL を返す。もしルート inode
     の読み込みがOKであれば、(ルートとなる)/という名前のdentryを割り当
     て、この inode を実体化する。

  6. さて、ファイルシステムの全ての inode を見て、内部の inode ビット
     マップの関連するビットを設定するために、それを全て読み込む。そし
     て、最後の inode のオフセットや最後のファイルの開始/終了ブロックの
     ようなその他の内部パラメータを計算する。読み込む各 inode は inode
     キャッシュへ iput()を通じて戻される。そう、必要以上に長く参照を保持
     できないのだ。

  7. もしファイルシステムが読み込み専用でマウントされなければ、スーパー
     ブロックバッファをdirtyと印をつけ、s->s_dirtフラグを設定する(TODO:
     なぜこれをやるのか? 普通は、これはminix_read_super()がやるからなの
     だが、minixもBFS もread_super()でスーパーブロックを書き換えていない
     ようなのだ)。

  8. 全てがうまく行けば、VFS層の呼び出し、つまりfs/super.c:read_super()
     へ、この初期化されたスーパーブロックを返す。

  read_super()関数が成功して戻った後、fs/super.c:get_sb_bdev()
  のget_filesystem(fs_type)呼び出しを通じて VFS はファイルシステムモ
  ジュールへの参照とブロックデバイスへの参照を得ます。

  ここで、ファイルシステムへ I/O を行うときになにが起るかを考えてみま
  しょう。すでに iget()が呼ばれるときにinodeがどのように読まれるか、ま
  たiput()でどのように解放されるかは見てきました。 inode の読み込みは、
  他のことに混じってinode->i_opとinode->i_fopの事前設定を行うことになり
  ます。ファイルのオープンは、inode->i_fopからfile->f_opへ伝搬します。

  さて、link(2)システムコールのコードの通り道をみていきましょう。システ
  ムコールの実装はfs/namei.c:sys_link()にあります。

  1. ユーザ空間の名前はカーネル空間へエラーチェックを行うgetname()関数を
     使ってコピーされる。

  2. これらの名前はpath_init()/path_walk()を使って nameidata に変換さ
     れ、dcache と相互作用する。結果は old_nd とnd構造体へ格納される。

  3. old_nd.mnt != nd.mntであったら、「クロスデバイスリンク」EXDEVが返さ
     れる。ファイルシステムを超えたリンクはできないが、Linux ではそれを
     ファイルシステムのマウントされたインスタンス(あるいは特定のファイル
     システム間)を超えたリンクができないと解釈している。

  4. 新しい dentry が作られndへlookup_create()によって関連づけられる。

  5. 一般的なvfs_link()関数が呼ばれ、ディレクトリに新しいエントリーを作
     ることができるかどうかチェックする。そして、dir->i_op->link()メソッ
     ドを呼び出す。これはファイルシステムに特有のfs/bfs/dir.c:bfs_link()
     関数を呼び出すことになる。

  6. bfs_link()の内部では、ディレクトリをリンクしようとしていないか
     チェックし、もしそうであればEPERMエラーで拒否する。これは標準ファイ
     ルシステム(ext2)を同じ挙動である。

  7. へルパー関数bfs_add_entry()を呼び出して新しいディレクトリエントリを
     特定のディレクトリに追加しようとする。この関数は、使用していないス
     ロット(de->ino == 0)を探して全てのエントリーを見ていき、見つかった
     ならば、 name/inode の対を関連するブロックへ書き出して、 dirtyの印
     を(スーパブロック以外の優先度で)つける。

  8. もし、ディレクトリエントリの追加が成功したら、操作が失敗することは
     ないため、inode->i_nlinkを増分し、inode->i_ctimeを更新して、inode
     と一緒にインスタンス化した新しいdentryと同様に、この inode を dirty
     とマークする。

  unlink()/rename()のような他の関連 inode 操作も、同じようなやり方で働き
  ます。したがって、あまりメリットがないため、ここで全ての詳細を扱うこと
  はしません。

  3.8.  実行ドメインとバイナリフォーマット

  Linux は、ディスクからのユーザアプリケーションバイナリの読み込みをサ
  ポートしています。もっと面白いことには、そのバイナリは、他のフォーマッ
  トで保存されていても構いません。プログラムに対するシステムコールを通じ
  たオペレーティングシステムの反応は、他の UNIX に見られるフォーマッ
  ト(COFFなど)をエミュレートし、さらに他のタイプ(Solaris, Unixwareなど)
  のシステムコールの振る舞いをエミュレートするために、必要ならば、標
  準(Linux の振る舞いをする標準)から外れることができます。これが、実行ド
  メインとバイナリフォーマットが必要になる理由になっています。

  各 Linux のタスクは、task_struct(p->personality)のなかにそれぞれのパー
  ソナリティを持っています。現在(正式なカーネルも追加パッチでも含め)存在
  するパーソナリティには、FreeBSD、Solaris、UnixWare、OpenServerのサポー
  トを含み、さらに他のメジャーなオペレーティングシステムもあります。その
  current->personality の値は2つの部分に分かれます。

  1. 高位の3バイト - バグエミュレーション: STICKY_TIMEOUTS,
     WHOLE_SECONDSなど

  2. 低位の1バイト - パーソナリティに固有な番号

  パーソナリティの変更により、私たちはオペレーティングシステムがシステム
  コールを取り扱う方法を変えることができます。たとえば、STICKY_TIMEOUT
  をcurrent->personalityへ与えることで、select(2)システムコールは、残り
  時間を保存するかわりに、最後の引数(タイムアウト)の値を維持します。プロ
  グラムの中には(Linuxにはない)バグのあるオペレーティングシステムに依存
  したバグのあるものが存在するため、ソースコードが入手できずバグを改修で
  きない場合のために、Linux はバグをエミュレートする方法を提供しているの
  です。

  実行ドメインは、パーソナリティの延長線上にあり、一つのモジュールとして
  実装されています。通常一つの実行ドメインは一つのパーソナリティを実装し
  ていますが、なかには、それほど多くない条件を満たすことで一つのモジュー
  ルの中に「閉じた」複数のパーソナリティを実装することもできます。

  実行ドメインはkernel/exec_domain.cに実装され、2.4 カーネルで 2.2.x か
  ら完全に書き直されました。サポートしているパーソナリティの種類に加え、
  カーネルが現在サポートしている実行ドメインのリスト
  は、/proc/execdomainsファイルを読み出すことで得ることができま
  す。PER_LINUX を除く実行ドメインは、動的に読み込まれるモジュールとして
  実装できます。

  ユーザインターフェースは、personality(2)システムコールを通じて、現在の
  プロセスのパーソナリティを設定できるようになっています。あるいは、もし
  引数が存在しないパーソナリティ 0xffffffff であった
  らcurrent->personalityをただ返します。明らかにこのシステムコールの振る
  舞いは、パーソナリティとは独立です。

  実行ドメイン登録のカーネルインターフェースは2つの関数になっています。

  o  int register_exec_domain(struct exec_domain *): 読み書きスピンロッ
     クexec_domain_lockによる書き込み保護のもと、単リンクリスト
     のexec_domainsへ実行ドメインをリンクすることで、登録する。成功すれ
     ば0を返し、失敗なら非0となる。

  o  int unregister_exec_domain(struct exec_domain *): exec_domiansリス
     トからはずすことにより実行ドメインを登録解除する。登録と同
     様、exec_domian_lockスピンロックを書き込みモードで利用する。成功す
     れば0を返す。

  exec_domains_lockが読み書きロックである理由は、登録と登録解除要求のみ
  がリストを書き換え、cat /proc/filesystems
  がfs/exec_domain.c:get_exec_domain_list()を呼ぶことだけが、リストの読
  み出しを行うためです。新しい実行ドメインの登録により、「lcall7 ハンド
  ラ」とシグナル番号の変換マップを定義されます。実際、ABIパッチは、この
  実行ドメインの考え方を拡張して、(ソケットオプションやソケットタイプ、
  アドレスファミリィやエラー番号マップといった)追加の情報を持つようにし
  ています。

  バイナリフォーマットは同じように実装されています。つまり、単リンクリス
  トフォーマットをfs/exec.cで定義し、読み書きロックのbinfmt_lockで保護し
  ます。exec_domains_lockのように、binfmt_lockは、バイナリフォーマットの
  登録や登録解除のときを除くほとんどの場合に読み込みのみになっています。
  新しいバイナリフォーマットを登録することで、core_dump()ができるのと同
  様に、新しいload_binary/load_shlib()関数によって、execve(2)システム
  コールは機能拡張されます。load_binary()メソッドが、execve(2)システム
  コールを実装しているdo_execve()からsearch_binary_handler()によって呼ば
  れているときに、load_shlib()メソッドは古いuselib(2)システムコールに
  よってのみ使わます。

  プロセスのパーソナリティは、若干の発見的手法をつかって対応するフォー
  マットのload_binary()メソッドから読み込まれたバイナリーフォーマットに
  よって決定されます。 UnixWare7バイナリを決定する例としては、ま
  ずelfmark(1)ユーティリティを使って、ELFのへッダーのe_flagsに、マジック
  値 0x314B4455に設定し、印をつけます。このマジック値は、ELFのロード時に
  検出され、current->personalityにPER_UW7を設定します。

  一度パーソナリティ(と、従ってcurrent->exzec_domain)が分かれば、システ
  ムコールは次のように取り扱います。プロセスが lcall7 ゲート命令を使って
  システムコールを発行したとしましょう。これは制御を
  arch/i386/kernel/entry.SのENTRY(lcall7)に移します。なぜならこれ
  は、arch/i386/kernel/traps.c:trap_init()に準備されているためです。適切
  なスタック割り当ての変換のあと、entry.S:lcall7はcurrent から
  exec_domain へのポインタと、そして(アセンブラで4にハードコードされてお
  り、struct exec_domainのCの定義で、handlerメンバを移動できないのである
  が)exec_domain中の lcall7 ハンドラのオフセットを得て、そこへジャンプし
  ます。そうです、Cでは、次のようになります。

       ______________________________________________________________________
       static void UW7_lcall7(int segment, struct pt_regs * regs)
       {
              abi_dispatch(regs, &uw7_funcs[regs->eax & 0xff], 1);
       }
       ______________________________________________________________________

  abi_dispatch()は、関数ポインタの表へのラッパーであり、それはこのパーソ
  ナリティのシステムコールのuw7_funcsを実装したものになっています。

  4.  Linux ページキャッシュ

  この章では、Linux 2.4のページキャッシュについて説明します。ページ
  キャッシュは、名前が示しているように、物理ページのキャッシュです。UNIX
  の世界ではページキャッシュの考え方は SVR4 UNIX の登場により、データI/O
  操作でのバッファーキャッシュを置き換えるようにして良く使われるようにな
  りました。 SVR4 ページキャッシュでは、ファイルシステムのデータキャッ
  シュとしてのみ使われており、したがって vnode 構造体とファイルのオフ
  セットをハッシュパラメータとして使っていますが、 Linux のページキャッ
  シュはより一般的に設計されているため、(以下で説明する)構造体
  address_space を一つ目のパラメタとして使います。 Linux ページキャッ
  シュは、アドレス空間の表記法に強く結び付けられているため、ページキャッ
  シュの働きを理解するには、少なくとも address_space の基本的な理解が必
  要です。 address_space はソフトウエア MMU の一種で、あるオブジェクト (
  例えば inode) のすべてのページを他の対応する値(典型的には物理ディスク
  ブロック)へと対応づけています。構造体 address_spece は
  include/linux/fs.h で以下のように定義されています。

  ______________________________________________________________________
          struct address_space {
                  struct list_head        clean_pages;
                  struct list_head        dirty_pages;
                  struct list_head        locked_pages;
                  unsigned long           nrpages;
                  struct address_space_operations *a_ops;
                  struct inode            *host;
                  struct vm_area_struct   *i_mmap;
                  struct vm_area_struct   *i_mmap_shared;
                  spinlock_t              i_shared_lock;
          };
  ______________________________________________________________________

  address_spaces の働く方法を理解するには、これらのメンバの一部を見るだ
  けでいいでしょう。 clean_pages、dirty_pages、locked_pages は、この
  address_space に属している clean、dirty、locked のそれぞれのページにつ
  いての双方向リンクリストです。また、 nrpages はこの address_space の総
  ページ数になっています。 a_ops は、このオブジェクトのメソッドを定義し
  ており、 host は、この address_space が属する inode へのポインタとなっ
  ています。これは NULL にもなります。例えば、swapper の address_space
  の場合 (mm/swap_state.c) がそれにあたります。

  clean_pages や dirty_pages、それに locked_pages、 nrpages の用法は自明
  だと思いますので、 address_space_operations 構造体をしっかりみていきま
  す。これは同じへッダファイルに定義されており、

       ______________________________________________________________________
               struct address_space_operations {
                       int (*writepage)(struct page *);
                       int (*readpage)(struct file *, struct page *);
                       int (*sync_page)(struct page *);
                       int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
                       int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
                       int (*bmap)(struct address_space *, long);
               };
       ______________________________________________________________________

  となっています。

  address_space と ページキャッシュの基本的な考え方からは、->writepage
  と ->readpageに注目する必要があります。しかし、実際には
  ->prepare_writeと ->commit_writeにも注目する必要があります。

  おそらくその名前からの連想だけで address_space_operations メソッドが何
  を実行するか、当てることができるでしょう。それでも、若干の追加説明は必
  要です。ファイルシステムデータ I/O での使われかたは、ページキャッシュ
  へ通じる王道であり、理解するのにうってつけです。多くの他のUNIX系のオペ
  レーティングシステムと違い、Linux では、ページキャッシュによるデータ
  I/O に対して一般的なファイル操作( SYSV の vnode 操作のサブセット)が可
  能です。これは、read/write/mmap においてはデータが直接ファイルシステム
  と作用せず、可能な限りページキャッシュへの書き込みや読み込みによって行
  われることを意味しています。ページキャッシュは、ユーザがメモリにない
  ページから読み込みたい場合やメモリが少ない場合にディスクへデータを書き
  込みたい場合に、データを実際の低レベルファイルシステムから取得しなけれ
  ばなりません。

  読み込みパスでは、一般的なメソッドでは、最初に必要とするinode/インデッ
  クスタプルに対応するページを見つけようとします。

       hash = page_hash(inode->i_mapping, index);

  そしてどのページが実際に存在しているかテストします。

       hash = page_hash(inode->i_mapping, index); page =
       __find_page_nolock(inode->i_mapping, index, *hash);

  もし存在しないときは新しいフリーページを割り当てページキャッシュへ追加
  します。

       page = page_cache_alloc(); __add_to_page_cache(page, mapping, index,
       hash);

  ページがハッシュされたら、->readpage アドレス空間操作を使って、ページ
  を実際にデータで埋め(ファイルはinodeのオープンインスタンス)ます。

       error = mapping->a_ops->readpage(file, page);

  最後にデータをユーザスペースにコピーします。

  ファイルシステムに書くには2つのパスがあります。1つはマップ(mmap)に書き
  込むこと、もう一つは write(2) のようなシステムコールを用いることで
  す。mmap の場合がとてもシンプルなことから、まず最初に取り上げることに
  します。ユーザがマップを変更するとき、VMサブシステムはページを dirty
  とマークします。

       SetPageDirty(page);

  バックグランドでの活動として、あるいはメモリが少なくなったのを理由とし
  て、ページを解放しようとする bdflush カーネルスレッドは、dirty とマー
  クされたページの ->writepage を呼ぼうとします。->writepage メソッド
  は、ページの内容をディスクに書出し、ページを解放しなければなりません。

  2つめの書き込みパスは、 *とても*たいへん複雑です。ユーザが書き込む各
  ページに対して、基本的に以下のことを行います(コードの全体は
  mm/filemap.c:generic_file_write() を参照)。

       page = __grab_cache_page(mapping, index, &cached_page); map-
       ping->a_ops->prepare_write(file, page, offset, offset+bytes);
       copy_from_user(kaddr+offset, buf, bytes); mapping->a_ops->com-
       mit_write(file, page, offset, offset+bytes);

  最初に、ハッシュされたページを見つけようとするか新しいページを割り当て
  ようとします。そして、->prepare_write アドレス空間メソッドを呼び出し、
  ユーザバッファをカーネルメモリにコピーし、そして最後に ->commit_write
  メソッドを呼び出します。おそらく ->prepare_write と ->commit_write
  は、->readpage と ->writepage からは基本的に異なるものに見えるでしょ
  う。これは物理 I/O を実際に求められた時だけではなく、ユーザがファイル
  を変更したときにはいつも呼び出されるからです。これを扱う方法は2つ(ある
  いはそれ以上?)あります。一つめは page->buffers ポインタを
  try_to_free_buffers(fs/buffers.c) で使われている buffer_heads で埋める
  ことで、物理 IO の遅延に Linux バッファーキャッシュを使います。もう一
  つの方法は、ページを dirty に単に設定し、後の全てを ->writepageにまか
  せることです。ページ構造体に有効なビットマップが欠けているた
  め、PAGE_SIZEより小さいgranualityをもっているファイルシステムでは働き
  ません。

  5.  IPC機構

  本章ではLinux 2.4 カーネルで実装されている IPC 機構のセマフォ、共有メ
  モリおよびメッセージキューについて記述します。四つの節で構成され、最初
  の三つの節では``セマフォ''と ``メッセージキュー''、``共有メモリ''を順
  に取り上げて、インターフェースとサポート関数を説明します。また、 ``最
  後''の節では、三つの機構で共有される共通関数群とデータ構造を説明しま
  す。

  5.1.  セマフォ

  この節で説明している関数はユーザレベルのセマフォ機構の実装になっていま
  す。この実装部分はカーネルスピンロックとカーネルセマフォの利用に頼って
  いることに注意しましょう。混乱を避けるため、カーネルのセマフォを参照す
  るときは「カーネルセマフォ」と呼ぶことにします。その他の単に「セマ
  フォ」というときは、ユーザレベルのセマフォを指すことにします。

  5.1.1.  セマフォシステムコールのインターフェース

  5.1.1.1.  sys_semget()

  全てのsys_semget()への呼出しは、カーネルグローバルな``sem_ids.sem''
  カーネルセマフォで保護されます。新しいセマフォのセットを作らなければな
  らない場合は、``newary()'' 関数が呼ばれて、新しいセマフォセットの作成
  と初期化をします。そして新しいセットID が呼出し元に戻されます。

  キーの値が既存のセマフォセットに対して発行されたものだった場合
  は、``ipc_findkey()'' が呼び出され、相当するセマフォディスクリプタ配列
  インデックスを見つけます。パラメータと呼出し元のパーミッションは、セマ
  フォのセットIDを返却する前に確認されます。

  5.1.1.2.  sys_semctl()

  ``IPC_INFO''と ``SEM_INFO'' そして ``SEM_STAT'' コマンドについては、必
  要な関数処理を行う ``semctl_nolock()'' を呼び出します。

  ``GETALL''、 ``GETVAL''、 ``GETPID''、``GETNCNT''、 ``GETZCNT''、
  ``IPC_STAT''、 ``SETVAL''、``SETALL'' コマンドについては、必要な関数処
  理を行う ``semctl_main()'' を呼び出します。

  ``IPC_RMID''や``IPC_SET'' コマンドについては、必要な関数処理を行う
  ``semctl_down()'' を呼び出します。両方の演算の間は、グローバルな
  ``sem_ids.sem'' カーネルセマフォを保持します。

  5.1.1.3.  sys_semop()

  呼出し引数の有効性を確認した後、セマフォの操作データをユーザ空間から一
  時バッファへコピーします。小さい一時バッファで大きさが十分であればス
  タックバッファが使われます。不十分なら、大きいバッファが割り当てられま
  す。セマフォの操作データのコピーが終わった後に、グローバルなセマフォス
  ピンロックをロックします。そしてユーザが指定したセマフォセットIDが確認
  されます。セマフォセットへのパーミッションも有効か確認されます。

  ユーザが指定したセマフォ操作が全て解析されます。この処理の間、カウント
  は SEM_UNDO フラグセットをもつ全ての操作を保持します。セマフォ値を減算
  する操作のときには、 decreaseフラグが設定されます。そして、セマフォの
  値を変更する(つまり増加もしくは減少する)もの全てについて、alter フラグ
  が設定されます。そして変更される各セマフォの値が有効か確認されます。

  もし、これらの操作のいずれかがセマフォの状態を変化させるのであれば、こ
  のセマフォセットに関連する undo 構造体が 現在のタスクの undo リストか
  ら探されます。この検索の間、もし undo 構造体の任意のセマフォセットIDが
  -1 であると分かったならば、``freeundos()'' が呼ばれ、 undo 構造体が解
  放されてリストから削除されます。もし、undo 構造体がこのセマフォセット
  について見つからなければ、 ``alloc_undo()'' が呼ばれて割り当ておよび初
  期化が行われます。

  一連の操作を実行するために  ``try_atomic_semop()'' 関数を do_undo パラ
  メータを 0 にして呼び出します。返り値は、操作が処理されたか失敗した
  か、あるいはブロックの必要があったため実行されなかったかを示していま
  す。各々の場合を以下に示します。

  5.1.1.3.1.  非ブロックセマフォ操作

  ``try_atomic_semop()'' 関数は、一連の全ての操作が成功したときに、ゼロ
  を返します。この場合には、``update_queue()'' が呼び出され、セマフォ
  セットのうち保留されているセマフォの操作のキューを走査して、もうブロッ
  クの必要がない休止タスクを起こします。この処理によって、この場合の
  sys_semop() システムコールの実行が完了します。

  5.1.1.3.2.  セマフォ操作の失敗

  もし、``try_atomic_semop()''が負の値を返した場合、障害状態に遭遇したこ
  とになります。この場合、操作は全て実行されません。これは、セマフォ操作
  が、無効なセマフォ値になったり、IPC_NOWAITにマークづけられた操作が実行
  完了できなかった場合に起ります。エラー状態はその後、sys_semop()の呼出
  し元に返されます。

  sys_memop()から戻る前に、セマフォセットの保留されたセマフォ操作の
  キューを走査するため update_queue() を呼出し、もうブロックの必要のない
  休止タスクを起こします。

  5.1.1.3.3.  ブロッキングセマフォ操作

  ``try_atomic_semop()''関数は、セマフォ操作の一つがブロックされ、一連の
  セマフォ操作が実行されなかった場合には 1 を返します。この場合、新し
  い``sem_queue''の要素がこれらのセマフォ操作を含めて初期化されます。も
  し、これらの操作のいずれかがセマフォの状態を変化させそうであれば、新し
  いキューの要素はキューの末尾に追加されます。そうでなければ新しいキュー
  の要素がキューの先頭に挿入されます。

  現在のタスクのsemsleeping要素は、タスクがこの``sem_queue'' 要素で休止
  されているかどうかを示しています。現在のタスクが TASK_INTERRUPTIBLE と
  マークされ、``sem_queue''のsleeper要素がこのタスクが休止していることを
  示すため設定されます。そしてグローバルセマフォスピンロックを解除し、現
  在のタスクを休止するためschedule()を呼び出します。

  起こされたとき、なぜ起こされたか、どのように反応すべきかを判断するた
  め、タスクはグローバルセマフォロックを再ロックします。以下のような場合
  が処理されます。

  o  もし、セマフォセットが削除されたなら、システムコールは、EIDRMで失敗
     する。

  o  もし、 ``sem_queue'' 構造体の status 要素が 1 に設定されていたら、
     タスクはセマフォ操作を再び試みるために起こされた。もう1
     度``try_atomic_semop()'' 呼出しが一連のセマフォ操作の実行を行う。も
     し try_atomic_sweep() が 1 を返すなら、タスクは上記のように再度ブ
     ロックされなければならない。そうでなければ成功して 0 が戻ってくる
     か、失敗した場合は適切なエラーコードが返される。

     sys_semop() が戻る前に、current->semsleeping がクリアされる。そし
     て、 ``sem_queue''は、キューから削除される。もし指定されたセマフォ
     操作がいずれも変更の操作(増分、減分)なら、``update_queue()'' が、セ
     マフォセットの保留されたセマフォ操作のキューを走査するために呼び出
     され、もうブロックの必要のない休止タスクを起こす。

  o  もし、``sem_queue'' 構造体の status 要素が 1 で 「ない」うえに
     ``sem_queue'' 要素がキューからはずされなければ、タスクは割り込みに
     より起こされたことになる。この場合システムコールは失敗し EINTR を返
     す。戻る前に current->semsleeping はクリアされ、``sem_queue''は
     キューから削除される。さらに、もし変更を加える操作があったら
     ``update_queue()'' も呼び出される。

  o  もし、``sem_queue'' 構造体の status 要素が1に設定されて『な
     く』、``sem_queue'' 要素がキューから削除されてなければ、セマフォ操
     作は既に ``update_queue()'' により実行されている。キューstatusは、
     成功すれば0に、失敗時に負のエラーコードになり、システムコールの返り
     値になる。

  5.1.2.  セマフォ独自にサポートする構造体

  以下の構造体は、セマフォのサポートに特に使われます。

  5.1.2.1.  struct sem_array

       ______________________________________________________________________
       /* One sem_array data structure for each set of semaphores in the system. */
       struct sem_array {
           struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */
           time_t sem_otime; /* last semop time */
           time_t sem_ctime; /* last change time */
           struct sem *sem_base; /* ptr to first semaphore in array */
           struct sem_queue *sem_pending; /* pending operations to be processed */
           struct sem_queue **sem_pending_last; /* last pending operation */
           struct sem_undo *undo; /* undo requests on this array * /
           unsigned long sem_nsems; /* no. of semaphores in array */
       };
       ______________________________________________________________________

  5.1.2.2.  struct sem

       ______________________________________________________________________
       /* One semaphore structure for each semaphore in the system. */
       struct sem {
               int     semval;         /* current value */
               int     sempid;         /* pid of last operation */
       };
       ______________________________________________________________________

  5.1.2.3.  struct seminfo

       ______________________________________________________________________
       struct  seminfo {
               int semmap;
               int semmni;
               int semmns;
               int semmnu;
               int semmsl;
               int semopm;
               int semume;
               int semusz;
               int semvmx;
               int semaem;
       };
       ______________________________________________________________________

  5.1.2.4.  struct semid64_ds

       ______________________________________________________________________
       struct semid64_ds {
               struct ipc64_perm sem_perm;             /* permissions .. see
       ipc.h */
               __kernel_time_t sem_otime;              /* last semop time */
               unsigned long   __unused1;
               __kernel_time_t sem_ctime;              /* last change time */
               unsigned long   __unused2;
               unsigned long   sem_nsems;              /* no. of semaphores in
       array */
               unsigned long   __unused3;
               unsigned long   __unused4;
       };
       ______________________________________________________________________

  5.1.2.5.  struct sem_queue

       ______________________________________________________________________
       /* One queue for each sleeping process in the system. */
       struct sem_queue {
               struct sem_queue *      next;    /* next entry in the queue */
               struct sem_queue **     prev;    /* previous entry in the queue, *(q->pr
       ev) == q */
               struct task_struct*     sleeper; /* this process */
               struct sem_undo *       undo;    /* undo structure */
               int                     pid;     /* process id of requesting process */
               int                     status;  /* completion status of operation */
               struct sem_array *      sma;     /* semaphore array for operations */
               int                     id;      /* internal sem id */
               struct sembuf *         sops;    /* array of pending operations */
               int                     nsops;   /* number of operations */
               int                     alter;   /* operation will alter semaphore */
       };
       ______________________________________________________________________

  5.1.2.6.  struct sembuf

       ______________________________________________________________________
       /* semop system calls takes an array of these. */
       struct sembuf {
               unsigned short  sem_num;        /* semaphore index in array */
               short           sem_op;         /* semaphore operation */
               short           sem_flg;        /* operation flags */
       };
       ______________________________________________________________________

  5.1.2.7.  struct sem_undo

       ______________________________________________________________________
       /* Each task has a list of undo requests. They are executed automatically
        * when the process exits.
        */
       struct sem_undo {
               struct sem_undo *       proc_next;      /* next entry on this process */
               struct sem_undo *       id_next;        /* next entry on this semaphore set */
               int                     semid;          /* semaphore set identifier */
               short *                 semadj;         /* array of adjustments, one per
        semaphore */
       };
       ______________________________________________________________________

  5.1.3.  セマフォサポート関数

  以下の関数は特にセマフォのサポートのために使われます。

  5.1.3.1.  newary()

  newary() は、新しいセマフォセットに必要となるメモリを割り当てるため
  に、 ``ipc_alloc()''を利用しています。そして、セマフォセット記述子と
  セット中の各セマフォの全てに十分なメモリを割り当てます。割り当てられた
  メモリはクリアされ、セマフォセットディスクリプタの最初の要素のアドレス
  が、``ipc_addid()'' へと渡されます。``ipc_addid()'' は、新しいセマフォ
  セット記述子のエントリ配列を予約し、データ(``struct kern_ipc_perm'')を
  初期化します。グローバルなused_sems 変数は、新しいセットのセマフォ数で
  更新されて、新しいセットのためのデータ(``struct kern_ipc_perm'')の初期
  化が完了します。このセットに関わる他の初期化は以下の通りです。

  o  セットのsem_base 要素が、新たに割り当てられたデータの(``struct
     sem_array'')のすぐあとのアドレスに初期化される。これは、セット中の
     最初のセマフォの場所に対応する。

  o  sem_pendingキューは、空に初期化される。

  ``ipc_addid()''の呼出しに続く全ての操作は、グローバルセマフォスピン
  ロックを保持した状態で実行します。グローバルセマフォスピンロックをアン
  ロックした後に、newary() は、(sem_buildid()経由で)``ipc_buildid()'' を
  呼出します。この関数は、セマフォセットディスクリプタのインデックスを使
  用し、ユニークな ID を生成します。そして、newary() の呼出し元に戻りま
  す。

  5.1.3.2.  freeary()

  freeary() は ``semctl_down()''によって呼び出され、以下に列挙する機能を
  実行します。グローバルなセマフォスピンロックがロックされた状態で呼び出
  され、スピンロックが解除された状態で戻されます。

  o  セマフォセットの ID を削除しセマフォセットへのポインタを回収するた
     め、 ``ipc_rmid()''  関数が、(sem_rmid()ラッパー経由で)呼び出され
     る。

  o  セマフォセット undo リストの有効性が確認される。

  o  全ての保留プロセスが起こされ、EIDRM の失敗を引き起こす。

  o  使用しているセマフォ数のカウンタは、削除したセットのセマフォ数分減
     らされる。

  o  セマフォセットに割り当てられたメモリを解放する。

  5.1.3.3.  semctl_down()

  semctl_down() は、 semctl()システムコールの ``IPC_RMID'' と
  ``IPC_SET'' 操作を提供します。セマフォセットIDとパーミッションは、これ
  らの操作に先立って確認され、どちらの場合も、操作の間はグローバルセマ
  フォスピンロックを保持します。

  5.1.3.3.1.  IPC_RMID

  IPC_RMID 操作では、セマフォセットの削除を行うため、 ``freeary()''を呼
  び出します。

  5.1.3.3.2.  IPC_SET

  IPC_SET 操作では、セマフォセットのuid、gid、modeおよびctime 要素を更新
  します。

  5.1.3.4.  semctl_nolock()

  semctl_nolock() は ``sys_semctl()'' から呼び出され、IPC_INFO、SEM_INFO
  および SEM_STAT 機能を実行します。

  5.1.3.4.1.  IPC_INFO と SEM_INFO

  IPC_INFO と SEM_INFO は、臨時に ``seminfo'' バッファを初期化し、変更さ
  れなかったセマフォの統計データを読み込ませます。そして、グローバル
  のsem_ids.sem カーネルセマフォを確保し、 ``seminfo'' 構造体の semusz
  と semaem 要素を与えられた(IPC_INFO または SEM_INFO)コマンドに従い更新
  します。システムコールの返り値は、セマフォセットIDの最大値になります。

  5.1.3.4.2.  SEM_STAT

  SEM_STAT は、臨時に``semid64_ds''バッファを初期化します。その後、グ
  ローバルセマフォスピンロックを保持し、sem_otime、sem_ctimeとsem_nsems
  の値をバッファにコピーします。このデータはその後ユーザ空間にコピーされ
  ます。

  5.1.3.5.  semctl_main()

  semctl_main() は、 ``sys_semctl()'' により呼び出され、以下の節で示して
  いる多くのサポートする機能を実行します。全ての操作に先だっ
  て、semctl_main() は、グローバルセマフォロックをロックし、セマフォセッ
  トIDとパーミッションの有効性を確認します。スピンロックは戻る前に解除さ
  れます。

  5.1.3.5.1.  GETALL

  GETALL 操作は、現在のセマフォ値を一時的なカーネルバッファへ読み込み、
  ユーザ空間へコピーします。もしセマフォバッファが小さければ、小さなス
  タックバッファが使われます。そうでなければ、スピンロックが一時的に落と
  され、大きなバッファを確保します。セマフォ値を一時バッファへコピーする
  間、スピンロックを保持します。

  5.1.3.5.2.  SETALL

  SETALL 操作は、セマフォ値をユーザ空間からカーネルの一時バッファへコ
  ピーし、その後セマフォセットへコピーします。一時バッファへユーザ空間か
  ら値をコピーする間、および正しい値か確認する間、スピンロックを落としま
  す。もしセマフォセットが小さければ、スタックバッファが使われ、そうでな
  ければ大きなバッファが確保されます。以下の操作をセマフォセットへ行う
  間、スピンロックを再取得し、保持します。

  o  セマフォ値がセマフォセットへコピーされる。

  o  セマフォセットのためのアンドゥキューにあるセマフォ修正はクリアされ
     る。

  o  セマフォセットのsem_ctime 値が設定される。

  o  ``update_queue()'' 関数が呼び出され、保留している semops のキューを
     走査し、SETALL 操作の結果として完了できるタスクを探す。もうブロック
     されていない全ての保留中タスクが起こされる。

  5.1.3.5.3.  IPC_STAT

  IPC_STAT 操作では、sem_otime、sem_ctimeとsem_nsems 値をスタックバッ
  ファへコピーする。そのデータは、その後スピンロックを落としてからユーザ
  空間へコピーする。

  5.1.3.5.4.  GETVAL

  エラーのない場合、GETVALでは、システムコールの返り値に指定したセマフォ
  の値が設定される。

  5.1.3.5.5.  GETPID

  エラーのない場合、GETPIDでは、システムコールの返り値にセマフォでの最後
  の操作に対応する pid を設定します。

  5.1.3.5.6.  GETNCNT

  エラーのない場合、GETNCNT ではシステムコールの返り値に、セマフォが 0
  未満になるのを待っているプロセスの数を設定します。この数値
  は``count_semncnt()'' 関数が計算します。

  5.1.3.5.7.  GETZCNT

  エラーのない場合、GETZCNT ではシステムコールの返り値に、セマフォが0に
  設定されるのを待っているプロセスの数を設定します。この数値
  は``count_semzcnt()'' 関数が計算します。

  5.1.3.5.8.  SETVAL

  新しいセマフォ値の有効性を確認したあと、以下の機能が実行されます。

  o  このセマフォの修正がないかどうか、undo キューを検索する。

  o  セマフォ値に、与えられた値をセットする。

  o  セマフォセットの sem_ctime 値を更新する。

  o  ``update_queue()''関数が呼ばれ、保留 semops キューを走査して、
     ``SETVAL'' 操作の結果として完了できるタスクを検索する。

  5.1.3.6.  count_semncnt()

  count_semncnt() は、セマフォ値が 0 より小さくなるのを待っているタスク
  数を数えます。

  5.1.3.7.  count_semzcnt()

  count_semzcnt() は、セマフォ値が 0 になるのを待っているタスク数を数え
  ます。

  5.1.3.8.  update_queue()

  update_queue() は、セマフォセットの保留中 semops キューを走査して、
  ``try_atomic_semop()'' を呼び出し、どのセマフォ操作のシーケンスが成功
  しそうかを判断します。もしキュー要素の状態により、ブロックされたタスク
  がすでに起こされていることが示されていたら、そのときはそのキュー要素は
  スキップされます。キューのその他の要素では、q-alter フラグが
  ``try_atomic_semop()'' の undo パラメータとして渡され、変更の操作から
  戻る前に実行される操作を表します。

  もし一連の操作がブロックされるなら、update_queue() は一切変更されるこ
  となく戻ります。

  もしセマフォ操作の一つが無効なセマフォ値を引き起こしたり、IPC_NOWAIT
  とマークされている操作が完了できなかったならば、一連の操作は失敗しま
  す。そのような場合は、セマフォ操作によりブロックされていたタスクは起こ
  され、キューの状態として適切なエラーコードが設定されます。キュー要素は
  もちろんキューから削除されます。

  もし一連の操作が変更を生じないならば、``try_atomic_semop()'' の undo
  パラメータとして 0 を渡します。もしそれらの操作が成功すれば、処理は完
  了したと見なして、キューから削除します。ブロックされたタスクが起こさ
  れ、キュー要素のstatusが成功を示すよう設定されます。

  もしセットの操作がセマフォの値を変更する場合で、処理が成功できるなら、
  もうブロックされる必要のない休止タスクが起こされます。操作が実行されな
  くても、キュー要素がキューから削除されることはありません。セマフォ操作
  は、起こされたタスクによって実行されます。

  5.1.3.9.  try_atomic_semop()

  try_atomic_semop() は、``sys_semop()'' と``update_queue()'' により呼び
  出され、一連のセマフォ操作が全て成功するかどうか判断します。これは、各
  操作の実行を試みることによって判断が行われます。

  もしブロックされた操作に遭遇したなら、プロセスはアボートし、全ての操作
  は差し戻されます。もしIPC_NOWAIT が設定されていたなら -EAGAIN が返され
  ます。そうでなければ 1 が返され、一連のセマフォ操作がブロックされたこ
  とを示します。

  もしセマフォ値がシステムの制限を越えて変更されるならば、全ての操作は差
  し戻され、 -ERANGE が返されます。

  もしシーケンス中の全ての操作が成功しても、do_undoパラメータが0でなけれ
  ば、全ての操作が差し戻されて、0 が返されます。もし do_undo パラメータ
  が 0 なら、全ての操作が成功し、強制的に残され、セマフォセット
  のsem_otime メンバが更新されます。

  5.1.3.10.  sem_revalidate()

  sem_revalidate() は、グローバルセマフォスピンロックが一時的に落とさ
  れ、再度ロックしなければならないときに呼び出されます。 つま
  り、``semctl_main()'' と ``alloc_undo()'' によって呼び出されます。セマ
  フォIDとパーミッションの有効性を確認し、良ければグローバルセマフォスピ
  ンロックをロックして戻ります。

  5.1.3.11.  freeundos()

  freeundos() は、プロセスの undo リストを走査し、要求された undo 構造体
  を探します。もし見つかれば、undo 構造体はリストから削除され、解放され
  ます。そして、プロセスリストの次の undo 構造体へのポインタが返されま
  す。

  5.1.3.12.  alloc_undo()

  alloc_undo() は、グローバルセマフォスピンロックを獲得して呼ばれること
  を期待しています。エラーがあった場合は、スピンロックを解除して戻りま
  す。

  グローバルセマフォスピンロックが解除され、 ``sem_undo''構造体と、セッ
  ト中の各セマフォの修正値の配列の両方に十分なメモリを確保するた
  め、kmalloc()を呼び出します。成功すれば、グローバルスピンロック
  が``sem_revalidate()''を呼び出すことで再確保されます。

  新しい semundo 構造体が初期化され、この構造体のアドレスが呼出し元に
  よって与えられたアドレスへ格納されます。新しい undo 構造体は、現在のタ
  スクの undo リストの先頭へ位置させられます。

  5.1.3.13.  sem_exit()

  sem_exit() は、do_exit() により呼び出され、存在するタスクの全ての undo
  修正を実行する責任があります。

  もし現在のプロセスがセマフォでブロックされているなら、 グローバルセマ
  フォスピンロックを保持して ``sem_queue''リストから削除します。

  そして現在のタスクのための undo リストが走査され、リストの各要素の処理
  毎にグローバルセマフォスピンロックの取得と解除が行われつつ、以下の操作
  が実行されます。以下の操作は、各 undo 要素ごとに実行されます。

  o  undo 構造体とセマフォセット ID について有効性を確認する。

  o  関連するセマフォセットの undo リストから、同じ undo 構造体への参照
     が検索され、リストからそれを削除する。

  o  undo 構造体に示されている修正をセマフォセットへ適用する。

  o  セマフォセットのsem_otime パラメータが更新される。

  o  ``update_queue()'' が呼び出され、保留中の semops のキューを走査し
     て、もうアンドゥ操作の実行の結果としてブロックする必要のない休止タ
     スクを起こす。
  o  undo 構造体を解放する。

  リストの処理が完了したならば、current->semundo の値をクリアします。

  5.2.  メッセージキュー

  5.2.1.  メッセージシステムコールインターフェース

  5.2.1.1.  sys_msgget()

  sys_msgget()へのコール全体は、グローバルメッセージキューセマ
  フォ(``msg_ids.sem'')により保護されています。

  新しいメッセージキューを作らなければならない場合、``newque()''関数が呼
  び出され、新しいメッセージキューの作成と初期化を行い、新しいキューIDを
  呼出し元に返します。

  もし与えられたキーの値が既存のメッセージキューへなら、
  ``ipc_findkey()''が呼び出され、グローバルメッセージキューデスクリプタ
  配列(msg_ids.entries)の対応するインデックスを探します。呼出し元のパラ
  メータとパーミッションは、メッセージキューIDを戻す前に確認されます。検
  索操作と確認は、グローバルメッセージキュースピンロック(msg_ids.ary)が
  保持された状態で行われます。

  5.2.1.2.  sys_msgctl()

  sys_msgctl()へ渡されるパラメータは、メッセージキューID(msqid)、操
  作(cmd)、タイプ``msgid_ds''のユーザ空間バッファへのポインタ(buf)です。
  6 つの操作がこの関数で提供されています。IPC_INFO, MSG_INFO, IPC_STAT,
  MSG_STAT, IPC_SET, IPC_RMIDです。メッセージキューIDと操作パラメータが
  確認され、操作(cmd)が以下のように実行されます。

  5.2.1.2.1.  IPC_INFO (または MSG_INFO)

  グローバルメッセージキューの情報がユーザ空間にコピーされます。

  5.2.1.2.2.  IPC_STAT (または MSG_STAT)

  タイプ ``struct msqid64_ds'' の一時バッファが初期化され、グローバル
  メッセージキュースピンロックを取得します。呼び出したプロセスのアクセス
  権を確認したら、メッセージキューIDに対応しているメッセージキューの情報
  を、一時バッファへ読み込みます。そしてメッセージキュースピンロックを解
  除します。一時バッファの内容は、ユーザ空間へ``copy_msqid_to_user()''
  により書き出します。

  5.2.1.2.3.  IPC_SET

  ユーザデータは、``copy_msqid_to_user()''によりコピーされます。グローバ
  ルメッセージキューセマフォとスピンロックはこのとき取得され、最後に解除
  されます。メッセージキューID と現在のプロセスのアクセス権の有効性が確
  認された後、メッセージキューの情報がユーザから与えられたデータで更新さ
  れます。その後、 ``expunge_all()'' と ``ss_wakeup()'' が呼び出され、全
  てのメッセージキューの受け手と送り手のウエイトキューで休止しているプロ
  セスを起こします。これは、そのときには受け手が厳密なアクセス権により排
  除されていたり、送り手がキューサイズが増したことによりメッセージの送信
  が可能になる場合があるためです。

  5.2.1.2.4.  IPC_RMID

  グローバルメッセージキューセマフォが取得され、グローバルメッセージ
  キュースピンロックがロックされます。メッセージキューIDと現在のタスクの
  アクセス権の有効性が確認されたあと、``freeque()''が呼び出されてメッ
  セージキューIDに対応づけられているリソースを解放します。そして、グロー
  バルメッセージキューセマフォとスピンロックが解除されます。

  5.2.1.3.  sys_msgsnd()

  sys_msgsng() は、パラメータとしてメッセージキューID(msqid)、``struct
  msg_msg'' タイプのバッファへのポインタ(msgp)、送るメッセージのサイ
  ズ(msgsz)と、待ちか否かを示すフラグ(msgflg)を受け取ります。メッセージ
  キューIDに対応するのは、2つのタスクウエイトキューと、1つのメッセージウ
  エイトキューがあります。受け手ウエイトキューにこのメッセージを待ってい
  るタスクがあれば、メッセージは直接受け手へ届けられ、受け手は起こされま
  す。そうでなければ、メッセージウエイトキューに十分な余裕があれば、メッ
  セージはこのキューへ格納されます。結局は、送り手のタスクは自身を送り手
  ウエイトキューへいれます。sys_msgsnd()が処理する操作のより詳細な内容は
  次のようになります。

  1. ユーザバッファアドレスとメッセージタイプの有効性を確認し、その後
     ``load_msg()'' を呼び出して、ユーザメッセージの内容を、``struct
     msg_msg''タイプの一時オブジェクトmsgへ読み込みます。 msgのメッセー
     ジタイプとメッセージサイズ領域も初期化されます。

  2. グローバルメッセージキュースピンロックをロックして、メッセージ
     キューIDに対応づけられるメッセージキュー記述子を取得します。そのよ
     うなメッセージキューが存在しなければ、EINVAL を返します。

  3. メッセージキューIDが有効であるか確認するため、(msg_checkid()経由
     で)``ipc_checkid()''を呼び出します。そして、``ipcperms()'' を呼び出
     して、呼出し元のプロセスのアクセス権をチェックします。

  4. メッセージのサイズとメッセージウエイトキューの残りサイズをチェック
     して、メッセージを格納するのに十分余裕があるかどうか調べます。もし
     不十分であれば、以下の副項目を実施します。

     a. もし IPC_NOWAIT がmsgflg に指定されていれば、グローバルメッセー
        ジキュースピンロックが解除され、メッセージ用のメモリ領域が解放さ
        れて、EAGAIN を返します。

     b. ``ss_add()'' を起動し、現在のタスクを送り元ウエイトキューへとい
        れます。これはまた、グローバルキュースピンロックを解除
        し、schedule() を呼び出して、現在のタスクを休止します。

     c. 起こされたら、再度グローバルスピンロックを取得し、メッセージ
        キューIDがまだ有効か確認します。もしメッセージキューIDが有効でな
        ければ、ERMID を返します。

     d. ``ss_del()'' を起動し、送り元ウエイトキューから送り手のタスクを
        削除します。もしタスクに保留されたシグナルがあれば、
        sys_msgsnd() はグローバルスピンロックを解除し、 ``free_msg()''
        を起動して、メッセージバッファを解放してEINTR を返します。そうで
        なければ、関数は ``戻り''、メッセージウエイトキューに十分な余裕
        があるか再度チェックします。

  5. ``pipelined_send()'' を起動して、メッセージを待っている受け手に直接
     送ります。

  6. もしこのメッセージを待つ受け手がなければ、msg をメッセージウエイト
     キュー(msg->q_messages) へ格納します。

     大域変数のmsg_bytes や msg_hdrs はメッセージに使われる総バイト数や
     システム全体のメッセージ数を意味していますが、 これらと同様、メッ
     セージキュー記述子の q_cbytes と q_qnum 領域も更新します。メッセー
     ジの送信が成功したり、キューにいれられた場合は、メッセージキュー記
     述子のq_lspidとq_stime 領域を更新し、グローバルメッセージスピンロッ
     クを解除します。
  5.2.1.4.  sys_msgrcv()

  sys_msgrcv() 関数は、パラメータとしてメッセージ
  キューID(msqid)、``msg_msg'' 型のバッファへのポインタ(msgp)、希望する
  メッセージのサイズ(msgsz)とフラグ(msgflg)を受け取ります。メッセージ
  キューIDに関連するメッセージウエイトキューを検索し、要求する型にマッチ
  するキューの最初のメッセージを見つけ、与えられたユーザバッファへコピー
  します。もし、そのようなメッセージが、メッセージウエイトキューに見つか
  らなかったら、要求元のタスクは希望するメッセージがくるまで受け手ウエイ
  トキューへ入れられます。ss_msgrcv() による操作の、より詳細な説明は以下
  の通りです。

  1. まず、msgtypから検索モードから派生して``convert_mode()''を起動す
     る。そして sys_msgrcv() はグローバルメッセージキュースピンロックを
     ロックする。そしてメッセージキューID に対応するメッセージキュー記述
     子を得る。もしメッセージキューがなければ EINVAL を返す。

  2. 現在のタスクがメッセージキューをアクセスするパーミッションを持って
     いるかどうかチェックします。

  3. メッセージウエイトキューの最初のメッセージから順に ``testmsg()'' を
     呼び出し、そのメッセージタイプが要求されたタイプと一致するか確認す
     る。 sys_msgrcv() は、適合するメッセージが見つかるか、全てのウエイ
     トキューを探し尽くすまで検索を継続する。もし検索モードが
     SEARCH_LESSEQUAL であれば、msgtyp 以下で最も小さいタイプのキューの
     最初のメッセージを探す。

  4. もしメッセージが見つかったら、 sys_msgrcv() は以下のサブステップを
     実行する。

     a. もしメッセージサイズが想定されたサイズより大きく、msgflg がエ
        ラー許容なしを示していたら、グローバルメッセージキュースピンロッ
        クを解除し、E2BIG を返す。

     b. メッセージウエイトキューからメッセージを削除し、メッセージキュー
        の統計情報を更新する。

     c. 送り手ウエイトキューで休止している全てのタスクを起こす。前のス
        テップでキューからメッセージを削除したことで、送り手の中には処理
        を継続できるものがあるからだ。そして、``最後のステップ''へと進
        む。

  5. もしメッセージウエイトキューに受け手の基準に合致するメッセージが見
     つからなかったら、msgflgが確認される。もし IPC_NOWAIT が設定されて
     いたら、グローバルメッセージキュースピンロックを解除し、ENOMSG を返
     す。そうでなければ、受け手は以下のように受け手ウエイトキューへ入れ
     られる。

     a. ``msg_receiver'' データ構造体の msr を割り当て、ウエイトキューの
        先頭に追加する。

     b. msr の r_tsk メンバに現在のタスクを設定する。

     c. r_msgtype と r_mode メンバは初期化され、メッセージのタイプとモー
        ドをそれぞれ要求された値にする。

     d. もし msgflg が MSG_NOERROR となっていたら、 msr の r_maxsize メ
        ンバは msgsz の値が設定される。そうでなければ INT_MAX が設定され
        る。

     e. メンバ r_msg はメッセージをまだ受け取っていない状態を表すように
        初期化される。
     f. 初期化の完了後、受け手のタスクの状態は TASK_INTERRUPTIBLE に設定
        される。そしてグローバルメッセージキュースピンロックは解除され、
        schedule() が呼び出される。

  6. 受け手が起きたあと、msr の r_msg メンバ がチェックされる。このメン
     バはパイプラインのメッセージの格納に使われ、エラーの場合にはエラー
     状態が格納される。もし、r_msg メンバが要求されたメッセージでいっぱ
     いになったら、``最後のステップ'' へ進む。それ以外の場合は、グローバ
     ルメッセージキュースピンロックが再度ロックされる。

  7. スピンロックの取得後、r_msg メンバは、スピンロックを待っている間に
     メッセージが受け取られていないか、再度確認される。もしメッセージが
     受け取られているなら、``最後のステップ'' へ進む。

  8. もし、r_msg メンバが変更されていないなら、再試行のためタスクが起こ
     される。この場合、msr はキューから削除される。もしタスクに保留され
     たシグナルがあれば、グローバルメッセージキュースピンロックは解除さ
     れ、 EINTR が返される。それ以外では、関数は``戻って'' 、再試行され
     る。

  9. もしr_msgメンバにより、休止中にエラーが発生したことが分かれば、グ
     ローバルメッセージキュースピンロックは解除され、エラーが返される。

  10.

     ユーザバッファのアドレスmspの有効性を確認したあと、メッセージタイプ
     がmsp の mtypeメンバに書き込まれ、 ``store_msg()'' が呼び出されて、
     メッセージの内容がmsrのmtextメンバへコピーされます。最後にメッセー
     ジ用のメモリが関数 ``free_msg()''により解放されます。

  5.2.2.  メッセージ独自の構造体

  メッセージキューのデータ構造体は msg.c で定義されています。

  5.2.2.1.  struct msg_queue

       ______________________________________________________________________
       /* one msq_queue structure for each present queue on the system */
       struct msg_queue {
               struct kern_ipc_perm q_perm;
               time_t q_stime;                 /* last msgsnd time */
               time_t q_rtime;                 /* last msgrcv time */
               time_t q_ctime;                 /* last change time */
               unsigned long q_cbytes;         /* current number of bytes on queue */
               unsigned long q_qnum;           /* number of messages in queue */
               unsigned long q_qbytes;         /* max number of bytes on queue */
               pid_t q_lspid;                  /* pid of last msgsnd */
               pid_t q_lrpid;                  /* last receive pid */

               struct list_head q_messages;
               struct list_head q_receivers;
               struct list_head q_senders;
       };
       ______________________________________________________________________

  5.2.2.2.  struct msg_msg

  ______________________________________________________________________
  /* one msg_msg structure for each message */
  struct msg_msg {
          struct list_head m_list;
          long  m_type;
          int m_ts;           /* message text size */
          struct msg_msgseg* next;
          /* the actual message follows immediately */
  };
  ______________________________________________________________________

  5.2.2.3.  struct msg_msgseg

       ______________________________________________________________________
       /* message segment for each message */
       struct msg_msgseg {
               struct msg_msgseg* next;
               /* the next part of the message follows immediately */
       };
       ______________________________________________________________________

  5.2.2.4.  struct msg_sender

       ______________________________________________________________________
       /* one msg_sender for each sleeping sender */
       struct msg_sender {
               struct list_head list;
               struct task_struct* tsk;
       };
       ______________________________________________________________________

  5.2.2.5.  struct msg_receiver

       ______________________________________________________________________
       /* one msg_receiver structure for each sleeping receiver */
       struct msg_receiver {
               struct list_head r_list;
               struct task_struct* r_tsk;

               int r_mode;
               long r_msgtype;
               long r_maxsize;

               struct msg_msg* volatile r_msg;
       };
       ______________________________________________________________________

  5.2.2.6.  struct msqid64_ds

       ______________________________________________________________________
       struct msqid64_ds {
               struct ipc64_perm msg_perm;
               __kernel_time_t msg_stime;      /* last msgsnd time */
               unsigned long   __unused1;
               __kernel_time_t msg_rtime;      /* last msgrcv time */
               unsigned long   __unused2;
               __kernel_time_t msg_ctime;      /* last change time */
               unsigned long   __unused3;
               unsigned long  msg_cbytes;      /* current number of bytes on queue */
               unsigned long  msg_qnum;        /* number of messages in queue */
               unsigned long  msg_qbytes;      /* max number of bytes on queue */
               __kernel_pid_t msg_lspid;       /* pid of last msgsnd */
               __kernel_pid_t msg_lrpid;       /* last receive pid */
               unsigned long  __unused4;
               unsigned long  __unused5;
       };
       ______________________________________________________________________

  5.2.2.7.  struct msqid_ds

       ______________________________________________________________________
        struct msqid_ds {
               struct ipc_perm msg_perm;
               struct msg *msg_first;          /* first message on queue,unused  */
               struct msg *msg_last;           /* last message in queue,unused */
               __kernel_time_t msg_stime;      /* last msgsnd time */
               __kernel_time_t msg_rtime;      /* last msgrcv time */
               __kernel_time_t msg_ctime;      /* last change time */
               unsigned long  msg_lcbytes;     /* Reuse junk fields for 32 bit */
               unsigned long  msg_lqbytes;     /* ditto */
               unsigned short msg_cbytes;      /* current number of bytes on queue */
               unsigned short msg_qnum;        /* number of messages in queue */
               unsigned short msg_qbytes;      /* max number of bytes on queue */
               __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
               __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
       };
       ______________________________________________________________________

  5.2.2.8.  msg_setbuf

       ______________________________________________________________________
       struct msq_setbuf {
               unsigned long   qbytes;
               uid_t           uid;
               gid_t           gid;
               mode_t          mode;
       };
       ______________________________________________________________________

  5.2.3.  メッセージサポート関数

  5.2.3.1.  newque()

  newque() は、新しいメッセージキュー記述子(``struct msg_queue'')のため
  のメモリを割り当て、``ipc_addid()'' を呼び出して、新しいメッセージ
  キュー記述子用にメッセージキュー配列のエントリを予約させます。メッセー
  ジキュー記述子は以下のように初期化されます。

  o  ``kern_ipc_perm''構造体が初期化される。

  o  メッセージキュー記述子のq_stimeとq_rtimeメンバは 0 に初期化され
     る。q_ctimeメンバは CURRENT_TIME に設定される。

  o  このキューメッセージで許される最大バイト数(q_qbytes)が MSGMNB に設
     定され、キューにより現在使われているバイト数(q_cbytes)が0に初期化さ
     れる。

  o  メッセージウエイトキュー(q_messages)、待ち手ウエイト
     キュー(q_receivers)、送り手ウエイトキュー(q_senders)がそれぞれ空に
     初期化される。

     ``ipc_addid()'' の呼出しにつづく全ての操作は、グローバルメッセージ
     キュースピンロックを保持して実行されます。スピンロックを解除
     後、newque() は、  ``ipc_buildid()'' に直接マップされている
     msg_buildid() を呼び出します。``ipc_buildid()'' は、メッセージ
     キュー記述子のインデックスを用いてユニークなメッセージキュー ID を
     作りだします。そしてこれは、 newque() により呼出し元に戻されます。

  5.2.3.2.  freeque()

  メッセージキューが削除されるときは、 freeque() 関数が呼ばれます。この
  関数は、グローバルメッセージキュースピンロックが、呼出し元関数によって
  既にロックされているものと仮定します。指定されるメッセージキューに関連
  している全てのカーネルリソースを解放します。まず、(msg_rmid経由
  で)``ipc_rmid()'' を呼び出し、グローバルメッセージキュー記述子の配列か
  らメッセージキュー記述子を削除します。そして、``expunge_all'' を呼び出
  して、全ての受け手を起こし、``ss_wakeup()''を呼び出して、このメッセー
  ジキューで休止している全ての送り手を起こします。最後に、グローバルメッ
  セージキュースピンロックを解除します。このメッセージキューに格納されて
  いる全てのメッセージは解放され、メッセージキュー記述子のメモリも解放さ
  れます。

  5.2.3.3.  ss_wakeup()

  ss_wakeup() は与えられたメッセージ送り手ウエイトキューで待ちになってい
  る全てのタスクを起こします。この関数が``freeque()''から呼ばれたなら、
  キューにある全ての送り手はキューから削除されます。

  5.2.3.4.  ss_add()

  ss_add() はパラメータとしてメッセージキュー記述子とメッセージ送り手
  データ構造体を受け取ります。現在のプロセスをもとにメッセージ送り手デー
  タ構造体のtsk メンバを埋め、現在のプロセスの状態を TASK_INTERRUPTIBLE
  に変更し、そして与えられたメッセージキューの送り手ウエイトキューの先頭
  へメッセージ送り手データ構造体を挿入します。

  5.2.3.5.  ss_del()

  与えられたメッセージ送り手データ構造体(mss) はまだ対応するウエイト
  キューに保管されたままです。そして、ss_del() は、mssをキューから削除し
  ます。

  5.2.3.6.  expunge_all()

  expunge_all() は、パラメータとしてメッセージキュー記述子(msq)と受け手
  が起こされた理由を示す整数値(res)を受け取ります。それぞれのmsqに対応し
  て休止中の受け手に対して、r_msg メンバが起こされた理由(res)を設定し、
  その対応する受け手のタスクが起こされます。この関数はメッセージキューが
  削除されるときと、メッセージ制御操作が行われるときに呼び出されます。

  5.2.3.7.  load_msg()

  プロセスがメッセージを送るとき、``sys_msgsnd()'' 関数は、最初に
  load_msg() 関数を呼び出し、メッセージをユーザ空間からカーネル空間へと
  読み込みます。メッセージはカーネルメモリ内ではデータブロックのリンクリ
  ストとして表現されています。最初のデータブロックに関連しているのは、全
  てのメッセージを表す``msg_msg'' 構造体です。msg_msg 構造体に関連づけら
  れているデータブロックは、DATA_MSG_LEN のサイズに制限されています。
  データブロックと構造体はメモリで一ページで取りうる最大の一つの連続した
  メモリブロックに割り当てられています。もし全てのメッセージが最初のデー
  タブロックに入りきらなければ、追加のデータブロックが割り当てられ、リン
  クリストに登録されます。これら追加のデータブロックは、DATA_SEG_LEN に
  サイズが制限されており、各々は関連する``msg_msgseg)'' 構造体を読み込み
  ます。msg_msgseg 構造体と対応するデータブロックはメモリで一ページで取
  りうる最大の一つの連続したメモリブロックに割り当てられています。この関
  数は、成功時には新しい``msg_msg'' 構造体のアドレスを返り値にもって戻り
  ます。

  5.2.3.8.  store_msg()

  store_msg() 関数は、``sys_msgrcv()'' により呼び出され、受けたメッセー
  ジを呼出し元の提供するユーザ空間バッファへ再度組み立てます。
  ``msg_msg'' 構造体と``msg_msgseg''構造体により示されるデータは、ユーザ
  空間バッファへ順次コピーされます。

  5.2.3.9.  free_msg()

  free_msg() 関数はメッセージデータ構造体 ``msg_msg'' とメッセージセグメ
  ントのメモリを解放します。

  5.2.3.10.  convert_mode()

  convert_mode() は ``sys_msgrcv()'' から呼び出されます。パラメータとし
  て、指定するメッセージタイプのポインタ(msgtyp)とフラグ(msgflg)を受け取
  ります。この関数は、msgtypとmsgflgの値に基づいて、呼出し元へ検索モード
  を返します。もし、msgtypが0ならば、 SEARCH_ANYを返します。もし、msgtyp
  の値が0より小さければ、msgtypeはその絶対値に設定され、SEARCH_LESSEQUAL
  を返します。もし MSG_EXCEPT がmsgflg に指定されていれ
  ば、SEARCH_NOTEQUAL が返されます。そうでなければ、SEARCH_EQUAL が返さ
  れます。

  5.2.3.11.  testmsg()

  testmsg() 関数は、メッセージが受け手の指定する条件に合致するか調べま
  す。もし、以下の条件の一つが真となれば、1を返します。

  o  検索モードが全メッセージ検索(SEARCH_ANY)の場合

  o  検索モードが SEARCH_LESSEQUAL に指定されており、メッセージタイプが
     指定されたタイプ以下である場合

  o  検索モードが SEARCH_EQUAL でメッセージタイプが指定されたタイプと同
     じである場合

  o  検索モードが SEARCH_NOTEQUAL で、メッセージタイプが指定されたタイプ
     と異なる場合
  5.2.3.12.  pipelined_send()

  プロセスは pipelined_send() を使うことで、対応づけられているメッセージ
  キューへ入れるのではなく、直接待機中の受け手へメッセージを送ることがで
  きます。与えられたメッセージを待つ最初の受け手を見つけるため、
  ``testmsg()'' 関数を呼び出します。もし見つかれば、待っている受け手は受
  け手ウエイトキューから削除され、関連する受け手のタスクは起こされます。
  メッセージは受け手のr_msgメンバへ格納されて、1を返します。メッセージを
  待っている受け手がいない場合、0を返します。

  受け手を探すプロセスでは、与えられたメッセージに対して小さすぎるサイズ
  を要求していることで、潜在的受け手が分かるでしょう。そのような受け手は
  キューから削除され、起こされてエラー状態 E2BIG を渡されます。このエ
  ラーは、r_msgメンバに格納されます。この検索は、有効な受け手が見つかる
  か、キューを見尽くすまで継続します。

  5.2.3.13.  copy_msqid_to_user()

  copy_msqid_to_user() はカーネルバッファの内容をユーザバッファにコピー
  します。パラメータとしてユーザバッファと ``msqid64_ds'' 型のカーネル
  バッファと、新しいIPC バージョンか古い IPC バージョンかを示すバージョ
  ンフラグを受け取ります。もしバージョンフラグが IPC_64 ならば、
  copy_to_user() が起動され、カーネルバッファからユーザバッファへ直接コ
  ピーします。そうでなければ、struct msqid_ds 型の一時バッファが初期化さ
  れ、カーネルデータはこの一時バッファへ変換されます。そし
  て、copy_to_user() が呼び出され、一時バッファの内容がユーザバッファへ
  コピーされます。

  5.2.3.14.  copy_msqid_from_user()

  copy_msqid_from_user() 関数は、パラメータとしてstruct msq_setbuf型の
  カーネルメッセージバッファとユーザバッファと、新しいIPC バージョンか古
  い IPC バージョンかを示すバージョンフラグを受け取ります。IPCバージョン
  が新しい場合、copy_from_user()が呼び出されて、ユーザバッファの内容を
  ``msqid64_ds'' 型の一時バッファへコピーします。そしてカーネルバッファ
  のqbytes, uid, gidとmode領域を一時バッファの対応する領域の値にします。
  古い IPC のバージョンならば、``msqid_ds'' 型の一時バッファを代りに用い
  ます。

  5.3.  共有メモリ

  5.3.1.  共有メモリシステムコールインターフェース

  5.3.1.1.  sys_shmget()

  sys_shmget() は、呼出し全体をグローバル共有メモリセマフォにより保護し
  ます。

  新しい共有メモリセグメントを作る必要のある場合、 関数``newseg()'' が呼
  び出され、新しい共有メモリセグメントを作成し初期化します。新しいセグメ
  ントのIDが呼出し元へ返されます。

  既存の共有メモリセグメントの キー値が与えられている場合、共有メモリ記
  述子配列の対応するインデックスを見つけ出し、パラメータと呼出し元のパー
  ミッションを確認後、共有メモリセグメントIDを返します。検索操作と有効性
  の確認はグローバル共有メモリスピンロックを保持した状態で行われます。

  5.3.1.2.  sys_shmctl()

  5.3.1.2.1.  IPC_INFO

  一時バッファ ``shminfo64'' がシステムワイドの共有メモリパラメータとと
  もに読み込まれ、呼出し元のアプリケーションからアクセスするユーザ空間へ
  コピーします。

  5.3.1.2.2.  SHM_INFO

  共有メモリのシステムワイドの統計情報を集める間は、グローバル共有メモリ
  セマフォとグローバル共有メモリスピンロックを取得します。関
  数``shm_get_stat()'' を呼び出し、メモリ中に存在する共有メモリページ数
  とスワップアウトした共有メモリページ数を計算します。 swap_attempts
  とswap_successesの計数は、常に0 とされています。これらの統計値は一時
  バッファ ``shm_info'' へ格納され、呼び出したアプリケーションのユーザ空
  間へとコピーされます。

  5.3.1.2.3.  SHM_STAT, IPC_STAT

  SHM_STAT と IPC_STAT のため、``struct shmid64_ds'' 型の一時バッファが
  初期化され、グローバル共有メモリスピンロックをロックします。

  SHM_STAT の場合は、共有メモリセグメント ID パラメータは、1次元のイン
  デックス(つまり、0 から n で、n はシステムの共有メモリ ID の番号) を期
  待しています。インデックスの有効性を確認後、``ipc_buildid()''
  が(shm_buildid()経由で)呼び出され、インデックスを共有メモリ ID へ変換
  します。SHM_STAT を渡した場合、共有メモリ ID が返り値になります。この
  機能は文献にないけれども、ipcs(8) プログラムのために準備されていること
  に注意しましょう。

  IPC_STAT の場合、共有メモリセグメント ID パラメータについて、ID
  が``shmget()'' を呼出して生成されていることを期待します。この ID は処
  理を行う前に有効性が確認されます。IPC_STAT を渡した場合、0 が返り値と
  なります。

  SHM_STATと IPC_STAT の双方について、呼出し元のアクセス権の有効性を確認
  します。要求された統計情報は一時バッファへ読み込まれ、その後呼出し元へ
  コピーされます。

  5.3.1.2.4.  SHM_LOCK, SHM_UNLOCK

  アクセス権の有効性を確認後、グローバル共有メモリスピンロックをロック
  し、共有メモリセグメントIDの有効性を確認します。SHM_LOCK と SHM_UNLOCK
  双方で、その処理を行うため、``shmem_lock()'' を呼び出しま
  す。``shmem_lock()''のパラメータには、処理すべき関数の識別子を渡しま
  す。

  5.3.1.2.5.  IPC_RMID

  IPC_RMID の処理の間は、グローバル共有メモリセマフォとグローバル共有メ
  モリスピンロックが取得されます。共有メモリID の有効性を確認したあと、
  もし current attachment がなければ、``shm_destroy()'' を呼出し、共有メ
  モリセグメントを破壊します。そうでなければ、SHM_DEST フラグをセット
  し、これが破壊するという印になります。そして、他のプロセスを共有メモ
  リIDの参照から守るために、IPC_PRIVATE フラグをセットします。

  5.3.1.2.6.  IPC_SET

  共有メモリセグメントIDとユーザのアクセス権の有効性を確認後、共有メモリ
  セグメントのuidやgid、そしてmode フラグをユーザデータで更新しま
  す。shm_ctime領域も更新します。これらの変更は、グローバル共有メモリセ
  マフォとグローバル共有メモリスピンロックを保持した状態で処理されます。

  5.3.1.3.  sys_shmat()

  sys_shmat() は、共有メモリセグメントIDと共有メモリセグメントを配置する
  アドレス(shmaddr)、そして以下に示すフラグをパラメータに取ります。

  もしshmaddrが0でなく、SHM_RND フラグが指定されていたなら、shmaddrは
  SHMLBA の倍数へと丸められます。もし、shmaddr がSHMLBAの倍数でな
  く、SHM_RND も指定されていなければ、EINVAL が返されます。

  呼出し元のアクセス権の有効性が確認され、共有メモリセグメント
  のshm_nattch領域が1つ増分されます。ここで、この増分は 割り当てカウント
  が0ではないことを保証し、共有メモリセグメントをセグメントの割り当て処
  理中の破壊から守るために行われます。これらの操作は、グローバル共有メモ
  リスピンロックを保持した状態で行われます。

  共有メモリセグメントページへの仮想メモリマップを作るため、 do_mmap()
  関数を呼び出します。これはカレントタスクの mmap_sem セマフォを保持した
  状態で行われます。MAP_SHARED フラグが do_mmap() へ渡されます。もしアド
  レスが呼出し元から渡されたなら、 MAP_FIXED フラグも do_mmap() へ渡され
  ます。そうでなければ、 共有メモリセグメントへ割り当てる仮想アドレスは
  do_mmap() が選びます。

  注: ``shm_inc()''が do_mmap() 関数コールの中で shm_file_operations 構
  造体を経て起動されます。この関数は、 PID を設定し、現在の時刻を設定
  し、この共有メモリセグメントへの割り当て数を増分するため呼び出されま
  す。

  do_mmap() を呼び出したあとは、グローバル共有メモリセマフォとグローバル
  共有メモリスピンロックの両方が取得されます。割り当てカウントは減少され
  ます。

  ``shm_inc()'' を呼び出すため、shmat()への呼び出しに対して割り当て数の
  正味の変更が 1 になります。もし割り当て数の減少した後で、セグメントが
  破壊にマーク(SHM_DEST)されたら、``shm_destroy()'' が呼び出され、共有メ
  モリセグメントの資源が解放されます。`

  最後に、共有メモリのマップされる仮想メモリが、呼出し元のユーザの指定し
  たアドレスへ返されます。 do_mmap() よりエラーコードが返されたときに
  は、このエラーコードがシステムコールの返り値として戻されます。

  5.3.1.4.  sys_shmdt()

  sys_shmdt() の処理中はグローバル共有メモリセマフォが保持されます。現在
  のプロセスのmm_struct について、共有メモリアドレスに対応する
  vm_area_struct を検索します。もし見つかれば、 do_munmap() が呼び出され
  て、共有メモリセグメントの仮想アドレスへのマッピングが解除されます。

  ここで、 do_munmap() は共有メモリの予約を維持する関数の``shm_close()''
  をコールバックし、他の割り当てがない共有メモリセグメントの資源を解放す
  ることに注意してください。

  sys_shmdt() は無条件に0を返します。

  5.3.2.  共有メモリサポート構造体

  5.3.2.1.  struct shminfo64

  ______________________________________________________________________
  struct shminfo64 {
          unsigned long   shmmax;
          unsigned long   shmmin;
          unsigned long   shmmni;
          unsigned long   shmseg;
          unsigned long   shmall;
          unsigned long   __unused1;
          unsigned long   __unused2;
          unsigned long   __unused3;
          unsigned long   __unused4;
  };
  ______________________________________________________________________

  5.3.2.2.  struct shm_info

       ______________________________________________________________________
       struct shm_info {
               int used_ids;
               unsigned long shm_tot;  /* total allocated shm */
               unsigned long shm_rss;  /* total resident shm */
               unsigned long shm_swp;  /* total swapped shm */
               unsigned long swap_attempts;
               unsigned long swap_successes;
       };
       ______________________________________________________________________

  5.3.2.3.  struct shmid_kernel

       ______________________________________________________________________
       struct shmid_kernel /* private to the kernel */
       {
               struct kern_ipc_perm    shm_perm;
               struct file *           shm_file;
               int                     id;
               unsigned long           shm_nattch;
               unsigned long           shm_segsz;
               time_t                  shm_atim;
               time_t                  shm_dtim;
               time_t                  shm_ctim;
               pid_t                   shm_cprid;
               pid_t                   shm_lprid;
       };
       ______________________________________________________________________

  5.3.2.4.  struct shmid64_ds

  ______________________________________________________________________
  struct shmid64_ds {
          struct ipc64_perm       shm_perm;       /* operation perms */
          size_t                  shm_segsz;      /* size of segment (bytes) */
          __kernel_time_t         shm_atime;      /* last attach time */
          unsigned long           __unused1;
          __kernel_time_t         shm_dtime;      /* last detach time */
          unsigned long           __unused2;
          __kernel_time_t         shm_ctime;      /* last change time */
          unsigned long           __unused3;
          __kernel_pid_t          shm_cpid;       /* pid of creator */
          __kernel_pid_t          shm_lpid;       /* pid of last operator */
          unsigned long           shm_nattch;     /* no. of current attaches */
          unsigned long           __unused4;
          unsigned long           __unused5;
  };
  ______________________________________________________________________

  5.3.2.5.  struct shmem_inode_info

       ______________________________________________________________________
       struct shmem_inode_info {
               spinlock_t      lock;
               unsigned long   max_index;
               swp_entry_t     i_direct[SHMEM_NR_DIRECT]; /* for the first blocks */
               swp_entry_t   **i_indirect; /* doubly indirect blocks */
               unsigned long   swapped;
               int             locked;     /* into memory */
               struct list_head        list;
       };
       ______________________________________________________________________

  5.3.3.  共有メモリサポート関数

  5.3.3.1.  newseg()

  newseg() 関数は、新しい共有メモリセグメントを作る必要が出たときに呼ば
  れます。これは、新しいセグメントのキー、フラグ、サイズの三つのパラメー
  タを受け取ります。作成する共有メモリセグメントのサイズが SHMMIN と
  SHMMAX の間にあり、共有メモリセグメントの総数が SHMALL を越えないと
  いった有効性を確認した後に、新しい共有メモリセグメント記述子を割り当て
  ます。 ``shmem_file_setup()'' 関数は、tmpfs 型のアンリンクされたファイ
  ルを作成するために、その後に呼び出されます。返されるファイルポインタ
  は、共有メモリセグメント記述子の対応するshm_file領域へ保存されます。
  ファイルサイズはセグメントサイズと同じに設定されます。新しい共有メモリ
  セグメント記述子は初期化され、グローバル IPC 共有メモリ記述子配列へ挿
  入されます。共有メモリセグメント ID は (``ipc_buildid()''経由で)
  shm_buildid() により作成されます。このセグメントIDは、inode に対応する
  i_ino 領域と同様に共有メモリセグメント記述子の id 領域へ保存されます。
  これに加えて、shm_file_operation構造体で定義される共有メモリ操作のアド
  レスは、対応するファイルへと保存されます。システム全体での共有メモリセ
  グメントの総数を示すグローバル変数 shm_tot の値も、この変更を反映して
  増分されます。成功した場合、セグメント ID が呼出し元アプリケーションへ
  返されます。

  5.3.3.2.  shm_get_stat()

  shm_get_stat() は、全ての共有メモリ構造体を順に辿っていき、共有メモリ
  に使われているメモリの全ページ数と、スワップアウトされている全メモリ
  ページ数を計算します。各共有メモリセグメントにはファイル構造体とiノー
  ド構造体があります。求めるデータをiノード経由で取得するため、アクセス
  される各iノード構造体のスピンロックのロックとロック解除が、それぞれに
  行われます。

  5.3.3.3.  shmem_lock()

  shmem_lock() はパラメータに共有メモリ記述子へのポインタとロックかアン
  ロックを示すフラグを受け取ります。共有メモリセグメントのロック状態は対
  応するiノードへ格納されます。この状態は、要求されたロック状態と比較さ
  れます。shmem_lock() は、一致した場合に単に返ります。

  対応するiノードのセマフォを保持している間に、iノードのロック状態が設定
  されます。各共有メモリセグメントの各ページにおいて、以下のリストの項目
  が実行されます。

  o  find_lock_page() は、ページをロック(PG_lockedを設定)し、ページの参
     照カウンタを1増加させます。参照カウンタの加算は共有メモリセグメント
     がこの操作中を通してメモリのロックされた状態にあることを保証するた
     めに実施されます。

  o  もし要求された状態がロック状態なら PG_locked はクリアされますが、参
     照カウンタは増加されたままにされます。

  o  もし要求された状態がアンロック状態なら、参照カウンタは2度減算されま
     す。一つは現在の参照、もう一つはメモリの中でロックされたままのペー
     ジになる原因の既存の参照のためです。それから PG_locked はクリアされ
     ます。

  5.3.3.4.  shm_destroy()

  shm_destroy() の間、共有メモリページ総数は、共有メモリセグメントの削除
  の責任をとって調整されます。 ``ipc_rmid()'' は、(shm_rmid() 経由で)呼
  び出され、共有メモリID を削除します。 ``shmem_lock'' が呼び出され、共
  有メモリページをアンロックし、各ページの参照カウントを効率的に 0 に減
  算します。 fput() が呼び出され、対応するファイルオブジェクトの利用カウ
  ンタf_countを1減算します。そして、必要であれば、ファイルオブジェクトの
  資源を解放します。kfree() が呼び出され、共有メモリセグメント記述子を解
  放します。

  5.3.3.5.  shm_inc()

  shm_inc() は PID を設定し、現在の時間を設定し、そして与えられた共有メ
  モリセグメントの付属数を1増加させます。これらの操作は、グローバル共有
  メモリスピンロックを保持して行われます。

  5.3.3.6.  shm_close()

  shm_close() は、shm_lprid と shm_dtim メンバを更新し、割り当て共有メモ
  リセグメント数を 1 減算します。共有メモリセグメントが他に割り当てられ
  ていなければ、``shm_destroy()'' を呼び出し、共有メモリセグメントの資源
  を解放します。これらの操作は全てグローバル共有メモリセマフォとグローバ
  ル共有メモリスピンロックの両方を保持して行われます。

  5.3.3.7.  shmem_file_setup()

  関数 shmem_file_setup() は、 tmpfs ファイルシステムに存在するアンリン
  クされたファイルを、与えられた名前とサイズに設定します。もしこのファイ
  ルに対して十分なシステムメモリリソースがあれば、新しい dエントリを
  tmpfs のマウントルートに作成します。そして新しいファイルディスクリプタ
  と tmpfs 型の新しい iノードオブジェクトを割り当てます。そし
  て、d_instantiate() を呼出し、新しい d エントリオブジェクトに新しい i
  ノードオブジェクトを対応づけます。dエントリオブジェクトのアドレスを
  ファイルディスクリプタへ保存します。iノードオブジェクトのi_size メンバ
  はファイルサイズに設定され、iノードがアンリンクされた印をつけるため
  i_nlink を 0 に設定します。そして、 shmem_file_setup()
  は、shmem_file_operations 構造体のアドレスも f_op メンバへ保存します。
  そして、ファイルディスクリプタの f_mode と f_vfsmnt メンバが適切に初期
  化されます。関数 shmem_truncate() が呼び出され、iノード オブジェクトの
  初期化を完了させます。成功したなら、 shmem_file_setup() は新しいファイ
  ルディスクリプタを返します。

  5.4.  Linux IPC プリミティブ

  5.4.1.  セマフォ、メッセージおよび共有メモリで使用する汎用 Linux IPCプ
  リミティブ

  Linux のセマフォ、メッセージおよび共有メモリ機構は一連の共通プリミティ
  ブで構成されています。これらのプリミティブを以下で説明します。

  5.4.1.1.  ipc_alloc()

  メモリ割り当てが PAGE_SIZE より大きければ、vmalloc() がメモリ割り当て
  に使われます。そうでなければ、 kmalloc() が GFP_KERNEL 付きで呼び出さ
  れメモリを割り当てます。

  5.4.1.2.  ipc_addid()

  新しいセマフォセット、メッセージキュー、ないしはメモリセグメントが追加
  されると、 ipc_addid() は、関連するディスクリプタの配列のサイズがシス
  テムの最大値に対して十分に大きいことを保証するため、最初に
  ``grow_ary()'' を呼び出します。そしてディスクリプタの配列の最初の未使
  用要素を検索します。もし未使用の要素が見つかったら、使用中のディスクリ
  プタのカウンタを1増加します。新しい資源のディスクリプタのために
  ``kern_ipc_perm'' 構造体を初期化し、新しいディスクリプタの配列のイン
  デックスを返します。 ipc_addid() が成功したら、与えられたIPC のタイプ
  のグローバルスピンロックを取得して戻ります。

  5.4.1.3.  ipc_rmid()

  ipc_rmid() は、IPCディスクリプタをIPCタイプのグローバルディスクリプタ
  配列から削除し、 使用されている ID のカウントを更新します。そして必要
  なら対応するディスクリプタ配列の最大 ID を調整します。与えられた IPC
  ID に対応する IPC ディスクリプタへのポインタを返します。

  5.4.1.4.  ipc_buildid()

  ipc_buildid() は与えられたIPCタイプの各ディスクリプタに対応するユニー
  クなIDを作成します。この ID は新しいIPC要素が追加されるときに作成され
  ます(例: 新しい共有メモリセグメントや新しいセマフォセット)。 IPC ID は
  対応するディスクリプタ配列のインデックスへ容易に変換できます。各IPCタ
  イプは、ディスクリプタが追加される毎に増加させるシーケンス番号を管理し
  ています。 ID はシーケンス番号をSEQ_MULTIPLIER 倍し、この積にディスク
  リプタ配列のインデックスを加算することで生成します。シーケンス番号が存
  在することにより、古い IPC ID の利用を検出することができます。

  5.4.1.5.  ipc_checkid()

  ipc_checkid() は、与えられた IPC IDを SEQ_MULTIPLIER で除算し、その商
  を対応するディスクリプタに保存されているシーケンス値と比較します。もし
  値が等しいなら、IPC ID は有効であると考えられ、1を返します。そうでなけ
  れば、0 を返します。

  5.4.1.6.  grow_ary()

  grow_ary() は、与えられた IPC タイプの 最大の(設定可能な) ID 数を動的
  に変更できる機能を取り扱います。これは現在の最大限度が恒久的なシステム
  の限度(IPCMNI)を越えないように強制し、必要なら引き下げます。これはまた
  既存のディスクリプタ配列が十分に大きくなるよう保証します。もし既存の配
  列サイズが十分に大きかったら、現在の最大限度を返します。そうでなけれ
  ば、新しい大きな配列を割り当てます。そして古い配列を新しい配列へコピー
  し、古い配列を解放します。与えられた IPC タイプのディスクリプタ配列を
  更新するときは、対応するグローバルスピンロックを保持します。

  5.4.1.7.  ipc_findkey()

  ipc_findkey() は、特定の ``ipc_ids'' オブジェクトのディスクリプタの配
  列を検索して特定のキーを探します。見つかったら、対応するディスクリプタ
  のインデックスを返します。もしキーが見つからなかったら、-1 を返しま
  す。

  5.4.1.8.  ipcperms()

  ipcperms() は、IPC 資源にアクセスするユーザ、グループと他のパーミッ
  ションをチェックします。もしパーミッションが有効なら0を返し、そうでな
  ければ-1を返します。

  5.4.1.9.  ipc_lock()

  ipc_lock() は、 IPC IDをパラメータの一つとして受け取ります。与えられた
  IPC タイプのグローバルスピンロックをロックし、特定のIPC IDに対応する
  ディスクリプタへのポインタを返します。

  5.4.1.10.  ipc_unlock()

  ipc_unlock() は、示された IPC タイプのグローバルスピンロックを解除しま
  す。

  5.4.1.11.  ipc_lockall()

  ipc_lockall() は、与えられたIPC機構(つまり共有メモリとセマフォとメッ
  セージング)用のグローバルスピンロックをロックします。

  5.4.1.12.  ipc_unlockall()

  ipc_unlockall() は、与えられたIPC機構(つまり共有メモリとセマフォとメッ
  セージング)用のグローバルスピンロックを解除します。

  5.4.1.13.  ipc_get()

  ipc_get() は、特定の IPC タイプ(つまり共有メモリとセマフォとメッセージ
  ング)へのポインタとディスクリプタIDを取り、対応する IPC ディスクリプタ
  へのポインタを返します。ここで、各 IPC タイプのディスクリプタが違う
  データタイプであっても、それぞれの場合で、共通の``kern_ipc_perm'' 構造
  体タイプが最初のエンティティとして埋め込まれます。 ipc_get() 関数は、
  この共有のデータタイプを返します。データ型を正しいディスクリプタのデー
  タ型へキャストするラッパー関数(たとえば shm_get())を通して、ipc_get()
  が呼び出されることを期待するモデルになっています。

  5.4.1.14.  ipc_parse_version()

  ipc_parse_version() は、 もしあればIPC_64 フラグをコマンドから削除しま
  す。そして、 IPC_64か IPC_OLDを返します。

  5.4.2.  セマフォ、メッセージおよび共有メモリで使われる汎用 IPC 構造体

  セマフォ、メッセージおよび共有メモリ機構は全て以下の共有の構造体を使う
  ようになっています。

  5.4.2.1.  struct kern_ipc_perm

  各 IPC ディスクリプタは、最初の要素にこの型のデータオブジェクトを持っ
  ています。これは、全ての汎用 IPC 関数からこのデータ型のポインタを用い
  て、全てのディスクリプタへアクセスできるようにするためです。

       ______________________________________________________________________
       /* used by in-kernel data structures */
       struct kern_ipc_perm {
           key_t key;
           uid_t uid;
           gid_t gid;
           uid_t cuid;
           gid_t cgid;
           mode_t mode;
           unsigned long seq;
       };
       ______________________________________________________________________

  5.4.2.2.  struct ipc_ids

  ipc_ids 構造体は、セマフォとメッセージキューと共有メモリの共通データを
  表しています。このデータ構造体には三つのグローバルインスタンスがありま
  す。それぞれセマフォ、メッセージ、共有メモリ用に、semid_ds、msgid_ds
  とshmid_dsです。各インスタンスでは、sem セマフォが使われ、構造体へのア
  クセスを保護しています。 entries メンバは、IPC ディスクリプタ配列を指
  しており、ary スピンロックがこの配列へのアクセスを保護しています。seq
  メンバは、新しい IPC 資源を作ったときに1増加されるグローバルシーケンス
  番号になっています。

       ______________________________________________________________________
       struct ipc_ids {
           int size;
           int in_use;
           int max_id;
           unsigned short seq;
           unsigned short seq_max;
           struct semaphore sem;
           spinlock_t ary;
           struct ipc_id* entries;
       };
       ______________________________________________________________________

  5.4.2.3.  struct ipc_id

  構造体 ipc_id の配列は、``ipc_ids'' 構造体の各インスタンスに存在しま
  す。この配列は動的に割り当てられ、必要に応じて``grow_ary()''によってよ
  り大きな配列に置き換えられることになります。``kern_ipc_perm'' データタ
  イプが IPC 汎用関数によって共通ディスクリプタデータタイプとして使われ
  るため、この配列は時々ディスクリプタ配列として参照されます。

  ______________________________________________________________________
  struct ipc_id {
      struct kern_ipc_perm* p;
  };
  ______________________________________________________________________

一覧に戻る
グリーンネット・トップページへ戻る

http://www.green.ne.jp/