(莱昂氏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态地址寄存器的设置如下图所示:

下面,让我们看看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操作系统版本6源代码 UNIX操作系统过程分类索引 3 UNIX操作系统文件及过程 5 UNIX操作系统定义的符号列表 7 UNIX操作系统源代码交叉引用列表 9 第一部分 初始化、进程初始化 25 第二部分 陷入、中断、系统调用和 进程管理 75 第三部分 程序交换、基本输入/输出、 块设备 109 第四部分 文件和目录、文件系统、管道 133 第五部分 面向字符的特殊文件 181 下篇 莱昂UNIX源代码分析 前言 207 第1章 绪论 209 1.1 UNIX操作系统 209 1.2 公用程序 209 1.3 其他文档 210 1.4 UNIX程序员手册 210 1.5 UNIX文档 211 1.6 UNIX操作系统源代码 211 1.7 源代码中各部分 212 1.8 源代码文件 212 1.9 分析的使用 212 1.10 对程序设计水平的一条注释 212 第2章 基础知识 214 2.1 处理机 214 2.2 处理机状态字 214 2.3 通用寄存器 214 2.4 指令集 215 2.5 寻址方式 216 2.5.1 寄存器方式 217 2.5.2 寄存器延迟方式 217 2.5.3 自动增1方式 217 2.5.4 自动减1方式 217 2.5.5 变址方式 217 2.5.6 立即方式 218 2.5.7 相对方式 218 2.6 UNIX汇编程序 219 2.7 存储管理 219 2.8 段寄存器 220 2.9 页说明寄存器 220 2.10 存储分配 220 2.11 状态寄存器 221 2.12 “i”和“d”空间 221 2.13 启动条件 221 2.14 专用设备寄存器 221 第3章 阅读“C”程序 222 3.1 某些选出的例子 222 3.2 例1 222 3.3 例2 223 3.4 例3 223 3.5 例4 225 3.6 例5 225 3.7 例6 227 3.8 例7 227 3.9 例8 228 3.10 例9 228 3.11 例10 229 3.12 例11 229 3.13 例12 230 3.14 例13 230 3.15 例14 231 3.16 例15 231 3.17 例16 232 3.18 例17 233 第4章 概述 235 4.1 变量分配 235 4.2 全局变量 235 4.3 “C”预处理程序 235 4.4 第一部分 236 4.4.1 第1组“.h”文件 236 4.4.2 汇编语言文件 237 4.4.3 在第一部分中的其他文件 237 4.5 第二部分 237 4.6 第三部分 238 4.7 第四部分 238 4.8 第五部分 239 第一部分 初始化、进程初始化 第5章 两个文件 241 5.1 文件malloc.c 241 5.1.1 列表维护规则 241 5.1.2 malloc(2528) 242 5.1.3 mfree(2556) 243 5.1.4 结论 244 5.2 文件prf.c 244 5.2.1 printf(2340) 244 5.2.2 printn(2369) 245 5.2.3 putchar(2386) 246 5.2.4 panic(2419) 247 5.2.5 prdev(2433)、deverror(2447) 247 5.3 包含的文件 247 第6章 系统初启 249 6.1 操作员的动作 249 6.2 start(0612) 249 6.3 main(1550) 251 6.4 进程 252 6.5 proc〔0〕的初始化 252 6.6 sched(1940) 253 6.7 sleep(2066) 253 6.8 swtch(2178) 253 6.9 再回到main 254 第7章 进程 256 7.1 进程映像 256 7.2 proc结构(0358) 257 7.3 user结构(0413) 257 7.4 每个进程数据区 258 7.5 段 258 7.6 映像的执行 258 7.7 核心态执行 259 7.8 用户态执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值