assert()和panic()
先来看assert()。你或许早就开始使用这个函数,但之前你使用的都是现成的assert,只要包含一个头文件,就可以方便地使用。如今什么都得自力更生了,不过不用怕,写一个assert函数并非难事,见下面的代码:
12 #define ASSERT
13 #ifdef ASSERT
14 void assertion_failure(char *exp, char *file, char *base_file, int line);
15 #define assert(exp) if (exp) ; \
16 else assertion_failure(#exp, __FILE__, __BASE_FILE__, __LINE__)
17 #else
18 #define assert(exp)
19 #endif
注意其中的__FILE__、__BASE_FILE__和__LINE__这三个宏,它们的意义如下[1]:
__FILE__: 将被展开成当前输入的文件。在这里,它告诉我们哪个文件中产生了异常。
__BASE_FILE__: 可被认为是传递给编译器的那个文件名。比如你在m.c中包含了n.h,而n.h中的某一个assert函数失败了,则__FILE__为n.h,__BASE_FILE__为m.c。
__LINE__: 将被展开成当前的行号。
明白了这几个宏的意义,剩下的assertion_failure()这个函数就显得容易了,它的作用就是将错误发生的位置打印出来:
42 PUBLIC void assertion_failure(char *exp, char *file, char *base_file, int line)
43 {
44 printl(”%c␣␣assert(%s)␣failed:␣file:␣%s,␣base_file:␣%s,␣ln%d”,
45 MAG_CH_ASSERT,
46 exp, file, base_file, line);
47
48 /**
49 * If assertion fails in a TASK, the system will halt before
50 * printl() returns. If it happens in a USER PROC, printl() will
51 * return like a common routine and arrive here.
52 * @see sys_printx()
53 *
54 * We use a forever loop to prevent the proc from going on:
55 */
56 spin(”assertion_failure()”);
57
58 /* should never arrive here */
59 __asm__ __volatile__(”ud2”);
60 }
注意这里使用了一点点小伎俩,那就是使用了一个改进后的打印函数,叫做printl(),它其实就是一个定义成printf的宏,不过这里的printf跟上一章中的稍有不同,它将调用一个叫做printx的系统调用,并最终调用函数sys_printx(),它位于tty.c中:
181 PUBLIC int sys_printx(int _unused1, int _unused2, char* s, struct proc* p_proc)
182 {
183 const char * p;
184 char ch;
185
186 char reenter_err[] = ”?␣k_reenter␣is␣incorrect␣for␣unknown␣reason”;
187 reenter_err[0] = MAG_CH_PANIC;
188
189 /**
190 * @note Code in both Ring 0 and Ring 1~3 may invoke printx().
191 * If this happens in Ring 0, no linear-physical address mapping
192 * is needed.
193 *
194 * @attention The value of ‘k_reenter’ is tricky here. When
195 * -# printx() is called in Ring 0
196 * - k_reenter > 0. When code in Ring 0 calls printx(),
197 * an ‘interrupt re-enter’ will occur (printx() generates
198 * a software interrupt). Thus ‘k_reenter’ will be increased
199 * by ‘kernel.asm::save’ and be greater than 0.
200 * -# printx() is called in Ring 1~3
201 * - k_reenter == 0.
202 */
203 if (k_reenter == 0) /* printx() called in Ring<1~3> */
204 p = va2la(proc2pid(p_proc), s);
205 else if (k_reenter > 0) /* printx() called in Ring<0> */
206 p = s;
207 else /* this should NOT happen */
208 p = reenter_err;
209
210 /**
211 * @note if assertion fails in any TASK, the system will be halted;
212 * if it fails in a USER PROC, it’ll return like any normal syscall
213 * does.
214 */
215 if ((*p == MAG_CH_PANIC) ||
216 (*p == MAG_CH_ASSERT && p_proc_ready < &proc_table[NR_TASKS])) {
217 disable_int();
218 char * v = (char*)V_MEM_BASE;
219 const char * q = p + 1; /* +1: skip the magic char */
220
221 while (v < (char*)(V_MEM_BASE + V_MEM_SIZE)) {
222 *v++ = *q++;
223 *v++ = RED_CHAR;
224 if (!*q) {
225 while (((int)v - V_MEM_BASE) % (SCR_WIDTH * 16)) {
226 /* *v++ = ’ ’; */
227 v++;
228 *v++ = GRAY_CHAR;
229 }
230 q = p + 1;
231 }
232 }
233
234 __asm__ __volatile__(”hlt”);
235 }
236
237 while ((ch = *p++) != 0) {
238 if (ch == MAG_CH_PANIC || ch == MAG_CH_ASSERT)
239 continue; /* skip the magic char */
240
241 out_char(tty_table[p_proc->nr_tty].p_console, ch);
242 }
243
244 return 0;
245 }
容易看到,sys_printx()将首先判断首字符是否为预先设定的“Magic Char”,如果是的话,则做响应的特殊处理。我们的assertion_failure()就使用了MAG_CH_ASSERT作为“Magic Char”。当sys_printx()发现传入字符串的第一个字符是MAG_CH_ASSERT时,会同时判断调用系统调用的进程是系统进程(TASK)还是用户进程(USER PROC),如果是系统进程,则停止整个系统的运转,并将要打印的字符串打印在显存的各处;如果是用户进程,则打印之后像一个普通的printx调用一样返回,届时该用户进程会因为assertion_failure()中对函数spin()的调用而进入死循环。换言之,系统进程的assert失败会导致系统停转,用户进程的失败仅仅使自己停转。