本記事の目的
- AI開発を行うエンジニアが下周りについて学習
- OSについて学ぶことで今後効率的に開発を進めることが目的
- 学んだ知識をアウトプットすることで知識定着を狙う
- 試して理解Linuxの仕組みという書籍に沿って知識を獲得していき,途中でわからなかった部分は補強
コンピュータシステムの概要
基本的なコンピュータシステムは,以下の要素で構成される.
- 狭義の意味のコンピュータ
- CPU
- メモリ(RAM)
- 外部構造
- ストレージデバイス
- ネットワークデバイス
- 入出力装置
コンピュータシステムが何らかのInputを得て,何らかのOutputを出力する例をタイピングを例に挙げて説明.
- ユーザーがキーボードを押すとキーボードコントローラが対応する「スキャンコード(押されたキーの位置情報)」を生成(ハードウェア層)
- 生成されたスキャンコードは,USB/HIDプロトコルに従い,PCのUSBホストコントローラへ送られる(インターフェース層)
- 割り込み発生とCPU応答.ホストコントローラはIRQ(割り込み要求)を発行し、CPUが現在の処理を一時中断して割り込みハンドラを呼び出し.(カーネル入口)
- OSカーネル内のキーボードドライバが割り込みハンドラとして動作し,スキャンコードを取り出す.ここでまずUSB HID Usage IDなどの「キーコード」に変換 → ASCII/Unicodeコードポイントにマッピング.
- 入力サブシステムでのキーイベント生成.カーネルの入力サブシステム(Linuxならevdev、WindowsならRaw Inputなど)が「キー ~ が押された」というイベントをOSが管理するメモリ上のキューに格納.(ここに格納されたイベントを順に実行していく)OSの仕事は,キーボード入力をされた際の振る舞いを決める割り込みハンドラの動作とイベントをキューに貯めること
- アプリやウィンドウシステムは,定期的に(あるいは待機して)このキューからイベントを取り出し,自分の処理(描画や画面更新)に使用.取り出すための窓口が「API」やメッセージ取得関数(Linuxならread()/poll())など
単語補足
- USBホストコントローラ
PC側でUSBデバイスとのやりとりを統括するハードウェア + ファームウェアのこと.
デバイス ⇔ ホストコントローラ(マザボ上に存在) ⇔ OS(ホストコントローラドライバ+USBコア)が協調してデバイス記述子をやり取りし、OSがデバイスを認識・初期化.- USBデバイス:Universal Serial Bus規格に準拠した末端機器のこと.USB規格を要約すると,1つのホスト(PCなど)と多数のデバイス(キーボードなど)に役割が分かれており,USB規格の端子には,「エンドポイント」等複数チャネルをもち,電源供給,情報伝達(自身の機能と使い方の自己紹介など)を行う
- ファームウェア:ハードウェアを動かすために機器内部に組み込まれた低レベルソフトウェア.一般のアプリよりハードよりで,起動直後から動作.
- 割り込み処理:割り込み処理とは,プログラムが逐次処理(Fetch → Decode → Execute)をしている途中で,外部からより優先して欲しい処理を受け取り,処理中のものを一度中断し,別の処理(割り込みハンドラ)を実行する処理
- 非割り込み処理(画面描画などの自ら定期的に情報を問い合わせる処理)よりも優先して実行
- 割り込み要求(IRQ: Interpret Request):ハードウェアデバイスが「CPUの注意を引いて,自分の処理を実行してほしい」と伝える信号.マザボ上やCPU上の割り込みコントローラー(物理的デバイス)がこれを受け取り,CPUに受け渡す
- 割り込み処理がないと,画面描画中にどのタイミングでキーボード入力を処理したら良いか決定できない
- 割り込みには優先度が決められており,優先順位の一次決定は,ハードウェア割り込みコントローラ が行い,さらに CPU 割り込み制御 → OS カーネル → スケジューラ と段階的に細かく管理
- 割り込み処理のおかげでリアルタイム性と資源有効活用が実現
- 割り込みハンドラ(Interpret Hundler):OSやデバイスドライバが,キーボードが押された際などのイベントが発生した際に用意する特別な関数
抽象化すると以下のようなイメージ
┌──────────────┐ ┌───────────────┐ ┌──────────────┐
│ キーボード │──通知──▶│ カーネルの入力キュー │──取得──▶│ アプリ/UI │
│ (押下イベント)│ │ (FIFOバッファ,OS) │ │ (文字処理など)│
└──────────────┘ └───────────────┘ └──────────────┘
プログラムの大まかな区分け
プログラム(処理のまとまり)は,大きく以下の3種類に区別される.
- アプリケーション:ユーザーがシステムを使用するためのもの.X, Inxtagramなど
- ミドルウェア:共通の処理をまとめて機能を提供することで,アプリケーションを助ける役割を持つ.Webサーバ,データベースなど
- OS:ハードウェアを直接操作することで,ミドルウェアやアプリケーションの実行に必要な処理を提供する.Linux,Windowsなど
これらは,互いに関わり合いながら動作する.階層は異なるものの,冗長な処理をまとめ抽象化するという役割は全てにおいて共通している.
OSの役割
OSには主に以下の役割が存在する.
- ハードウェア抽象化:ハードウェアの使用方法を抽象化して使いやすくする.e.g. printfでディスプレイを操作できるようにする
- プロセス管理(Process Management):プロセス(それぞれの処理)の生成,終了,スケジューリング,CPU時間配分などを行う.e.g. ブラウザとWordを同時に動かす
- メモリ管理(Memory Management):物理メモリと仮想メモリのマッピングやキャッシュの管理などを行う
- ファイルシステム管理(File System Management):デバイス上データに階層的にアクセスできるようにする.アプリケーションでは統一的なAPIでアクセスできるようにする.e.g. ブラウザから起動するファイルアップロードの統一化
- デバイス管理とI/O処理:センサやデバイスからのInputやOutputのイベントのキューイングや割り込みハンドラを組み合わせた効率的な入出力制御など
- ネットワーク(Networking Management System):TCP/IPスタックの実装やブリッジ機能などの通信に必要な部分を担う
┌───────────────────────┐
│ ユーザー空間 │ ← アプリケーション、シェル、デーモン
│ (glibc, systemd, │
│ X11/Wayland, etc.) │
└───────────────────────┘
▲
システムコール/API
▼
┌───────────────────────┐
│ カーネル空間 │ ← Linux カーネル本体
│ (プロセス管理, メモリ管理,│
│ VFS, ネットワーク, │
│ ドライバ, セキュリティ) │
└───────────────────────┘
▲
デバイスドライバ
▼
┌───────────────────────┐
│ ハードウェア層 │ ← CPU, メモリ, ディスク, NIC, GPU, USB
└───────────────────────┘
ここでは,デバイス管理について少し詳しく説明する.また,ここで出てきた「ユーザ空間」,「カーネル空間」という言葉については,後程説明する.
デバイス管理
デバイス管理はOSの仕事の一つであるが,もしOSがこの仕事をしなかった場合,以下の問題が生じる.C言語を使用して,SSD上のデータにアクセスするケースを考える.
- SSDにも多くの種類が存在するが,異なるデバイスであるため,その操作方法も本来は違い,SSDごとに異なる処理をアセンブリのレベルで書く必要があるわけだが,それはあまりにも非効率的すぎる
- コンピュータでは,複数のプロセスが動いているため,それぞれのプロセスがSSDに書き込みや読み込みを行う際に,タイミングによっては,思わぬ書き換えなどが発生してしまう可能性がある
これらの「ハードウェア操作の抽象化」や「プロセス管理」などの役割を果たしているのがデバイスドライバである.
カーネル
カーネルとはコンピュータにおける制御センター(指揮者)のようなものである.オーケストラの演奏において,どの楽器がどのタイミングで割り込んでくるかを指揮者が制御するように,カーネルが複数のプロセスにおいて,どのプロセスがどの部分に割り込んでくるのかなどを制御する.主な仕事は以下の通りである.
- システムコールの窓口:アプリがファイル開いて,メモリ確保してなどの要求をしてきた場合,要望を受け取る係
- プロセス管理:複数プロセスにCPU時間を割り当てたり,切り替えたりする
- メモリ管理:アプリごとに使える領域を確保,分割.足りなければスワップもやってくれる
- ドライバ:デバイスの操作方法を覚えて,束ねておいてくれる
- ファイルシステム:ディスクの読み込みを仲介し,「/home/」のように階層を整えて人間にわかりやすいようにする
この中で,「システムコール」という言葉が出てきたが,この部分を少し深ぼる
システムコール
デバイスにアクセスする際に,なんの制限もしなかった場合,適当な場所のデータを上書きしてしまったり,複数プロセスが同時にアクセスして整合性が取れなくなってしまったりする.これを防ぐための仕組みがシステムコールである.
システムコールとは,デバイス操作を抽象化し,安全にユーザーが使用できるようにする仕組みである.ユーザーは,デバイスの操作を行う際に「システムコール」という仕組みを利用して,カーネルにデバイス操作を依頼する.つまり,システムコールは,安全性の考慮が必要な処理への窓口と言える.
ユーザーモードとカーネルモード
なんらかの処理を実行する際に,ユーザーモードとカーネルモードという2つのモードが存在する.これらは,「命令ごとに権限を制御してシステムを壊さないように仕組み」である.例えば,通常のプロセスから,以下の処理が行われると問題が起こる.
- メモリ管理:Wordのアプリを動かすためのプロセスが強制的にChromeのプロセス部分を侵略するとまずい
- プロセス管理:Wordプロセスが勝手にChromeプロセスをkillするとまずい
- デバイスへの自由なアクセス:SSDの写真が保存されている領域があったとして,Wordのドキュメントがそこに勝手にデータを保存する仕様だとまずい
このように,プロセスはその権限をある程度制限されるべきである.このように,プロセスように権限が制限された実行モードをユーザーモード,デバイスへのアクセスやスケジューリングなどの権限が必要な操作を行えるのがカーネルモードである.
かといって,プロセスがデータの保存が全くできないとなると,アプリケーションは非常に不便になる.そこで,そのようなカーネルモードの権限での実行を必要に応じて実行できるように安全なインターフェースを提供し,カーネルに依頼するための仕組みが「システムコール」である.つまり,システムコールはユーザーモードとカーネルモードの橋渡しの役割を担う.
更に,ユーザーモードから,ファイル操作の依頼を受けたカーネルは,デバイスドライバを通して,デバイスを操作する.つまりデバイスドライバは,OSとデバイスの橋渡しの役割を担う.
なお,カーネル = OSではなく,カーネルはOSの一機能であることに注意.
参考資料
- 試して理解Linuxの仕組み