do_page_fault関数
ページの割り当ては遅延割り当てのため、ページフォルト例外処理の中でおこなわれます。しかしここでの処理は、プログラムエラーによる不正のアクセスのケースとをチェックする必要があります。例外が発生した箇所(ユーザ空間/カーネル空間)および、例外が発生した時の動作モード(ユーザモード/カーネルモード)、又その場所がプロセスの有している空間かそうでないか等のチェックが行われることになります。
これらの処理はdo_page_fault()で行われます。本関数はgoto/returnを多用しており、全体の流れが今ひとつすっきりしずらいです。
例外が発生すると、そのアドレスはcr2レジスタにセットされています。14行のaddress = read_cr2()でaddressには例外が発生したアドレスが設定されることになります。
21行目のf (unlikely(address >= TASK_SIZE))で例外発生箇所によるチェックです。TASK_SIZEは0xC0000000で、すなわちif文の中はカーネルメモリ空間で例外が発生した処理になります。
error_codeには例外が発生時の情報が以下のように設定されています。
PF_PROT bit 0 == 0 ページが無い。
PF_WRITE bit 1 == 0 writeモードでない。
PF_USER bit 2 == 0 カーネルモード。
PF_RSVD bit 3 == 1 予約ページをアクセスした。
PF_INSTR bit 4 == 1 実行モードでアクセスした。
従って22行目のif文チェックは、カーネルモードでページがまだ割り当てていないため発生した、正常なケースです。この場合vmalloc_fault()でページを割り当てます。
それ以外の場合、ユーザモードでアクセスした。(ユーザモードでカーネル領域はアクセスできません。カーネルモードでも予約領域をアクセスした。(カーネルのバグ?)カーネルモードでしかもページが有るのに例外が発生した。(カーネルのバグないしハードウエアのトラブル?)の場合は、bad_area_nosemaphoreへとエラー処理へとジャンプします。
spurious_fault()はTLBキャッシュに掛かる処理のようで、notify_page_fault()はkprobに掛かる処理です。枝葉と言うこともあり、本当の処よくわかりませんのでスキップです。
以降はユーザ空間で例外が発生した場合の処理になります。37から41行は、確か例外が発生したら割り込み禁止となってたかと・・・。それで、仮想86モード(?)ないし、例外発生前に割り込み許可だったら、割り込み許可に設定しているようです。
43行目はクリティカル区間(割り込みハンドラ等)カーネルスレッドだったら、bad_area_nosemaphoreへジャンプです。これらの処理はユーザ空間を参照することはないからです。
47行目のdown_read_trylock()でvmのセマフォを取得しようとしています。取得できないということは、すでに取得されているということです。これは他の同じvm空間を共有するプロセスが、そのメモリー空間を参照している可能性があるからだと思います。このあたり多分デッドロックなんか考慮してだと思いますが・・・。この処理で例外が発生したとすると、カーネルモードにおいてユーザ空間での例外となります。これはシステムコールで引数のユーザバッファーを不正参照した。ということだと思います。そしてデータをセットする関数(put_user()等)でインプリメントされている、exception_tablesがあるかどうかをチェックします。なければbad_area_nosemaphoreへジャンプです。
54行目までくれば、ユーザモードでユーザ空間を参照した例外ということです。(カーネルモードでカーネルスタックの例外も含んでいるようです。カーネルスタックはユーザ空間だったような・・・)find_vma()で例外アドレスがユーザプロセスが有するメモリ空間かどうかチェックします。vmaがNULLならそのアドレスは該当プロセスにメモリ空間にありません。bad_areaにジャンプです。以降のif文はメモリ空間にあるということです。そのメモリーリージョンのstartより大きいと、そのアドレスはユーザプロセス空間です。(正規な例外ということです。)
そうでない場合は、スタックエリアを参照して例外が発生した可能性があります。スタック低位に伸びていきます。この場合vma->vm_start > address && vma->vm_end > addressとなります。そしこのメモリーリージョンがVM_GROWSDOWNでないなら、これはスタック領域でありません。bad_areaにジャンプです。
スタック領域の例外チェックをするのに、ユーザモードかカーネルモードかで処理が異なっています。ユーザモードの時、その時のSPを65536 + 32 * sizeof(unsigned long)超えてのスタック領域への参照はbad_areaとしています。カーネルモードではそのようなチェックはありません。カーネルはそのようなスタックの使い方をしないからでしょうか?
で、OKなら、(expand_stack()でスタックリージョンを拡張します。これは基本的にはvma->vm_startにaddressを設定するだけです。ここまできたら、67行目のgood_area:ラベルが示すように、正規なメモリー空間参照による例外です。しかし、まだページを割り当てられません。参照権限によるチェックが必要だからです。
71行の書き込みでページが有る場合と、72行目の書き込みでページが無い場合の処理は同じです。どちらもメモリーリージョンがVM_WRITEかチェックします。もしそうなら正規の例外となります。なお、71行の書き込みでページが有る場合ですが、これはページ(PET)が読み込み設定の場合で、たぶんコピー・オン・ライト時のケースかと・・・。
77行目はページが有るのに、読み込みで例外が発生したケースです。これは問答無用にbad_areaへジャンプです。
79行目はページが無くて、読み込みのケースです。ページは遅延割付されるのでありる話です。この場合メモリーリージョンがVM_READ | VM_EXEC | VM_WRITEでないと、bad_areaへジャンプです。
84行目までくれば正規の例外発生です。handle_mm_fault()でページを割り当てることになります。この時ページ割り当てに失敗したとき、エラーに応じてout_of_memory/do_sigbusにジャンプします。
93から96行目はhandle_mm_fault()でページ割り当てで、スワップ等で遅延が発生した時、VM_FAULT_MAJORが設定されるようで、たぶん統計情報でしょうか、その数をカウントしています。
98行目は仮想86モードで例外が発生したケースだと思います。VRAM領域でしょうか?そこで例外が発生したなら、取得したページ分をtsk->thread.screen_bitmapにセットしているようです。
そして、46行目および52行目の処理で、mm->mmap_semのセマフォを取得したのを解放して、めでたくページ取得ということのようです。
out_of_memory:/do_sigbus:も似たような処理ですが、out_of_memoryで例外を発生させたプロセスがinitの場合、yield()で(確かschedule処理を呼び出すようになっていたと思います。)後、再度メモリー割り当てを試みています。ユーザモード時はSIGKILLで、カーネルモードではno_contextにジャンプしていますが、結果的にOOPSでdieということになるんじゃないでしょうか?
p/s
まとまりもなく、だらだらとなってしまいました。自分で書いといて、再度読み返すと、かったるく成ってしまいました。
これらの処理はdo_page_fault()で行われます。本関数はgoto/returnを多用しており、全体の流れが今ひとつすっきりしずらいです。
例外が発生すると、そのアドレスはcr2レジスタにセットされています。14行のaddress = read_cr2()でaddressには例外が発生したアドレスが設定されることになります。
21行目のf (unlikely(address >= TASK_SIZE))で例外発生箇所によるチェックです。TASK_SIZEは0xC0000000で、すなわちif文の中はカーネルメモリ空間で例外が発生した処理になります。
error_codeには例外が発生時の情報が以下のように設定されています。
PF_PROT bit 0 == 0 ページが無い。
PF_WRITE bit 1 == 0 writeモードでない。
PF_USER bit 2 == 0 カーネルモード。
PF_RSVD bit 3 == 1 予約ページをアクセスした。
PF_INSTR bit 4 == 1 実行モードでアクセスした。
従って22行目のif文チェックは、カーネルモードでページがまだ割り当てていないため発生した、正常なケースです。この場合vmalloc_fault()でページを割り当てます。
それ以外の場合、ユーザモードでアクセスした。(ユーザモードでカーネル領域はアクセスできません。カーネルモードでも予約領域をアクセスした。(カーネルのバグ?)カーネルモードでしかもページが有るのに例外が発生した。(カーネルのバグないしハードウエアのトラブル?)の場合は、bad_area_nosemaphoreへとエラー処理へとジャンプします。
spurious_fault()はTLBキャッシュに掛かる処理のようで、notify_page_fault()はkprobに掛かる処理です。枝葉と言うこともあり、本当の処よくわかりませんのでスキップです。
以降はユーザ空間で例外が発生した場合の処理になります。37から41行は、確か例外が発生したら割り込み禁止となってたかと・・・。それで、仮想86モード(?)ないし、例外発生前に割り込み許可だったら、割り込み許可に設定しているようです。
43行目はクリティカル区間(割り込みハンドラ等)カーネルスレッドだったら、bad_area_nosemaphoreへジャンプです。これらの処理はユーザ空間を参照することはないからです。
47行目のdown_read_trylock()でvmのセマフォを取得しようとしています。取得できないということは、すでに取得されているということです。これは他の同じvm空間を共有するプロセスが、そのメモリー空間を参照している可能性があるからだと思います。このあたり多分デッドロックなんか考慮してだと思いますが・・・。この処理で例外が発生したとすると、カーネルモードにおいてユーザ空間での例外となります。これはシステムコールで引数のユーザバッファーを不正参照した。ということだと思います。そしてデータをセットする関数(put_user()等)でインプリメントされている、exception_tablesがあるかどうかをチェックします。なければbad_area_nosemaphoreへジャンプです。
54行目までくれば、ユーザモードでユーザ空間を参照した例外ということです。(カーネルモードでカーネルスタックの例外も含んでいるようです。カーネルスタックはユーザ空間だったような・・・)find_vma()で例外アドレスがユーザプロセスが有するメモリ空間かどうかチェックします。vmaがNULLならそのアドレスは該当プロセスにメモリ空間にありません。bad_areaにジャンプです。以降のif文はメモリ空間にあるということです。そのメモリーリージョンのstartより大きいと、そのアドレスはユーザプロセス空間です。(正規な例外ということです。)
そうでない場合は、スタックエリアを参照して例外が発生した可能性があります。スタック低位に伸びていきます。この場合vma->vm_start > address && vma->vm_end > addressとなります。そしこのメモリーリージョンがVM_GROWSDOWNでないなら、これはスタック領域でありません。bad_areaにジャンプです。
スタック領域の例外チェックをするのに、ユーザモードかカーネルモードかで処理が異なっています。ユーザモードの時、その時のSPを65536 + 32 * sizeof(unsigned long)超えてのスタック領域への参照はbad_areaとしています。カーネルモードではそのようなチェックはありません。カーネルはそのようなスタックの使い方をしないからでしょうか?
で、OKなら、(expand_stack()でスタックリージョンを拡張します。これは基本的にはvma->vm_startにaddressを設定するだけです。ここまできたら、67行目のgood_area:ラベルが示すように、正規なメモリー空間参照による例外です。しかし、まだページを割り当てられません。参照権限によるチェックが必要だからです。
71行の書き込みでページが有る場合と、72行目の書き込みでページが無い場合の処理は同じです。どちらもメモリーリージョンがVM_WRITEかチェックします。もしそうなら正規の例外となります。なお、71行の書き込みでページが有る場合ですが、これはページ(PET)が読み込み設定の場合で、たぶんコピー・オン・ライト時のケースかと・・・。
77行目はページが有るのに、読み込みで例外が発生したケースです。これは問答無用にbad_areaへジャンプです。
79行目はページが無くて、読み込みのケースです。ページは遅延割付されるのでありる話です。この場合メモリーリージョンがVM_READ | VM_EXEC | VM_WRITEでないと、bad_areaへジャンプです。
84行目までくれば正規の例外発生です。handle_mm_fault()でページを割り当てることになります。この時ページ割り当てに失敗したとき、エラーに応じてout_of_memory/do_sigbusにジャンプします。
93から96行目はhandle_mm_fault()でページ割り当てで、スワップ等で遅延が発生した時、VM_FAULT_MAJORが設定されるようで、たぶん統計情報でしょうか、その数をカウントしています。
98行目は仮想86モードで例外が発生したケースだと思います。VRAM領域でしょうか?そこで例外が発生したなら、取得したページ分をtsk->thread.screen_bitmapにセットしているようです。
そして、46行目および52行目の処理で、mm->mmap_semのセマフォを取得したのを解放して、めでたくページ取得ということのようです。
1 void __kprobes do_page_fault(struct pt_regs *regs, unsigned long error_code) 2 { 3 struct task_struct *tsk; 4 struct mm_struct *mm; 5 struct vm_area_struct *vma; 6 unsigned long address; 7 int write, si_code; 8 int fault; 9 10 tsk = current; 11 mm = tsk->mm; 12 prefetchw(&mm->mmap_sem); 13 14 address = read_cr2(); 15 16 si_code = SEGV_MAPERR; 17 18 if (unlikely(kmmio_fault(regs, address))) 19 return; 20 21 if (unlikely(address >= TASK_SIZE)) { 22 if (!(error_code & (PF_RSVD|PF_USER|PF_PROT)) && 23 vmalloc_fault(address) >= 0) 24 return; 25 26 if (spurious_fault(address, error_code)) 27 return; 28 29 if (notify_page_fault(regs)) 30 return; 31 goto bad_area_nosemaphore; 32 } 33 34 if (notify_page_fault(regs)) 35 return; 36 37 if (user_mode_vm(regs)) { 38 local_irq_enable(); 39 error_code |= PF_USER; 40 } else if (regs->flags & X86_EFLAGS_IF) 41 local_irq_enable(); 42 43 if (unlikely(in_atomic() || !mm)) 44 goto bad_area_nosemaphore; 45 46 again: 47 if (!down_read_trylock(&mm->mmap_sem)) { 48 if ((error_code & PF_USER) == 0 && 49 !search_exception_tables(regs->ip)) 50 goto bad_area_nosemaphore; 51 down_read(&mm->mmap_sem); 52 } 53 54 vma = find_vma(mm, address); 55 if (!vma) 56 goto bad_area; 57 if (vma->vm_start <= address) 58 goto good_area; 59 if (!(vma->vm_flags & VM_GROWSDOWN)) 60 goto bad_area; 61 if (error_code & PF_USER) { 62 if (address + 65536 + 32 * sizeof(unsigned long) < regs->sp) 63 goto bad_area; 64 } 65 if (expand_stack(vma, address)) 66 goto bad_area; 67 good_area: 68 si_code = SEGV_ACCERR; 69 write = 0; 70 switch (error_code & (PF_PROT|PF_WRITE)) { 71 default: /* 3: write, present */ 72 case PF_WRITE: /* write, not present */ 73 if (!(vma->vm_flags & VM_WRITE)) 74 goto bad_area; 75 write++; 76 break; 77 case PF_PROT: /* read, present */ 78 goto bad_area; 79 case 0: /* read, not present */ 80 if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE))) 81 goto bad_area; 82 } 83 84 survive: 85 fault = handle_mm_fault(mm, vma, address, write); 86 if (unlikely(fault & VM_FAULT_ERROR)) { 87 if (fault & VM_FAULT_OOM) 88 goto out_of_memory; 89 else if (fault & VM_FAULT_SIGBUS) 90 goto do_sigbus; 91 BUG(); 92 } 93 if (fault & VM_FAULT_MAJOR) 94 tsk->maj_flt++; 95 else 96 tsk->min_flt++; 97 98 if (v8086_mode(regs)) { 99 unsigned long bit = (address - 0xA0000) >> PAGE_SHIFT; 100 if (bit < 32) 101 tsk->thread.screen_bitmap |= 1 << bit; 102 } 103 up_read(&mm->mmap_sem); 104 return; 105 106 bad_area: 107 up_read(&mm->mmap_sem); 108 109 bad_area_nosemaphore: 110 if (error_code & PF_USER) { 111 local_irq_enable(); 112 113 if (is_prefetch(regs, address, error_code)) 114 return; 115 116 if (is_errata100(regs, address)) 117 return; 118 119 if (show_unhandled_signals && unhandled_signal(tsk, SIGSEGV) && 120 printk_ratelimit()) { 121 printk( 122 "%s%s[%d]: segfault at %lx ip %p sp %p error %lx", 123 task_pid_nr(tsk) > 1 ? KERN_INFO : KERN_EMERG, 124 tsk->comm, task_pid_nr(tsk), address, 125 (void *) regs->ip, (void *) regs->sp, error_code); 126 print_vma_addr(" in ", regs->ip); 127 printk("\n"); 128 } 129 130 tsk->thread.cr2 = address; 131 tsk->thread.error_code = error_code | (address >= TASK_SIZE); 132 tsk->thread.trap_no = 14; 133 force_sig_info_fault(SIGSEGV, si_code, address, tsk); 134 return; 135 } 136 137 if (is_f00f_bug(regs, address)) 138 return; 139 140 no_context: 141 if (fixup_exception(regs)) 142 return; 143 144 if (is_prefetch(regs, address, error_code)) 145 return; 146 147 if (is_errata93(regs, address)) 148 return; 149 150 bust_spinlocks(1); 151 152 show_fault_oops(regs, error_code, address); 153 154 tsk->thread.cr2 = address; 155 tsk->thread.trap_no = 14; 156 tsk->thread.error_code = error_code; 157 158 die("Oops", regs, error_code); 159 bust_spinlocks(0); 160 do_exit(SIGKILL); 161 162 out_of_memory: 163 up_read(&mm->mmap_sem); 164 if (is_global_init(tsk)) { 165 yield(); 166 down_read(&mm->mmap_sem); 167 goto survive; 168 } 169 170 printk("VM: killing process %s\n", tsk->comm); 171 if (error_code & PF_USER) 172 do_group_exit(SIGKILL); 173 goto no_context; 174 175 do_sigbus: 176 up_read(&mm->mmap_sem); 177 178 if (!(error_code & PF_USER)) 179 goto no_context; 180 if (is_prefetch(regs, address, error_code)) 181 return; 182 183 tsk->thread.cr2 = address; 184 tsk->thread.error_code = error_code; 185 tsk->thread.trap_no = 14; 186 force_sig_info_fault(SIGBUS, BUS_ADRERR, address, tsk); 187 }106行目のbad_area:ラベル以降は、イリーガルな処理となります。上で見てきたようにユーザ空間に掛かるr処理ではmm->mmap_sem取得のち、処理を行うようになっているため、bad_area/bad_area_nosemaphoreへのジャンプ先が異なっていまが、ユーザモードでの例外ならSIGSEGVを送信しています。カーネルモードなら140行目のno_contextへと進んで、fixupテーブルにかかるものなら、そのテーブル内の処理へと進むことになります。そしてなにやらわけのわからない処理をした後、OOPSでdieしているようです。
out_of_memory:/do_sigbus:も似たような処理ですが、out_of_memoryで例外を発生させたプロセスがinitの場合、yield()で(確かschedule処理を呼び出すようになっていたと思います。)後、再度メモリー割り当てを試みています。ユーザモード時はSIGKILLで、カーネルモードではno_contextにジャンプしていますが、結果的にOOPSでdieということになるんじゃないでしょうか?
p/s
まとまりもなく、だらだらとなってしまいました。自分で書いといて、再度読み返すと、かったるく成ってしまいました。