by cszhao1980
现在,我们已经储备了足够的知识,该吹响向EXEC sys call冲锋的号角了。
exec是系统中最重要也是最复杂的系统调用之一,它的作用是执行指定的“可执行文件”。一般说来,
exec与fork配合使用,fork生成一个新进程,而exec是新进程执行其应该执行的代码。
莱昂对exec有着比较详细的介绍,但很不幸,这些代码理解起来仍然困难重重。所以,我要在这里多啰嗦几句。
1. exec的参数
exec拥有可变个数的参数,其中:
(1) 第一个参数指向一个字符串,而该字符串内存放的为要执行文件的路径名;
如#1进程的初始exec调用中,路径名为:“/etc/init”
(2) 其他参数也都为指向字符串的指针;
(3) 参数及其指向的内容,均在user态地址空间中。
而代码3050~3070行的作用,就是把user态地址空间的参数内容,拷贝到kernel态的缓冲区内。
3049: cp = bp->b_addr;
3050: na = 0;
3051: nc = 0;
3052: while(ap = fuword(u.u_arg[1])) {
3053: na++;
3054: if(ap == -1)
3055: goto bad;
3056: u.u_arg[1] =+ 2;
3057: for(;;) {
3058: c = fubyte(ap++);
3059: if(c == -1)
3060: goto bad;
3061: *cp++ = c;
3062: nc++;
3063: if(nc > 510) {
3064: u.u_error = E2BIG;
3065: goto bad;
3066: }
3067: if(c == 0)
3068: break;
3069: }
3070: }
2. 可执行文件的“执行”信息
可执行文件的前8个byte记录了该文件的“执行”信息,如text、data、bss段的size等。
3085~3108行的作用是读取这8个byte,并根据其信息进行设置。
1. estabur()的多次调用。
exec中多次调用了estabur()——这是非常奇怪的事情,我们知道该函数的作用是设置user态
的8个地址寄存器。因此,一般说来,这个函数调用一次即可。为理解这个问题,我们首先
复习一下estabur()的实现。extabur()的前三个参数,分别为text、data、stack segment的size,
而函数也会分别为三个segment分配user态地址寄存器:
(1) Text、Data Segment顺序占据逻辑地址的低地址部分;
(2) Stack Segment占据逻辑地址的高地址部分,且从最高地址向地址延伸。
举例说明,当三个segment都占据2个page时,其user态地址寄存器的设置如下图所示:
下面,让我们看看exec中的几次调用:
(1)第一次调用
3116: ts = ((u.u_arg[1]+63)>>6) & 01777;
3117: ds = ((u.u_arg[2]+u.u_arg[3]+63)>>6) & 01777;
3118: if(estabur(ts, ds, SSIZE, sep))
3119: goto bad;
这次调用比较好理解,当时刚刚得到了“执行信息”(即text、dataSegment的Size),
其目的是为了检查该“执行信息”是否合法。
(2)第二次调用
3138: estabur(0, ds, 0, 0);
3139: u.u_base = 0;
3140: u.u_offset[1] = 020+u.u_arg[1];
3141: u.u_count = u.u_arg[2];
3142: readi(ip);
此次调用的目的是为了方便的读取文件中的Data Segment。调用中只是指定了data segment的
size(Text Segment的size为0),按照前面的描述,user[0]将指向data Segment的首地址,即进程
的data Segment的逻辑起始地址为0。而其后的代码将文件中的Data Segment读入以0开始的地址,
即进程所分配的Data Segment当中。
(3)第三次调用
3148: u.u_tsize = ts;
3149: u.u_dsize = ds;
3150: u.u_ssize = SSIZE;
3151: u.u_sep = sep;
3152: estabur(u.u_tsize, u.u_dsize, u.u_ssize, u.u_sep);
此次调用为最终调用,正确设置了进程需要的user态地址寄存器。
相信有些读者会有这样的疑惑:本次调用过后,其user态地址寄存器得到了重置,其设置显然
与第二次调用的不同。那末,在第二次调用后读入的Data Segment是否还有效?
好问题!如果您仔细研究estabur()就会发现,在这两次调用下,Data Segment的逻辑地址有变化,
但其物理地址是重合的。因此,刚刚读入的Data Segment仍然有效。
1. 奇怪的负数地址
太奇怪了,下列代码竟然指定了负数地址,3156行将na(参数个数)“set”到user空间的负数地址:
3154: ap = -nc - na*2 - 4;
3155: u.u_ar0[R6] = ap;
3156: suword(ap, na);
3157: c = -nc;
3158: while(na--) {
3159: suword(ap=+2, c);
3160: do
3161: subyte(c++, *cp);
3162: while(*cp++);
3163: }
3164: suword(ap+2, -1);
呵呵。ap并非负数地址,而是一个很大的正数,事实上它距最高地址也就“nc + na*2 +4”个word。
前面说过,最高地址为stack所处的位置。而这几行代码将参数个数和参数本身压入stack Segment的
最高地址部分,并将user sp设置好。简单的说,即将用户参数压入用户栈而已。
由于user sp已经设置好,因此,在下面的代码clear user寄存器,唯独放过了user sp:
2677: char regloc[9]
2678: {
2679: R0, R1, R2, R3, R4, R5, R6, R7, RPS
2680: };
3186: for(cp = ®loc[0]; cp < ®loc[6];)
3187: u.u_ar0[*cp++] = 0;
3188: u.u_ar0[R7] = 0;
2. 返回user态
exec系统调用最后会通过call汇编例程(0805: rtt)返回user态。而exec已经将user pc清0,这样,
系统调用返回时,将直接跳到user态地址0开始执行,而这正是text segment的起始地址。
博客地址:http://blog.csdn.net/cszhao1980
博客专栏地址:http://blog.csdn.net/column/details/lions-unix.html