タダノキロク

日々の考えたことの記録 主に無駄なこと

#詳細Linuxカーネル 5章 同期処理を読む

詳細Linuxカーネル 5章 同期処理を読む

Linuxではマルチプロセッサでの割込み・システムコール・例外発生時に、
同じ変数・メモリへのアクセスを適切に同期(順序付け)が必要になる。

5章ではそのためにの同期処理方法を説明している。
今回は、カーネルが使用している同期技法のスピンロックを読む。

スピンロック

スピンロックによる同期の実現は以下の カーネルは共有しているデータへのアクセス時に資源確保しロックする。
アクセスが終わった時点でロックを解除する。
すでにロックされた状態だと、そのカーネルはロックが解除するまでループして待つ。

スピンロックでは、ロック開放の待ち時間はCPUを消費し続けるので、
無駄に見えるがカーネル資源の多くはロック時間が短いため問題ない。

スピンロックによるデットロックの対策は何かされているのか?

スピンロックの構造体 spinlock_t

スピンロックはspinlock_t構造体で実現。
構造体の中身はraw_spinlock構造体とデバッグ用の何か。

  • slock :スピンロック状態を示す。1がロック開放状態。
  • break_lock :ロックによって待っているプロセスがいることを示す。

スピンロックの確保 spin_lock()

スピンロックを確保するための関数 spin_lock()は、
spinlock_t構造体のアドレスを引数にとる。

Spin_lock()raw_spin_lock()->_raw_spin_lock()ー>__raw_spin_lock()-の順にコール/マクロ化され、実体は__raw_spin_lockになる。

static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
  preempt_disable();
  spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
  LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
  1. preempt_disable()をコール
      カーネル内のプリエンプションを禁止(5.1章に書いているから後で読む)

  2. spin_acuore()をコール
      デバック用の関数っぽい。あとでみる

  3. LOCK_CONTENDEDのマクロを実行

    1. do_raw_spin_trylockでスピンロックが取れているか確認。
      実体はarch_spin_trylock。ここでなにかしてるっぽい。
      ここまでにするのか・・・
#define LOCK_CONTENDED(_lock, try, lock)      \
do {                \
  if (!try(_lock)) {          \
    lock_contended(&(_lock)->dep_map, _RET_IP_);  \
    lock(_lock);          \
  }             \
  lock_acquired(&(_lock)->dep_map, _RET_IP_);     \ 
} while (0)

12章 VFS 1/9アップデート

詳細Linuxカーネル 12章 VFSを読む

LinuxVFSを導入することで、 いろんなファイルシステムを扱うことを可能にしている。

共通ファイルモデルの役割

VFSは共通ファイルモデルに従っている。
共通ファイルモデルで Linuxカーネルは各ファイルシステム固有の関数を呼び出し、 ソフト・アプリ側に意識をさせない役割がある。

VFSが取り扱うシステムコール

12.6.1参照

ファイルコピーのプログラムcp()を通してVFSシステムコールを確認する。
cpが以下のコードで実現できるらしい。(正常系のみ)

nf = open("/floppy/TEST", O_RDONLY, 0);
outf = open("/tmp/test", O_WRONLY | O_CREAT | O_TRUNC, 0600); 
do {
  len = read(inf, buf, 4096);
  write(outf, buf, len); 
} while (len); 
close(outf);
close(inf);

open() システムコール

open()の実体はdo_sys_open()になる。
引数は以下の4つ。

  • dfd:
  • filename:ファイルのパス名
  • flags:アクセスモードフラグ
  • mode:パーミッションビット

ソースを追うと

  • builud_open_flagsflagsmodeでアクセスモード、フラグをopen_flagに設定

  • getnameでファイルのパス名を読み取り

    カーネルがユーザ空間のファイル名を読み取りを実施する。
    具体的にはgetname_flags__getnameをコールしてkmemからファイルを展開する領域確保する。 そして__getnameで確保した領域に、 stryncpy_from_user()でユーザ空間のファイル名をカーネル空間にコピー。 もしも、ファイル名がEMBEDDED_NAME_MAXより大きいと、PATH_MAXで再度コピー。  

  • get_unused_fd_flags()でfd(ファイルディスクリプタ)の空きを確保する。

  • do_filp_opne()でfile構造体を取得する。

    以下の順序にて設定

    • set_nameidata()dfdとファイル名からnameidata構造体を生成  ー>これを何に使うかはわからなん。

    • path_openat()がfile構造体を取得するメインの関数。

      • get_empty_filp()でからのfile構造体を取得。

      • フラグopen_flagでfile構造体のファイルフラグを初期化。

      • nameidata構造体(nd)の初期化とファイル名からPathの検索を実施。  絶対パスの場合、dfd=AT_FDCWDの場合を考えてる。  ここをもう少し読みたいが、まずはnameidata構造体を理解すべきか。

  • fd_installでflie構造体のポインタをfdに格納する。

12章 VFS

詳細Linuxカーネル 12章 VFSを読む

LinuxVFSを導入することで、 いろんなファイルシステムを扱うことを可能にしている。

共通ファイルモデルの役割

VFSは共通ファイルモデルに従っている。
共通ファイルモデルで Linuxカーネルは各ファイルシステム固有の関数を呼び出し、 ソフト・アプリ側に意識をさせない役割がある。

VFSが取り扱うシステムコール

ファイルコピーのプログラムcp()を通してVFSシステムコールを確認する。
cpが以下のコードで実現できる。(正常系のみ)

nf = open("/floppy/TEST", O_RDONLY, 0);
outf = open("/tmp/test", O_WRONLY | O_CREAT | O_TRUNC, 0600); 
do {
  len = read(inf, buf, 4096);
  write(outf, buf, len); 
} while (len); 
close(outf);
close(inf);

open() システムコール

open()の実体はdo_sys_open()になる。
+getnameでファイルのパス名を読み取り +get_unused_fd_flagsで空きを作成

力尽きた・・・

9章プロセスアドレス空間 20151121Linux Kernel 勉強会 #13

詳細Linuxカーネルの9章のプロセスアドレス空間を読んだメモ。

9章 プロセスアドレス空間

ー>カーネルがプロセスから依頼されてメモリ確保の規則
 ・ユーザモードプロセスへの動的メモリ割り当ては遅延させる
 ・ユーザモードのプロセスはアドレスバグがあるため、
  アドレッシングエラーを補足する

9.1 プロセスのアドレス空間
   =プロセスが使用できるすべてのリニアアドレス

カーネルはメモリリージョンをプロセスに付与
      +先頭リニアアドレス 4kB単位
      +大きさ・サイズ   4KB単位
      +アクセス権

カーネルページフォルト要因を2つに識別
 ・プログラミングエラーにより発生した
 ・ページが存在しないために発生した
  (カーネルがプロセスに渡したリニアアドレスのページ割当をしていなかった場合?)

9.2 メモリディスクリプタ
   =プロセスアドレス空間に関する情報
    (持ち主はプロセス?)

メモリディスクリプタの構造体:mm_struct
(プロセスディスクリプタ mmから参照する)

mm_users : mm_struct データを共有する軽量プロセス数
mm_count : メモリディスクリプタ参照数
カーネルがプロセスのメモリディスクリプタを参照するときには、いずれかのメンバ1加える


9.3 メモリリージョン
 vm_area_struct構造体(メモリリ0ジョンディスクリプタ)でメモリリージョンを管理

カーネル側がメモリリージョンをどうやって管理しているか?

9.4 ページフォルト例外ハンドラ

割り込みのハンドラはページフォルト例外発生のアドレスが、
プロセス空間かつメモリリージョンに割当済みであれば、ページ割当を実施。
それ以外はプロセス/カーネル異常とする。

ページフォルト例外ハンドラの”do_page_falut()”を以下で確認していく。

・エラー発生時のリニアアドレスを取得する。
Intelx86の場合はCR2に格納されている。

unsigned long address = read_cr2(); /* Get the faulting address */

・__do_page_falut()をコールする
 割り込み処理中であることを宣言してコール
 引数は、pt_regs 構造体のアドレス regs/例外発生時のErrorCode/リニアアドレス(address)

・以下ページフォルトの実体 __do_page_falut

   if (unlikely(fault_in_kernel_space(address))) {      /* リニアアドレスがカーネル空間でではないことを確認*/
                                                                                    /*  address > 0xc0000000   */
     if (!(error_code & (PF_RSVD | PF_USER | PF_PROT))) {    /* ErrorCodeにbit0,2,3にエラーがない場合)*/
       if (vmalloc_fault(address) >= 0)  /*アドレスがvmalloc空間の場合はメモリ確保?
         return;
     
       if (kmemcheck_fault(regs, address, error_code)) /*?????? */
         return;
     }
	/* ErrorCodeにbit1にエラーがある場合 */
	/* Can handle a stale RO->RW TLB: */
	 if (spurious_fault(error_code, address))  /* 擬似的なページフォルト*/
		return;                                /* Write時/プレフィチェ時のページレベル違反以外はページ割当を実施*/


	 /* kprobes don't want to hook the spurious faults: */
	 if (kprobes_fault(regs))
	 	return;
	 /*
		 * Don't take the mm semaphore here. If we fixup a prefetch
		 * fault we could otherwise deadlock:
		 */
	 bad_area_nosemaphore(regs, error_code, address);
	 return;
	}