CbCインターフェースによる CbCXv6 の書き換え
概要
- OS の信頼性を上げたい
- CbCを使って xv6 という OS を書き換える
- Paging の書き換えを行った
- まだ実装中
- 将来はコンテナやVMをメタ計算として実装できるはず
OS の信頼性を上げたい
- 仕様を満たすことを証明する
- 証明しやすい形の記述を作る
- CbCの goto で書く
- 状態遷移系に近い形で記述できる(証明しやすい)
- 関数型の記述
- CbCのinterface で書く
- 記述のモジュール化
メタレベルとノーマルレベル
- ノーマルレベル
- メタレベル
- Paging などのメモリやCPU自体の操作
- プログラムの正しさの証明
- Context
- メタレベルで使用される Meta Data を置く場所
Continuation based C
- ノーマルレベルとメタレベルの処理を同じ言語で行えるようにした言語(以下CbC)
- Code Gear
- Data Gear
- Meta Code Gear
- goto meta
- Code Gear の間に挟める計算
- Meta Data Gear
goto による継続
- Code Gear の処理の間を goto によって遷移していく
Data Gear の継続
- Code Gear で定義されたデータ
- goto の際に Data Gear も継続される
- 実際にはノーマルレベルの間にメタレベルの処理がある
- CbC では接続可能な Code Gear, Data Gear のリスト
- Data Gear を確保するメモリ空間
- ノーマルレベルでの書き換えやアクセスを防ぐために存在
Xv6
- MIT の講義用教材として作られたOS
- 規格化される前のCで書かれたUNIX V6 を書き換えた
- 1万行程の軽量なOS
- Linuxだと数千万行
- Xv6 をCbCで書き換える
Xv6の構成
- systemcall
- Scheduler
- Virtual Memory
- file system
- tty
カーネル空間
- OS の中核となるプログラムで Meta Level に相当する
- Xv6 ではカーネルとユーザープログラムは分離されている
- ユーザープログラムはカーネルに直接アクセスできない。
- ユーザープログラムによる書き換えやアクセスを防ぐため
- 呼び出す場合は system call
system call
- system call 呼び出し
- トラップ の発生
- ユーザープログラムの中断
- 処理がカーネルに切り替わる
Paging
- Page と呼ばれる固定長のブロックに分割して、メモリとスワップ領域で Page を入れ替えて管理
- 仮想メモリとして扱うことでフラグメンテーションの解消と空き番地を探す必要がなくなる
Xv6の書き換え方針
CbCインターフェース
CbCインターフェース
インターフェースの定義
- Xv6 の Virtual Memory の API 部分のインターフェース
typedef struct vm<Type,Impl> {
__code init_vmm(Impl* vm, __code next(...));
__code kpt_freerange(Impl* vm, uint low, uint hi, __code next(...));
__code kpt_alloc(Impl* vm ,__code next(...));
__code switchuvm(Impl* vm ,struct proc* p, __code next(...));
__code init_inituvm(Impl* vm, pde_t* pgdir, char* init, uint sz, __code next(...));
__code loaduvm(Impl* vm,pde_t* pgdir, char* addr, struct inode* ip, uint offset, uint sz, __code next(...));
__code allocuvm(Impl* vm, pde_t* pgdir, uint oldsz, uint newsz, __code next(...));
インターフェースの命名
- typedef struct の直後にインターフェース名(vm)を書く
- Data Gear にマッピングされる
typedef struct vm<Type,Impl> {
インターフェースの Code Gear
- Code Gear は __code CodeGear名(引数); で記述する
- 第1引数の Impl* vm がインターフェースの実装の型になる
typedef struct vm<Type,Impl> {
__code init_vmm(Impl* vm, __code next(...));
__code kpt_freerange(Impl* vm, uint low, uint hi, __code next(...));
__code kpt_alloc(Impl* vm ,__code next(...));
__code switchuvm(Impl* vm ,struct proc* p, __code next(...));
__code init_inituvm(Impl* vm, pde_t* pgdir, char* init, uint sz, __code next(...));
__code loaduvm(Impl* vm,pde_t* pgdir, char* addr, struct inode* ip, uint offset, uint sz, __code next(...));
__code allocuvm(Impl* vm, pde_t* pgdir, uint oldsz, uint newsz, __code next(...));
next(…)
- __code next(…) は次の Code Gear の継続先
- それぞれの Code Gear の引数の1つに設定する
__code kpt_freerange(Impl* vm, uint low, uint hi, __code next(...));
__code next(...);
} vm;
__code exit(){
}
goto vm->kpt_freerange(vm, low, hi, exit);
Interface の実装の型
- 実装側のヘッダーファイルも vm_impl と同じように用意する
typedef struct vm_impl<Impl, Isa> impl vm{
...
__code loaduvm_ptesize_check(Type* vm_impl, uint i, pte_t* pte, uint sz,
__code next(...));
インターフェースの実装
- 実装は型と実装をそれぞれ別ファイルで定義する(vm_impl.h と vm_impl.cbc)
- 実装するインターフェースは #interface で宣言する
#interface "vm.h"
vm_impl のコンストラクタ
-
create_imple の関数内で vm の型を定義し、vm->CodeGear名 で対応させていく
-
実装を Code Gear で記述していく。
-
struct vm* vm = new vm();
-
vm->void_ret = C_vm_void_ret;
- Code Gear の enum の設定
- インターフェースのAPIと enum の番号を紐付けている
vm* createvm_impl(struct Context* cbc_context) {
struct vm* vm = new vm();
....
vm->void_ret = C_vm_void_ret;
vm->init_vmm = C_init_vmmvm_impl;
vm->kpt_freerange = C_kpt_freerangevm_impl;
vm->kpt_alloc = C_kpt_allocvm_impl;
- APIの実装の例(init_vmm)
- C_init_vmmvm_impl が メタレベルでinit_vmmvm_impl と対応する
__code init_vmmvm_impl(struct vm_impl* vm,__code next(...)) {
initlock(&kpt_mem.lock, "vm");
kpt_mem.freelist = NULL;
goto next(...);
}
インターフェース実装内の CbC
- for文やif文がある場合はさらに実装を分ける
- インターフェースは外から呼び出されるAPI
- それに対してインターフェースの実装の Code Gearから明示的に呼び出される Code Gearは、Java の private メソッドのように扱われる。
- 実際に vm.c の loaduvm の実装を分けた記述を説明する
実装内の明示的な遷移の処理
- vm と同じ create_impl 内で vm_impl を定義し、private で実装する Code Gear を定義する
- loaduvmvm_impl で goto によって private に遷移する
__code loaduvmvm_impl(struct vm_impl* vm, pde_t* pgdir, char* addr, struct inode* ip, uint offset, uint sz, __code next(...)) {
Gearef(cbc_context, vm_impl)->pgdir = pgdir;
Gearef(cbc_context, vm_impl)->addr = addr;
Gearef(cbc_context, vm_impl)->ip = ip;
Gearef(cbc_context, vm_impl)->offset = offset;
Gearef(cbc_context, vm_impl)->sz = sz;
Gearef(cbc_context, vm_impl)->next = next;
goto loaduvm_ptesize_checkvm_impl(vm, next(...));
}
vm* createvm_impl(struct Context* cbc_context) {
...
struct vm_impl* vm_impl = new vm_impl();
...
vm_impl->loaduvm_ptesize_check = C_loaduvm_ptesize_checkvm_impl;
....
vm->loaduvm = C_loaduvmvm_impl;
....
}
loaduvmの CbCによる書き換え
- loaduvmは何でなぜ書き換えてるのか
- vm.cのloaduvmの処理をCbC で書き換える
int loaduvm (pde_t *pgdir, char *addr, struct inode *ip, uint offset, uint sz)
{
uint i, pa, n;
pte_t *pte;
if ((uint) addr % PTE_SZ != 0) {
panic("loaduvm: addr must be page aligned");
}
for (i = 0; i < sz; i += PTE_SZ) {
if ((pte = walkpgdir(pgdir, addr + i, 0)) == 0) {
panic("loaduvm: address should exist");
}
pa = PTE_ADDR(*pte);
if (sz - i < PTE_SZ) {
n = sz - i;
} else {
n = PTE_SZ;
}
if (readi(ip, p2v(pa), offset + i, n) != n) {
return -1;
}
}
return 0;
}
- loaduvm_impl がインターフェースから呼ばれる実装
__code loaduvmvm_impl(struct vm_impl* vm, pde_t* pgdir, char* addr, struct inode* ip, uint offset, uint sz, __code next(...)) {
Gearef(cbc_context, vm_impl)->pgdir = pgdir;
Gearef(cbc_context, vm_impl)->addr = addr;
Gearef(cbc_context, vm_impl)->ip = ip;
Gearef(cbc_context, vm_impl)->offset = offset;
Gearef(cbc_context, vm_impl)->sz = sz;
Gearef(cbc_context, vm_impl)->next = next;
goto loaduvm_ptesize_checkvm_impl(vm, next(...));
}
- loaduvm_impl から private な Code Gear が呼ばれる
#interface "vm_impl.h"
__code loaduvm_ptesize_checkvm_impl(struct vm_impl* vm_impl, __code next(...)) {
char* addr = vm_impl->addr;
if ((uint) addr %PTE_SZ != 0) {
}
goto loaduvm_loopvm_impl(vm_impl, next(...));
}
- vm.cではここから for だが CbC は if文の中と外にgoto を用意して実装する
__code loaduvm_loopvm_impl(struct vm_impl* vm_impl, __code next(...)) {
uint i = vm_impl->i;
uint sz = vm_impl->sz;
if (i < sz) {
goto loaduvm_check_pgdir(vm_impl, next(ret, ...));
}
goto loaduvm_exit(vm_impl, next(...));
}
__code loaduvm_check_pgdir(struct vm_impl* vm_impl, __code next(...)) {
pte_t* pte = vm_impl->pte;
pde_t* pgdir = vm_impl->pgdir;
uint i = vm_impl->i;
char* addr = vm_impl->addr;
uint pa = vm_impl->pa;
if ((pte = walkpgdir(pgdir, addr + i, 0)) == 0) {
}
pa = PTE_ADDR(*pte);
vm_impl->pte = pte;
vm_impl->pgdir = pgdir;
vm_impl->addr = addr;
vm_impl->pa = pa;
goto loaduvm_check_PTE_SZ(vm_impl, next(...));
}
__code loaduvm_check_PTE_SZ(struct vm_impl* vm_impl, __code next(int ret, ...)) {
if (sz - i < PTE_SZ) {
n = sz - i;
} else {
n = PTE_SZ;
}
if (readi(ip, p2v(pa), offset + i, n) != n) {
ret = -1;
goto next(ret, ...);
}
vm_impl->n = n;
goto loaduvm_loopvm_impl(vm_impl, next(ret, ...));
}
__code loaduvm_exit(struct vm_impl* vm_impl, __code next(int ret, ...)) {
ret = 0;
goto next(ret, ...);
}
int loaduvm (pde_t *pgdir, char *addr, struct inode *ip, uint offset, uint sz)
{
uint i, pa, n;
pte_t *pte;
if ((uint) addr % PTE_SZ != 0) {
panic("loaduvm: addr must be page aligned");
}
for (i = 0; i < sz; i += PTE_SZ) {
if ((pte = walkpgdir(pgdir, addr + i, 0)) == 0) {
panic("loaduvm: address should exist");
}
pa = PTE_ADDR(*pte);
if (sz - i < PTE_SZ) {
n = sz - i;
} else {
n = PTE_SZ;
}
if (readi(ip, p2v(pa), offset + i, n) != n) {
return -1;
}
}
return 0;
}
stub
- goto meta はstub を呼び込んでいる
- 説明
C を CbC に部分的に書き直す手法
- CbC の場合 goto による 遷移を行うので、関数呼び出しのように goto 以降のコードを実行できない
- 例) goto すると戻ってこれないため それ以降が実行されなくなる。
CbC から C への遷移
- 最初の命令は next で戻ってこれるので、dummy の関数を用意してそこで実行する
void cbc_init_vmm_dummy(struct Context* cbc_context, struct proc* p, pde_t* pgdir, char* init, uint sz)
{
struct vm* vm = createvm_impl(cbc_context);
Gearef(cbc_context, vm)->vm = (union Data*) vm;
Gearef(cbc_context, vm)->pgdir = pgdir;
Gearef(cbc_context, vm)->init = init;
Gearef(cbc_context, vm)->sz = sz ;
Gearef(cbc_context, vm)->next = C_vm_void_ret ;
goto meta(cbc_context, vm->init_inituvm);
}
void userinit(void)
{
struct proc* p;
extern char _binary_initcode_start[], _binary_initcode_size[];
p = allocproc();
initContext(&p->cbc_context);
initproc = p;
if((p->pgdir = kpt_alloc()) == NULL) {
panic("userinit: out of memory?");
}
cbc_init_vmm_dummy(&p->cbc_context, p, p->pgdir, _binary_initcode_start, (int)_binary_initcode_size);
まとめ
CbCインターフェースによる CbCXv6 の書き換え
概要
OS の信頼性を上げたい
メタレベルとノーマルレベル
Continuation based C
goto による継続
Data Gear の継続
Meta Code Gear
Meta Data Gear
Xv6
Xv6の構成
カーネル空間
system call
Paging
Xv6の書き換え方針
メタレベルとノーマルレベルを記述できるOSを実装したい
段階的に書き換えていきたい
Paging を書き換える理由
__code で書き直していく
CbCインターフェース
ノーマルレベルからメタレベルの記述が記述が煩雑になるためインターフェースを導入
インターフェースによる他のメリット
実装は別で定義し、呼び出す
インターフェースによって実装を置き換えることができる
CbCインターフェース
Data Gear と Data Gear に対して操作を行う Code Gear の集合を表現する Meta Data Gear
インターフェース実装と定義の図入れる
インターフェースの定義
インターフェースの命名
インターフェースの Code Gear
next(…)
Interface の実装の型
インターフェースの実装
vm_impl のコンストラクタ
create_imple の関数内で vm の型を定義し、vm->CodeGear名 で対応させていく
実装を Code Gear で記述していく。
struct vm* vm = new vm();
vm->void_ret = C_vm_void_ret;
インターフェース実装内の CbC
実装内の明示的な遷移の処理
loaduvmの CbCによる書き換え
stub
C を CbC に部分的に書き直す手法
CbC から C への遷移
まとめ