読者です 読者をやめる 読者になる 読者になる

覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。 twitter: @yyoshikaw

Linuxプログラミングの概念

「Linuxシステムプログラミング」からの抜粋

Linuxシステムプログラミング

Linuxシステムプログラミング

ファイルとファイルシステム

  • ファイルはLinuxの中でも、もっとも基本的かつ重要な概念。
  • Linuxには、すべてものはファイルである(everything-is-a-file)という思想がある。
  • ファイルディスクリプタ
    • オープンしたファイルを使用する際にはディスクリプタを使用する。
    • ディスクリプタは一意な識別子で、オープンしたファイルのメタデータとファイルを対応させるものです。
    • Linuxカーネル内では、ディスクリプタは整数で表現され、ファイルディスクリプタまたは省略してfdと呼ばれる。

通常ファイル

  • 一般的に「ファイル」と呼ぶものは、Linuxでは"通常ファイル"と名付けている。
  • 「通常ファイル」はその内容としてバイトがリニアに並んだデータ(バイトストリーム、バイト列)を持っている。Linuxのファイルにはこの構造しかありません
    • 他のOSではレコード等の高度に構造化されたファイルを備えたものがあるが、Linuxにはありません。
  • ファイルポジション
    • ファイルの読み書きは指定されたバイトから開始する。この開始バイトはファイル内の「位置」と考えられ、ファイルポジションまたはファイルオフセットと言う。
    • オープンされたファイルに対しカーネルが対応させるメタデータの中でも重要なものです。
    • ファイルが初めてオープンされた時のファイルポジションは0
  • ファイルサイズ
    • ファイルのサイズはバイト単位で表現し、ファイルサイズと呼ぶ。
      • 言い換えれば、ファイルを構成するバイトストリームのバイト数
    • トランケート操作を行うと、ファイルサイズを変更できる。
      • 元のサイズよりも小さなサイズへトランケートした場合は、新しいサイズ以降のバイトは削除される。
      • 元のサイズよりも大きなサイズへもトランケート可能。
        • この場合、元のサイズ以降には0('\0')が埋められる。
      • ファイルを空(サイズが0でバイトストリームが存在しないも)にすることもできる。
  • ファイルのオープン
    • 同じファイルでも複数回オープンすることが可能。(同じプロセスでも、異なるプロセスからでも可能 )
    • 複数のプロセス間で同じファイルディスクリプタを共有することも可能です。
    • カーネルは同時に行われるファイルアクセスに対し何らの制限を加えない。
      • 複数プロセスが同じファイルに対し、同時に読み書き可能。
      • 同時アクセスの結果は、それぞれの操作が行われた順序に依存し、通常は予測不可能。
      • 同時アクセスの同期を取る必要がある場合は、通常はアプリケーションで処理する。
  • inode
    • ファイルへアクセスする際にはファイル名を使用するのが常だが、実はファイル名は直接にファイルに対応していない
    • ファイルに実際に対応し、参照の際に使用されるのはinodeであり、一意な数字が割り振られている。
    • 割り振られている数字のことをinode番号と呼ぶ。
    • inodeには、更新時刻、オーナ、種類、サイズ等のファイルに対応するメタデータが保存されている
    • ファイルの内容(ファイルのデータ)のディスク上の保存位置もinodeが保持する。しかし、ファイル名は持っていません。
    • inodeはUnixのファイルシステムのディスクに存在する物理的なオブジェクトであると同時に、Linuxカーネル内のデータ構造を表す論理的なエンティティでもある。

ディレクトリとリンク

  • inode番号を使用したファイルアクセスは手間がかかるため、ユーザ空間からのファイルオープンはinode番号ではなく常にファイル名を使用する。
  • アクセスするファイルのファイル名を保持ししているのはディレクトリで、ファイル名とinode番号を対応付ける。
    • ファイル名とinodeの組をリンク(link)と言う。
      • リンクのディスク上の物理的な実装は単純なテーブルやハッシュテーブルなど、ファイルシステムにより様々な形式がある。
  • 概念としてのディレクトリは他のファイルと同じように見えるが、その内容としてファイル名とinode番号の組のみを持つ点が異なる。
  • ユーザアプリケーションがファイル名を指定しファイルオープンを要求すると、カーネルは対象ファイル名を持つ親ディレクトリをオープンし、指定されたファイル名を検索する。\ さらにファイル名からinode番号を、inode番号からinodeを特定する。\ inodeにはファイルに対応するメタデータが保存されており、ファイルの内容を保存したディスク上の位置などの情報が得られる。
  • カーネルはパス名とともにファイルオープンの要求を受け付け、パス名から各ディレクトリエントリを辿り、次のエントリを検索する。(順々にinodeを特定していく)
    • この処理をディレクトリ解決またはパス解決と言う
    • Linuxではdentry cache(dcache)というキャッシュも使用しており、パス解決の結果をメモリ上に蓄えるようにしている。
      • キャッシュにより同じディレクトリに対する以降のパス解決が高速に行える。

ハードリンク

  • 上記の説明をもとに考えれば、異なる複数のファイル名から同じinodeへ解決することは何も問題が無い。できて当然であるし、実際できる。
  • 同じinodeに対しファイル名が異なる複数の組が存在する状態をハードリンク(hard link)と呼ぶ。
  • ハードリンクは同一ディレクトリ下でも、異なる複数ディレクトリ間でも作成可能。
  • ファイルの削除時
    • ファイル削除処理には、ディレクトリからリンクを削除する(unlink)という作業が必要となる。
      • すなわち、ディレクトリ内から名前とinode番号の組を削除する作業。
      • Linuxではハードリンクに対応しているので、unlink処理で常にinodeや対応するデータを破棄して良いとは限らない。
        • ファイルシステム上に他のハードリンクが存在している場合があり得るため。
      • 全てのリンクが削除されるまでデータを破棄したいために、inodeにはリンクカウントというデータが存在する。
        • リンクカウントはファイルシステム上に存在する現在のリンク数を表します。
      • リンクカウントが0になった場合のみ、inodeや対応するデータがファイルシステムから破棄される。

シンボリックリンク

  • ハードリンクは異なるファイルシステムにまたがることはできない。inode番号はそのinodeが存在するファイルシステム外では意味を持たなくなるため。
  • ファイルシステム間をまたぐリンクとして、Unixシステムではシンボリックリンクも備えている。
  • シンボリックリンクは自身のinodeとデータを持ち、シンボリックリンク先のパス名を保持する
  • シンボリックリンクは異なるファイルシステムに存在するファイルやディレクトリ参照可能であり、存在しないものでも参照先として使用できる。
    • 存在しないファイルを参照するシンボリックリンクをbroken linkと呼ぶ。

スペシャルファイル

  • スペシャルファイル(特殊ファイル)はファイルとして表現されたカーネルオブジェクト。
  • Linuxでは以下の4種類のスペシャルファイルに対応している。
    • ブロックデバイス
    • キャラクタデバイス
    • 名前付きパイプ(named pipe)
    • Unixドメインソケット
  • スペシャルファイルは抽象化した概念をファイルシステム内にきれいに収める方法で、「すべてのものはファイルである」という思想に沿ったものである。
  • デバイスファイル
    • デバイスへのアクセスはデバイスファイルを介して行われる。
    • Unixのデバイスは一般的に、キャラクタデバイスとブロックデバイスの2種類に分類される。
  • 名前付きパイプ
    • FIFOとも呼ばれる。
    • IPC(プロセス間通信)の仕組みの1つで、スペシャルファイルのファイルディスクリプタを介した通信チャネル。
  • ソケット
    • ソケットはIPCが進化したもので、プロセス間の通信に使用する。
    • 同じマシン内にプロセスに限定せず、異なるマシン間でも通信可能。

ファイルシステムと名前空間

  • ファイルシステム(filesystem)
    • ファイルシステムとは、ファイルやディレクトリの集合体で、ある形式にしたがい階層を構成するもの。
    • グルーバルなファイル/ディレクトリの名前空間へ、個別にファイルシステムを追加/削除可能。
      • この操作をマウント(mount)、アンマウント(unmount)と言う。
      • ファイルシステムは名前空間内のそれぞれ指定された位置へマウントされる。
        • この位置をマウントポイント(mount point)と言う。
    • ファイルシステムは通常はディスク上に保存され、物理的に存在するものだが、Linuxではメモリ上にのみ存在する仮想ファイルシステム(virtual filesystem)や\ ネットワーク越しのマシン上に存在するネットワークファイルシステムも使用可能。
    • ファイルシステムへのアクセスはブロック(block)という単位で行われる。
      • ブロックはファイルシステムの概念であり、ファイルシステムが存在する物理メディアの概念ではない。
  • 元来Unixシステムは名前空間を1つしか持たず、システム内のすべてのユーザ、プロセスで共有し、同じ名前空間を参照していた。
    • Linuxではプロセスごとの名前空間という革新的な方式を採用しており、プロセスそれぞれに固有のファイル/ディレクトリ階層を持てるようになっている。

プロセス

  • プロセスとは実行状態にあるオブジェクトコードの事で、実行中のプログラムがそうです。

スレッド

  • プロセスには1つまたは複数のスレッドがある。
  • スレッドとはプロセス内の実行単位で、処理実行を担当する抽象概念です。

プロセスの階層

  • 各プロセスにはプロセスID(pid)という一意な正の整数が割り振られます。最初のプロセスのpidは1で、以降のプロセスには連続した一意のpidが与えられる。
  • プロセスツリー
    • Linuxではプロセスは厳密な階層を構成する。これをプロセスツリーと言う。
    • プロセスツリーの最上位はinit(通常はinit(8)というコマンド)
  • プロセスが終了してもすぐにはシステムからは削除されない。
    • カーネルはプロセスの一部をメモリ内に残しておき、親プロセスが子プロセスの終了状態を確認できるようにする。これをプロセスの終了待ち(wating on)と言う。
    • 親プロセスの子プロセス終了待ち合わせが完了すると子プロセスは完全に破棄される。
    • 終了してもまだ終了待ち合わせが行われていないプロセスをゾンビプロセス(zombie)と呼ぶ。

ユーザとグループ

  • Linuxでの認証はユーザ(user)とグループ(group)という単位で管理される。
  • ユーザ
    • ユーザにはユーザID(uid)という一意な正の整数が割り振られる。
    • Linuxカーネル内ではuidはユーザを表す概念に過ぎない。
    • ユーザは自信や他のユーザを表すのにユーザ名(username)を使用する。(数字ではない)
  • グループ
    • ユーザは1つまたは複数のグループに所属する

パーミッション

  • 全てのファイルは1つのオーナーと1つのグループに属し、パーミッションビットを持つ。
  • パーミッションビットは、オーナ、グループ、その他のユーザそれぞれに対しファイルの読み取り、書き込み、実行権限を表す。
  • オーナ、グループ、その他のユーザごとに3ビットを用い、全体では9ビットになる。
  • ファイルのオーナとパーミッションはinodeへ保存される。
  • ディレクトリの場合
    • 読み取り権限あり -> ディレクトリ下のファイル一覧を取得できる
    • 書き込み権限あり -> ディレクトリ下に新たなリンクを作成できる
    • 実行権限あり -> パス解決処理でディレクトリを参照できるようになる

シグナル

  • シグナル(signal)とは非同期片方向の通信方法です。カーネルからプロセスへ送られる場合もあるし、プロセスから他のプロセスへ、またプロセス自身へ送られる場合もある。
  • 通常、シグナルはアクセス違反(セグメンテーション違反)やユーザが割り込みキー(Ctrl-C)を入力した時など何らかのイベントをプロセスへ通知するために使用する。
  • プロセスはシグナルを受け取った際の動作をカスタマイズできる。
    • しかし、SIGKILLとSIGSTOPの動作は変更できない。
  • プロセスは独自のシグナル処理を明示的に追加することができる。
    • 独自のシグナル処理を加えた場合は、ユーザ定義のシグナルハンドラ関数が実行される。

プロセス間通信

  • Linuxカーネルでは、System VとPOSIXの両方で定義/標準化された従来からあるUnixのIPC(プロセス間通信)だけではなく、独自の仕組みも追加しています。
  • LinuxのIPCには以下のものがあります。
    • パイプ
    • 名前付きパイプ
    • セマフォ
    • メッセージキュー
    • 共有メモリ
    • futex(Fast Userspace muTex)

エラー処理

  • errnoの値に応じたエラーメッセージも用意されている。#defineを使用し、errnoの値それぞれに定数マクロを定義している。