coLinuxにやっぱりdebianをインストール

どうもプレインストールイメージからのアップグレードでUbuntuをいじくるのが気に食わなかったので、Debianクリーンインストールを試すことにした:
http://www.aglabo.com/agl/proevo/Linux/coLinux/debian/etch-01_installer.html
CDブートは出来ないが、実はinitrdさえ渡してしまえばインストーラも問題なく動いたりする。
但し、フレームバッファやXを使うインストーラだとマトモに動かないのでちうい。
あと、やっぱりディスクデバイスは普通のカーネルと違うから、そこらへんはある程度手動で手当てしてやらねばならない。

NetBSD-current ip_flowを通らない場合のNetBSD-currentのIPパケット受信処理

NetBSD-currentのip_flowに関して調査している。

まずは、ip_flowを通らない場合のNetBSD-currentのIPパケット受信処理を調べて
みた。

個々のイーサネットデバイスドライバは、デバイスからパケットデータを取り出
してmbufに詰め、最後に
(*ifp->if_input)(ifp, m);
を呼ぶ。
イーサネットではこのポインタはether_input()を指している。
ether_input()では、以下の作業を行っている:
・パケットサイズが有効なサイズかチェック
マルチキャストパケットへの手当て
・ブリッジが有効ならbridge_input()へ処理を譲る
・CARPが有効ならcarp_input()へ処理を譲る
・PFIL_HOOKSが有効ならpfil_run_hooks()を実行
 return値が0なら終了、0以外なら処理続行
 (此処がパケットフィルタ&NATなはず)
・VLANが有効ならvlan_input()へ処理を譲る
・AGRが有効ならagr_input()へ処理を譲る
・特別なイーサネットフレームの処理
 ・VLANパケットはvlan_input()へ渡す
 ・PPPOEパケットはsoftint_schedule(pppoe_softintr)するかドロップする
 ・SLOWPROTOCOLSはieee8023ad_lacp_input()かieee8023ad_marker_input()へ渡す
・通常のイーサネットフレームの処理
 ・IPパケットならipflow_fastforward()を実行
  return値が0以外なら終了、0ならschednetisr(NETISR_IP)を呼び出し
 ・ARPパケットならschednetisr(NETISR_ARP)を呼び出し
 ・REVARPパケットならrevarpinput()へ処理を譲る
 ・IPv6パケットならip6flow_fastforward()を実行
  return値が0以外なら終了、0ならschednetisr(NETISR_IPV6)を呼び出し
 ・IPXパケットならschednetisr(NETISR_IPX)を呼び出し
 ・ATALKパケットならschednetisr(NETISR_ATALK)を呼び出し
 ・AARPパケットならaarpinput()へ処理を譲る
 ・認識出来なかったパケットは捨てる
・上述の2つとも違う種類のパケットの処理(省略)
・キューがフルならパケットを捨てる、開いてればキューを入れとく
 (netisrが後で取りにくる?)

ここまでは割り込みコンテキスト内で行われる(トップハーフ)。
これ以降はnetisrで、次のコンテキスト切り替え時に行われる(ボトムハーフ)。
netisrは独自のAPIを持っているが、softintで実装されている。
最初からsoftintで書いていないのは歴史的事情?

ファストパスとBSD

IPパケットフォワーディングの高速化手法として、ファストパスというものがあるらしい。
どこで始まったのか知らないが、少なくともYAMAHAのルータの機能として日本語での資料が存在している:
http://www.rtpro.yamaha.co.jp/RT/docs/fastpath/

要するに、パケット処理を割り込みコンテキストからキューイングし後でやって貰うのではなく、割り込みコンテキスト内で最小限に省略した形でやってしまえ、という事である。

これとほぼ同様と思われる機能がNetBSD/FreeBSDにも存在する。
それが、ipflow(netinet/ip_flow.c)である。

が、これでは機能面での不足を感じたのか、FreeBSDでは2003年よりip_flowをip_fastforward(netinet/ip_fastfwd.c)に置き換えている。

以下、ip_fastforwardの説明文。
Short description of ip_fastforward:

o adds full direct process-to-completion IPv4 forwarding code
o handles ip fragmentation incl. hw support (ip_flow did not)
o sends icmp needfrag to source if DF is set (ip_flow did not)
o supports ipfw and ipfilter (ip_flow did not)
o supports divert, ipfw fwd and ipfilter nat (ip_flow did not)
o returns anything it can't handle back to normal ip_input

中々と高機能である。

Windows 95の仕組み

http://itpro.nikkeibp.co.jp/article/COLUMN/20080929/315586/?ST=win&P=1
正直、WindowsについてはNTカーネルの基本構造についてちょっとかじった位にしか知らんかったー。
Windowsってとっても難しい事考えて作ってるよねぇ。
この場合の”難しい”と良い設計っていうのは必ずしも全然一致してないと思うんだけれども。
ていうか、Win95って単に32bit化されたWindows 3.1なのかと思ってたんだけれど、一応プリエンプティブな構造になってるんだねぇ。
そういえばPC立ち上げたらwinって打たないとWindows立ち上がらない時代がありましたなぁ。なつかしす。
#その頃の自分にUNIXを与えてやりたい

セグメントの仕組み

GDTについては少し調べたけれど、実際の所セグメントの仕組みがどのように動くのか正確に理解してなかったので、もう一度確認してまとめてみた。
■セグメント方式
x86 CPUではメモリ管理方式としてセグメント方式を使用している。
セグメント方式では、メモリ空間をセグメントと呼ばれる可変長の区画に分けて管理する。
ディスクリプタテーブル
セグメントの定義はOSによって行われ、ディスクリプタテーブルと呼ばれるテーブルに保存される。
ディスクリプタ上の個々のセグメント定義レコードはセグメントディスクリプタと呼ばれ、セグメントベース(セグメントの開始アドレス)、リミット(セグメントの大きさ)と属性などを持つ。
プログラムからセグメントを指定する時には、ディスクリプタテーブルのインデックス値を使う。
この値をセグメントアドレス又はセレクタと呼ぶ。
■セグメントレジスタ
x86 CPUでは6つのセグメントレジスタを持っている。
・CS 現在のコードセグメントを指定する
・DS 現在のデータセグメントを指定する
・SS 現在のスタックセグメントを指定する
・ES,FS,GS 追加のデータセグメントレジスタ 他のデータセグメントレジスタへもアクセスさせる時に使う
現在使用しているセグメントとして、CS/DS/SSに有効なセレクタ値が指定されている必要がある。
■メモリアドレスの求め方
ちょっと想像が入ってるので怪しいけど多分こう:
1.オペコードでオフセットアドレスが指定される
(色々アドレスの指定方法があるが、結局はオフセットアドレスになってるはず
 セグメントを明示する時は例外だけど)
2.セグメントレジスタから現在のセグメントを調べる
(コードへのアクセスならCS、データへのアクセスならDS)
3.セグメントディスクリプタからセグメントベースを調べる
4.セグメントベース+オフセットアドレスでメモリアドレスを求める
■現在のOSでの使われ方
現在のOSでは、セグメントベースアドレス:0x0  リミット: 4GBと定義されたセグメントがコード・データ・スタックセグメント向けに定義されており、これを固定的に使っているだけになっている。
つまり、事実上フラットメモリモデルとして使われている。
後付けで足されたページング機構を使う事でフラットメモリモデルで仮想メモリシステム・メモリ保護などが実現出来、セグメントを活用するよりも使いやすい事からこのような使い方をするのが一般的になっている。

TSSの構造、タスク管理機構

以下、タスク管理機構のまとめ。

一つのタスクに対し、一つのTSSとTSSディスクリプタを定義する。
TSSはメモリの任意の場所に設定され、TSSディスクリプタディスクリプタテーブルの上に設定される。
TSSはレジスタ保存部、OS用領域、IO許可マップの三部からなり、レジスタ保存部は固定的に定義されており、OS用領域は任意長である。
IO許可マップの位置はレジスタ保存部のiobaseで指定する。

定義されたタスクを開始するには、セグメント間ジャンプ命令、またはセグメント間コール命令を実行する。
セグメント間コール命令を発行した場合はTSSのバックリンク領域に元のタスクのセレクタ値が保存され、iretでreturn出来る。
現在実行中のタスクのセレクト値はtrレジスタに設定されており、ltrで書込み、strで読み込みが出来る。
通常は直接アクセスする必要は無いが、タスク機構の初期化の際にはltrで初期タスクのセレクタを書き込む必要がある。

割り込み機構

割り込みはcliで禁止され、stiで許可される。
割り込みに対する処理はIDTで定義される。
構造はグローバルディスクリプタテーブルに類似しており、割り込みゲート、トラップゲート、タスクゲートの三種類のゲートディスクリプタが割り込み番号順に並べられる。
割り込みゲートとトラップゲートはコールゲートと同じ形をしており、割り込みがかかると設定されたアドレスにジャンプする。
割り込みゲートでは割り込みが自動的に禁止されるが、トラップゲートでは禁止されない。
タスクゲートでは、割り込みがかかると設定されたタスクへタスク切り替えが行われる。
切り替えはセグメント間コールのように行われるので、iretで切り替え前のタスクに戻る事が出来る。

コンテキスト切り替えの実装方法検討

現状では特にTSSが機能的に不足する事は考えにくい為、これをうまく利用したコンテキスト切り替えを考える。
利用方法としては、単にコンテキスト切り替えだけでなく、タスクゲートを利用した割り込みハンドラでの自動的なコンテキスト切り替えが考えられる。
このようにTSSを上手く使えば、結果的にアセンブリでしか書けないコンテキスト切り替えと割り込み禁止フラグの操作がハードで行なわれるようになるので、コードがCで書け分かりやすくなる、という副次的効果も得られる。

BIOSへの割り込み

BIOSの機能を使うには、通常INT15hを呼び出したりする訳だが、この場合、仮想86モードでの割り込みディスクリプタテーブルとか、リアルモードで0番地に存在するテーブルとかってどうなってんのかな?
15hのエントリだけ無視される?
それとも適切な飛び先を書いてやる必要があるのかな?
仮想86モードからBIOS呼び出せると聞いて、ふと疑問におもった。

なんか僕勘違いしてんのだろうか?

リングプロテクションとコールゲートとシステムコール

ウィキペディア
http://ja.wikipedia.org/wiki/リングプロテクション
を読んでいて思ったんだけれど、システムプロテクションの為にリングプロテクションにおいて他の特権レベルへの切り替えに発案されたコールゲートって考え方がシステムコールというOS構造に発展してったのかな。
僕はコールゲートのハードウェアサポートが無いアーキテクチャシステムコールを勉強したので、むしろコールゲートって何ぞ?って感じだった訳だが。
RISCが削った数々の機能の一つという事かしら。

信州大学

信州大学の授業の資料が参考になりそう。
http://kaiya.cs.shinshu-u.ac.jp/2005/os/
レベルを抑えて学部生にもわかるようにしつつ、一応x86固有な話とかしっかりやってそげ。
こういう授業ばかりだったら大学も楽しいんだけどねぇ。

システムコールの実装とsysenter命令

システムコールは一般的には割り込みによって実現する。
x86の場合は、これに加えコールゲートやsysenter命令を使うなどの方法も存在する。
Solarisや古いFreeBSDではコールゲートを使っているようである。
sysenter命令についてはここのサイトが非常に参考になる:
http://www.marbacka.net/asm64/arkiv/int2e_sysenter_syscall.html
こういうのをみると、ほんもののWindowsプログラマってすごいんだなぁ、と思う。
カーネルソースコードは一切なく、逆アセンブラも使わず、メモリダンプでなんとかしちゃうというのはちょっと真似したくない。
ちなみにこのサイトの他の記事も大変詳しい話が書いてあって参考になるです。

グローバルディスクリプタテーブル

グローバルディスクリプタテーブルは、以下の用途に使われている:
全プログラム共通のセグメントの定義(グローバルセグメントディスクリ
プタ)
コールゲートの定義(ゲートディスクリプタ
TSSディスクリプタの定義
タスクゲートディスクリプタの定義
ローカルディスクリプタテーブルのセグメントディスクリプタを定義

割り込みハンドラの定義、ローカルなセグメントの定義、ページテーブルの定義はそれぞれの固有テーブルが存在する。

コールゲートでは、低いDPLから高いDPLへの切り替えをセグメント間関数ジャンプやセグメント間関数ゴールで実現する。
タスクゲートでは、特定のタスクに対してタスク切り替えを許可する。

GRUB解析 続き

やっぱりどう調べてもTSSは設定されてない。
docs/kernel.cのmain()に

  unsigned tr; 
  asm volatile ("str %0" : "=m"(tr)); 
  printf("tr: %x\n", tr); 

と書いてTRレジスタをダンプして見たところ、結果は
tr: 0
だった。

どうも、プロテクトモードを走らせるだけならTSSの初期化は必須ではないっぽい。

Omicron コンテキストスイッチ

http://tiki.is.os-omicron.org/tiki.cgi?c=v&p=%A5%B3%A5%F3%A5%C6%A5%AD...
ここにTSSが何故使えないのかについての話が載っている。
が、イマイチピンとこない。
スレッドサポートがなくて遅いって事?
他の理由は何をいってるのかよく分からない。
何が使わない決定的な理由なんだろ?

ltrをやった瞬間にqemuがすっ飛んだ件について

shiitakeにて、空のTSSを作ってそれを指すTSSディスクリプタを作り、ltrで読み込んであげたら、読み込んだ瞬間に
qemu: fatal: triple fault
ですっとんだ。

ltrって、最初のタスクスイッチ時にカレントTSSが無いとコンテキストの保存が出来ないからカレントTSSを用意してあげるよ、って意味だと思ったんだけど。
(ソフトウェアで実現する時はそういう書き方をするよね? process 0とかの話だけれど)
違うのかな?
もしかして、ltrした瞬間にそのTSSへスイッチがかかる?
少なくともセグメントレジスタを書き換えたりしてる?
うーん。
ここら辺の挙動は、どこを読めばわかるんだ?