BH
マルチプロセッサでも複数実行は不可
登録数は32まで。
(2.4の実装としては高優先度タスクレットとして登録され実行される)
タスクキュー
BHの拡張。
一つのBHに複数のハンドラを登録できる。
以下のタスクキューはBHの中で処理される。
tq_immediate 即実行用タスクキュー
tq_timer タイマ用タスクキュー
他にもタスクキューは存在する。
ソフト割り込み
マルチプロセッサの場合、各プロセッサで同じ割り込みが動作可能
タスクレット
ソフト割り込みの一つ。
複数のハンドラを登録できる。
複数のCPUで同時実行できるが同じタスクレットは同時実行できない。
softirq_vec[]
+--------------+
| | HI_SOFTIRQ:tasklet_hi_action()
+--------------+
| | NET_TX_SOFTIRQ
+--------------+
| | NET_RX_SOFTIRQ
+--------------+
| | TASKLET_SOFTIRQ:tasklet_action()
+--------------+
:
+--------------+
| |
+--------------+
ソフト割り込みのハンドラが登録される。
open_softirq()で登録する。
raise_softirq()でスケジュールする
tasklet_vec[] (HI_SOFTIRQ用にtasklet_hi_vec[]もある)
+--------------+
| CPU#0 | --> tasklet_struct -> tasklet_struct ->
+--------------+
| CPU#1 |
+--------------+
:
タスクレットのハンドラを登録する。
tasklet_init()でtasklet_structを初期化して、
tasklet_schedule()によってtasklet_structをチェーンして、
TASKLET_SOFTIRQをスケジュールする
bh_task_vec[]
+--------------+
| | bh_action()
+--------------+
| | bh_action()
+--------------+
:
+--------------+
| | bh_action()
+--------------+
BHは高優先度タスクレット(HI_SOFTIRQ)の一つとして実行される。
BH呼出し用のタスクレット一覧。
mark_bh()によってtasklet_hi_vec[]にチェーンされる。
bh_base[]
+--------------+
| |
+--------------+
| |
+--------------+
:
+--------------+
| |
+--------------+
BHのハンドラが登録される。
init_bh()で登録。
do_softirq()
softirq_vecに登録されているハンドラを呼出
タスクレットのソフト割り込みが発生していたらtasklet_(hi_)action()
を呼び出してタスクレットを処理
tasklet_hi_action()
tasklet_hi_vec[]に登録されているタスクレットハンドラを呼出
BHのタスクレットもここに登録される。
BHのタスクレットはハンドラbh_action()を指している。
tasklet_action()
tasklet_vec[]に登録されているタスクレットハンドラを呼出
(チェーンされているタスクレットを全て実行)
bh_action() <-- 高優先度タスクレットとして実行
bh_base[]に登録されているBHハンドラを呼出
queue_task()
タスクキューに関数を追加
run_task_queue()
タスクキュー内の関数を全て実行
IMMEDIATE_BH <--デバイスドライバ等から適宜スケジュールされる
immediate_bh()
run_task_queue(&tq_immediate);
TQUEUE_BH <-- TIMER_BHと一緒にスケジュールされる。
tqueue_bh()
run_task_queue(&tq_timer);
do_softirq()呼出契機
ksoftirqd()
do_irq()でハードウェア割り込みを処理した後
例外やシステムコールからの戻りでは呼ばれなくなっている。
代わりにraise_softirq()でソフト割り込みをスケジュールすると
ksoftirqd()がwakeupされる。
スケジュールしてからksoftirqd()が動き出す前に
ユーザモードに戻ってしまう事はない?
ソフトウェア割り込みをマスクするには?
Linux2.2ならdisable_bh()がある。
各割り込み種別毎に独自に実施?
cliする?
Kernel Thread
UserProcessと異なりKernelMode(SupervisorMode)で動作するため、
プリエンプトされない?
明示的にBlockしてCPUを手放す必要がある?
-------------------------------------
排他制御
cli ハードウェア割り込み禁止
Uniprocessor - cli命令実行するだけ
MultiPorcessor - 全CPUで割り込み禁止にする。
実際にはLocalCPUのみcliしてRemoteCPUについては
get_irqlock()でspinlockを取得し、
割り込みハンドラの実行を遅延させている。
(get_irqlock()コール時、既に他のCPUで割り込み処理中なら
終了までspinして待たされる。)
cliでハードウェア割り込みを禁止している最中に
Blockされる処理を実行してはいけない。
Freezeする可能性あり。
(Blockして切り替わった先がユーザプロセスでwhile(1);で
Systemコールも呼ばない場合は、プロセスの切替えが発生できずに
即Freeze)
atomic_add()
addl命令に展開されている。
1命令なのでUniProcessorならこれでatomicであるが、
メモリアクセスはRead/Writeで2回発生するので
MultiProcessorだとatomicとならない。
このためSMPの時は、lock prefixをつけてatomicとなるようにする。
spin_lock()
SpinLock取得
取得できるまでSpin
spin_lock_irq()
LocalCPUについてcliで割り込み禁止にして、
SpinLock取得
割り込みハンドラからも使用されるデータを排他制御する場合は、
DeadLock防止のため、こちらを使用する。
LocalCPUについて割り込み禁止しておかないと
通常ContextでSpinLock取得した後、
割り込みが発生して同じSpinLockを取ろうとした時、
DeadLockする。
他のCPUの割り込みは禁止にする必要はない。
-------------------------------------
VFS
sys_read(fd, buf, count)
fdからfile構造体を取得
file->f_op->read() - ext2ならgeneric_file_read()
-------------------------------------
Ext2
ページキャッシュへの読み込み
ext2_readpage(struct file *file, struct page *page)
block_read_full_page() <-- 汎用ルーチン呼ぶだけ
(inode,オフセット(Block数)) -> ブロック番号変換ルーチン
ext2_get_block() - bhにBlock番号を設定して返す
ext2_block_to_path()
ファイル先頭からのオフセット(Block#)から
i_block[]のどこをたどればよいかをoffsetsに格納する。
ext2_get_branch()
offsetsが指しているi_block[]に格納されているBlock番号をchainに格納していく。
Indirectアクセス(オフセットBlock数が12以上)なら
sb_bread()でi_block[]のあるBlockを読み込んでたどっていく。
<--内部はbread()。デバイスの指定Blockの読み込み。
BufferCacheにCacheされる。(既にCacheにあればディスクアクセスはしない)
chain[]の最後のエントリに格納されたBlock番号が取得したかったBlock番号
取得したBlock番号をBHに設定して返す。
ext2_read_inode()
inodeが格納されているBlock#を計算
bread() - inodeのあるBlockを読み込み
読み込んだBlockのinodeをメモリinodeへコピー
-------------------------------------
ページキャッシュ
mm/filemap.cがメイン
ファイルをキャッシュする。
キャッシュはPageそのもの。
inodeとファイル先頭からのオフセット(PageSize単位)で識別される。
キャッシュしているページは複数のバッファから構成される。
<--- バッファは"一時的なもの"でhash_table[]には登録されない?
バッファキャッシュではない?
バッファキャッシュへ書き込みした時の同期はどうする?
generic_file_read() - 指定ファイルの指定offsetからの読み込み
ページキャッシュにデータがあればそのデータを
ユーザバッファに返す。なければ、一旦ページキャッシュに
読み込んでからそれを返す。(読み込み完了するまではBlock)
do_generic_file_read()
offset(*ppos)からページ番号とページ内のオフセットを算出
:
page_hash()でハッシュのエントリ取得
__find_page_nolock() でハッシュエントリからページキャッシュを下す
PageCacheが見付からなければno_cache_pageへ。
found_page:
:
page_ok:
actor() - file_read_actor()
ユーザエリアのバッファへページのデータをコピー
終了。
readpage:
mapping->a_ops->readpage(filp, page) ページキャッシュへの読み込み
(Ext2ならext2_readpage())
PageがUptodate状態ならpage_okへ飛んでバッファにデータをコピー
Uptodateでなければ、generic_file_readahead()で先読みを実施し
Blockする。
(end_buffer_io_async()でPageがUptodateになるとwakeupされる)
(readpage()は非同期実行なので、ここでは基本的にUptodateではない?)
:
リードエラーなら。。。
no_cached_page: ページキャッシュが無かった場合
page_cache_alloc() ページキャッシュ用にBuddySystemから1Page取得
__add_to_page_cache() 取得したページをハッシュに登録
add_page_to_inode_queue(mapping, page);
mapping->clean_pagesに挿入
add_page_to_hash_queue(page, hash);
ハッシュエントリに挿入
lru_cache_add() LRUキャッシュにも登録
zone->active_listに登録
goto readpage
ページキャッシュ管理方法
(1) ページハッシュテーブル
page_hash_table
+-------------+ struct page
| |---> +--------+
+-------------+ | | ----> ...
| | | | next_hash
+-------------+ | |
:
+-------------+
| |
+-------------+
(2) iノードキュー
以下、ソースはbuffer.cにあるがページキャッシュの処理
(ページとバッファの橋渡しをしている)
block_read_full_page(page,get_block) - ページキャッシュへの読み込み汎用ルーチン
get_block引数に(inode,ファイルオフセット) -> ブロック番号
変換関数を渡す。(ext2ではext2_get_block())
page->buffersが無ければcreate_empty_buffer()で空のバッファ作成
ページディスクリプタpageで指定したページ内に
複数バッファが作成される。
page->buffersに先頭のBufferHeadが格納される。
ページ内の全バッファがUptodateならページをUptodate状態にして終了
get_block(); - (inode,ファイルオフセット) -> ブロック番号変換
(Ext2ならext2_get_block())
bh->b_blocknr にBlock#が設定され
BH_Mappedがセットされる
:
以下、ページ内のUptodateでないBufferに対してI/O要求を出す
set_buffer_async_io()
BufferHeadを設定
(1) I/O終了時のコールバックをend_buffer_io_async()に設定
(2) BH_Asyncを設定
submit_bh(READ, arr[i]);
:
"Blockデバイス - 共通部分"参照
end_buffer_io_async() バッファI/O完了時に呼ばれるコールバック
BufferをUptodate状態にする。
Page内のBufferが全て読み込み完了していたら、
PageをUptodate状態にする。
UnlockPage()
Pageのリード待ちになっていたプロセスをwakeup
mappingとは???
struct address_space
-------------------------------------
バッファキャッシュ
ディスクのブロックをキャッシュしている。
bread() - 指定デバイスの指定ブロックから読み込み
BufferCacheに指定ブロックがキャッシュされていればそれを返す。
BufferCacheにない場合は、デバイスからCacheに読み込んだ後
Cacheを返す。(読み込みが完了するまでBlockする)
BufferHeadをハッシュから取得。
(なければ新規作成)
Uptodate状態なら既にキャッシュが最新状態なので
それを返す。
そうでなければ、ll_rw_block(READ)で
デバイスに読み込み要求を発行する。
(実際には要求は即時に発行されるわけではない)
wait_on_buffer(bh) - Lock解除までBlockする
TaskQueue(tq_disk)に登録しているハンドラを処理する。
<-- ここでDeviceがUnplugされてRequestが発行される?
BufferがLockされていなければそのまま処理続行。
LockされていたらWaitQueue(bh->b_wait)にtask_structを登録し
Block(UNINTERRUPTIBLE状態にしてタスクスイッチ)する。
bwrite() は存在しない。
各書き込み処理で以下を行う?
1. getblk()でBufferCacheを取得
2. 書き込み
3. BH_Dirtyセット
4. I/O要求発行
バッファキャッシュ管理方法
hash_table[]
+-------------+ struct buffer_head
| |----> +-------+ ----> ...
+-------------+ | | b_next
| | | |
+-------------+
:
:
+-------------+
| |
+-------------+
---------------------
Blockデバイス - 共通部分
ll_rw_block() - BlockデバイスにRead/Write要求を出す
すぐにはデバイスにRequestを発行しない
bh->b_end_io = end_buffer_io_sync
submit_bh()
generic_make_request()
__make_request()
RequestQueueが空なら
q->plug_device_fn(generic_plug_device())
q->plugged = 1
q->plug_tq(generic_unplug_device())を
tq_diskに追加する。
Requestを可能ならマージする。
マージできなければ、新規リクエスト作成
RequestQueue(device毎に持つ)へ挿入。
end_buffer_io_sync(bh, uptodate) - I/O完了時のハンドラ
BufferをUptodate状態に。
(エラーならフラグクリア)
unlock_buffer()
BH_Lockクリア
wake_up(&bh->b_wait)
<-- Buffer読み込み待ちでBlockしていたProcess
をWakeup
run_task_queue(&tq_disk)すると、
登録されたハンドラ(generic_unplug_device())が実行され
Unplug(q->plugged = 0)された後、
q->request_fn(IDE-HDの場合、do_hd_request())を実行して
溜ったRequestを実際にデバイスに発行する。
run_task_queue(&tq_disk)する契機
wait_on_buffer()
:
:
Plugされていると、、、
---------------------
Blockデバイス - IDE HardDiskの例
do_hd_request() - Requestの処理
hd_request()
:
:
デバイスのRequestが完了もしくはエラー終了すると...
end_request()
end_that_request_first()
bh->b_end_io(bh, uptodate) - I/O完了ハンドラ
例:
バッファI/Oの場合 - end_buffer_io_sync()
ページI/Oの場合 - end_buffer_io_async()
デバイスドライバの外部インタフェースは
Request受け付け
ioctl
だけ??
-------------------------------------
仮想記憶
kswapd() - PageOut用カーネルスレッド