《操作系统真象还原》第15章(下)

15.5加载用户进程

exec会把一个可执行文件的的绝对路径作为参数,把当前正在运行的用户进程的进程体(代码段、数据段、堆、栈)用该可执行文件的进程体替换,从而实现了新进程的执行。

有了exec,用户便可以完成任意外部命令(用户进程)的运行。

执行外部命令时bash才会fork出子进程并调用exec从磁盘上加载外部命令对应的程序,然后执行该程序,从而实现了外部命令的执行。

15.6实现系统调用wait和exit

exit的作用很直白,就是使进程“主动”退出。

任何时候进程都会调用exit,即使程序员未写入调用exit的代码,在C运行库的最后也会发起exit的调用。由此可见,结束程序运行始终是通过调用exit系统调用实现的,因为这是唯一让系统重新拿回处理器控制权的机会。

wait的作用是阻塞父进程自己,直到任意一个子进程结束运行。

wait的作用主要有两个,一是使父进程阻塞,二是获得子进程的返回值。

这本质上属于进程同步的问题,也就是协调父子进程某些代码的执行次序,我们希望子进程执行完某些代码后再让父进程执行。对于父子进程的同步,可以用wait系统调用来解决,这其实就是wait的初心。

 

回收pcb内存空间的工作是在系统调用wait对应的内核实现中。

当父进程提前退出时,它所有的子进程还在运行,没有一个执行了exit,因为它们的生命周期尚未结束,还在运行中,个个都拥有“全尸”(进程体),这些进程就被称为孤儿进程。这时候所有的子进程会被init进程收养,init进程会成为这些子进程的新父亲,当子进程退出时会由init负责为其“收尸”的。

僵尸进程就是针对子进程的返回值能否成功提交给父进程而提出的,父进程不调用wait,就无法获知子进程的返回值,从而内核就无法回收子进程pcb所占的空间,因此就会在队列中占据一个进程表项。僵尸进程是没有进程体的,因为其进程体已经在调用exit时被内核回收了。现在只剩下一个pcb还在进程队列中。

15.7管道

进程间通信的方式有很多种,有消息队列、共享内存、socket网络通信等,还有一种就是管道。

管道其实就是内核空间中的环形内存缓冲区。

因此通常的用法是进程在创建管道之后,马上调用fork,克隆出一个子进程,子进程完全继承了父进程的一切,也就是说和父进程一模一样,因此继承了管道的描述符,这为父子进程通信提供了保证。

一般情况下,父子进程中都是一个读数据,一个写数据,并不会存在一方又读又写的情况,因此在父子进程中会分别关掉不使用的管道描述符。


步骤:

5.加载用户进程

6.wait和exit系统调用

7.(匿名)管道


5.加载用户进程

①userprog/exec.c:

复制代码
  1 #include "exec.h"
  2 #include "thread.h"
  3 #include "stdio-kernel.h"
  4 #include "fs.h"
  5 #include "string.h"
  6 #include "global.h"
  7 #include "memory.h"
  8 
  9 extern void int_exit(void);
 10 typedef uint32_t Elf32_Word,Elf32_Addr,Elf32_Off;
 11 typedef uint16_t Elf32_Half;
 12 #define TASK_NAME_LEN 16
 13 /* 32位elf头 */
 14 struct Elf32_Ehdr{
 15     unsigned char e_ident[16];
 16     Elf32_Half    e_type;
 17     Elf32_Half    e_machine;
 18     Elf32_Word    e_version;
 19     Elf32_Addr    e_entry;
 20     Elf32_Off    e_phoff;
 21     Elf32_Off    e_shoff;
 22     Elf32_Word    e_flags;
 23     Elf32_Half    e_ehsize;
 24     Elf32_Half    e_phentsize;
 25     Elf32_Half    e_phnum;
 26     Elf32_Half    e_shentsize;
 27     Elf32_Half    e_shnum;
 28     Elf32_Half    e_shstrndx;
 29 };
 30 
 31 /* 程序头表Program header,即段描述头 */
 32 struct Elf32_Phdr{
 33     Elf32_Word    p_type;
 34     Elf32_Off    p_offset;
 35     Elf32_Addr    p_vaddr;
 36     Elf32_Addr    p_paddr;
 37     Elf32_Word    p_filesz;
 38     Elf32_Word    p_memsz;
 39     Elf32_Word    p_flags;
 40     Elf32_Word    p_align;
 41 };
 42 
 43 /* 段类型 */
 44 enum segment_type{
 45     PT_NULL,    // 可忽略
 46     PT_LOAD,    // 可加载程序段
 47     PT_DYNAMIC, // 动态加载信息
 48     PT_INTERP,    // 动态加载器名称
 49     PT_NOTE,    // 一些辅助信息
 50     PT_SHLIB,    // 保留
 51     PT_PHDR    // 程序头表
 52 };
 53 
 54 /* 将文件描述符fd指向的文件中,偏移为offset,大小filesz的段加载到虚拟内存vaddr */
 55 static bool segment_load(int32_t fd,uint32_t offset,uint32_t filesz,uint32_t vaddr){
 56     uint32_t vaddr_first_page=vaddr & 0xfffff000;   // vaddr所在的页框
 57     uint32_t size_in_first_page=PG_SIZE-(vaddr & 0x00000fff);   // 加载到内存后,文件第一个页框所占用的字节大小
 58     uint32_t occupy_pages=0;
 59     /* 若第一个页框容不下该段 */
 60     if (filesz>size_in_first_page){
 61     uint32_t left_size=filesz-size_in_first_page;
 62     occupy_pages=DIV_ROUND_UP(left_size,PG_SIZE)+1;
 63     }else {
 64     occupy_pages=1;
 65     }
 66 
 67     /* 为进程分配内存 */
 68     uint32_t page_idx=0;
 69     uint32_t vaddr_page=vaddr_first_page;
 70     while (page_idx<occupy_pages){
 71     uint32_t* pde=pde_ptr(vaddr_page);
 72     uint32_t* pte=pte_ptr(vaddr_page);
 73 
 74     /* 如果pde或者pte不存在就分配内存,
 75      * pde的判断要在pte之前,否则pde不存在会导致
 76      * 判断pte时缺页异常 */
 77     if (!(*pde & 0x00000001) || !(*pte & 0x00000001)){
 78         if (get_a_page(PF_USER,vaddr_page)==NULL){
 79         return false;
 80         }
 81     }   // 若原进程页表已经分配了,就利用现有的物理页,直接覆盖进程体
 82     vaddr_page+=PG_SIZE;
 83     ++page_idx;
 84     }
 85     sys_lseek(fd,offset,SEEK_SET);
 86     sys_read(fd,(void*)vaddr,filesz);
 87     return true;
 88 }
 89 
 90 /* 从文件系统上加载用户程序pathname,成功则返回程序的起始地址,失败则返回-1 */
 91 static int32_t load(const char* pathname){
 92     int32_t ret=-1;
 93     struct Elf32_Ehdr elf_header;
 94     struct Elf32_Phdr prog_header;
 95     memset(&elf_header,0,sizeof(struct Elf32_Ehdr));
 96 
 97     int32_t fd=sys_open(pathname,O_RDONLY);   // 只读
 98     if (fd==-1){
 99     return -1;
100     }
101 
102     if (sys_read(fd,&elf_header,sizeof(struct Elf32_Ehdr))!=sizeof(struct Elf32_Ehdr)){
103     ret=-1;
104     goto done;
105     }
106     
107     /* 校验elf头 */
108     if (memcmp(elf_header.e_ident,"\177ELF\1\1\1",7) \
109     || elf_header.e_type!=2 \
110     || elf_header.e_machine!=3 \
111     || elf_header.e_version!=1 \
112     || elf_header.e_phnum>1024 \
113     || elf_header.e_phentsize!=sizeof(struct Elf32_Phdr)){
114     ret=-1;
115     goto done;
116     }
117 
118     Elf32_Off prog_header_offset=elf_header.e_phoff;
119     Elf32_Half prog_header_size=elf_header.e_phentsize;
120 
121     /* 遍历所有程序头 */
122     uint32_t prog_idx=0;
123     while (prog_idx<elf_header.e_phnum){
124     memset(&prog_header,0,prog_header_size);
125 
126     /* 将文件的指针定位到程序头 */
127     sys_lseek(fd,prog_header_offset,SEEK_SET);
128 
129     /* 只获取程序头 */
130     if (sys_read(fd,&prog_header,prog_header_size)!=prog_header_size){
131         ret=-1;
132         goto done;
133     }
134 
135     /* 如果是可加载段就调用segment_load加载到内存 */
136     if (prog_header.p_type==PT_LOAD){
137         if (!segment_load(fd,prog_header.p_offset,prog_header.p_filesz,prog_header.p_vaddr)){
138         ret=-1;
139         goto done;
140         }
141     }
142 
143     /* 更新下一个程序头的偏移 */
144     prog_header_offset+=elf_header.e_phentsize;
145     ++prog_idx;
146     }
147     ret=elf_header.e_entry;
148 done:
149     sys_close(fd);
150     return ret;
151 }
152 
153 /* 用path指向的程序替换当前进程 */
154 int32_t sys_execv(const char* path,const char* argv[]){
155     uint32_t argc=0;
156     while (argv[argc]){
157     ++argc;
158     }
159     int32_t entry_point=load(path);
160     if (entry_point==-1){   // 加载失败
161     return -1;
162     }
163 
164     struct task_struct* cur=running_thread();
165     /* 修改进程名 */
166     memcpy(cur->name,path,TASK_NAME_LEN);
167     cur->name[TASK_NAME_LEN-1]=0;
168 
169     struct intr_stack* intr_0_stack=(struct intr_stack*)((uint32_t)cur+PG_SIZE-sizeof(struct intr_stack));
170     /* 将参数传递给用户进程 */
171     intr_0_stack->ebx=(int32_t)argv;
172     intr_0_stack->ecx=argc;
173     intr_0_stack->eip=(void*)entry_point;
174     /* 使用户进程的栈地址为最高用户空间地址 */
175     intr_0_stack->esp=(void*)0xc0000000;
176 
177     /* exec不同于fork,为使新进程更快被执行,直接从中断返回 */
178     asm volatile("movl %0,%%esp;jmp intr_exit"::"g"(intr_0_stack):"memory");
179     return 0;
180 }
View Code
复制代码

感觉还是有点怪怪的,居然不把结构体放在.h文件中,虽然也无所谓吧。

②userprog/exec.h:

复制代码
1 #include "stdio.h"
2 int main(void){
3     printf("prog_no_arg from disk.\n");
4     while (1);   // 因为没有exit(),用while (1); 卡住
5     return 0;
6 }
View Code
复制代码

③shell/shell.c,修改my_shell():

复制代码
 1 /* 简单的shell */
 2 void my_shell(void){
 3     cwd_cache[0]='/';
 4     while (1){
 5     print_prompt();
 6     memset(final_path,0,MAX_PATH_LEN);
 7     memset(cmd_line,0,MAX_PATH_LEN);
 8     readline(cmd_line,MAX_PATH_LEN);
 9     if (cmd_line[0]==0){   // 只键入回车
10         continue;
11     }
12     argc=-1;
13     argc=cmd_parse(cmd_line,argv,' ');
14     if (argc==-1){
15         printf("num of arguments exceed %d\n",MAX_ARG_NR);
16         continue;
17     }
18     if (!strcmp("ls",argv[0])){
19         buildin_ls(argc,argv);
20     }else if (!strcmp("cd",argv[0])){
21         if (buildin_cd(argc,argv)!=NULL){
22         memset(cwd_cache,0,MAX_PATH_LEN);
23         strcpy(cwd_cache,final_path);
24         }
25     }else if (!strcmp("pwd",argv[0])){
26         buildin_pwd(argc,argv);
27     }else if (!strcmp("ps",argv[0])){
28         buildin_ps(argc,argv);
29         }else if (!strcmp("clear",argv[0])){
30             buildin_clear(argc,argv);
31         }else if (!strcmp("mkdir",argv[0])){
32             buildin_mkdir(argc,argv);
33         }else if (!strcmp("rmdir",argv[0])){
34             buildin_rmdir(argc,argv);
35         }else if (!strcmp("rm",argv[0])){
36             buildin_rm(argc,argv);
37     }else {      // 如果是外部命令,需要从磁盘上加载
38         int32_t pid=fork();
39         if (pid){      // 父进程
40         /* 必须加while (1);,一般情况下父进程比子进程先执行,
41          * 则会进行下一轮循环将final_path清空,这样子进程将无法从final_path中获得参数 */
42         while (1);
43         }else {      // 子进程
44         make_clear_abs_path(argv[0],final_path);
45         argv[0]=final_path;
46         /* 先判断下文件是否存在 */
47         struct stat file_stat;
48         memset(&file_stat,0,sizeof(struct stat));
49         if (stat(argv[0],&file_stat)==-1){
50             printf("my_shell: cannot access %s: No such file or directory\n",argv[0]);
51         }else {
52             execv(argv[0],argv);
53         }
54         while (1);
55         }
56     }
57     int32_t arg_idx=0;
58     while (arg_idx<MAX_ARG_NR){
59         argv[arg_idx]=NULL;
60         ++arg_idx;
61     }
62     }
63     PANIC("my_shell: should not be here");
64 }
View Code
复制代码

④创建新目录command,command/prog_no_arg.c:

1 #include "stdio.h"
2 int main(void){
3     printf("prog_no_arg from disk.\n");
4     while (1);   // 因为没有exit(),用while (1); 卡住
5     return 0;
6 }

就只是写个程序试试,还不算真正的(外部)命令 。

⑤command/compile.sh:

复制代码
 1 BIN="prog_no_arg"
 2 CFLAGS="-Wall -m32 -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes -Wsystem-headers"
 3 LIB="../lib/"
 4 OBJS="../build/string.o ../build/syscall.o ../build/stdio.o ../build/assert.o"
 5 DD_IN=$BIN
 6 DD_OUT="/home/zbb/bochs/hd60M.img"
 7 
 8 gcc $CFLAGS -I $LIB -o $BIN".o" $BIN".c"
 9 ld -m elf_i386 -e main $BIN".o" $OBJS -o $BIN
10 SEC_CNT=$(ls -l $BIN|awk '{printf("%d", ($5+511)/512)}')
11 
12 if [ -f $BIN ];then
13     dd if=./$DD_IN of=$DD_OUT bs=512 \
14     count=$SEC_CNT seek=300 conv=notrunc
15 fi
View Code
复制代码

细心的你已经看到,在OBJS内有个新的assert.o。是的,这就是之前那个小写的panic和assert所在的文件,因为我发现如果用debug.o(即PANIC和ASSERT所在地)文件的话,是会报错显示很多函数符号找不到,所以不得不写assert.c和assert.h。要问我怎么知道这assert.c的内容的——好吧,我瞄了一下作者的源码,链接放在文末了 :)

⑥lib/user/assert.c:

1 #include "assert.h"
2 #include "stdio.h"
3 void user_spin(char* filename, int line, const char* func, const char* condition) {
4    printf("\n\n\n\nfilename %s\nline %d\nfunction %s\ncondition %s\n", filename, line, func, condition);
5    while(1);
6 }

⑦lib/user/assert.h:

复制代码
 1 #ifndef __LIB_USER_ASSERT_H
 2 #define __LIB_USER_ASSERT_H
 3 
 4 #define NULL ((void*)0)
 5 
 6 void user_spin(char* filename, int line, const char* func, const char* condition);
 7 #define panic(...) user_spin(__FILE__, __LINE__, __func__, __VA_ARGS__)
 8 
 9 #ifdef NDEBUG
10    #define assert(CONDITION) ((void)0)
11 #else
12    #define assert(CONDITION)  \
13       if (!(CONDITION)) {     \
14      panic(#CONDITION);   \
15       }    
16 
17 #endif/*NDEBUG*/
18 
19 #endif/*__LIB_USER_ASSERT_H*/
View Code
复制代码

然后,string.c中的各函数因为用到了ASSERT(),所以要换成assert()。

再在command目录下执行“sh compile.sh”,就会出现:

好吧,这个prog_no_arg好大。。。所以下面main()的第27行就和书中的有所不同。

⑧kernel/main.c:

复制代码
 1 #include "print.h"
 2 #include "init.h"
 3 #include "thread.h"
 4 #include "interrupt.h"
 5 #include "console.h"
 6 #include "ioqueue.h"
 7 #include "process.h"
 8 #include "syscall.h"
 9 #include "syscall-init.h"
10 #include "stdio.h"
11 #include "memory.h"
12 #include "fs.h"
13 #include "string.h"
14 #include "file.h"
15 #include "shell.h"
16 #include "debug.h"
17 #include "ide.h"
18 #include "stdio-kernel.h"
19 
20 void init(void);
21 
22 int main(void){
23     put_str("Welcome,\nI am kernel!\n");
24     init_all();
25     intr_enable();
26 
27     uint32_t file_size=10284;;
28     uint32_t sec_cnt=DIV_ROUND_UP(file_size,512);
29     struct disk* sda=&channels[0].devices[0];
30     void* prog_buf=sys_malloc(file_size);
31     ide_read(sda,300,prog_buf,sec_cnt);
32     int32_t fd=sys_open("/prog_no_arg",O_CREAT|O_RDWR);
33     if (fd!=-1){
34     if (sys_write(fd,prog_buf,file_size)==-1){
35         printk("file write error!\n");
36         while (1);
37     }
38     }
39 
40     cls_screen();
41     console_put_str("[zbb@LAPTOP //]$ ");
42     while(1);
43     return 0;
44 }
45 
46 /* init进程 */
47 void init(void){
48     uint32_t ret_pid=fork();
49     if (ret_pid){   // 父进程
50         printf("parent...\n");
51         while (1);
52     }else {   // 子进程
53         printf("son...\n");
54         my_shell();
55     }
56     PANIC("init: should not be here.");
57 }
View Code
复制代码

记得修改一下makefile,就有了结果:

果然没有这么顺利,其实我的内心在看到这一刻的时候有点小崩,因为我一直以来参考的那位博主到这一步因为缺页错误也寄了,然后我也倒在了这一步。。。、

然后我知道,内存管理和文件系统的代码是海量的,根本不可能一步步跟着走,看看到底是哪一步,甚至哪个二进制错了。我也不可能再去从源码替换每一个文件找错,哎。

难道故事的终点就到这里了吗?

诚然,如果你是后来者,你一定会看到我结束此篇,所以我没有放弃——我打印出了每一步执行的参数、变量,就像我曾经在刷题时一样(好吧,这让我回想起了以前调试线段树的时光,那时的我还会花一个上午盯着屏幕看每个值的变化)——直到在memory.c的get_a_page()中输出vaddr与cur->userprog_vaddr.vaddr_start,我发现它们俩的值是相同的,说实话,可能是因为我对内存的理解不够深刻的原因,一开始并没有觉得它们俩可能相同,然后我苦苦思索,到底是为什么它们俩会相同。我想不明白,直到有个想法冒出来:“为什么它们俩不能相同呢”,我突然觉得这两个值可以相同啊,只不过一个是prog_no_arg的某个可执行段的虚拟地址的所在页框首地址,一个是当前用户进程的起始地址。也许这也只是一个美丽的误会,让我以为它们两个真的可以相同。于是我又去看了此时的源码中的kernel/memory.c,那个ASSERT(bit_idx>0)偷偷变成了ASSERT(bit_idx>=0),果然是的!我认定就是这里有问题,而没有一个人指出来,包括本书作者和我看过的所有博主,太离谱了。然后,你懂了,它真的就行了 > <

话不多说,来看看一晚上折磨的结果:

看,成功输出了“prog_no_arg from disk”,吼吼。


接下来我们使用户进程支持参数

 ①command/prog_arg.c:

复制代码
 1 #include "stdio.h"
 2 #include "syscall.h"
 3 #include "string.h"
 4 int main(int argc,char** argv){
 5    int arg_idx=0;
 6    while (arg_idx<argc){
 7       printf("argv[%d] is %s.\n",arg_idx,argv[arg_idx]);
 8       ++arg_idx;
 9    }
10    int pid=fork();
11    if (pid){
12       int delay=900000;
13       while (delay--);
14       printf("\n      I'm father prog, my pid: %d, I will show process list.\n",getpid()); 
15       ps();
16    } else{
17       char abs_path[512]={0};
18       printf("\n      I'm child prog, my pid: %d, I will exec %s right now.\n",getpid(),argv[1]); 
19       if (argv[1][0]!='/'){
20      getcwd(abs_path,512);
21      strcat(abs_path,"/");
22      strcat(abs_path,argv[1]);
23      execv(abs_path,argv);
24       } else{
25      execv(argv[1],argv);     
26       }
27    }
28    while(1);
29    return 0;
30 }
View Code
复制代码

②command/compile2.sh:

复制代码
 1 BIN="prog_arg"
 2 CFLAGS="-Wall -m32 -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes -Wsystem-headers"
 3 LIB="../lib/ ../lib/kernel/ ../lib/user/ ../kernel/ ../device/ ../thread/ ../userprog ../fs/ ../shell/"
 4 OBJS="../build/string.o ../build/syscall.o ../build/stdio.o ../build/assert.o start.o"
 5 DD_IN=$BIN
 6 DD_OUT="/home/zbb/bochs/hd60M.img"
 7 
 8 nasm -f elf ./start.S -o ./start.o
 9 ar rcs simple_crt.a $OBJS start.o
10 gcc $CFLAGS -I $LIB -o $BIN".o" $BIN".c"
11 ld -m elf_i386 $BIN".o" simple_crt.a -o $BIN
12 SEC_CNT=$(ls -l $BIN|awk '{printf("%d", ($5+511)/512)}')
13 
14 if [ -f $BIN ];then
15     dd if=./$DD_IN of=$DD_OUT bs=512 \
16     count=$SEC_CNT seek=300 conv=notrunc
17 fi
View Code
复制代码

此时,注意要把ld那一行的 ”-e main“删除,这样才会以start.S中的_start为默认入口地址。

③command/start.S:

复制代码
1 [bits 32]
2 extern main
3 section .text
4 global _start
5 _start:
6    ; 下面这两个要和execv中load之后的寄存器一致
7    push ebx   ; 压入argv
8    push ecx   ; 压入argc
9    call main
View Code
复制代码

再在command目录下执行“sh compile2.sh”,就会出现:

④kernel/main.c:

复制代码
 1 #include "print.h"
 2 #include "init.h"
 3 #include "thread.h"
 4 #include "interrupt.h"
 5 #include "console.h"
 6 #include "ioqueue.h"
 7 #include "process.h"
 8 #include "syscall.h"
 9 #include "syscall-init.h"
10 #include "stdio.h"
11 #include "memory.h"
12 #include "fs.h"
13 #include "string.h"
14 #include "file.h"
15 #include "shell.h"
16 #include "debug.h"
17 #include "ide.h"
18 #include "stdio-kernel.h"
19 
20 void init(void);
21 
22 int main(void){
23     put_str("Welcome,\nI am kernel!\n");
24     init_all();
25     intr_enable();
26 
27     uint32_t file_size=10448;
28     uint32_t sec_cnt=DIV_ROUND_UP(file_size,512);
29     struct disk* sda=&channels[0].devices[0];
30     void* prog_buf=sys_malloc(file_size);
31     ide_read(sda,300,prog_buf,sec_cnt);
32     int32_t fd=sys_open("/prog_arg",O_CREAT|O_RDWR);
33     if (fd!=-1){
34         if (sys_write(fd,prog_buf,file_size)==-1){
35             printk("file write error!\n");
36             while (1);
37         }
38     }
39 
40     cls_screen();
41     console_put_str("[zbb@LAPTOP //]$ ");
42     while(1);
43     return 0;
44 }
45 
46 /* init进程 */
47 void init(void){
48     uint32_t ret_pid=fork();
49     if (ret_pid){   // 父进程
50         printf("parent...\n");
51         while (1);
52     }else {   // 子进程
53         printf("son...\n");
54         my_shell();
55     }
56     PANIC("init: should not be here.");
57 }
View Code
复制代码

相较前一个kernel/main.c,修改了file_size和sys_open()。

哎,GG了。对不起各位,这个我改不出来了,已经一个下午了,真的失去耐心了。我尝试过在start.S中添加一句“jmp $”,然后也没能死循环,而在exec.c的最后一句内嵌汇编上面输出是可行。推测一下是给栈赋值的时候出现了问题。。。就这样吧,试试看后面还可以继续写不。


6.wait和exit系统调用

①thread/thread.h,在pcb中增加一个exit_status成员,并添加函数声明:

复制代码
    int8_t exit_status;         // 进程结束时自身调用exit()传入的参数

void sys_ps(void);
void thread_exit(struct task_struct* thread_over,bool need_schedule);
struct task_struct* pid2thread(int32_t pid);
void release_pid(pid_t pid);
View Code
复制代码

②kernel/memory.c,新增一个释放物理页框的函数:

复制代码
 1 /* 根据物理页框地址pg_phy_addr在相应的内存池的位图清0,不改动页表 */
 2 void free_a_phy_page(uint32_t pg_phy_addr){
 3     struct pool* mem_pool;
 4     uint32_t bit_idx=0;
 5     if (pg_phy_addr>=user_pool.phy_addr_start){
 6     mem_pool=&user_pool;
 7     bit_idx=(pg_phy_addr-user_pool.phy_addr_start)/PG_SIZE;   // 获取位图索引
 8     }else {
 9     mem_pool=&kernel_pool;
10     bit_idx=(pg_phy_addr-kernel_pool.phy_addr_start)/PG_SIZE;
11     }
12     bitmap_set(&mem_pool->pool_bitmap,bit_idx,0);   // 物理内存池中的相应位置0
13 }
View Code
复制代码

③kernel/memory.h:

void free_a_phy_page(uint32_t pg_phy_addr);

④thread/thread.c:

修改thread_init():

复制代码
 1 /* 初始化线程环境 */
 2 void thread_init(void){
 3     put_str("thread_init start\n");
 4 
 5     list_init(&thread_ready_list);
 6     list_init(&thread_all_list);
 7     pid_pool_init();
 8 
 9     /* 先创建第一个用户进程: init */
10     process_execute(init,"init");   // 首先初始化init进程,其pid为1
11 
12     /* 将当前main函数创建为线程 */
13     make_main_thread();
14 
15     /* 创建idle线程 */
16     idle_thread=thread_start("idle",10,idle,NULL);
17 
18     put_str("thread_init done\n");
19 }
View Code
复制代码

增加几个基础函数:

复制代码
 1 /* 回收thread_over的pcb和页表,并将其从调度队列中移出 */
 2 void thread_exit(struct task_struct* thread_over,bool need_schedule){
 3     /* 要保证schedule在关中断的情况下调用 */
 4     intr_disable();
 5     thread_over->status=TASK_DIED;
 6 
 7     /* 如果thread_over不是当前线程,就可能还在队列中,将其从中移出 */
 8     if (elem_find(&thread_ready_list,&thread_over->general_tag)){
 9     list_remove(&thread_over->general_tag);
10     }
11     if (thread_over->pgdir){   // 如果是进程,回收进程的页表
12     mfree_page(PF_KERNEL,thread_over->pgdir,1);
13     }
14 
15     /* 从all_thread_list中去掉此任务 */
16     list_remove(&thread_over->all_list_tag);
17 
18     /* 回收pcb所在的页,主线程的pcb不再堆中,跨过 */
19     if (thread_over!=main_thread){
20     mfree_page(PF_KERNEL,thread_over,1);
21     }
22 
23     /* 归还pid */
24     release_pid(thread_over->pid);
25 
26     /* 如果需要下一轮调度则主动调用schedule */
27     if (need_schedule){
28     schedule();
29     PANIC("thread_exit: should not be here.\n");
30     }
31 }
32 
33 /* 比对任务的pid */
34 static bool pid_check(struct list_elem* pelem,int32_t pid){
35     struct task_struct* pthread=elem2entry(struct task_struct,all_list_tag,pelem);
36     if (pthread->pid==pid){
37     return true;
38     }
39     return false;
40 }
41 
42 struct task_struct* pid2thread(int32_t pid){
43     struct list_elem* pelem=list_traversal(&thread_all_list,pid_check,pid);
44     if (pelem==NULL){
45     return NULL;
46     }
47     struct task_struct* thread=elem2entry(struct task_struct,all_list_tag,pelem);
48     return thread;
49 }
View Code
复制代码

⑤userprog/wait_exit.c:

复制代码
  1 #include "wait_exit.h"
  2 #include "global.h"
  3 #include "debug.h"
  4 #include "thread.h"
  5 #include "list.h"
  6 #include "stdio-kernel.h"
  7 #include "memory.h"
  8 #include "bitmap.h"
  9 #include "fs.h"
 10 
 11 /* 释放用户进程资源:
 12  * 1.页表中对应的物理页
 13  * 2.虚拟内存池占物理页框
 14  * 3.关闭打开的文件 */
 15 static void release_prog_resource(struct task_struct* release_thread){
 16     uint32_t* pgdir_vaddr=release_thread->pgdir;
 17     uint16_t user_pde_nr=768,pde_idx=0;
 18     uint32_t pde=0;
 19     uint32_t* v_pde_ptr=NULL;   // v表示var,和函数pde_ptr区分
 20 
 21     uint16_t user_pte_nr=1024,pte_idx=0;
 22     uint32_t pte=0;
 23     uint32_t* v_pte_ptr=NULL;   // 和pte_ptr区分
 24 
 25     uint32_t* first_pte_vaddr_in_pde=NULL;   // 用于记录pde中第0个pte的地址
 26     uint32_t pg_phy_addr=0;
 27 
 28     /* 回收页表中用户空间的页框 */
 29     while (pde_idx<user_pde_nr){
 30     v_pde_ptr=pgdir_vaddr+pde_idx;
 31     pde=*v_pde_ptr;
 32     if (pde & 0x00000001){   // 如果页目录项p位为1,表示该目录项下可能有页表
 33         first_pte_vaddr_in_pde=pte_ptr(pde_idx * 0x400000);   // 一个页表为4M
 34         pte_idx=0;
 35         while (pte_idx<user_pte_nr){
 36         v_pte_ptr=first_pte_vaddr_in_pde+pte_idx;
 37         pte=*v_pte_ptr;
 38         if (pte & 0x00000001){
 39             /* 将pte中记录的物理页框直接在相应的内存池的位图中清0 */
 40             pg_phy_addr=pte & 0xfffff000;
 41             free_a_phy_page(pg_phy_addr);
 42         }
 43         ++pte_idx;
 44         }
 45         /* 将pde中记录的物理页框直接在相应内存池的位图中清0 */
 46         pg_phy_addr=pde & 0xfffff000;
 47         free_a_phy_page(pg_phy_addr);
 48     }
 49     ++pde_idx;
 50     }
 51     
 52     /* 回收用户虚拟地址池所占的物理内存 */
 53     uint32_t bitmap_pg_cnt=(release_thread->userprog_vaddr.vaddr_bitmap.btmp_bytes_len)/PG_SIZE;
 54     uint8_t* user_vaddr_pool_bitmap=release_thread->userprog_vaddr.vaddr_bitmap.bits;
 55     mfree_page(PF_KERNEL,user_vaddr_pool_bitmap,bitmap_pg_cnt);
 56 
 57     /* 关闭进程打开的文件 */
 58     uint8_t fd_idx=3;
 59     while (fd_idx<MAX_FILES_OPEN_PER_PROC){
 60     if (release_thread->fd_table[fd_idx]!=-1){
 61         sys_close(fd_idx);
 62     }
 63     ++fd_idx;
 64     }
 65 }
 66 
 67 /* list_traversal的回调函数,
 68  * 查找pelem的parent_pid是否是ppid,成功则返回true,失败则返回false */
 69 static bool find_child(struct list_elem* pelem,int32_t ppid){
 70     /* elem2entry中间的参数all_list_tag取决于pelem对应的变量名 */
 71     struct task_struct* pthread=elem2entry(struct task_struct,all_list_tag,pelem);
 72     if (pthread->parent_pid==ppid){
 73     return true;
 74     }
 75     return false;
 76 }
 77 
 78 /* list_traversal的回调函数,
 79  * 查找状态为TASK_HANGING的任务 */
 80 static bool find_hanging_child(struct list_elem* pelem,int32_t ppid){
 81     struct task_struct* pthread=elem2entry(struct task_struct,all_list_tag,pelem);
 82     if (pthread->parent_pid==ppid && pthread->status==TASK_HANGING){
 83     return true;
 84     }
 85     return false;
 86 }
 87 
 88 /* list_traversal的回调函数,
 89  * 将一个子进程过继给init */
 90 static bool init_adopt_a_child(struct list_elem* pelem,int32_t pid){
 91     struct task_struct* pthread=elem2entry(struct task_struct,all_list_tag,pelem);
 92     if (pthread->parent_pid==pid){
 93     pthread->parent_pid=1;   // init进程的pid=1
 94     }
 95     return false;
 96 }
 97 
 98 /* 等待子进程调用exit,将子进程的退出状态保存到status指向的变量。
 99  * 成功则返回子进程pid,失败则返回-1 */
100 pid_t sys_wait(int32_t* status){
101     struct task_struct* parent_thread=running_thread();
102     
103     while (1){
104     /* 优先处理已经是挂起状态的任务 */
105     struct list_elem* child_elem=list_traversal(&thread_all_list,find_hanging_child,parent_thread->pid);
106     /* 若有挂起的子进程 */
107     if (child_elem!=NULL){
108         struct task_struct* child_thread=elem2entry(struct task_struct,all_list_tag,child_elem);
109         *status=child_thread->exit_status;
110 
111         /* thread_exit之后,pcb被回收,因此提前获取pid */
112         uint16_t child_pid=child_thread->pid;
113 
114         /* 2.从就绪队列和全部队列中删除进程表项 */
115         thread_exit(child_thread,false);
116         /* 进程表项是进程或线程的最后保留的资源,至此该进程彻底消失 */
117 
118         return child_pid;
119     }
120 
121     /* 判断是否有子进程 */
122     child_elem=list_traversal(&thread_all_list,find_child,parent_thread->pid);
123     if (child_elem==NULL){   // 若没有子进程则出错返回
124         return -1;
125     }else {
126         /* 若子进程还未运行完,即还未调用exit(),则挂起 */
127         thread_block(TASK_WAITING);   // 等待子进程执行exit()唤醒自己
128     }
129     }
130 }
131 
132 /* 子进程结束自己时调用 */
133 void sys_exit(int32_t status){
134     struct task_struct* child_thread=running_thread();
135     child_thread->exit_status=status;
136     if (child_thread->parent_pid==-1){
137     PANIC("sys_exit: child_thread->parent_pid is -1\n");
138     }
139 
140     /* 将进程child_thread的所有子进程都过继给init */
141     list_traversal(&thread_all_list,init_adopt_a_child,child_thread->pid);
142 
143     /* 回收进程child_thread的资源 */
144     release_prog_resource(child_thread);
145 
146     /* 如果父进程正在等待子进程退出,将父进程唤醒 */
147     struct task_struct* parent_thread=pid2thread(child_thread->parent_pid);
148     if (parent_thread->status==TASK_WAITING){
149     thread_unblock(parent_thread);
150     }
151 
152     /* 将自己挂起,等待父进程获取其status,并回收其pcb */
153     thread_block(TASK_HANGING);
154 }
View Code
复制代码

⑥userprog/wait_exit.h:

复制代码
1 #ifndef __USERPROG_WAIT_EXIT_H
2 #define __USERPROG_WAIT_EXIT_H
3 #include "thread.h"
4 pid_t sys_wait(int32_t* status);
5 void sys_exit(int32_t status);
6 #endif
View Code
复制代码

⑦lib/user/syscall.h+lib/user/syscall.c+userprog/syscall-init.c:

很平常的增加exit和wait系统调用的步骤,就不赘述了:)

⑧shell/shell.c,修改my_shell():

复制代码
 1 /* 简单的shell */
 2 void my_shell(void){
 3     cwd_cache[0]='/';
 4     while (1){
 5     print_prompt();
 6     memset(final_path,0,MAX_PATH_LEN);
 7     memset(cmd_line,0,MAX_PATH_LEN);
 8     readline(cmd_line,MAX_PATH_LEN);
 9     if (cmd_line[0]==0){   // 只键入回车
10         continue;
11     }
12     argc=-1;
13     argc=cmd_parse(cmd_line,argv,' ');
14     if (argc==-1){
15         printf("num of arguments exceed %d\n",MAX_ARG_NR);
16         continue;
17     }
18     if (!strcmp("ls",argv[0])){
19         buildin_ls(argc,argv);
20     }else if (!strcmp("cd",argv[0])){
21         if (buildin_cd(argc,argv)!=NULL){
22         memset(cwd_cache,0,MAX_PATH_LEN);
23         strcpy(cwd_cache,final_path);
24         }
25     }else if (!strcmp("pwd",argv[0])){
26         buildin_pwd(argc,argv);
27     }else if (!strcmp("ps",argv[0])){
28         buildin_ps(argc,argv);
29         }else if (!strcmp("clear",argv[0])){
30             buildin_clear(argc,argv);
31         }else if (!strcmp("mkdir",argv[0])){
32             buildin_mkdir(argc,argv);
33         }else if (!strcmp("rmdir",argv[0])){
34             buildin_rmdir(argc,argv);
35         }else if (!strcmp("rm",argv[0])){
36             buildin_rm(argc,argv);
37     }else {      // 如果是外部命令,需要从磁盘上加载
38         int32_t pid=fork();
39         if (pid){      // 父进程
40         int32_t status;
41         int32_t child_pid=wait(&status);   // 子进程若没有执行exit(),则my_shell会被阻塞
42         if (child_pid==-1){
43             PANIC("my_shell: no child.\n");
44         }
45         printf("child_pid %d, it's status: %d\n",child_pid,status);
46         }else {      // 子进程
47         make_clear_abs_path(argv[0],final_path);
48         argv[0]=final_path;
49         /* 先判断下文件是否存在 */
50         struct stat file_stat;
51         memset(&file_stat,0,sizeof(struct stat));
52         if (stat(argv[0],&file_stat)==-1){
53             printf("my_shell: cannot access %s: No such file or directory\n",argv[0]);
54             exit(-1);
55         }else {
56             execv(argv[0],argv);
57         }
58         }
59     }
60     int32_t arg_idx=0;
61     while (arg_idx<MAX_ARG_NR){
62         argv[arg_idx]=NULL;
63         ++arg_idx;
64     }
65     }
66     PANIC("my_shell: should not be here");
67 }
View Code
复制代码

考虑到下面的cat需要连接到多个库文件,所以先修改makefile并make all后,再对command内进行修改。

⑨command/start.S:

复制代码
 1 [bits 32]
 2 extern main
 3 extern exit
 4 section .text
 5 global _start
 6 _start:
 7    ; 下面这两个要和execv中load之后的寄存器一致
 8    push ebx       ; 压入argv
 9    push ecx       ; 压入argc
10    call main
11 
12    ; 将main的返回值通过栈传给exit,gcc用eax存储返回值(ABI规定)
13    push eax
14    call exit       ; exit不会返回
View Code
复制代码

⑩command/cat.c:

复制代码
 1 #include "syscall.h"
 2 #include "stdio.h"
 3 #include "string.h"
 4 int main(int argc,char** argv){
 5     if (argc>2 || argc==1){
 6     printf("cat: only support 1 argument.\neg: cat filename\n");
 7     exit(-2);
 8     }
 9     int buf_size=1024;
10     char abs_path[512]={0};
11     void* buf=malloc(buf_size);
12     if (buf==NULL){
13     printf("cat: malloc memory failed.\n");
14     return -1;
15     }
16     if (argv[1][0]!='/'){
17     getcwd(abs_path,512);
18     strcat(abs_path,"/");
19     strcat(abs_path,argv[1]);
20     }else {
21     strcpy(abs_path,argv[1]);
22     }
23     int fd=open(abs_path,O_RDONLY);
24     if (fd==-1){
25     printf("cat: open: open%s failed.\n",argv[1]);
26     return -1;
27     }
28     int read_bytes=0;
29     while (1){
30     read_bytes=read(fd,buf,buf_size);
31     if (read_bytes==-1){
32         break;
33     }
34     write(1,buf,read_bytes);
35     }
36     free(buf);
37     close(fd);
38     return 66;
39 }
View Code
复制代码

⑪command/compile3:

复制代码
 1 BIN="cat"
 2 CFLAGS="-Wall -m32 -fno-stack-protector -c -fno-builtin -W -Wstrict-prototypes \
 3       -Wmissing-prototypes -Wsystem-headers"
 4 #LIB= "../lib/ ../lib/kernel/ ../lib/user/ ../kernel/ ../device/ ../thread/ \
 5 #           ../userprog/ ../fs/ ../shell/"
 6 OBJS="../build/string.o ../build/syscall.o \
 7       ../build/stdio.o ../build/assert.o start.o"
 8 DD_IN=$BIN
 9 DD_OUT="/home/zbb/bochs/hd60M.img" 
10 
11 nasm -f elf ./start.S -o ./start.o
12 ar rcs simple_crt.a $OBJS start.o
13 
14 gcc $CFLAGS -I "../lib/" -I "../lib/kernel/" -I "../lib/user/" -I "../kernel/" -I "../device/"  -I "../thread/" -I "../userprog/" -I "../fs/" -I "../shell/"  -o  $BIN".o" $BIN".c"
15 ld -m elf_i386  $BIN".o" simple_crt.a -o $BIN
16 SEC_CNT=$(ls -l $BIN|awk '{printf("%d", ($5+511)/512)}')
17 
18 if [ -f $BIN ];then
19    dd if=./$DD_IN of=$DD_OUT bs=512 \
20    count=$SEC_CNT seek=300 conv=notrunc
21 fi
View Code
复制代码

此处因为编译器的原因,需要在CFLAGS中添加“-fno-stack-protector”,否则会报错。

然后获得:

因为prog_no_arg和prog_arg用不到了所以我就删掉了。

⑫最后,就是修改kernel/main.c:

复制代码
 1 #include "print.h"
 2 #include "init.h"
 3 #include "thread.h"
 4 #include "interrupt.h"
 5 #include "console.h"
 6 #include "ioqueue.h"
 7 #include "process.h"
 8 #include "syscall.h"
 9 #include "syscall-init.h"
10 #include "stdio.h"
11 #include "memory.h"
12 #include "fs.h"
13 #include "string.h"
14 #include "file.h"
15 #include "shell.h"
16 #include "debug.h"
17 #include "ide.h"
18 #include "stdio-kernel.h"
19 
20 void init(void);
21 
22 int main(void){
23     put_str("Welcome,\nI am kernel!\n");
24     init_all();
25     intr_enable();
26 
27     uint32_t fd0=open("/file1",O_CREAT);
28     close(fd0);
29     fd0=open("/file1",O_RDWR);
30     printf("fd: %d\n",fd0);
31     write(fd0,"Hello world\n",12);
32     close(fd0);
33 
34     uint32_t file_size=10440;
35     uint32_t sec_cnt=DIV_ROUND_UP(file_size,512);
36     struct disk* sda=&channels[0].devices[0];
37     void* prog_buf=sys_malloc(file_size);
38     ide_read(sda,300,prog_buf,sec_cnt);
39     int32_t fd=sys_open("/cat",O_CREAT|O_RDWR);
40     if (fd!=-1){
41         if (sys_write(fd,prog_buf,file_size)==-1){
42             printk("file write error!\n");
43             while (1);
44         }
45     }
46 
47     // cls_screen();
48     console_put_str("[zbb@LAPTOP //]$ ");
49     thread_exit(running_thread(),true);
50     while(1);
51     return 0;
52 }
53 
54 /* init进程 */
55 void init(void){
56     uint32_t ret_pid=fork();
57     if (ret_pid){   // 父进程
58         int status;
59     int child_pid;
60     /* init在此处不停地回收僵尸进程 */
61         while (1){
62         child_pid=wait(&status);
63         printf("I'm init, My pid is 1, I receive a child. It's pid is %d, status is %d\n",child_pid,status);
64     }
65     }else {   // 子进程
66         my_shell();
67     }
68     PANIC("init: should not be here.");
69 }
View Code
复制代码

和书中不同的是,因为此前file1文件已经被删除了,所以在main()中创建并写入“Hello world\n”。

就酱,因为上一步的失利,并不觉得能够成功。。。

结果——

——欸,竟然成功了,啊???我反而更困惑了。


7.(匿名)管道

①device/ioqueue.c,增加一个求数据长度的函数ioq_length():

复制代码
 1 /* 返回环形缓冲区中的数据长度 */
 2 uint32_t ioq_length(struct ioqueue* ioq){
 3     uint32_t len=0;
 4     if (ioq->head>=ioq->tail){
 5     len=ioq->head-ioq->tail;
 6     }else {
 7     len=bufsize-(ioq->tail-ioq->head);
 8     }
 9     return len;
10 }
View Code
复制代码

②device/ioqueue.h:

uint32_t ioq_length(struct ioqueue* ioq);

③shell/pipe.c:

复制代码
 1 #include "pipe.h"
 2 #include "memory.h"
 3 #include "fs.h"
 4 #include "file.h"
 5 #include "ioqueue.h"
 6 #include "thread.h"
 7 
 8 /* 判断文件描述符local_fd是否是管道 */
 9 bool is_pipe(uint32_t local_fd){
10     uint32_t global_fd=fd_local2global(local_fd);
11     return file_table[global_fd].fd_flag==PIPE_FLAG;
12 }
13 
14 /* 创建管道,成功返回0,失败返回-1 */
15 int32_t sys_pipe(int32_t pipefd[2]){
16     int32_t global_fd=get_free_slot_in_global();
17 
18     /* 申请一页内核内存作环形缓冲区 */
19     file_table[global_fd].fd_inode=get_kernel_pages(1);
20 
21     /* 初始化环形缓冲区 */
22     ioqueue_init((struct ioqueue*)file_table[global_fd].fd_inode);
23     if (file_table[global_fd].fd_inode==NULL){
24     return -1;
25     }
26 
27     /* 将fd_flag复用为管道标志 */
28     file_table[global_fd].fd_flag=PIPE_FLAG;
29 
30     /* 将fd_pos复用为管道打开数 */
31     file_table[global_fd].fd_pos=2;
32     pipefd[0]=pcb_fd_install(global_fd);
33     pipefd[1]=pcb_fd_install(global_fd);
34     return 0;
35 }
36 
37 /* 从管道中读数据 */
38 uint32_t pipe_read(int32_t fd,void* buf,uint32_t count){
39     char* buffer=buf;
40     uint32_t bytes_read=0;
41     uint32_t global_fd=fd_local2global(fd);
42 
43     /* 获取管道的环形缓冲区 */
44     struct ioqueue* ioq=(struct ioqueue*)file_table[global_fd].fd_inode;
45 
46     /* 选择较小的数据读取量,避免阻塞 */
47     uint32_t ioq_len=ioq_length(ioq);
48     uint32_t size=ioq_len>count?count:ioq_len;
49     while (bytes_read<size){
50     *buffer=ioq_getchar(ioq);
51     ++bytes_read;
52     ++buffer;
53     }
54     return bytes_read;
55 }
56 
57 /* 往管道中写数据 */
58 uint32_t pipe_write(int32_t fd,const void* buf,uint32_t count){
59     uint32_t bytes_write=0;
60     uint32_t global_fd=fd_local2global(fd);
61     struct ioqueue* ioq=(struct ioqueue*)file_table[global_fd].fd_inode;
62 
63     /* 选择较小的数据写入量,避免阻塞 */
64     uint32_t ioq_left=bufsize-ioq_length(ioq);
65     uint32_t size=ioq_left>count?count:ioq_left;
66 
67     const char* buffer=buf;
68     while (bytes_write<size){
69     ioq_putchar(ioq,*buffer);
70     ++bytes_write;
71     ++buffer;
72     }
73     return bytes_write;
74 }
View Code
复制代码

对了,记得在fs/fs.c中将fd_local2global()改为非static,否则会error找不到这个函数。

④shell/pipe.h:

复制代码
 1 #ifndef __SHELL_PIPE_H
 2 #define __SHELL_PIPE_H
 3 #include "stdint.h"
 4 #include "global.h"
 5 
 6 #define PIPE_FLAG 0XFFFF
 7 bool is_pipe(uint32_t local_fd);
 8 int32_t sys_pipe(int32_t pipefd[2]);
 9 uint32_t pipe_read(int32_t fd,void* buf,uint32_t count);
10 uint32_t pipe_write(int32_t fd,const void* buf,uint32_t count);
11 #endif
View Code
复制代码

⑤fs/fs.c,改写sys_close、sys_write和sys_read并添加头文件:

复制代码
 1 #include "pipe.h"
 2 
 3 /* 关闭文件描述符fd指向的文件,成功返回0,否则-1 */
 4 int32_t sys_close(int32_t fd){
 5     int32_t ret=-1;
 6     if (fd>2){
 7     uint32_t global_fd=fd_local2global(fd);
 8     if (is_pipe(fd)){
 9         /* 如果此管道上的描述符都被关闭,释放管道的环形缓冲区 */
10         if (--file_table[global_fd].fd_pos==0){
11         mfree_page(PF_KERNEL,file_table[global_fd].fd_inode,1);
12         file_table[global_fd].fd_inode=NULL;
13         }
14         ret=0;
15     }else {
16         ret=file_close(&file_table[global_fd]);
17     }
18     running_thread()->fd_table[fd]=-1;   // 使该文件描述符可用
19     }
20     return ret;
21 }
22 
23 /* 将buf中连续count个字节写入文件描述符fd,成功则返回写入的字节数,失败则返回-1 */
24 int32_t sys_write(int32_t fd,const void* buf,uint32_t count){
25     if (fd<0){
26     printk("sys_write: fd error\n");
27     return -1;
28     }
29     if (fd==stdout_no){
30     /* 标准输出有可能被重定向为管道缓冲区,因此要判断 */
31     if (is_pipe(fd)){
32         return pipe_write(fd,buf,count);
33     }else {
34         char tmp_buf[1024]={0};
35         memcpy(tmp_buf,buf,count);
36         console_put_str(tmp_buf);
37         return count;
38     }
39     }else if (is_pipe(fd)){   // 若是管道
40     return pipe_write(fd,buf,count);
41     }else {
42         uint32_t _fd=fd_local2global(fd);
43         struct file* wr_file=&file_table[_fd];
44         if (wr_file->fd_flag & O_WRONLY || wr_file->fd_flag & O_RDWR){
45         uint32_t bytes_written=file_write(wr_file,buf,count);
46         return bytes_written;
47         }else {
48         console_put_str("sys_write: not allowed to write file without flag O_RDWR or O_WRONLY\n");
49         return -1;
50     }
51     }
52 }
53 
54 /* 从文件描述符fd指向的文件中读取count个字节到buf,若成功则返回读出的字节数,到文件尾则返回-1 */
55 int32_t sys_read(int32_t fd,void* buf,uint32_t count){
56     ASSERT(buf!=NULL);
57     int32_t ret=-1;
58     uint32_t global_fd=0;
59     if (fd<0 || fd==stdout_no || fd==stderr_no){
60     printk("sys_read: fd error\n");
61     }else if (fd==stdin_no){
62     /* 标准输入有可能被重定向为管道缓冲区,因此要判断 */
63     if (is_pipe(fd)){
64         ret=pipe_read(fd,buf,count);
65     }else {
66         char* buffer=buf;
67         uint32_t bytes_read=0;
68         while (bytes_read<count){
69         *buffer=ioq_getchar(&kbd_buf);
70         ++bytes_read;
71         ++buffer;
72         }
73         ret=(bytes_read==0?-1:(int32_t)bytes_read);
74     }
75     }else if (is_pipe(fd)){   // 若是管道就调用管道的方法
76     ret=pipe_read(fd,buf,count);
77     }else {
78     global_fd=fd_local2global(fd);
79     ret=file_read(&file_table[global_fd],buf,count);
80     }
81     return ret;
82 }
View Code
复制代码

⑥userprog/fork.c,增加管道打开数:

复制代码
 1 /* 更新inode打开数 */
 2 static void update_inode_open_cnts(struct task_struct* thread){
 3     int32_t local_fd=3,global_fd=0;
 4     while (local_fd<MAX_FILES_OPEN_PER_PROC){
 5     global_fd=thread->fd_table[local_fd];
 6     ASSERT(global_fd<MAX_FILE_OPEN);
 7     if (global_fd!=-1){
 8         if (is_pipe(local_fd)){
 9         ++file_table[global_fd].fd_pos;
10         }else {
11         ++file_table[global_fd].fd_inode->i_open_cnts;
12         }
13     }
14     ++local_fd;
15     }
16 }
View Code
复制代码

⑦userprog/wait_exit.c,修改release_prog_resource,并添加头文件:

复制代码
 1 #include "file.h"
 2 #include "pipe.h"
 3 
 4 /* 释放用户进程资源:
 5  * 1.页表中对应的物理页
 6  * 2.虚拟内存池占物理页框
 7  * 3.关闭打开的文件 */
 8 static void release_prog_resource(struct task_struct* release_thread){
 9     uint32_t* pgdir_vaddr=release_thread->pgdir;
10     uint16_t user_pde_nr=768,pde_idx=0;
11     uint32_t pde=0;
12     uint32_t* v_pde_ptr=NULL;   // v表示var,和函数pde_ptr区分
13 
14     uint16_t user_pte_nr=1024,pte_idx=0;
15     uint32_t pte=0;
16     uint32_t* v_pte_ptr=NULL;   // 和pte_ptr区分
17 
18     uint32_t* first_pte_vaddr_in_pde=NULL;   // 用于记录pde中第0个pte的地址
19     uint32_t pg_phy_addr=0;
20 
21     /* 回收页表中用户空间的页框 */
22     while (pde_idx<user_pde_nr){
23     v_pde_ptr=pgdir_vaddr+pde_idx;
24     pde=*v_pde_ptr;
25     if (pde & 0x00000001){   // 如果页目录项p位为1,表示该目录项下可能有页表
26         first_pte_vaddr_in_pde=pte_ptr(pde_idx * 0x400000);   // 一个页表为4M
27         pte_idx=0;
28         while (pte_idx<user_pte_nr){
29         v_pte_ptr=first_pte_vaddr_in_pde+pte_idx;
30         pte=*v_pte_ptr;
31         if (pte & 0x00000001){
32             /* 将pte中记录的物理页框直接在相应的内存池的位图中清0 */
33             pg_phy_addr=pte & 0xfffff000;
34             free_a_phy_page(pg_phy_addr);
35         }
36         ++pte_idx;
37         }
38         /* 将pde中记录的物理页框直接在相应内存池的位图中清0 */
39         pg_phy_addr=pde & 0xfffff000;
40         free_a_phy_page(pg_phy_addr);
41     }
42     ++pde_idx;
43     }
44     
45     /* 回收用户虚拟地址池所占的物理内存 */
46     uint32_t bitmap_pg_cnt=(release_thread->userprog_vaddr.vaddr_bitmap.btmp_bytes_len)/PG_SIZE;
47     uint8_t* user_vaddr_pool_bitmap=release_thread->userprog_vaddr.vaddr_bitmap.bits;
48     mfree_page(PF_KERNEL,user_vaddr_pool_bitmap,bitmap_pg_cnt);
49 
50     /* 关闭进程打开的文件 */
51     uint8_t local_fd=3;
52     while (local_fd<MAX_FILES_OPEN_PER_PROC){
53     if (release_thread->fd_table[local_fd]!=-1){
54         if (is_pipe(local_fd)){
55         uint32_t global_fd=fd_local2global(local_fd);
56         if (--file_table[global_fd].fd_pos==0){
57             mfree_page(PF_KERNEL,file_table[global_fd].fd_inode,1);
58             file_table[global_fd].fd_inode=NULL;
59         }
60         }else {
61         sys_close(local_fd);
62         }
63     }
64     ++local_fd;
65     }
66 }
View Code
复制代码

⑧lib/user/syscall.h+lib/user/syscall.c+userprog/syscall-init.c,不必贴出。

⑨command/prog_pipe.c,作为我们管道间进程通信的测试用例:

复制代码
 1 #include "stdio.h"
 2 #include "syscall.h"
 3 #include "string.h"
 4 int main(int argc,char** argv){
 5     int32_t fd[2]={-1};
 6     pipe(fd);
 7     int32_t pid=fork();
 8     if (pid){   // 父进程
 9     close(fd[0]);   // 关闭输入
10     write(fd[1],"Hi, my son, I love u!",22);
11     printf("\nI'm father, my pid is %d\n",getpid());
12     return 8;
13     }else {
14     close(fd[1]);   // 关闭输出
15     char buf[32]={0};
16     read(fd[0],buf,22);
17     printf("\nI'm child, my pid is %d\n",getpid());
18     printf("I'm child, my father said to me: \"%d\"\n",buf);
19     return 9;
20     }
21 }
View Code
复制代码

⑩command/compile4.sh,和compile3.sh几乎一样:

复制代码
 1 BIN="prog_pipe"
 2 CFLAGS="-Wall -m32 -fno-stack-protector -c -fno-builtin -W -Wstrict-prototypes \
 3       -Wmissing-prototypes -Wsystem-headers"
 4 #LIB= "../lib/ ../lib/kernel/ ../lib/user/ ../kernel/ ../device/ ../thread/ \
 5 #           ../userprog/ ../fs/ ../shell/"
 6 OBJS="../build/string.o ../build/syscall.o \
 7       ../build/stdio.o ../build/assert.o start.o"
 8 DD_IN=$BIN
 9 DD_OUT="/home/zbb/bochs/hd60M.img"
10 
11 nasm -f elf ./start.S -o ./start.o
12 ar rcs simple_crt.a $OBJS start.o
13 
14 gcc $CFLAGS -I "../lib/" -I "../lib/kernel/" -I "../lib/user/" -I "../kernel/" -I "../device/"  -I "../thread/" -I "../userprog/" -I "../fs/" -I "../shell/"  -o  $BIN".o" $BIN".c"
15 ld -m elf_i386  $BIN".o" simple_crt.a -o $BIN
16 SEC_CNT=$(ls -l $BIN|awk '{printf("%d", ($5+511)/512)}')
17 
18 if [ -f $BIN ];then
19    dd if=./$DD_IN of=$DD_OUT bs=512 \
20    count=$SEC_CNT seek=300 conv=notrunc
21 fi
View Code
复制代码

结果如下:

幸好还是成功地输出了。


到了最后一节了,在shell中支持管道。

①shell/pipe.c,增加重定向函数:

复制代码
 1 /* 将文件描述符old_local_fd重定向为new_local_fd */
 2 void sys_fd_redirect(uint32_t old_local_fd,uint32_t new_local_fd){
 3     struct task_struct* cur=running_thread();
 4     /* 针对恢复标准描述符 */
 5     if (new_local_fd<3){
 6     cur->fd_table[old_local_fd]=new_local_fd;
 7     }else {
 8     uint32_t new_global_fd=cur->fd_table[new_local_fd];
 9     cur->fd_table[old_local_fd]=new_global_fd;
10     }
11 }
View Code
复制代码

②shell/pipe.h,添加头文件:

void sys_fd_redirect(uint32_t old_local_fd,uint32_t new_local_fd);

③shell/shell.c,增加cmd_execute()并修改my_shell:

复制代码
  1 #include "shell.h"
  2 #include "stdint.h"
  3 #include "fs.h"
  4 #include "file.h"
  5 #include "syscall.h"
  6 #include "stdio.h"
  7 #include "global.h"
  8 #include "string.h"
  9 #include "debug.h"
 10 #include "buildin_cmd.h"
 11 
 12 #define MAX_ARG_NR 16    // 加上命令名外,最多支持15个参数
 13 
 14 /* 存储输入的命令 */
 15 static char cmd_line[MAX_PATH_LEN]={0};
 16 char final_path[MAX_PATH_LEN]={0};   // 清除路径缓冲
 17 
 18 /* 用来记录当前目录,是当前目录的缓存,每次执行cd命令时会更新此内容 */
 19 char cwd_cache[MAX_PATH_LEN]={0};
 20 
 21 /* 输出提示符 */
 22 void print_prompt(void){
 23     printf("[zbb@LAPTOP %s]$ ",cwd_cache);
 24 }
 25 
 26 /* 从键盘缓冲区中最多读入count个字节到buf */
 27 static void readline(char* buf,int32_t count){
 28     ASSERT(buf!=NULL && count>0);
 29     char* pos=buf;
 30 
 31     while (read(stdin_no,pos,1)!=-1 && (pos-buf)<count){   // 直至回车符停止
 32     switch (*pos){
 33         /* 找到回车或换行符后认为键入命令结束 */
 34         case '\n':
 35         case '\r':
 36         *pos=0;   // 添加cmd_line的终止字符0
 37         putchar('\n');
 38         return;
 39 
 40         case '\b':
 41         if (cmd_line[0]!='\b'){   // 到行首
 42             --pos;
 43             putchar('\b');
 44         }
 45         break;
 46         
 47         /* Ctrl+l 清屏 */
 48         case 'l'-'a':
 49         /* 1.先将当前字符'l'-'a'置为0 */
 50         *pos=0;
 51         /* 2.再将屏幕清空 */
 52         clear();
 53         /* 3.打印提示符 */
 54         print_prompt();
 55         /* 4.将之前键入的内容再次打印 */
 56         printf("%s",buf);
 57         break;
 58 
 59         /* Ctrl+u 清掉输入 */
 60         case 'u'-'a':
 61         while (buf!=pos){
 62             putchar('\b');
 63             *(pos--)=0;
 64         }
 65         break;
 66         
 67         /* 非控制则输出字符 */
 68         default:
 69         putchar(*pos);
 70         ++pos;
 71     }
 72     }
 73     printf("readline: can't find enter_key in the cmd_line, max num of char is 128\n");
 74 }
 75 
 76 /* 分析字符串cmd_str中以token为分隔符的单词,将各单词的指针存入argv数组 */
 77 static int32_t cmd_parse(char* cmd_str,char** argv,char token){
 78     ASSERT(cmd_str!=NULL);
 79     int32_t arg_idx=0;
 80     while (arg_idx<MAX_ARG_NR){
 81     argv[arg_idx]=NULL;
 82     ++arg_idx;
 83     }
 84     char* next=cmd_str;
 85     int32_t argc=0;
 86     /* 外层循环处理整个命令行 */
 87     while (*next){
 88     /* 去除命令字或参数间空格 */
 89     while (*next==token){
 90         ++next;
 91     }
 92     /* 处理最后一个参数后接空格的情况 */
 93     if (*next==0){
 94         break;
 95     }
 96     argv[argc]=next;
 97 
 98     /* 内层循环处理命令行中的每个命令字及参数 */
 99     while (*next && *next!=token){   // 在字符串结束前找单词分隔符
100         ++next;
101     }
102 
103     /* 如果未结束(是token字符),使token变成0 */
104     if (*next){
105         *next++=0;
106     }
107 
108     /* argv数组越界则返回-1 */
109     if (argc>MAX_ARG_NR){
110         return -1;
111     }
112     ++argc;
113     }
114     return argc;
115 }
116 
117 /* 执行命令 */
118 static void cmd_execute(uint32_t argc,char** argv){
119     if (!strcmp("ls",argv[0])){
120         buildin_ls(argc,argv);
121     }else if (!strcmp("cd",argv[0])){
122         if (buildin_cd(argc,argv)!=NULL){
123             memset(cwd_cache,0,MAX_PATH_LEN);
124             strcpy(cwd_cache,final_path);
125         }
126     }else if (!strcmp("pwd",argv[0])){
127             buildin_pwd(argc,argv);
128     }else if (!strcmp("ps",argv[0])){
129             buildin_ps(argc,argv);
130     }else if (!strcmp("clear",argv[0])){
131             buildin_clear(argc,argv);
132     }else if (!strcmp("mkdir",argv[0])){
133             buildin_mkdir(argc,argv);
134     }else if (!strcmp("rmdir",argv[0])){
135             buildin_rmdir(argc,argv);
136     }else if (!strcmp("rm",argv[0])){
137             buildin_rm(argc,argv);
138     }else if (!strcmp("help",argv[0])){
139         buildin_help(argc,argv);
140     }else {      // 如果是外部命令,需要从磁盘上加载
141     int32_t pid=fork();
142         if (pid){      // 父进程
143             int32_t status;
144             int32_t child_pid=wait(&status);   // 子进程若没有执行exit(),则my_shell会被阻塞
145         if (child_pid==-1){
146             PANIC("my_shell: no child.\n");
147             }
148             printf("child_pid %d, it's status: %d\n",child_pid,status);
149         }else {      // 子进程
150         make_clear_abs_path(argv[0],final_path);
151             argv[0]=final_path;
152 
153             /* 先判断下文件是否存在 */
154             struct stat file_stat;
155             memset(&file_stat,0,sizeof(struct stat));
156             if (stat(argv[0],&file_stat)==-1){
157                 printf("my_shell: cannot access %s: No such file or directory\n",argv[0]);
158                 exit(-1);
159             }else {
160                 execv(argv[0],argv);
161             }
162         }
163     }
164 }
165 
166 char* argv[MAX_ARG_NR]={NULL};   // argv必须为全局变量
167 int32_t argc=-1;
168 
169 /* 简单的shell */
170 void my_shell(void){
171     cwd_cache[0]='/';
172     while (1){
173     print_prompt();
174         memset(final_path,0,MAX_PATH_LEN);
175         memset(cmd_line,0,MAX_PATH_LEN);
176         readline(cmd_line,MAX_PATH_LEN);
177         if (cmd_line[0]==0){   // 只键入回车
178             continue;
179         }
180 
181     /* 针对管道的处理 */
182     char* pipe_symbol=strchr(cmd_line,'|');
183     if (pipe_symbol){
184     /* 支持多重管道操作,如cmd1|cmd2|...|cmdn,
185      * cmd1的标准输出和cmdn的标准输入需要单独处理 */
186 
187         /* 1.生成管道 */
188         int32_t fd[2]={-1};
189         pipe(fd);
190         /* 将标准输出重定向到fd[1],使后续的输出信息重定向到内核环形缓冲区 */
191         fd_redirect(1,fd[1]);
192 
193         /* 2.第一个命令 */
194         char* each_cmd=cmd_line;
195         pipe_symbol=strchr(each_cmd,'|');
196         *pipe_symbol=0;
197 
198         /* 执行第一个命令,命令的输出会写道环形缓冲区 */
199         argc=-1;
200         argc=cmd_parse(each_cmd,argv,' ');
201         cmd_execute(argc,argv);
202 
203         /* 跨过'|',处理下一个命令 */
204         each_cmd=pipe_symbol+1;
205 
206         /* 将标准输入重定向到fd[0],使之指向内核环形缓冲区 */
207         fd_redirect(0,fd[0]);
208 
209         /* 3.中间的命令,命令的输入和输出都指向内核环形缓冲区 */
210         while ((pipe_symbol=strchr(each_cmd,'|'))){
211         *pipe_symbol=0;
212         argc=-1;
213         argc=cmd_parse(each_cmd,argv,' ');
214         cmd_execute(argc,argv);
215         each_cmd=pipe_symbol+1;
216         }
217 
218         /* 4.处理管道中最后一个命令 */
219         /* 将标准输出恢复到屏幕 */
220         fd_redirect(1,1);
221 
222         /* 执行最后一个命令 */
223         argc=-1;
224         argc=cmd_parse(each_cmd,argv,' ');
225         cmd_execute(argc,argv);
226 
227         /* 5.将标准输入恢复到键盘 */
228         fd_redirect(0,0);
229 
230         /* 6.关闭管道 */
231         close(fd[0]);
232         close(fd[1]);
233     }else {   // 一般无管道操作的命令
234         argc=-1;
235             argc=cmd_parse(cmd_line,argv,' ');
236         if (argc==-1){
237             printf("num of arguments exceed %d\n",MAX_ARG_NR);
238         continue;
239             }
240         cmd_execute(argc,argv);
241     }
242     }
243     PANIC("my_shell: should not be here");
244 }
View Code
复制代码

④fs/fs.c,为了支持help命令:

复制代码
 1 /* 显示系统支持的内部命令 */
 2 void sys_help(void){
 3    printk("\
 4  buildin commands:\n\
 5        ls: show directory or file information\n\
 6        cd: change current work directory\n\
 7        mkdir: create a directory\n\
 8        rmdir: remove a empty directory\n\
 9        rm: remove a regular file\n\
10        pwd: show current work directory\n\
11        ps: show process information\n\
12        clear: clear screen\n\
13  shortcut key:\n\
14        ctrl+l: clear screen\n\
15        ctrl+u: clear input\n\n");
16 }
View Code
复制代码

⑤fs/fs.h:

void sys_help(void);

⑥lib/user/syscall.c+lib/user/syscall.h+userprog/syscall-init.c,同样不再啰嗦。

⑦command/cat.c:

复制代码
 1 #include "syscall.h"
 2 #include "stdio.h"
 3 #include "string.h"
 4 int main(int argc,char** argv){
 5     if (argc>2){
 6     printf("cat: argument error\n");
 7     exit(-2);
 8     }
 9 
10     if (argc==1){
11     char buf[512]={0};
12     read(0,buf,512);
13     printf("%s",buf);
14     exit(0);
15     }
16 
17     int buf_size=1024;
18     char abs_path[512]={0};
19     void* buf=malloc(buf_size);
20     if (buf==NULL){
21     printf("cat: malloc memory failed.\n");
22     return -1;
23     }
24     if (argv[1][0]!='/'){
25     getcwd(abs_path,512);
26     strcat(abs_path,"/");
27     strcat(abs_path,argv[1]);
28     }else {
29     strcpy(abs_path,argv[1]);
30     }
31     int fd=open(abs_path,O_RDONLY);
32     if (fd==-1){
33     printf("cat: open: open%s failed.\n",argv[1]);
34     return -1;
35     }
36     int read_bytes=0;
37     while (1){
38     read_bytes=read(fd,buf,buf_size);
39     if (read_bytes==-1){
40         break;
41     }
42     write(1,buf,read_bytes);
43     }
44     free(buf);
45     close(fd);
46     return 66;
47 }
View Code
复制代码

记得用compile3.sh进行编译链接。

⑧kernel/main.c,修改两句话:

复制代码
 1 #include "print.h"
 2 #include "init.h"
 3 #include "thread.h"
 4 #include "interrupt.h"
 5 #include "console.h"
 6 #include "ioqueue.h"
 7 #include "process.h"
 8 #include "syscall.h"
 9 #include "syscall-init.h"
10 #include "stdio.h"
11 #include "memory.h"
12 #include "fs.h"
13 #include "string.h"
14 #include "file.h"
15 #include "shell.h"
16 #include "debug.h"
17 #include "ide.h"
18 #include "stdio-kernel.h"
19 
20 void init(void);
21 
22 int main(void){
23     put_str("Welcome,\nI am kernel!\n");
24     init_all();
25     intr_enable();
26 
27     uint32_t file_size=10488;
28     uint32_t sec_cnt=DIV_ROUND_UP(file_size,512);
29     struct disk* sda=&channels[0].devices[0];
30     void* prog_buf=sys_malloc(file_size);
31     ide_read(sda,300,prog_buf,sec_cnt);
32     int32_t fd=sys_open("/cat",O_CREAT|O_RDWR);
33     if (fd!=-1){
34         if (sys_write(fd,prog_buf,file_size)==-1){
35             printk("file write error!\n");
36             while (1);
37         }
38     }
39 
40     // cls_screen();
41     console_put_str("[zbb@LAPTOP //]$ ");
42     thread_exit(running_thread(),true);
43     while(1);
44     return 0;
45 }
46 
47 /* init进程 */
48 void init(void){
49     uint32_t ret_pid=fork();
50     if (ret_pid){   // 父进程
51         int status;
52     int child_pid;
53     /* init在此处不停地回收僵尸进程 */
54         while (1){
55         child_pid=wait(&status);
56         printf("I'm init, My pid is 1, I receive a child. It's pid is %d, status is %d\n",child_pid,status);
57     }
58     }else {   // 子进程
59         my_shell();
60     }
61     PANIC("init: should not be here.");
62 }
View Code
复制代码

一定要在hd80M.img中先把此前的cat文件rm掉,再bin/bochs -f bochsrc.disk哈。

最后执行:

很明显,'|'命令是可行的,但是其实有个小问题:如果要在屏幕中打印比较多的字符,程序就会悬停(如两幅图的最后一个命令),我猜应该是管道的缓冲区满了。


本章实现了三个重要的系统调用——exec、wait和exit,让我们来看看吧:

我觉得有必要先说明一下fork、exec、wait和exit的使用:

在本项目中,我们在init程序中fork()出子进程。此时,对于父进程,它的任务就是wait()等待子进程的exit(),因为init作为父进程,所以此时的回收的子进程都被视作僵尸进程。对于子进程,其任务就是my_shell()创建shell,然后再exec()执行一系列操作。

而一般的,是在父进程中fork()出子进程。此时,对于父进程,wait()等待子进程exit(),如果父进程没有wait(),则子进程会沦为“僵尸进程”,内核无法回收它的pcb;如果父进程先于子进程exit(),则子进程沦为“孤儿进程”,由init进程收养之。对于子进程,则会直接exec()去执行相应的程序,最后exit()。我们站在子进程的视角看,fork()与exec()通常是先后调用的,至于为什么不将二者合二为一,这就又是历史包袱的问题了。

对于exec——sys_exec()

1.根据文件路径获取程序的入口地址entry_point——load(path)

每个可执行文件的都有一个ELF头elf_header,elf_header中的e_entry属性即为程序的入口地址。同时,将每个可加载段segment_load()加载到内存

2.获得当前进程的PCB后,将中断栈的eip=entry_point,esp=0xc0000000(用户空间最高处)。因为exec()后,程序会直接去执行相应的程序,所以要"jmp intr_exit"直接从中断返回,获得上下文,开始执行相应程序。

对于wait——sys_wait()——等待子进程调用exit(),一旦有一个子进程调用exit(),就返回子进程pid:

1.获得当前进程的PCB。

2.对于当前进程的子进程:

若已经有挂起状态(TASK_HANGING),则回收该子进程——thread_exit(),回收它的pgdir、PCB、从list中移除——并返回pid。

若没有挂起的进程,则挂起当前进程(其实就是thread_block()它),等有子进程exit()了再唤醒。

对于exit——sys_exit()——进程结束自己时调用:

1.若子进程还有子进程,则将孙子进程过继给init——init_adopt_a_child()——将parent_pid属性改为1即可(因为init的pid=1)。

2.释放子进程的资源——release_prog_resource()——释放资源如下:

(1)页表中对应的物理页,这与上方的回收pgdir可不同,pgdir是进程的页目录,这里是页表和物理页框,页寻址有三级结构呢。

(2)虚拟内存池占用的物理页框。

(3)关闭打开的文件。

3.如果有父进程正在wait()等待子进程exit(),则thread_unblock()唤醒父进程。因为父进程不会立刻执行,因此将自己挂起,等待父进程回收自己。

注意到,wait和exit都会释放进程资源,不过我觉得它们有两点不同:①wait是父进程释放子进程的资源,exit是进程自己释放自己的资源 ②wait释放的资源是进程的核心——PCB;exit释放的是除PCB外的其它进程资源。


 参考博客:


结束语:

好吧,关于最后的话语我还是放在最后一篇博客(至今没有)吧,现在是周一下午,本来说是要在上一周结束掉的,但是这一章是在bug太多了,感觉dubug的时间要远多于学习和coding的时间,而且周上末还和室友出去嗨皮了一下,遂至今日完成,over!

posted @   Hell0er  阅读(134)  评论(0编辑  收藏  举报
编辑推荐:
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示