Closed10

Linux Syscallのmmapの実装を辿る on RV64

ピン留めされたアイテム
NanamiiiiiNanamiiiii

TL;DR

Linuxにおける,仮想アドレスへのマッピングを行うシステムコールmmap()の実装を追う必要に迫られたので,メモ

個人的Goal

Kernel DriverでMisc Deviceを作成し,そこで実装したf_op->mmap()が呼ばれるまでの流れや引き渡されるデータを追う

Reference

https://qiita.com/akachochin/items/259c865cf4ab1fcd11df

NanamiiiiiNanamiiiii

システムコールの定義から追う
ユーザーランドから呼ばれるのはこれ

void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);

Context Switchを挟んでカーネル側で呼ばれる実体はarch/riscv/sys_riscv.cにある

https://github.com/torvalds/linux/blob/v6.5/arch/riscv/kernel/sys_riscv.c#L20-L39

下側のSYSCALL_DEFINE6のところがRISC-Vにおける実質的なmmap()の定義.
これはそのまま上側のriscv_sys_mmap()へ引数を渡して呼んでいるのみ.

riscv_sys_mmap()offsetをチェックし,ページ単位に変換してksys_mmap_pgoff()を呼ぶ.

NanamiiiiiNanamiiiii

ここではNOMMUでない場合の実装を追うので,ksys_mmap_pgoff()の実装はmm/mmap.cにある.
ちなみにこれはmmap_pgoff()としてシステムコール定義もされている.

https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L1371-L1421

まず,flagsMAP_ANONYMOUSの指定があるかで分岐する.このフラグが指定されている場合,ファイルとの関連付けをせず,fdoffsetを無視する.
MAP_ANONYMOUSの指定がない場合,上側のifブロックを通る.
https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L1378-L1388

  • audit_mmap_fd()
    • syscall監査を有効(CONFIG_AUDITSYSCALL = y)にしてビルドしたカーネルの場合,ここでシステムコールの監査が入る.無効であれば何もしない.
  • fget()
    • struct fileのポインタを取ってくる
  • is_file_hugepages()
    • ファイルがhugepageを扱うかどうか?f_opのポインタで判定しているみたい(hugepageのf_opがグローバル定数で定義されているため.細かく読んでないので違うかも.)
    • hugepageの場合,マッピングする長さをhugepageのサイズにalignする
    • hugepageでないのにflagsMAP_HUGETLBの指定がある場合,エラー

MAP_ANONYMOUSの指定があり,かつMAP_HUGETLBでもあるときは以下.
https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L1389-L1407

  • hstate構造体を得てマップサイズをalign
  • hugetlb_file_setup()
    • ここではanonymousなのでダミーのファイルをセットしている?

ここまで下処理で,これが主処理.
https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L1409

NanamiiiiiNanamiiiii

do_mmap()

https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L1188-L1369

L1234までは諸々の引数の処理など(validation等)

get_unmapper_area()で仮想アドレス空間から空き領域を得る
https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L1236-L1241
ここで引数で渡されたaddrを書き換えている.引数として渡されるaddrはあくまでマップするアドレスのヒントであり,可能であればaddrにマップされるよう.ただ,基本は0(NULL)を指定する(マップするアドレスは0には絶対にならないので,addrは実際にマッピングするアドレスに書き換えられる).

L1243-L1266あたりまでは,フラグに関連した処理だったりが続く.その後,バッキングストアが存在するかで処理が分岐.

NanamiiiiiNanamiiiii

バッキングストアがあるとき

https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L1268-L1327

  • file_mmap_ok()
    • マップできる最大のサイズをlenが超えていないかチェック
    • (そもそもマップ自体が可能かの確認も)
  • 残りはマップフラグ関連の処理
    • MAP_SHAREDMAP_SHARED_VALIDATEMAP_PRIVATE
    • いずれもない時はEINVAL

バッキングストアなし

https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L1327-L1347

  • 同様にマップフラグ関連の処理
    • MAP_SHAREDMAP_PRIVATE
    • これもいずれもない時はEINVAL

その後MAP_NORESERVEの処理を挟み,mmap_region()を呼ぶ.
https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L1353-L1367

NanamiiiiiNanamiiiii

mmap_region()

https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L2643-L2645


https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L2659-L2671

  • アドレス空間を拡張できるかチェック
  • そのままでは拡張できない場合,交差するvmaをremoveすることで拡張可能かをチェックする

https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L2673-L2685

  • 範囲内のマッピングをすべてunmap
  • accountable_mapping
    • Private Writableなマッピングかどうか
    • security_vm_enough_memory_mmが新しい仮想マップ全体のパーミッションをチェックする

https://github.com/torvalds/linux/blob/v6.5/mm/mmap.c#L2687-L2719

  • 古いマッピングの拡張を試行
  • 後続,前方の順でvmaを拡張できるかチェックし,merged vmaのパラメータを設定
  • 最終的に可能であればmergeを実行
このスクラップは2023/09/26にクローズされました