Hatena::ブログ(Diary)

まだ見えない先の

2007-11-21

[][] sizeof と kobjsize

上記に関連して.

uClinuxで使う mm/nommu.c 中の重要関数 do_mmap_pgoff() では,

...
struct vm_area_struct *vma = NULL;
...
vma = kzalloc(sizeof(struct vm_area_struct), GFP_KERNEL);
...
realalloc += kobjsize(vma);
askedalloc += sizeof(*vma);

としている箇所がある.

ここで,realalloc は実際に確保された値.askedalloc は本来欲しかった値.

sizeof演算子では,struct vm_area_struct のサイズが取得できる.

一方,kobjsize(vma) では,vma が kzalloc()(kmalloc()) によってスラブオブジェクトとして確保されるため,サイズは2の冪乗となり,その値が返る.

よって,realalloc には実際に確保された2の冪乗サイズ.askedalloc には要求された構造体のサイズが入る.

ちなみん,これら二つの変数は,2.6.23-mm1 現在全く使われていない.

2007-11-16

[][] uClinux 調査環境について

kernel-2.6.22,linux-2.6.22-uc0-big.patch について,CONFIG_MMU,nommu 文字列を含むファイル,および CONFIG_MMU 時に利用される(include,Makefileから呼ばれる)ファイルを調査対象とした.調査内容は,1) mmu 時との相違点,2) 1)が起こる理由,の二つ.

関数名についているページ番号は,詳解 LINUX カーネル第3版初版の索引とする.

またアーキテクチャは ARM を想定しており,詳細については,ARM Architecture Reference Manual (DDI0100) と ARM組み込みソフトウェア入門―記述例で学ぶ組み込み機器設計のためのシステム開発 (Design Wave Advanceシリーズ) (ARM入門)を参考にしている.

[][] ページフォルトについて

uClinux 調査環境について - まだ見えない先の

詳解LINUXカーネル p.401

uClinux では,arch/arm/head.S にて MMU を有効にしていないためページ変換フォルトによるアボートは発生しない.

ページ変換フォルトによりアボートされたアクセスがデータアクセスかキャッシュ保守操作の場合、データアボート例外が発生する*1

Linux では,データアボート例外ハンドラを arch/arm/kernel/entry-armv.S にてセットしている*2.データアボート例外時は arch/arm/mm/fault.c do_DataAbort() が呼ばれる.

asmlinkage void __exception do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)

例外原因をフォルトステータスレジスタ fsr から割り出す.ページ変換フォルトによるデータアボート例外時,fsr は 0x0b00111 となる*3

const struct fsr_info *inf = fsr_info + (fsr & 15) + ((fsr & (1 << 10)) >> 6);
=> inf = fsr_info[7]
=> { do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" },

となり,fault.c do_page_fault() が呼ばれる.

static int do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)

do_page_fault()から呼ばれる include/linux/mm.h handle_mm_fault() にて,noMMU時は

/* should never happen if there's no MMU */
BUG();
return VM_FAULT_SIGBUS;

となっている.

よって,uClinuxではページフォルトは起こらないと仮定していると考えられる.

[][] デマンドページングについて

uClinux 調査環境について - まだ見えない先の

詳解LINUXカーネル p.411

動的なメモリ割り当て手法であるデマンドページングは,ページフォルト例外までページフレーム割り当てを遅延させる.

上記で述べたように,uClinuxではページフォルトは起こらないとしているので,デマンドページングを行うことができない.

詳解 LINUX カーネル 9章 プロセスアドレス空間の序文によると,カーネルが利用するオブジェクトに関しては遅延をさせないで即座に確保し,ユーザモードのプロセスへのメモリ割り当てのみ遅延させる方針であることが記述されている.

ユーザモードプロセスのメモリ管理はメモリリージョン毎に vm_area_struct 構造体で管理される.デマンドページングによりサイズが伸張するのはこの vm_area_struct 構造体オブジェクトである.

heap,stack,mmap の各領域はそれぞれ vma (vm_area_struct オブジェクト) で管理され,デマンドページングにより伸張する.しかし uClinux ではデマンドページングを行うことができないため,vma 生成時に mm/nommu.c do_mmap_private() にて kmalloc を用いて静的にメモリ領域を割り当てる.

[][] heap について

uClinux 調査環境について - まだ見えない先の

詳解LINUXカーネルp.420

asmlinkage unsigned long sys_brk(unsigned long brk) p.421

MMU(ELFバイナリ) 時は,heap 用の vma は fs/binfmt_elf.c set_brk()にて do_brk() を呼び出すことで生成される(はず).sys_brk() でも do_brk() を呼び出し vma の伸張を行う.また,mm->brk の増加を行う.

noMMU 時は,mm->start_brk と mm->context.end_brk の中であれば,mm->brk の変化を許す.それ以外では現在の mm->brk を返す.プロセス起動時に割り当てた heap 領域の増減は行わない.

unsigned long do_brk(unsigned long addr, unsigned long len) p.422

noMMU 時は下記になる.

return -ENOMEM;

これは,前述したように heap 領域を変化させないためだと考えられる.


[][] stack について

uClinux 調査環境について - まだ見えない先の

詳解LINUXカーネル p.405

Linux で使用される ELF バイナリは,fs/binfmt_elf.c load_elf_binary() にて setup_arg_pages() を呼び出して stack 領域の vma オブジェクトを生成する.vma が拡張されるのはページフォルト例外時で,例外ハンドラである do_page_fault() から呼ばれる expand_stack() にて行われる.プロセス生成時(exec前)には,スタックポインタは親プロセスと同じ場所を指す (p.125).コピーオンライトによってスタックは複製される.

FLAT バイナリについて - まだ見えない先のにて述べたように,uClinux で使用される FLAT バイナリはfs/binfmt_flat.c load_flat_binary() にて,静的に確保された領域内の mm->start_stack がセットされる.

int expand_stack(struct vm_area_struct *vma, unsigned long address) p.406

noMMU 時は下記になる.

return -ENOMEM;

この関数は,MMU 時はページフォルト時に arch/arm/mm/fault.c __do_page_fault() から呼ばれる.

プロセス起動時に割り当てる stack 領域の増減は行わないため,エラーを返すと考えられる.

void install_arg_page(struct vm_area_struct *vma, struct page *page, unsigned long address)

fs/binfmt_elf.c load_elf_binary() => fs/exec.c setup_arg_page() 経由で呼び出される.コマンドライン引数環境変数文字列を置いたページフレームを新たに生成した vma に割り当てる.(p.890)

noMMU 時は,この関数が定義されない.noMMU 時はコマンドライン引数環境変数文字列は load_flat_binary() にて1バイトづつスタック領域にコピーされる.その部分のコメント.

/* copy the arg pages onto the stack, this could be more efficient :-) */

ここでコマンドライン引数環境変数文字列は,struct linux_binprm *bprm->page に格納されている.

[][] コピーオンライトについて

uClinux 調査環境について - まだ見えない先の

詳解LINUXカーネル p.414

MMU は書き込みが禁止されているページへの変更時にアクセス許可フォルトに起因する例外を発生する.

故意にページテーブルエントリを書き込み禁止にし,変更時までページの複製を遅延させる.参照されるだけならば複製の必要はないという考えに基づく.

fork時,子プロセスが親プロセスアドレス空間にアクセスする際などに用いられる.

forkの場合,kernel/fork.c do_fork() => ... => dup_mmap() => mm/memory.c copy_page_range() => ... => copy_one_pte() にて pte_wrprotect() で (is_cow_mapping(vm_flags)が真ならば)ページテーブルエントリに書き込み禁止をセットしている.

fork と vfork p.123

noMMUではCOWができずfork時にメモリ空間を共有する(& 変更まで共有してしまう).そこでvforkにより,子プロセスが実行を終了,もしくはexecするまで,親プロセスの実行を止める方法を用いる.

noMMU時は,

asmlinkage int sys_fork(struct pt_regs *regs)
{
#ifdef CONFIG_MMU
        return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
#else
        /* can not support in nommu mode */
        return(-EINVAL);
#endif
}

と,fork時にエラーが返る.

sys_fork()とsys_vfork()の違いは,下記の通り.

sys_fork(): return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
sys_vfork(): return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);

CLONE_VFORK p.124

#define CLONE_VFORK     0x00004000      /* set if the parent wants the child to wake it up on mm_release */

1. 親プロセスは子プロセス構造体のvfork_doneを使って,子プロセスのexit,execを待つ

1.1 kernel/fork.c: do_fork(), 下記は親プロセスの動作

...
struct completion vfork;

if (clone_flags & CLONE_VFORK) {
        p->vfork_done = &vfork;
        init_completion(&vfork);
}

...

if (clone_flags & CLONE_VFORK) {
        freezer_do_not_count();
        wait_for_completion(&vfork);
        freezer_count();
        if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) {
                current->ptrace_message = nr;
                ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
        }
}
...

CLONE_VFORK時,親プロセスプロセス構造体のvfork_doneメンバを利用して,wait_for_completion()に入る.

ここで,vforkはカーネル空間(カーネルスタック)上の変数であるため(ページテーブル操作をしなくても)メモリ空間は共有されており,子プロセスによるvforkへの変更は親プロセスから見ることが可能.

1.2 kernel/sched.c wait_for_completion(struct completion *x)

...
do {
        __set_current_state(TASK_UNINTERRUPTIBLE);
        spin_unlock_irq(&x->wait.lock);
        schedule();
        spin_lock_irq(&x->wait.lock);
} while (!x->done);
...

プロセスは,x->done が 非0 になるまで TASK_UNINTERRUPTIBLE で回る.また,下記で呼ばれる complete() によって x->done++ される.

1.3 p->vfork_done は下記でのみ変更される.また,下記は子プロセスの動作.

kernel/fork.c: mm_release()

...
/* notify parent sleeping on vfork() */
if (vfork_done) {
        tsk->vfork_done = NULL;
        complete(vfork_done);
}
...

mm_release()時,子プロセスのcomplete()によって,親プロセスはループから抜ける.

mm_release()は,kernel/exit.c do_exit()=>exit_mm(),fs/exec.c do_execve() => search_binary_handler() => fs/binfmt_{elf,flat,...}.c load_*() => fs/exec.c flush_old_exec() => exec_mmap(),(kernel/exit.c daemonize()=>exit_mm()) から呼ばれる.

つまり,子プロセスが exit,exec を行う際に mm_release() は呼ばれ,親プロセスはループから抜ける.

fs/exec.c int coredump_wait()

...
/*                                                                                                           
 * Make sure nobody is waiting for us to release the VM,                                                     
 * otherwise we can deadlock when we wait on each other                                                      
 */
vfork_done = tsk->vfork_done;
if (vfork_done) {
        tsk->vfork_done = NULL;
        complete(vfork_done);
}
...

coredump_wait()は,kernel/signal.c get_signal_to_deliver()にて配送するシグナルのアクションが coredump の際,do_coredump() 経由で呼ばれる.

確認: coredump 時は,do_exit()経由の終了処理は行われない?

*1:DDI0100 B4.5.1 MMU フォルト

*2:正確には,entry-armv.S にてハンドラは __vectors_start,__vectors_end の間にセットされ,arch/arm/kernel/traps.c trap_init() にてそれをメモリ中のベクタテーブルにコピーしている.

*3:DDI0100 表B4-6 フォルトステータスレジスタエンコード

2007-11-15

[][] FLAT バイナリについて

f:id:junkawa:20071024170937j:image

data,bss,heap,stack を含む vma について,kmalloc() で領域を確保するため,確保される実際のサイズが 2 の冪乗となる.FLAT では,一度領域を確保した後で実際のサイズが大きくなっていた場合,そのサイズで do_mremap() しなおす.また,その差分が heap 領域サイズとなる.stack サイズのデフォルト値が 8k なので,少しは heap 領域が確保されると思われる.これらは,fs/binfmt_flat.c load_flat_file() にて行われる.

このように静的に heap 領域が確保されるため uClinux 使用時のユーザ空間での malloc() の実装は,heap を使う brk() の代りに 無名リージョンを確保する mmap() を用いる方法が提案されている.

mm->start_brk は vma->vm_start+data_len+bss_len,mm->context.end_brk は vma->vm_end-stack_len.

heap 領域のサイズについて正確には,data_len+max(bss_len+stack_len, リロケーション情報領域) となる.ファイル中では初期化済みデータの後に bss も stack もなく,代りにリロケーション情報が入っており,リロケーションが実行される際にはこの領域も一度メモリにコピーする必要があるため,実際に確保されるのは max(bss_len+stack_len, リロケーション情報領域) となる.仮りにリロケーション情報の方が大きい場合でも再配置後にサイズを小さくすることはなく,そのままの領域を使う.ここでも heap 領域が大きくなる余地が有る(その他のサイズは固定のため).

参考: 404 Not Found

2007-10-24

[][] uClinux調査

加筆修正して,2007-11-16 - まだ見えない先の にて公開.