Linux Syscallのmmapの実装を辿る on RV64
TL;DR
Linuxにおける,仮想アドレスへのマッピングを行うシステムコールmmap()
の実装を追う必要に迫られたので,メモ
個人的Goal
Kernel DriverでMisc Deviceを作成し,そこで実装したf_op->mmap()
が呼ばれるまでの流れや引き渡されるデータを追う
Reference
読むソース
GitHub上のソースツリーのv6.5
システムコールの定義から追う
ユーザーランドから呼ばれるのはこれ
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
Context Switchを挟んでカーネル側で呼ばれる実体はarch/riscv/sys_riscv.c
にある
下側のSYSCALL_DEFINE6
のところがRISC-Vにおける実質的なmmap()
の定義.
これはそのまま上側のriscv_sys_mmap()
へ引数を渡して呼んでいるのみ.
riscv_sys_mmap()
はoffset
をチェックし,ページ単位に変換してksys_mmap_pgoff()
を呼ぶ.
ここではNOMMU
でない場合の実装を追うので,ksys_mmap_pgoff()
の実装はmm/mmap.c
にある.
ちなみにこれはmmap_pgoff()
としてシステムコール定義もされている.
まず,flags
にMAP_ANONYMOUS
の指定があるかで分岐する.このフラグが指定されている場合,ファイルとの関連付けをせず,fd
とoffset
を無視する.
MAP_ANONYMOUS
の指定がない場合,上側のifブロックを通る.
-
audit_mmap_fd()
- syscall監査を有効(
CONFIG_AUDITSYSCALL = y
)にしてビルドしたカーネルの場合,ここでシステムコールの監査が入る.無効であれば何もしない.
- syscall監査を有効(
-
fget()
-
struct file
のポインタを取ってくる
-
-
is_file_hugepages()
- ファイルがhugepageを扱うかどうか?
f_op
のポインタで判定しているみたい(hugepageのf_op
がグローバル定数で定義されているため.細かく読んでないので違うかも.) - hugepageの場合,マッピングする長さをhugepageのサイズにalignする
- hugepageでないのに
flags
にMAP_HUGETLB
の指定がある場合,エラー
- ファイルがhugepageを扱うかどうか?
MAP_ANONYMOUS
の指定があり,かつMAP_HUGETLB
でもあるときは以下.
-
hstate
構造体を得てマップサイズをalign -
hugetlb_file_setup()
- ここではanonymousなのでダミーのファイルをセットしている?
ここまで下処理で,これが主処理.
vm_mmap_pgoff()
の実装
-
security_mmap_file()
-
mmap
のpermissionチェック
-
- lockを取得して
do_mmap()
を呼ぶ
do_mmap()
L1234までは諸々の引数の処理など(validation等)
get_unmapper_area()
で仮想アドレス空間から空き領域を得る
ここで引数で渡されたaddr
を書き換えている.引数として渡されるaddr
はあくまでマップするアドレスのヒントであり,可能であればaddr
にマップされるよう.ただ,基本は0(NULL
)を指定する(マップするアドレスは0には絶対にならないので,addr
は実際にマッピングするアドレスに書き換えられる).
L1243-L1266あたりまでは,フラグに関連した処理だったりが続く.その後,バッキングストアが存在するかで処理が分岐.
バッキングストアがあるとき
-
file_mmap_ok()
- マップできる最大のサイズを
len
が超えていないかチェック - (そもそもマップ自体が可能かの確認も)
- マップできる最大のサイズを
- 残りはマップフラグ関連の処理
-
MAP_SHARED
とMAP_SHARED_VALIDATE
とMAP_PRIVATE
- いずれもない時は
EINVAL
-
バッキングストアなし
- 同様にマップフラグ関連の処理
-
MAP_SHARED
とMAP_PRIVATE
- これもいずれもない時は
EINVAL
-
その後MAP_NORESERVE
の処理を挟み,mmap_region()
を呼ぶ.
mmap_region()
- アドレス空間を拡張できるかチェック
- そのままでは拡張できない場合,交差するvmaをremoveすることで拡張可能かをチェックする
- 範囲内のマッピングをすべてunmap
-
accountable_mapping
- Private Writableなマッピングかどうか
-
security_vm_enough_memory_mm
が新しい仮想マップ全体のパーミッションをチェックする
- 古いマッピングの拡張を試行
- 後続,前方の順でvmaを拡張できるかチェックし,merged vmaのパラメータを設定
- 最終的に可能であればmergeを実行
- 拡張できなかった場合,Label:
cannot_expand
へ入る - 新たなvmaを割り当て,イテレータに挿入
- バッキングストアの有無で分岐
-
call_mmap()
でf_op->mmap()
が呼ばれる (L2751)
call_mmap()
- ここで渡される
vma
は新たに割り当てられたvma