(莱昂氏unix源代码分析导读-47) exec

                                                   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()的前三个参数,分别为textdatastack segmentsize

而函数也会分别为三个segment分配user态地址寄存器:

 

(1)         TextData Segment顺序占据逻辑地址的低地址部分;

(2)         Stack Segment占据逻辑地址的高地址部分,且从最高地址向地址延伸。

 

举例说明,当三个segment都占据2个page时,其user态地址寄存器的设置如下图所示:

(莱昂氏unix源代码分析导读-47) exec_第1张图片

下面,让我们看看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;

 

 这次调用比较好理解,当时刚刚得到了“执行信息”(即textdataSegmentSize),

其目的是为了检查该“执行信息”是否合法。

 

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

sizeText Segmentsize0),按照前面的描述,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 = &regloc[0]; cp < &regloc[6];)

3187:       u.u_ar0[*cp++] = 0;

3188:    u.u_ar0[R7] = 0;

 

2.        返回user

exec系统调用最后会通过call汇编例程(0805: rtt)返回user态。而exec已经将user pc0,这样,

系统调用返回时,将直接跳到user态地址0开始执行,而这正是text segment的起始地址。

 

 

博客地址:http://blog.csdn.net/cszhao1980

博客专栏地址:http://blog.csdn.net/column/details/lions-unix.html

 

你可能感兴趣的:((莱昂氏unix源代码分析导读-47) exec)