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のセマフォを取得したのを解放して、めでたくページ取得ということのようです。
  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
まとまりもなく、だらだらとなってしまいました。自分で書いといて、再度読み返すと、かったるく成ってしまいました。

最終更新 2011/08/01 18:58:25 - north
(2011/08/01 18:41:58 作成)


検索

アクセス数
3698014
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。