第1回 (2018-02-15 10:00 ~)
syscallが呼ばれる際の動作
割り込みベクターテーブル
(Interrupt Vector Table; IVT)
参考: Interrupt
vector table - Wikipedia
OSの起動プロセス
Memory Layout
Overview
こんな感じのレイアウトになっている。
このリストは、アドレスが小さい順に並べてある。
- User space
- Text segment
- Data segment
- BSS segment
- Heap
- Memory mapping segment
- Stack
- Kernel space
参考: 「Segmentation
mémoire et Buffer Overflow – 0x0ff.info」に掲載されている画像
User Space
- プロセスごとに分離されたアドレス空間が用意される。
プロセス切り替えの際に、マッピングが変更される。
- Memory mapping
segmentに、ライブラリのファイルがマッピングされる。
Kernel Space
- プロセスを切り替えても、マッピングは切り替わらない。
- 大きく分けて2つの領域がある
- プロセスごとのスタック (syscallを処理するため)
- non-processが使用する領域
参考: Anatomy
of a Program in Memory | Many But Finite
Linuxのファイルシステムの構造
VFS
- struct file - open(2)したときにカーネル側で作成される。
- struct path
- struct vfsmount*
- struct dentry* - direntのキャッシュ
- struct inode -
- struct file_operations
- ほとんどのフィールドには、
mm/filemap.c
のgeneric_file_*()
関数が入ってることが多いらしい。
- 全ての操作を定義する必要がない。
__vfs_write()
は、.write
がnullなら.write_iter
を呼び出す。
.read
に関しても同様。
- kernel.org/doc/Documentation/filesystems/vfs.txt
- 長い….
物理ファイルシステム
ELF
その他
x86-64 CPUのレジスタ
第2回 (2018-02-21 10:00 ~)
Ext2
Linux v4.16-rc2 (commit 91ab883eb21325ad80f3473633f794c78ac87f51)
でのext2の実装を調査してみた。
Ext2 - file_operations
- fileとdirで異なるopを指定している。
- file
- Direct I/Oのための分岐処理が書いてある。:
read_iter
/
write_iter
/ mmap
open
はほぼ何もしてない
get_unmapped_area
/ release_file
?
- dir
read
できない
iterate_shared
-
dir.c:727:ext2_readdir
Ext2 - inode_operations
- ファイルの種類別に異なるinode_operationsを指定している。
file
/ dir
/ special file
/
symlink (fast and slow)
の5種類
- file
- ファイルの属性 :
setattr
/ get_acl
/
set_acl
- ファイルの中身が書いてあるblockを、メモリにマップする :
fiemap
- dir
- 基本的なファイル操作のためのメソッド :
create
/
link
/ mkdir
/ …
- 名前からinodeを探し出し、dentryにセットする :
lookup
- special file
- symlink
- pageにはリンク先が直接書いてあるようだ。それを読み込んで返す :
get_link
- fast_symlink
- キャッシュを使ってリンク先を調べる。非常に高速(だと思う) :
simple_get_link
Dirをreadするとき
(ext2の場合)
- readdir.c:459:getdents(2):
アプリケーションがgetdents(2)を呼び出す。
※ readdir(2)は廃止され、代わりにgetdents(2)を使用する。
- readdir.c:478:getdents(2): direntをキャッシュに読み込んで、user
spaceにコピー
- readdir.c:40:iterate_dir: inodeのロックを取る
- readdir.c:51:iterate_dir: file->f_op->iterate_shared()
- dir.c:308:ext2_readdir: ページキャッシュを取得する (何を?
どこから?)
この中の処理は、よく分からない。
ページキャッシュに乗ってたらそれのアドレスを返す。
キャッシュに乗ってなかったら…?
(mm/filemap.c:2768:wait_on_page_read)
- dir.c:341:ext2_readdir:
readdir.c:218
で指定したコールバック関数(filldir)を呼び出す。
filldir
の中では、user
spaceにある配列にdirentの内容をコピーしている。
- dir.c:350:ext2_readdir: 確保したページを開放する。
- readdir.c:58:iterate_dir: inodeのロックを開放
- readdir.c:488:getdents(2): fdのposを更新
Fileをopenしたとき
(ext2の場合)
- open.c:1077:open(2): do_sys_open()
- open.c:1057:do_sys_open: fd構造体を割り当てる
- open.c:1059:do_sys_open: do_filp_open()
- namei.c:3553:do_filp_open: path_openat()
- namei.c:3496:path_openat: file構造体を割り当てる
- namei.c:3518:path_openat: link_path_walk() - パス名解決をする
(後述)
- namei.c:3519:path_openat: do_last()
- namei.c::do_last: 色々チェックしたり、automountしたり。
- namei.c:3378:do_last: vfs_open()
- open.c:857:vfs_open: do_dentry_open
- open.c:750:do_dentry_open: f_op->open()
openには、dquot_file_open()へのポインタが入ってる。
- dquot.c:2153:dquot_file_open: generic_file_open()
- dquot.c:2154:dquot_file_open: 書き込みモードで開いた (dquot.c:2154)
& quotaが有効化されていたとき
- dquot.c:1436:__dquot_initialize:
3種類のQuotaを初期化されているかチェック
- dquot.c:1487:__dquot_initialize:
初期化されて無ければ、初期化する。
- open.c:1065:do_sys_open: fdをfdtableに書き込む
Fileをreadしたとき
(ext2の場合)
- read_write:568:read(2): fd(数値)からfd構造体を取得する。
- read_write:573:read(2): vfs_read()
- read_write:440:vfs_read:
ユーザが指定したバッファがvalidなものかチェック
- read_write:443:vfs_read: readアクセス可能な領域かチェック
- read_write:447:vfs_read:
__vfs_read()
- read_write:413:__vfs_read: new_sync_read()
- read_write:401:new_sync_read: call_read_iter() ==>
f_op.read_iter()
- file.c:170:ext2_file_read_iter: generic_file_read_iter()
- filemap.c:2364:generic_file_read_iter: generic_file_buffered_read()
- filemap:2071:generic_file_buffered_read: indexとoffsetを計算
- filemap:2071:generic_file_buffered_read: 色々やってる
- read_write:575:read(2): positionをファイル構造体のf_posに保存
使用されていた略語
- DAX: Direct Access
- kiocb: Kernel I/O Control Block
- ki_*: KIocb
- filp: FILe Pointer
- ra: ReadAhead
- bio: Block I/O
- RCU: Read Copy Update
参照をブロックせずにデータを更新する方法。
atomic.LoadPointer(&ptr) currentPtr
/
atomic.StorePointer(&ptr, newPtr)
みたいなことをやってる。
参考: RCU(Read Copy
Update)をちゃんと知る(1)-1 ほんとの概要
処理の最小単位
- VFS, mapping layer, FS: block
- block device: segment
- disk cache: page
- generic block layer: any
第3回 (2018-02-27 10:00 ~)
(VFS) パス解決の流れ
パス解決は、fs/namei.c
の中のlink_path_walk()
に実装されている。
パスのコンポーネント (“/”で区切られたディレクトリやファイル名のこと)
1つ1つを、以下のような手順で走査していき、最終的に目的とするinodeを得る。
- 親ディレクトリのアクセス権限 (executeビットが立っているかなど)
をチェック
- コンポーネント名から、32bitのハッシュ値を計算する。
戻り値の64bit整数は、上位32bitがコンポーネント名の長さ、下位32bitがハッシュ値が入っている。
- 連続する”/“を全て除去する。
例えば、”/usr//////lib”でもちゃんとパス解決出来るようにするため。
- walk_component()
- “.” (
last_type == LAST_DOT
) なら何もしない。
- “..” (
last_type == LAST_DOTDOT
)
なら、follow_dotdotを呼び出して親ディレクトリに移動する。
- それ以外は、親ディレクトリとファイル名のハッシュ値をキーに、dcacheを検索する。(
lookup_fast
)
dcacheにヒットしなかった場合は、i_op->lookup()
を呼び出す。(lookup_slow
)
lookup_slow
はファイルシステムの実装を呼び出しており、(多分)I/Oを伴う操作になる。
ext2のdisk layout
参考: Disk
layout of ext2 and ocfs2の9ページ目
ext2
struct ext2_sb_info
- super-block data in memory
ext2のinode_operations
Fileをmapする
ext2_fiemap
から、generic_block_fiemap
を呼び出しているだけ
Dirにファイルを作る
- ext2/namei.c:106:ext2_create:
- ext2/ialloc.c:447:ext2_new_inode:
struct inode
を作る
- ext2/ialloc.c:454:ext2_new_inode:
inodeを配置するgroupを決定。directoryのinodeを配置するgroupを決めるアルゴリズムは2種類ある。
参考: The
Second Extended
Filesystemのoldalloc
とorlov
を参照。
- ext2/ialloc.c:467:ext2_new_inode: inode
tableが空いているgroupを探す。全て埋まっていた場合は、隣のgroupから探す。
- ext2/ialloc.c:482:ext2_new_inode: inode
bitmapをスキャンして、空いている場所にflagを立てる。
- ext2/ialloc.c:519:ext2_new_inode: inode
bitmapに対応するbuffer_headをdirty (diskと同期が取れていない状態)
にする。
- ext2/ialloc.c:534:ext2_new_inode: カウンタを更新
- ext2/ialloc.c:551:ext2_new_inode:
struct inode
とstruct ext2_inode_info
のフィールドを初期化
- ext2/ialloc.c:602:ext2_new_inode:
新しく確保したinodeをdirtyにする。
- ext2/ialloc.c:604:ext2_new_inode:
デバイスが混雑していなければ先読み
bio周り
わからん
第4回 (2018-03-06 16:00 ~)
ネットワーク
パケットフォーマット
通信手順 (TCPクライアント)
- socket(2)
- bind(2)
- connect(2)
- read(2) / write(2) / send(2) / recv(2)
- close(2)
TCPクライアント側でwrite(2)/send(2)したときに起きること
(概要)
- 送信したいデータをsocket bufferに保存
- 送信先のmacアドレスを決める
- ルーティングテーブルを見て、本当の送信先のIPアドレスを引く
- ARPテーブルを見て、送信先のmacアドレスを引く
- NICがパケットを送信
- ACKが帰ってきた分のデータをsocket bufferから削除
- IP層: MTU、送り先の決定、分割されたIPパケットを復元
- TCP層: スピードの制御、再送
実装
プロトコル毎にハンドラが異なるため、input/outputハンドラをプロトコル毎に定義してもらう実装になっているみたい。
プロセス切り替え
- プロセス切り替えが起こるタイミング
- 同期命令のsleepを実行したとき
- wakeupのほうは実行可能状態にするだけで、プロセス切り替えをするわけではない
- イベントドリブンスケジューリング
- タイムスライススケジューリング: CPU割り当てを平等にするために
- プリエンプティブ:
Kernel側が動いているときでも、プロセス切り替えが起こる。
プロセス切り替えをするときに起こること
- CPUのレジスタの値をスタックに退避
User側 + Kernel側のスタックが切り替わる
- 切り替え先のプロセスを決める
- スタックに退避した値をレジスタに復元
第5回 (2018-03-26 15:00 ~)
デバイスドライバ: パケットをmacパケットを受信 デバイスドライバ:
macパケットのヘッダーに書いてあるプロトコル番号を見て、それぞれのプロトコルのルーチンをcallする。
ip_input.c: ip_recv() IP headerのverifyをする
nf_hookの中で、これ以降の処理を処理をするスレッドを割り当てる。
ルーティングテーブルの確認。 dst_input(): ???
packet: forward/receive/drop ## TCP層 パケットを受信したとき *
ACK返し、ついでにWindow Sizeを更新。 * バッファに追加。 *
readシステムコールでブロックされているプロセスを起こす。
TCP層
パケットを送信するとき
- ヘッダをTCPパケットを作る。
- 流量コントロールをする
- パケットの再送処理
- ACK帰ってきた分をバッファから消す。
pci_driver.prove
inet_add_protocol
kernelで使われている同期命令の種類の一覧
mmの仕事
- address spaceは何種類かある
- file: cache
- process: working set
- etc…
- ページを割り当て
- free pagesが足りないときは、他のaddress spaceから剥がす。
- 必要なら PTE (Page Table Entry) の更新
第6回 (2018-04-05 13:00 ~)
Semaphore
User processで使うsemaphore
- P()で停止するときは、そのプロセスを実行不可状態にして、他のプロセスに切り替える。
- V()を呼び出したときは、P()状態で待っているプロセスを実行可能状態にする。
Kernelで使うsemaphore
- P()で停止するときは、直接プロセススケジューラを呼び出す。
使われ方
スレッド間・プロセス間でデータ共有するとき
mutexの実装
mutex_lock()
ロック取れなかったら、schedule()を呼び出し、その中でアーキテクチャごとに実装されている
__switch_to_asm()
と __switch_to()
に飛ぶ。
レジスタの中身とアドレススペースを切り替え、TLBを消す。
Thread
Linuxでは、一つの論理アドレス空間を共有している複数のプロセスとして実装されている。
https://mookjp.github.io/memo/linux%E3%81%AE%E3%83%A1%E3%83%A2%E3%83%AA%E7%AE%A1%E7%90%86%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/
第7回 (2018-04-17 18:30 ~)
プロセス切り替えの仕方
__schedule()
で次に切り替えるタスクを選び、context_switch()
を呼び出す。
その中でswitch_mm_irqs_off()
が呼び出され、ユーザ空間のアドレス空間を切り替える。
その後、__switch_to_asm()
を呼び出し、タスクを切り替える。
__switch_to_asm()
は、先頭でレジスタを保存しているが、C言語から呼び出したときに自動的に保存されるなどの事情で、asmでは一部のレジスタしかpushしてない。
task_struct 構造体
プロセスに関するデータが詰まっている。
実行状態(state
)や、pidなど。
レジスタの内容など、アーキテクチャ依存な部分はthread_struct
に実装されている。
略語
smp: shared memory processer rq: runqueue