do_execve与进程34的生前死后

第四章笔记

打开终端设备文件及复制文件句柄

1
2
3
4
5
6
7
8
9
10
void init(void)
{
int pid,i;

setup((void *) &drive_info);
(void) open("/dev/tty0",O_RDWR,0);//创建标准输入设备
(void) dup(0);//创建标准输出设备
(void) dup(0);
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int open(const char * filename, int flag, ...)
{
register int res;
va_list arg;

va_start(arg,flag);
__asm__("int $0x80"
:"=a" (res)
:"0" (__NR_open),"b" (filename),"c" (flag),
"d" (va_arg(arg,int)));
if (res>=0)
return res;
errno = -res;
return -1;
}

在GCC中的内联汇编中,数值操作数约束(numeric operand constraints)如"0"用于指定汇编代码的输入和输出寄存器。该数字指的是操作数在操作数列表中的位置。

在这个上下文中,"0"特指第一个操作数约束,也被称为“约束0”(constraint 0)。在x86调用约定中,EAX寄存器通常用于从函数中返回值。通过将"0"指定为输出操作数的约束("=a" (res)),编译器得知在内联汇编块之后,EAX寄存器中的值应该赋给变量res

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int sys_open(const char * filename,int flag,int mode) //filename "/dev/tty0"
{
struct m_inode * inode;
struct file * f;
int i,fd;

mode &= 0777 & ~current->umask;
//打开文件的进程是当前进程,在filep里面找一个空闲项
for(fd=0 ; fd<NR_OPEN ; fd++) // #define NR_OPEN 20
if (!current->filp[fd])
break;
if (fd>=NR_OPEN)
return -EINVAL;
current->close_on_exec &= ~(1<<fd);
f=0+file_table;
//找完找file table,引用计数为0的空闲项
for (i=0 ; i<NR_FILE ; i++,f++)//#define NR_FILE 64
if (!f->f_count) break;
if (i>=NR_FILE)
return -EINVAL;
(current->filp[fd]=f)->f_count++;//当前进程的空闲的filep的哪一项指向空闲的file table里面的项,引用计数++
//读i结点,filename:sysopen的参数const char*类型,返回给inode
//如果open_namei成功返回0,失败返回1
if ((i=open_namei(filename,flag,mode,&inode))<0) {
current->filp[fd]=NULL;
f->f_count=0;
return i;
}
/* ttys are somewhat special (ttyxx major==4, tty major==5) */
if (S_ISCHR(inode->i_mode))//tty0是设备文件
//设备文件的设备号放在i_zone[0]里面
if (MAJOR(inode->i_zone[0])==4) {
if (current->leader && current->tty<0) {
current->tty = MINOR(inode->i_zone[0]);
tty_table[current->tty].pgrp = current->pgrp;
}
} else if (MAJOR(inode->i_zone[0])==5)
if (current->tty<0) {
iput(inode);
current->filp[fd]=NULL;
f->f_count=0;
return -EPERM;
}
/* Likewise with block-devices: check for floppy_change */
if (S_ISBLK(inode->i_mode))
check_disk_change(inode->i_zone[0]);
f->f_mode = inode->i_mode;
f->f_flags = flag;
f->f_count = 1;
f->f_inode = inode;
f->f_pos = 0;
return (fd);
}

进程2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void init(void)
{
...
printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
NR_BUFFERS*BLOCK_SIZE);
printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
if (!(pid=fork())) {
// 进程2:
close(0);//关闭设备标准输入
if (open("/etc/rc",O_RDONLY,0))
_exit(1);
execve("/bin/sh",argv_rc,envp_rc); //走syscall3,需要打开的执行文件和环境变量
_exit(2);
}
if (pid>0)
//进程1等待子进程退出
while (pid != wait(&i))
/* nothing */;
while (1) {
if ((pid=fork())<0) {
printf("Fork failed in init\r\n");
continue;
}
if (!pid) {
close(0);close(1);close(2);
setsid();
(void) open("/dev/tty0",O_RDWR,0);
(void) dup(0);
(void) dup(0);
_exit(execve("/bin/sh",argv,envp));
}
...
}

sys_waitpid

1
2
#define FIRST_TASK task[0]
#define LAST_TASK task[NR_TASKS-1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
{
int flag, code;
struct task_struct ** p;

verify_area(stat_addr,4);
repeat:
flag=0;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
if (!*p || *p == current) // 跳过空项和本进程项。
continue;
if ((*p)->father != current->pid)// 如果不是当前进程的子进程则跳过。
continue;
// 此时扫描选择到的进程 p 肯定是当前进程的子进程。
// 如果等待的子进程号 pid>0,但与被扫描子进程 p 的 pid 不相等,说明它是当前进程另外的子进程,
// 于是跳过该进程,接着扫描下一个进程。
if (pid>0) {
if ((*p)->pid != pid)
continue;
// 否则,如果指定等待进程的 pid=0,表示正在等待进程组号等于当前进程组号的任何子进程。如果
// 此时被扫描进程 p 的进程组号与当前进程的组号不等,则跳过。
} else if (!pid) {
if ((*p)->pgrp != current->pgrp)
continue;
// 否则,如果指定的 pid<-1,表示正在等待进程组号等于 pid 绝对值的任何子进程。如果此时被扫描
// 进程 p 的组号与 pid 的绝对值不等,则跳过。

} else if (pid != -1) {
if ((*p)->pgrp != -pid)
continue;
}
// 如果前 3 个对 pid 的判断都不符合,则表示当前进程正在等待其任何子进程,也即 pid=-1 的情况。
// 此时所选择到的进程 p 正是所等待的子进程。接下来根据这个子进程 p 所处的状态来处理。
switch ((*p)->state) {
// 子进程 p 处于停止状态时,如果此时 WUNTRACED 标志没有置位,表示程序无须立刻返回,于是继续
// 扫描处理其它进程。如果 WUNTRACED 置位,则把状态信息 0x7f 放入*stat_addr,并立刻返回子进程
// 号 pid。这里 0x7f 表示的返回状态使 WIFSTOPPED()宏为真。参见 include/sys/wait.h,14 行
case TASK_STOPPED://进程2处于停止状态
if (!(options & WUNTRACED))
continue;
put_fs_long(0x7f,stat_addr);
return (*p)->pid;
// 如果子进程 p 处于僵死状态,则首先把它在用户态和内核态运行的时间分别累计到当前进程(父进程)
// 中,然后取出子进程的 pid 和退出码,并释放该子进程。最后返回子进程的退出码和 pid。
case TASK_ZOMBIE://进程2属于僵死状态
current->cutime += (*p)->utime;
current->cstime += (*p)->stime;
flag = (*p)->pid;
code = (*p)->exit_code;
release(*p);
put_fs_long(code,stat_addr);
return flag;
// 如果这个子进程 p 的状态既不是停止也不是僵死,那么就置 flag=1。表示找到过一个符合要求的
// 子进程,但是它处于运行态或睡眠态。
default:
flag=1;
continue;
}
}
// 在上面对任务数组扫描结束后,如果 flag 被置位,说明有符合等待要求的子进程并没有处于退出
// 或僵死状态。如果此时已设置 WNOHANG 选项(表示若没有子进程处于退出或终止态就立刻返回),
// 就立刻返回 0,退出。否则把当前进程置为可中断等待状态并重新执行调度。
if (flag) {
if (options & WNOHANG)
return 0;
current->state=TASK_INTERRUPTIBLE;//// 置当前进程为可中断等待状态。
schedule();// 重新调度。
// 当又开始执行本进程时,如果本进程没有收到除 SIGCHLD 以外的信号,则还是重复处理。否则,
// 返回出错码并退出。
if (!(current->signal &= ~(1<<(SIGCHLD-1))))
goto repeat;
else
return -EINTR;
}
return -ECHILD;
}

启动shell (进程2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void init(void)
{
...

if (!(pid=fork())) {
close(0);//关闭设备标准输入
if (open("/etc/rc",O_RDONLY,0))//用rc文件替换标准输入文件
_exit(1);
execve("/bin/sh",argv_rc,envp_rc); //走syscall3,需要打开的执行文件和环境变量,加载shell程序
//argv_rc是参数,envp_rc是环境变量
_exit(2);
}
...
}

execve会对应到

_sys_execve

1
2
3
4
5
6
_sys_execve:
lea EIP(%esp),%eax #把EIP值所在栈空间的地址值压栈
pushl %eax
call _do_execve
addl $4,%esp
ret

do_execve

栈空间与函数传参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
int do_execve(unsigned long * eip,long tmp,char * filename,
char ** argv, char ** envp)
{
struct m_inode * inode;
struct buffer_head * bh;
struct exec ex;
unsigned long page[MAX_ARG_PAGES];
int i,argc,envc;
int e_uid, e_gid;
int retval;
int sh_bang = 0;
unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4;
// eip指向系统调用前的eip,eip[1]则指向cs,判断一下这时候的cs是不是用户的cs
if ((0xffff & eip[1]) != 0x000f)
panic("execve called from supervisor mode");
// 初始化参数和环境串空间的页面指针数组(表)。
for (i=0 ; i<MAX_ARG_PAGES ; i++) /* clear page-table */
page[i]=0;
if (!(inode=namei(filename))) //获得文件i结点 /* get executables inode */
return -ENOENT;
// 计算参数个数和环境变量个数。
argc = count(argv);
envc = count(envp);
// 执行文件必须是常规文件。若不是常规文件则置出错返回码,跳转到 exec_error2(第 347 行)。
restart_interp:
if (!S_ISREG(inode->i_mode)) { /* must be regular file */
retval = -EACCES;
goto exec_error2;
}
// 以下检查被执行文件的执行权限。根据其属性(对应 i 节点的 uid 和 gid),看本进程是否有权执行它。
i = inode->i_mode;
e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;
if (current->euid == inode->i_uid)
i >>= 6;
else if (current->egid == inode->i_gid)
i >>= 3;
//检查权限
if (!(i & 1) &&
!((inode->i_mode & 0111) && suser())) {
retval = -ENOEXEC;
goto exec_error2;
}
//拷贝可执行文件的第一块:就是文件头,有可执行文件的详细信息
if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) {
retval = -EACCES;
goto exec_error2;
}
ex = *((struct exec *) bh->b_data); /* read exec-header */
// 是脚脚本文件,不是编译后的文件,sh_bang控制只会进入一次
if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang)) {
/*
* This section does the #! interpretation.
* Sorta complicated, but hopefully it will work. -TYT
*/

char buf[1023], *cp, *interp, *i_name, *i_arg;
unsigned long old_fs;

strncpy(buf, bh->b_data+2, 1022);
brelse(bh);
iput(inode);
buf[1022] = '\0';
if (cp = strchr(buf, '\n')) {
*cp = '\0';
for (cp = buf; (*cp == ' ') || (*cp == '\t'); cp++);
}
if (!cp || *cp == '\0') {
retval = -ENOEXEC; /* No interpreter name found */
goto exec_error1;
}
interp = i_name = cp;
i_arg = 0;
for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) {
if (*cp == '/')
i_name = cp+1;
}
if (*cp) {
*cp++ = '\0';
i_arg = cp;
}
/*
* OK, we've parsed out the interpreter name and
* (optional) argument.
*/
if (sh_bang++ == 0) {
p = copy_strings(envc, envp, page, p, 0);
p = copy_strings(--argc, argv+1, page, p, 0);
}
/*
* Splice in (1) the interpreter's name for argv[0]
* (2) (optional) argument to interpreter
* (3) filename of shell script
*
* This is done in reverse order, because of how the
* user environment and arguments are stored.
*/
p = copy_strings(1, &filename, page, p, 1);
argc++;
if (i_arg) {
p = copy_strings(1, &i_arg, page, p, 2);
argc++;
}
p = copy_strings(1, &i_name, page, p, 2);
argc++;
if (!p) {
retval = -ENOMEM;
goto exec_error1;
}
/*
* OK, now restart the process with the interpreter's inode.
*/
old_fs = get_fs();
set_fs(get_ds());
if (!(inode=namei(interp))) { /* get executables inode */
set_fs(old_fs);
retval = -ENOENT;
goto exec_error1;
}
set_fs(old_fs);
goto restart_interp;
}
brelse(bh);
//通过检查文件头信息判断该执行文件是否和要求
if (N_MAGIC(ex) != ZMAGIC || ex.a_trsize || ex.a_drsize ||
ex.a_text+ex.a_data+ex.a_bss>0x3000000 ||
inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)) {
retval = -ENOEXEC;
goto exec_error2;
}
//文件头大小不等于1k也不能执行
if (N_TXTOFF(ex) != BLOCK_SIZE) {
printk("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);
retval = -ENOEXEC;
goto exec_error2;
}
//如果不是脚本文件
if (!sh_bang) {
p = copy_strings(envc,envp,page,p,0);//将环境变量复制到进程空间
p = copy_strings(argc,argv,page,p,0);//将参数复制到进程空间
// 如果 p=0,则表示环境变量与参数空间页面已经被占满,容纳不下了。转至出错处理处。
if (!p) {
retval = -ENOMEM;
goto exec_error2;
}
}
/* OK, This is the point of no return */
// 如果原程序也是一个执行程序,则释放其 i 节点,并让进程 executable 字段指向新程序 i 节点。
if (current->executable)
iput(current->executable);
current->executable = inode;
for (i=0 ; i<32 ; i++)
current->sigaction[i].sa_handler = NULL;
for (i=0 ; i<NR_OPEN ; i++)
if ((current->close_on_exec>>i)&1)
sys_close(i);
current->close_on_exec = 0;//将原来共享的文件脱钩
// 根据指定的基地址和限长,释放原来进程代码段和数据段所对应的内存页表指定的内存块及页表本身。
// 此时被执行程序没有占用主内存区任何页面。在执行时会引起内存管理程序执行缺页处理而为其申请
// 内存页面,并把程序读入内存。
//脱掉的是用户代码,现在运行的是内核的代码
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));//脱钩
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
// 如果“上次任务使用了协处理器”指向的是当前进程,则将其置空,并复位使用了协处理器的标志。
if (last__used_math == current)
last__used_math = NULL;
current->used_math = 0;
//重置该进程的ldt
// 根据 a_text 修改局部表中描述符基址和段限长,并将参数和环境空间页面放置在数据段末端。
// 执行下面语句之后,p 此时是以数据段起始处为原点的偏移值,仍指向参数和环境空间数据开始处,
// 也即转换成为堆栈的指针。
p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;
// create_tables()在新用户堆栈中创建环境和参数变量指针表,并返回该堆栈指针。
p = (unsigned long) create_tables((char *)p,argc,envc);
// 修改当前进程各字段为新执行程序的信息。令进程代码段尾值字段 end_code = a_text;令进程数据
// 段尾字段 end_data = a_data + a_text;令进程堆结尾字段 brk = a_text + a_data + a_bss。
current->brk = ex.a_bss +
(current->end_data = ex.a_data +
(current->end_code = ex.a_text));
// 设置进程堆栈开始字段为堆栈指针所在的页面,并重新设置进程的有效用户 id 和有效组 id。
current->start_stack = p & 0xfffff000;
current->euid = e_uid;
current->egid = e_gid;
i = ex.a_text+ex.a_data;

while (i&0xfff)
put_fs_byte(0,(char *) (i++));
// 将原调用系统中断的程序在堆栈上的代码指针替换为指向新执行程序的入口点,并将堆栈指针替换
// 为新执行程序的堆栈指针。返回指令将弹出这些堆栈数据并使得 CPU 去执行新的执行程序,因此不会
// 返回到原调用系统中断的程序中去了。
eip[0] = ex.a_entry;//int 80 的返回地址改成了可执行文件的入口地址 /* eip, magic happens :-) */
eip[3] = p; /* stack pointer */ //没经过调度一口气就到执行函数的main函数了
return 0;
exec_error2:
iput(inode);
exec_error1:
for (i=0 ; i<MAX_ARG_PAGES ; i++)
free_page(page[i]);
return(retval);
}

shell执行

shell开始执行后,因为其线性地址空间对应的程序内容没有加载,所以会发生页异常中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
_page_fault:
xchgl %eax,(%esp)
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%edx
mov %dx,%ds
mov %dx,%es
mov %dx,%fs
movl %cr2,%edx
pushl %edx
pushl %eax
testl $1,%eax
jne 1f
call _do_no_page
jmp 2f
1: call _do_wp_page
2: addl $8,%esp
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret

do_no_page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void do_no_page(unsigned long error_code,unsigned long address)
{
int nr[4];
unsigned long tmp;
unsigned long page;
int block,i;

address &= 0xfffff000;//找到页的头
tmp = address - current->start_code; //在创建进程的时候,在copy_mem函数里面将 current->start_code设置为程序的段基址
if (!current->executable || tmp >= current->end_data) {
get_empty_page(address);
return;
}
if (share_page(tmp))//判断是否共享
return;
if (!(page = get_free_page())) //申请新的页
oom();
/* remember that 1 block is used for header */
block = 1 + tmp/BLOCK_SIZE;//计算块号,+1是还有文件头
for (i=0 ; i<4 ; block++,i++)//4是一页对应4块
nr[i] = bmap(current->executable,block);
bread_page(page,current->executable->i_dev,nr);
//对超出部分进行处理
i = tmp + 4096 - current->end_data;
tmp = page + 4096;
while (i-- > 0) {
tmp--;
*(char *)tmp = 0;
}
if (put_page(page,address))//将物理地址映射到线性地址空间
return;
free_page(page);
oom();
}

创建update (进程3)

shell之前用rc替换了标准输入设备文件tty0

rc:

1
2
3
/etc/update &
...
echo "/dev/hd1">/etc/mlab

因此,此时shell会创建进程3:update,并加载,加载过程与创建shell的过程类似

update的功能:将缓冲区的数据同步到外设上,每隔一段时间这个进程就会被唤醒同步一下,再被挂起。

shell退出

1
2
3
4
int sys_exit(int error_code)
{
return do_exit((error_code&0xff)<<8);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int do_exit(long code)
{
int i;
//释放进程的代码段和数据段,释放进程
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
//检查是否有子进程
for (i=0 ; i<NR_TASKS ; i++)
if (task[i] && task[i]->father == current->pid) {
//在退出前将子进程的父进程设为1
task[i]->father = 1;
if (task[i]->state == TASK_ZOMBIE)
/* assumption task[1] is always init */
//如果子进程处于僵死状态,向父进程发送终止信号
(void) send_sig(SIGCHLD, task[1], 1);
}
//接触进程和其它文件的关系
for (i=0 ; i<NR_OPEN ; i++)
if (current->filp[i])
sys_close(i);
iput(current->pwd);
current->pwd=NULL;
iput(current->root);
current->root=NULL;
iput(current->executable);
current->executable=NULL;
if (current->leader && current->tty >= 0)
tty_table[current->tty].pgrp = 0;
if (last_task_used_math == current)
last_task_used_math = NULL;
if (current->leader)
kill_session();
current->state = TASK_ZOMBIE;//将当前进程设置为僵死状态
current->exit_code = code;
tell_father(current->father);//给父进程发送信号通知它本进程要退出
schedule();//进程切换
return (-1); /* just to suppress warnings */
}

进程1执行wait pid

之前进程1在等待shell进程2退出:

1
2
if (pid>0)
while (pid != wait(&i))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
{
...
switch ((*p)->state) {
...
//退出的shell进程属于僵死状态
case TASK_ZOMBIE://进程2属于僵死状态
current->cutime += (*p)->utime;
current->cstime += (*p)->stime;
//记录shell的进程号
flag = (*p)->pid;
code = (*p)->exit_code;
release(*p); //释放shell进程的task struct
put_fs_long(code,stat_addr);
return flag;
...
}
void release(struct task_struct * p)
{
int i;
if (!p)
return;
for (i=1 ; i<NR_TASKS ; i++)
if (task[i]==p) {
task[i]=NULL;
free_page((long)p);
schedule();
return;
}
panic("trying to release non-existent task");
}

由此可见子进程的task struct资源在退出的时候是由父进程释放的

返回init:

1
2
if (pid>0)
while (pid != wait(&i))

创建完pid2的时候pid=2,wait退出后返回flag=2,所以退出while循环

重建shell(进程4)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void init(void)
{
...
while (1) {
if ((pid=fork())<0) {
printf("Fork failed in init\r\n");
continue;
}
if (!pid) {//重启shell
close(0);close(1);close(2);//新的shell进程关闭所有打开的文件
setsid();//创建新的会话
(void) open("/dev/tty0",O_RDWR,0);//重新打开标准输入设备文件
(void) dup(0);//重新打开标准输出设备文件
(void) dup(0);//重新打开标准错误输出设备文件
_exit(execve("/bin/sh",argv,envp));//加载shell进程
}
while (1)
if (pid == wait(&i))
break;
printf("\n\rchild %d died with code %04x\n\r",pid,i);
sync();
}
_exit(0); /* NOTE! _exit, not exit() */
}

之前已经启动了update进程并将其变成进程1的子进程,每个一段时间会update一下。

在进入rw_char函数后,shell进程会被设为可中断等待状态,切换到进程0怠速。怠速后用户通过shell进程提供的端口和系统交互,

通过键盘输入信息存储到字符缓冲队列上,该缓冲队列上的内容就是tyy0文件的内容,shell会不断读取缓冲队列的信息。

如果用户没有下达指令,则缓冲队列为0,shell处于可中断等待状态。

如果用户下达指令,发送键盘中断,中断服务程序会将内容输入到缓冲队列,并给shell进程发信号信号将导致shell进程进入就绪状态被唤醒,处理缓冲队列里面的信息,处理完后又会被挂起。

子进程执行可执行文件

要调整线性地址空间

  1. fork

  2. 把代码和数据拷贝到进程的线性地址空间,通过缺页机制(虚拟拷贝)

  3. 虚拷贝:将硬盘的空间和进程内存的空间同构

    unsigned long start_code,end_code,end_data,brk,start_stack;