好きこそものの上手なるyukichi

プログラミング学習の備忘録。万歳車輪の再発明。

【試して理解】Linuxのしくみ 読んだメモ

忘れないようメモ📝LinuxのシステムをC言語でプログラム書いて色々覗いてみようという本。 個人的に見よう見まねでCを書けて楽しかったし、メモリ操作とかあってハードウェアに近い言語なんだなって体感した。 Cをもう少しやってみたいので、東大でCの基礎を学ぶ講義が公表されているのでこれをやってみようと思います。

1

2. ユーザーモード

3. プロセス管理

  • プロセス生成は2種類
    • 同じプログラムの処理を複数プロセスに分けて処理
      • fork()(内部的にはclone()):発行したプロセスを元に、新たにプロセスを1つ生成。
    • 全く別のプログラムを生成 ‐ プロセス数が増えるのではなく、あるプロセスを他のプロセスで上書きする
      • execve():
    • プログラム実行の流れ:プログラムファイルの情報(補助的情報・コード領域・データ領域)に基づいてメモリ上にマップされる → エントリポイントからプログラムを実行する
    • プロセス終了時は_exit()関数を使用。直接呼び出すことは少なく、Cならexit()呼び出しで終了

4. システムスケジューラ

  • 論理CPU上である瞬間に動作できるプロセスは1つのみ
  • 複数プロセス実行中の場合、途中でプロセスを切り替えている→切り替えをコンテキストスイッチという
  • プロセスのスリープ状態時はCPUはアイドリング状態(使っていない)
  • レイテンシ:処理の終了までの所要時間
  • スループット:単位時間当たりの総仕事量
  • 論理CPU0と、論理CPU/2の番号のものは独立している
  • プロセス数を論理CPU数より多くしても、スループットは上がらない
  • nice(): 特定のプロセスに優先度を設定する。-19(高い)~20(低い)。優先度を上げられるのはrootのみ

5. メモリ管理

  • メモリ使用量が増えて身動きが取れない事:OOM(out of memory), メモリ管理システムはこの時適当なプロセスをkillしてメモリを開放する機能がある。
  • sysctlのvm.panic_on_oomが0→デフォルト、killer発動, 1->OOM時にシステム強制終了
  • プログラムはメモリが断片化していると、使用できないこともある
  • 仮想記憶:物理メモリにプロセスから直接アクセスするのではなく、仮想アドレスを用いて間接的にアクセスすること。仮想アドレス=プロセスから見えるメモリのアドレス。プロセスから物理アドレスに直接アクセスする方法はなし。
  • 仮想アドレス≠物理アドレス
  • 仮想アドレスと物理 -アドレスの対応表をページテーブルという。サイズは4KB(x86_64アーキテクチャ).
  • メモリ割り当て方法
    1. 必要な領域を物理メモリに割り当て、必要なデータをそこにコピー
    2. カーネルのメモリ内にページテーブルを作って、仮想物理アドレス空間を紐づける
    3. 所定のアドレスから実行
  • mmap関数:ページ単位で仮想メモリ取得、大きめメモリをプールしておく→その後malloc関数:バイト単位でメモリ取得
  • ファイルマップ:mapp()を所定の方法で呼び出すことで、ストレージデバイス内の該当ファイルをメモリに呼び出す→その空間を仮想アドレス空間マッピングできる
  • デマンドページング:
    1. プロセス生成時に仮想アドレス空間内の領域を獲得、が物理アドレスには未割当
    2. プログラムがエントリポイントから実行されると、CPUにてページフォルトが発生し、カーネルページフォルトハンドラが物理メモリにアドレスを割り当てる…続く
  • コピーオンライト;fork()時は、親プロセスのページテーブルを子プロセスにコピーする(一緒の物理アドレスを指している)この時書き込み権限は無効化されている
    • 親・子どちらかがページを更新したいとき
      1. カーネルページフォルトハンドラがアクセスされたページを別の場所にコピーして、書き込みしようとしたプロセスに和知宛てる
      2. 親・子それぞれ共有が解除されたページに対応するページテーブルを書き換える
      3. 書き込みしたい側は新たな物理ページと紐づけて、書き込みの許可をする。
      4. もう片方の当該ページも書き込みの許可をする
        親と子で共有されているメモリは、それぞれのプロセスで二重計上される
  • スワップ:ストレージデバイスの一部を一時的にメモリの代わりとして使用する。物理メモリが枯渇した際、使用の一部をストレージに退避させる。メモリを退避させる領域をスワップ領域(windowsでは仮想メモリ)、退避させることをスワップアウトという。取り戻すことをスワップイン、合わせてスワッピングという
    • どの領域を退避させるかの判断はカーネルのアルゴによる
    • スワッピングが頻繁に行われることをスラッシングといい、重くなる。
    • スワッピングのようにストレージへのアクセスが発生するページフォルトメジャーフォルトという
  • ヒュージページ;プロセスのべージテーブルに必要なメモリ量を減らせる、データベースなど仮想メモリを大量に使用するときに検討すると吉

6. 記憶階層

  • コンピュータの動作の流れ
    1. 命令をもとに、メモリからレジスタにデータを読み出す
    2. レジスタ上のデータをもとに計算する
    3. 計算結果をメモリに戻す
  • レジスタのメモリのやり取りのところで時間がかかる→キャッシュメモリを利用
    • キャッシュメモリ:基本CPU内にある。メモリのデータをキャッシュメモリにキャッシュする
      • レジスタの値が変えられたら、まず変更データをキャッシュメモリに上書きして、ダーティという印をつける→バックグラウンド処理としてメモリに書き戻される(キャッシュメモリはダーティでなくなる)
      • 階層型でレベルが存在する。一番レジスタに近く、容量が少なく、高速なのがL1
      • /sys/devices/system/cpu/cpu0/cache/index0/:cpu0のL1キャッシュの情報
    • ページキャッシュ:ストレージのデータをメモリにキャッシュすること ‐ プロセスがファイルのデータを読み出す場合、まずカーネルのメモリ上のページキャッシュ領域にコピーして情報を保持→そのデータをプロセスのメモリにコピーする.再度呼び出された場合早く呼び出せる。
      • プロセスにより更新されたら、ページキャッシュにデータを書き込んでダーティマークをつける ‐ ダーティページが多い&メモリ不足なシステムライトバック(ページキャッシュからストレージへの書き込み)が頻発するため遅くなる
        • sysctl vm.dirty_writeback_centisecs: ダーティページのライトバック周期センチ秒(1/100秒)
        • sysctl vm.dirty_background_radio: 全メモリの内、表示された割合のダーティページが超えた場合、ライトバック処理が走る
      • ページキャッシュ&バッファキャッシュ=ストレージ内のデータをメモリ上に置いておく仕組み(ざっくり)
      • echo 3 > /proc/sys/vm/drop_caches: システムのページキャッシュを削除する
  • ハイパースレッド機能:CPUコア内のレジスタなどを複数用意して、それぞれを論理CPUと認識されるようにするハードウェア機構スループットが単純に倍になるという訳でもない。
    • /sys/devices/system/cpu/cpuNum/topology/thread_siblings_list: ペアとなるスレッドの表示

7. ファイルシステム

  • どこにどんなデータがあり、どこに空き領域があるか管理する仕組み
  • linuxは複数のファイルシステムを扱える(ext4, XFS, Btrfs... ストレージデバイス上に存在する)
  • データ(データの中身・内容)とメタデータ(種類・時刻・権限情報)の2種類がある
  • ファイルシステムごとに使用できる容量を制限する機能があり、クォータという ‐ ユーザクォータ、ディレクトリクォータ…
  • システム不整合を防ぐ方法
    • ジャーナリング(for ext4, XFS): 更新に必要な処理一覧をジャーナル領域(ファイルシステム内のメタデータ)に書き出す→更新中にシャットダウンしたら、ジャーナルログを最初からもう一度再生する
    • コピーオンライト(for Btrfs)
      • 前提としてコピーオンライト型のファイルシステムは、ファイルに更新が入ったら書き換えられた部分のみを別の場所にコピーする
  • tmpfs: メモリ上に作成するファイルシステム、再起動後はデータが消えている
    • mount | grep ^tmpfs
    • freeコマンドのsharedフィールドの値はtmpfsによって使用されているメモリ量(KB)
  • ネットワークファイルシステムリモートホスト上のファイルにアクセス。windowsはcifs, UNIX系はnfs

8. ストレージデバイス

  • HDD
    • プラッタという磁気ディスク
    • セクタ単位で読み書きする
    • スイングアームという針のようなものが上下することでセクタを選び、先端の磁気ヘッドで読み書きする
    • ブロックデバイス:ランダムアクセス可能で、一定量(セクタ)ごとにアクセス可能なデバイスの事
      • 大体ファイルシステムを介してアクセス
      • 各種ブロックデバイスに共通の処理はカーネル内のブロックデバイス層で対応
        • I/Oスケジューラ:ブロックデバイス層にある機能。マージ(複数の連続セクタのI/O要求を1つにまとめる)、ソート(複数の不連続なセクタへのI/O要求をセクタ番号順に並び変える)を行う ‐ 先読み機能:ある領域にアクセスした時、それに続く領域も読み取ること。もし使われなければデータを捨てる
  • SSD
    • データへのアクセスに機械的な動作がなく、電気的な動作だけで済む→HDDより高速
    • I/O支援機能により速さが下がることも。複数のI/O要求をためておく処理のオーバーヘッドがSSDでは無視できないため ‐ point
      1. ファイル内のデータを連続する or 近い領域に配置する
      2. 連続する領域へのアクセスは、複数回に分けずにまとめる
      3. ファイルにはなるべく大きなサイズでシーケンシャルアクセスする

使ったコマンド💻

  • strace: プロセスが呼び出すシステムコールをみる
  • sar: プロセスがユーザーモードカーネルモードのどちらで実行しているのかみる
    • sar -P ALL 1(1秒ごとに見る) sar -P ALL 1 1(1秒ごとに1回測定)
    • ユーザーモード→%userと%niceの合計 ‐ カーネルモード→%system
    • sar -q 1 1: runq-szフィールド→実行中or実行待ちプロセス数
    • sar -r 1: 1秒ごとのメモリ状況。kbmemused->物理メモリ使用量、kbswpused->スワップ領域の使用量、kbpgtbl->ページテーブルに使用している量, kbcached->ページキャッシュの総量(kb)
    • sar -W 1: ``ごとのスラッシング確認、pswpin/sがスワップイン ‐ sar -d -p 1: ストレージデバイスに対するI/O量
  • ldd: プログラムがどのライブラリにリンクしているかみる
    • ldd /bin/echo ‐ readelf
    • readelf -h /bin/sleep: 開始アドレスを表示
  • /proc/pid/maps: プログラム実行時に作成されたプロセスのメモリマップ表示、仮想アドレスを表示
  • taskset: 指定の論理CPUで動作させる
    • task -c 0 ./sched
  • ps ax: 動かしているプロセスの確認
    • STAT: R->実行or実行待ち状態、S, D->スリープ状態(シグナルによって実行状態に戻るのがS), Z-> ゾンビ状態(親プロセスの終了待ち)
  • ps -eo pid,comm,etime,time: 経過時間(elapsed)と使用時間(time)
  • grep -c processor /proc/cpuinfo:論理CPUの数
  • timeコマンド:CPUの経過時間(real)と使用時間(user + sys,全コアの合計使用時間)がみれる ‐ time taskset -c 0 ./sched 1 10000 10000
  • nice -n 5 echo hello: 優先度5でecho helloする
  • free: memory容量確認(kバイト)
  • swap --show: システムのスワップ領域確認
  • cat /sys/kernel/mm/transparent_hugepage/enabled: トランスペアレントヒュー次ページの使用を確認
  • df: ファイルシステムのストレージ使用量
  • /proc/pid: 各プロセスの情報を得る
    • maps, cmdline, stat
    • /procにはその他ハード情報が盛りだくさん、詳しくはman procで ‐ /sys: procのようなカーネルのプロセスに関するもの以外の雑多な情報