莱昂的分析比较清晰。在这里我只说明几个容易让人感到迷惑的问题。
首先是trap函数的长长的参数列表,同clock函数一样,这些参数来自于入口处的设置,
回头看一下栈图,这些参数的容易理解了。
函数开头就检查进程的“前状态”——绝大多数情况下,应该是user态。核态的陷入在
大多数情况下都是一个错误,会引起panic的调用,导致系统down掉。
在“2701: u.u_ar0 = &r0;”中,r0是函数的参数,而非PDP11寄存器。设置之后,通过u_ar0数组
可以神奇的取用各个寄存器,各个寄存器的数组标号定义如下:
2605: #define R0 (0)
2606: #define R1 (-2)
2607: #define R2 (-9)
2608: #define R3 (-8)
2609: #define R4 (-7)
2610: #define R5 (-6)
2611: #define R6 (-3)
2612: #define R7 (1)
2613: #define RPS (2)
要想弄懂这个问题,还得靠我们的老朋友——栈图,如下图所示:
您需要结合这两幅栈图,可以很容易的理解上述问题。
在Trap处理多种类型的陷入中,最重要的要数system entry call了。系统定义了
sysent数组用于配置各个systerm call的处理程序。
2667: struct sysent {
2668: int count; /* argument count */
2669: int (*call)(); /* name of handler */
2670: } sysent[64];
强调一下:这里的参数个数,指的是紧跟在“trap指令”后面传进来的参数,呆会我们
会看到,trap(2693)函数会将存放在u.u_arg[ ]数组中。而system call还可以通过r0传入一个
参数,这个参数在sysent[ ]中是不计数的。
2910: int sysent[]
2911: {
2912: 0, &nullsys, /* 0 = indir */
2913: 0, &rexit, /* 1 = exit */
2914: 0, &fork, /* 2 = fork */
……
2923: 2, &exec, /* 11 = exec */
……
2946 0, &nice, /* 34 = nice */
2947: 0, &sslep, /* 35 = sleep *
…..
}
显然,以system entry number为index访问该数组即可找到该系统调用的处理程序。
一个比较难懂的地方是关于system call处理的第2754行:
“2754: callp = &sysent[fuiword(pc-2)&077];”
还记得我们以前对trap指令的说明吗——trap指令用于实现system call,其后8个bit(unix用来6个bit)
是system entry number——但是,pc不是就在栈里(参数)里,直接拿来用不就得了,2754这个古怪
的操作在干什么?当然不行,因为压栈的pc是upgraded pc,正好在trap指令的后面一个word。而2754
正好能取到trap这条指令的code。
对于大多数系统调用来说,参数就跟在trap指令的后面,“trap函数”会依次取得参数,放入
u_arg[]数组之中,如下所示:
2765: for(i=0; i<callp->count; i++) {
2766: u.u_arg[i] = fuiword(pc);
2767: pc =+ 2;
2768: }
而#0系统调用是“间接”系统调用,经其传送的参数是位于用户程序数据空间中一个系统
调用及其参数序列的起始地址。取得参数之后,会通过trap1函数调用真正的系统调用处理程序。
还有一点需要注意的是,多数情况下,trap()函数在退出时,会调用setpri()重新设置active
process的优先级——这增加了return回trap(汇编例程)后,进行process switch的机会。
下一节我们继续讨论系统调用。
【注】:我们的讨论跳过了关于swap、signal的部分。
博客地址:http://blog.csdn.net/cszhao1980
博客专栏地址:http://blog.csdn.net/column/details/lions-unix.html