《操作系统真象还原》第15章(上)
终于到最后一章了,行百里者半九十,本周搞定它!
15.1 fork的实现与原理
“
fork之后,由之前的一个进程变成了两个进程,也就是说内存中多了一个进程,进程拥有独立的地址空间,因此两个进程执行的是独立且相同的代码,也就是两套代码。
子进程是在fork函数返回之后才开始执行的,因此执行的是fork之后的代码,所以在fork之后,父子进程像是“分道扬镳”了。
fork利用老进程克隆出一个新进程并使新进程执行,新进程之所以能够执行,本质上它具备程序体,这其中包括代码和数据等资源。因此fork就是把某个进程的全部资源复制了一份,然后让处理器的cs:eip寄存器指向新进程的指令部分。
”
15.2 实现一个简单的shell
“
操作系统是为用户服务的,要想实现和用户的交互,操作系统得有办法感知用户的输入并给予反馈,也就是必须要为用户提供个交互接口。在Windows中,图形界面的资源管理器和命令行窗口都是交互接口,尽管这些交互接口名字及外观各异,但它们往往被统称为“外壳”程序。
shell的功能大致是获取用户的键入,然后分析输入的字符串,判断是内部命令,还是外部命令,然后执行不同的策略。
”
步骤:
1.fork系统调用
2.read系统调用
3.putchar、clear系统调用
4.实现一个简单的shell
1.fork系统调用
①thread/thread.h,在pcb结构体中增加一个成员,并增加一句函数声明:
int16_t parent_pid; // 父进程pid
pid_t fork_pid(void);
②thread/thread.c,在init_thread()中将parent_pid初始化为-1,在初始化线程时先创建"init"线程:
1 pthread->parent_pid=-1; // 父进程pid默认为-1表示没有父进程
2
3 /* 初始化线程环境 */
4 void thread_init(void){
5 put_str("thread_init start\n");
6 list_init(&thread_ready_list);
7 list_init(&thread_all_list);
8 lock_init(&pid_lock);
9 process_execute(init,"init"); // 首先初始化init进程,其pid为1
10
11 /* 将当前main函数创建为线程 */
12 make_main_thread();
13
14 /* 创建idle线程 */
15 idle_thread=thread_start("idle",10,idle,NULL);
16
17 put_str("thread_init done\n");
18 }
③kernel/memory.c:
1 /* 安装一页大小的vaddr,专门针对fork虚拟地址位图无须操作的情况 */
2 void* get_a_page_without_opvaddrbitmap(enum pool_flags pf,uint32_t vaddr){
3 struct pool* mem_pool=pf & PF_KERNEL?&kernel_pool:&user_pool;
4 lock_acquire(&mem_pool->lock);
5 void* page_phyaddr=palloc(mem_pool);
6 if (page_phyaddr==NULL){
7 lock_release(&mem_pool->lock);
8 return NULL;
9 }
10 page_table_add((void*)vaddr,page_phyaddr);
11 lock_release(&mem_pool->lock);
12 return (void*)vaddr;
13 }
④kernel/memory.h,添加上述函数的声明:
void* get_a_page_without_opvaddrbitmap(enum pool_flags pf,uint32_t vaddr);
⑤userprog/fork.c:

1 #include "fork.h"
2 #include "process.h"
3 #include "memory.h"
4 #include "interrupt.h"
5 #include "debug.h"
6 #include "thread.h"
7 #include "string.h"
8 #include "file.h"
9
10 extern void intr_exit(void);
11
12 /* 将父进程的pcb、虚拟地址位图拷贝给子进程 */
13 static int32_t copy_pcb_vaddrbitmap_stack0(struct task_struct* child_thread,struct task_struct* parent_thread){
14 /* 1.复制pcb所在的整个页,里面包含进程pcb信息及0级栈,包含返回地址,再单独修改部分信息 */
15 memcpy(child_thread,parent_thread,PG_SIZE);
16 child_thread->pid=fork_pid();
17 child_thread->elapsed_ticks=0;
18 child_thread->status=TASK_READY;
19 child_thread->ticks=child_thread->priority; // 为新进程初始化时间片
20 child_thread->parent_pid=parent_thread->pid;
21 child_thread->general_tag.prev=child_thread->general_tag.next=NULL;
22 child_thread->all_list_tag.prev=child_thread->all_list_tag.next=NULL;
23 block_desc_init(child_thread->u_block_desc);
24 /* 2.复制父进程的虚拟地址池的位图 */
25 uint32_t bitmap_pg_cnt=DIV_ROUND_UP((0xc0000000-USER_VADDR_START)/PG_SIZE/8,PG_SIZE);
26 void* vaddr_btmp=get_kernel_pages(bitmap_pg_cnt);
27 if (vaddr_btmp==NULL) return -1;
28 /* 此时child_thread->userprog_vaddr.vaddr_bitmap.bits还是指向父进程虚拟地址的位图地址
29 * 下面将child_thread->userprog_vaddr.vaddr_bitmap.bits指向自己的位图vaddr_bimp*/
30 memcpy(vaddr_btmp,child_thread->userprog_vaddr.vaddr_bitmap.bits,bitmap_pg_cnt*PG_SIZE);
31 child_thread->userprog_vaddr.vaddr_bitmap.bits=vaddr_btmp;
32 /* 调试用 */
33 ASSERT(strlen(child_thread->name)<11); // pcb.name的长度为16,为避免下面strcat越界
34 strcat(child_thread->name,"_fork");
35 return 0;
36 }
37
38 /* 复制子进程的进程体(代码和数据)及用户栈 */
39 static void copy_body_stack3(struct task_struct* child_thread,struct task_struct* parent_thread,void* buf_page){
40 uint8_t* vaddr_btmp=parent_thread->userprog_vaddr.vaddr_bitmap.bits;
41 uint32_t btmp_bytes_len=parent_thread->userprog_vaddr.vaddr_bitmap.btmp_bytes_len;
42 uint32_t vaddr_start=parent_thread->userprog_vaddr.vaddr_start;
43 uint32_t idx_byte=0;
44 uint32_t idx_bit=0;
45 uint32_t prog_vaddr=0;
46
47 /* 在父进程的用户空间中查找已有数据的页 */
48 while (idx_byte<btmp_bytes_len){
49 if (vaddr_btmp[idx_byte]){
50 idx_bit=0;
51 while (idx_bit<8){
52 if ((BITMAP_MASK<<idx_bit)&vaddr_btmp[idx_byte]){
53 prog_vaddr=(idx_byte*8+idx_bit)*PG_SIZE+vaddr_start;
54 /* 下面的操作是将父进程用户空间中的数据通过内核空间做中转,最终复制到子进程的用户空间 */
55
56 /* 2.1 将父进程在用户空间中的数据复制到内核缓冲区buf_page,
57 * 目的是下面切换到子进程的页表后,还能访问到父进程的数据 */
58 memcpy(buf_page,(void*)prog_vaddr,PG_SIZE);
59
60 /* 2.2 将页表切换到子进程,目的是避免下面申请内存的函数将pte及pde安装在父进程的页表中 */
61 page_dir_activate(child_thread);
62 /* 2.3 申请虚拟地址prog_vaddr */
63 get_a_page_without_opvaddrbitmap(PF_USER,prog_vaddr);
64
65 /* 2.4 从内核缓冲区中将父进程数据复制到子进程的用户空间 */
66 memcpy((void*)prog_vaddr,buf_page,PG_SIZE);
67
68 /* 2.5 恢复父进程页表 */
69 page_dir_activate(parent_thread);
70 }
71 ++idx_bit;
72 }
73 }
74 ++idx_byte;
75 }
76 }
77
78 /* 为子进程构建thread_stack和修改返回值 */
79 static int32_t build_child_stack(struct task_struct* child_thread){
80 /* 1.使子进程pid返回值为0 */
81 /* 获得子进程0级栈栈顶 */
82 struct intr_stack* intr_0_stack=(struct intr_stack*)((uint32_t)child_thread+PG_SIZE-sizeof(struct intr_stack));
83 /* 修改子进程的返回值0 */
84 intr_0_stack->eax=0;
85
86 /* 2.为switch_to构建struct thread_stack,将其构建在紧邻intr_stack之下的空间 */
87 uint32_t* ret_addr_in_thread_stack=(uint32_t*)intr_0_stack-1;
88
89 /* 以下三行非必须 */
90 uint32_t* esi_ptr_in_thread_stack=(uint32_t*)intr_0_stack-2;
91 uint32_t* edi_ptr_in_thread_stack=(uint32_t*)intr_0_stack-3;
92 uint32_t* ebx_ptr_in_thread_stack=(uint32_t*)intr_0_stack-4;
93 /**********************************************************/
94
95 /* ebp在thread_stack中的地址便是当时esp(0级栈栈顶),
96 * 即esp为(uint32_t*)intr_0_stack-5 */
97 uint32_t* ebp_ptr_in_thread_stack=(uint32_t*)intr_0_stack-5;
98
99 /* switch_to的返回地址更新为intr_exit,直接重中断返回 */
100 *ret_addr_in_thread_stack=(uint32_t)intr_exit;
101
102 /* 下面这两行复制只是为了使构建的thread_stack更加清晰,其实也不需要,
103 * 因为在进入intr_exit后一系列的pop会把寄存器中的数据覆盖 */
104 *ebp_ptr_in_thread_stack=*ebx_ptr_in_thread_stack=\
105 *edi_ptr_in_thread_stack=*esi_ptr_in_thread_stack=0;
106 /*********************************************************/
107
108 /* 把构建的thread_stack的栈顶作为switch_to恢复数据时的栈顶 */
109 child_thread->self_kstack=ebp_ptr_in_thread_stack;
110 return 0;
111 }
112
113 /* 更新inode打开数 */
114 static void update_inode_open_cnts(struct task_struct* thread){
115 int32_t local_fd=3,global_fd=0;
116 while (local_fd<MAX_FILES_OPEN_PER_PROC){
117 global_fd=thread->fd_table[local_fd];
118 ASSERT(global_fd<MAX_FILE_OPEN);
119 if (global_fd!=-1){
120 file_table[global_fd].fd_inode->i_open_cnts++;
121 }
122 ++local_fd;
123 }
124 }
125
126 /* 子进程拷贝父进程本身所占用的资源 */
127 static int32_t copy_process(struct task_struct* child_thread,struct task_struct* parent_thread){
128 /* 内核缓冲区,作为父进程用户空间的数据复制到子进程用户空间的中转 */
129 void* buf_page=get_kernel_pages(1);
130 if (buf_page==NULL){
131 return -1;
132 }
133
134 /* 1.子进程复制父进程的pcb、虚拟地址位图、内核 */
135 if (copy_pcb_vaddrbitmap_stack0(child_thread,parent_thread)==-1){
136 return -1;
137 }
138
139 /* 2.为子进程创建页表,此页表仅包括内核空间 */
140 child_thread->pgdir=create_page_dir();
141 if (child_thread->pgdir==NULL){
142 return -1;
143 }
144
145 /* 3.复制父进程进程体及用户栈给子进程 */
146 copy_body_stack3(child_thread,parent_thread,buf_page);
147
148 /* 4.构建子进程thread_stack和修改返回值pid */
149 build_child_stack(child_thread);
150
151 /* 5.更新文件inode的打开数 */
152 update_inode_open_cnts(child_thread);
153
154 mfree_page(PF_KERNEL,buf_page,1);
155 return 0;
156 }
157
158 /* fork子进程,内核线程不可直接调用 */
159 pid_t sys_fork(void){
160 struct task_struct* parent_thread=running_thread();
161 struct task_struct* child_thread=get_kernel_pages(1);
162 if (child_thread==NULL){
163 return -1;
164 }
165 ASSERT(INTR_OFF==intr_get_status() && parent_thread->pgdir!=NULL);
166
167 if (copy_process(child_thread,parent_thread)==-1){
168 return -1;
169 }
170
171 /* 添加到就绪队列和所有线程队列,子进程由调试器安排运行 */
172 ASSERT(!elem_find(&thread_ready_list,&child_thread->general_tag));
173 list_append(&thread_ready_list,&child_thread->general_tag);
174 ASSERT(!elem_find(&thread_all_list,&child_thread->all_list_tag));
175 list_append(&thread_all_list,&child_thread->all_list_tag);
176
177 return child_thread->pid; // 父进程返回子进程pid
178 }
⑥userprog/fork.h:

1 #ifndef __USERPROG_FORK_H
2 #define __USERPROG_FORK_H
3 #include "thread.h"
4 /* fork子进程,只能由用户进程通过系统调用fork调用,
5 * 内核线程不可直接调用,原因是会从0级栈中获取esp3等切换到用户进程栈 */
6 pid_t sys_fork(void);
7 #endif
⑦lib/user/syscall.h,在枚举变量SYSCALL_NR中添加SYS_FORK,并添加fork()的函数声明:

1 #ifndef __LIB_USER_SYSCALL_H
2 #define __LIB_USER_SYSCALL_H
3 #include "stdint.h"
4 enum SYSCALL_NR{
5 SYS_GETPID,
6 SYS_WRITE,
7 SYS_MALLOC,
8 SYS_FREE,
9 SYS_FORK
10 };
11 uint32_t getpid(void);
12 uint32_t write(int32_t fd,const void* buf,uint32_t count);
13 void* malloc(uint32_t size);
14 void free(void* ptr);
15 int16_t fork(void);
16 #endif
⑧lib/user/syscall.c:
/* 派生子进程,返回子进程pid */
pid_t fork(void){
return _syscall0(SYS_FORK);
}
⑨userprog/syscall-init.c,添加fork.h头文件并注册fork系统调用:
#include "fork.h"
syscall_table[SYS_FORK]=sys_fork;
⑩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
16 void init(void);
17
18 int main(void){
19 put_str("Welcome,\nI am kernel!\n");
20 init_all();
21
22 while(1);
23 return 0;
24 }
25
26 /* init进程 */
27 void init(void){
28 uint32_t ret_pid=fork();
29 if (ret_pid){
30 printf("I'm father, my pid is %d, child pid is %d.\n",getpid(),ret_pid);
31 }else {
32 printf("I'm child, my pid is %d, ret pid is %d.\n",getpid(),ret_pid);
33 }
34 while (1);
35 }
最后还要修改makefile。
来看看效果:
2.read系统调用+3.putchar、clear系统调用
①fs/fs.c,修改sys_read()并添加sys_putchar():
1 /* 从文件描述符fd指向的文件中读取count个字节到buf,若成功则返回读出的字节数,到文件尾则返回-1 */
2 int32_t sys_read(int32_t fd,void* buf,uint32_t count){
3 ASSERT(buf!=NULL);
4 int32_t ret=-1;
5 if (fd<0 || fd==stdout_no || fd==stderr_no){
6 printk("sys_read: fd error\n");
7 }else if (fd==stdin_no){
8 char* buffer=buf;
9 uint32_t bytes_read=0;
10 while (bytes_read<count){
11 *buffer=ioq_getchar(&kbd_buf);
12 ++bytes_read;
13 ++buffer;
14 }
15 ret=(bytes_read==0?-1:(int32_t)bytes_read);
16 }else {
17 uint32_t _fd=fd_local2global(fd);
18 ret=file_read(&file_table[_fd],buf,count);
19 }
20 return ret;
21 }
22
23 /* 向屏幕输出一个字符 */
24 void sys_putchar(char char_asci){
25 console_put_char(char_asci);
26 }
②fs/fs.h:
void sys_putchar(char char_asci);
以下和第一步相似:
③lib/user/syscall.c:
1 /* 从文件描述符fd中读取count个字节到buf */
2 int32_t read(int32_t fd,void* buf,uint32_t count){
3 return _syscall3(SYS_READ,fd,buf,count);
4 }
5
6 /* 输出一个字符 */
7 void putchar(char char_asci){
8 _syscall1(SYS_PUTCHAR,char_asci);
9 }
10
11 /* 清空屏幕 */
12 void clear(void){
13 _syscall0(SYS_CLEAR);
14 }
④lib/user/syscall.h:

1 #ifndef __LIB_USER_SYSCALL_H
2 #define __LIB_USER_SYSCALL_H
3 #include "stdint.h"
4 enum SYSCALL_NR{
5 SYS_GETPID,
6 SYS_WRITE,
7 SYS_MALLOC,
8 SYS_FREE,
9 SYS_FORK,
10 SYS_READ,
11 SYS_PUTCHAR,
12 SYS_CLEAR
13 };
14 uint32_t getpid(void);
15 uint32_t write(int32_t fd,const void* buf,uint32_t count);
16 void* malloc(uint32_t size);
17 void free(void* ptr);
18 int16_t fork(void);
19 int32_t read(int32_t fd,void* buf,uint32_t count);
20 void putchar(char char_asci);
21 void clear(void);
22 #endif
⑤userprog/syscall-init.c:
syscall_table[SYS_READ]=sys_read;
syscall_table[SYS_PUTCHAR]=sys_putchar;
syscall_table[SYS_CLEAR]=cls_screen;
⑥lib/kernel/print.S,实现cls_screen:
1 global cls_screen
2 cls_screen:
3 pushad
4 ;;;;;;;;;;
5 ; 由于用户程序的cpl为3,显存段的dpl为0,故用于显存段的选择子gs在低于自己特权的环境中为0,
6 ; 由于用户程序再次进入中断后,gs为0,故直接在put_str中每次都为gs赋值
7 mov ax,SELECTOR_VIDEO
8 mov gs,ax
9
10 mov ebx,0
11 mov ecx,80*25
12 .cls:
13 mov word [gs:ebx],0x0720 ; 0x0720是黑底白字的空格键
14 add ebx,2
15 loop .cls
16 mov ebx,0
17
18 .set_cursor:
19 ;;;;;;;;;; 1.先设置高8位 ;;;;;;;;;;
20 mov dx,0x03d4
21 mov al,0x0e
22 out dx,al
23 mov dx,0x03d5
24 mov al,bh
25 out dx,al
26
27 ;;;;;;;;;; 2.再设置低8位 ;;;;;;;;;;
28 mov dx,0x03d4
29 mov al,0x0f
30 out dx,al
31 mov dx,0x03d5
32 mov al,bl
33 out dx,al
34 popad
35 ret
4.实现一个简单的shell
先弄个shell的雏形吧。
勘误:书中并没有写到重实现ASSERT为assert、PANIC为panic,所以我们就直接使用大写的两个函数吧,记得加上debug.h头文件。
①shell/shell.c:
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
11 #define cmd_len 128 // 最大支持键入128个字符的命令行输入
12 #define MAX_ARG_NR 16 // 加上命令名外,最多支持15个参数
13
14 /* 存储输入的命令 */
15 static char cmd_line[cmd_len]={0};
16
17 /* 用来记录当前目录,是当前目录的缓存,每次执行cd命令时会更新此内容 */
18 char cwd_cache[64]={0};
19
20 /* 输出提示符 */
21 void print_prompt(void){
22 printf("[zbb@LAPTOP %s]$ ",cwd_cache);
23 }
24
25 /* 从键盘缓冲区中最多读入count个字节到buf */
26 static void readline(char* buf,int32_t count){
27 ASSERT(buf!=NULL && count>0);
28 char* pos=buf;
29 while (read(stdin_no,pos,1)!=-1 && (pos-buf)<count){
30 switch (*pos){
31 /* 找到回车符或换行符后认为键入的命令结束,直接返回 */
32 case '\n':
33 case '\r':
34 *pos=0; // 添加cmd_line的终止字符0
35 putchar('\n');
36 return;
37
38 case '\b':
39 if (buf[0]!='\b'){ // 没到达行首
40 --pos;
41 putchar('\b');
42 }
43 break;
44
45 /* 非控制键则输出字符 */
46 default:
47 putchar(*pos);
48 ++pos;
49 }
50 }
51 printf("readline: can't find enter_key in the cmd_line, max num of char is 128\n");
52 }
53
54 /* 简单的shell */
55 void my_shell(void){
56 cwd_cache[0]='/';
57 while (1){
58 print_prompt();
59 memset(cmd_line,0,cmd_len);
60 readline(cmd_line,cmd_len);
61 if (cmd_line[0]==0){ // 只键入回车
62 continue;
63 }
64 }
65 PANIC("my_shell: should not be here");
66 }
②shell/shell.h:
1 #ifndef __KERNEL_SHELL_H
2 #define __KERNEL_SHELL_H
3 void print_prompt(void);
4 void my_shell(void);
5 #endif
③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
18 void init(void);
19
20 int main(void){
21 put_str("Welcome,\nI am kernel!\n");
22 init_all();
23 cls_screen();
24 console_put_str("[zbb@LAPTOP //]$ ");
25 while(1);
26 return 0;
27 }
28
29 /* init进程 */
30 void init(void){
31 uint32_t ret_pid=fork();
32 if (ret_pid){ // 父进程
33 printf("parent...\n");
34 while (1);
35 }else { // 子进程
36 printf("son...\n");
37 my_shell();
38 }
39 PANIC("init: should not be here.");
40 }
看,它真的只有一个空壳:(以下是没有清屏的,因为我想捋一下整个运行过程)
清屏后:
然后就要添加Ctrl+u和Ctrl+l快捷键了。
①shell/shell.c:

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
11 #define MAX_ARG_NR 16 // 加上命令名外,最多支持15个参数
12
13 /* 存储输入的命令 */
14 static char cmd_line[MAX_PATH_LEN]={0};
15 char final_path[MAX_PATH_LEN]={0}; // 清除路径缓冲
16
17 /* 用来记录当前目录,是当前目录的缓存,每次执行cd命令时会更新此内容 */
18 char cwd_cache[64]={0};
19
20 /* 输出提示符 */
21 void print_prompt(void){
22 printf("[zbb@LAPTOP %s]$ ",cwd_cache);
23 }
24
25 /* 从键盘缓冲区中最多读入count个字节到buf */
26 static void readline(char* buf,int32_t count){
27 ASSERT(buf!=NULL && count>0);
28 char* pos=buf;
29
30 while (read(stdin_no,pos,1)!=-1 && (pos-buf)<count){ // 直至回车符停止
31 switch (*pos){
32 /* 找到回车或换行符后认为键入命令结束 */
33 case '\n':
34 case '\r':
35 *pos=0; // 添加cmd_line的终止字符0
36 putchar('\n');
37 return;
38
39 case '\b':
40 if (cmd_line[0]!='\b'){ // 到行首
41 --pos;
42 putchar('\b');
43 }
44 break;
45
46 /* Ctrl+l 清屏 */
47 case 'l'-'a':
48 /* 1.先将当前字符'l'-'a'置为0 */
49 *pos=0;
50 /* 2.再将屏幕清空 */
51 clear();
52 /* 3.打印提示符 */
53 print_prompt();
54 /* 4.将之前键入的内容再次打印 */
55 printf("%s",buf);
56 break;
57
58 /* Ctrl+u 清掉输入 */
59 case 'u'-'a':
60 while (buf!=pos){
61 putchar('\b');
62 *(pos--)=0;
63 }
64 break;
65
66 /* 非控制则输出字符 */
67 default:
68 putchar(*pos);
69 ++pos;
70 }
71 }
72 printf("readline: can't find enter_key in the cmd_line, max num of char is 128\n");
73 }
74
75 /* 分析字符串cmd_str中以token为分隔符的单词,将各单词的指针存入argv数组 */
76 static int32_t cmd_parse(char* cmd_str,char** argv,char token){
77 ASSERT(cmd_str!=NULL);
78 int32_t arg_idx=0;
79 while (arg_idx<MAX_ARG_NR){
80 argv[arg_idx]=NULL;
81 ++arg_idx;
82 }
83 char* next=cmd_str;
84 int32_t argc=0;
85 /* 外层循环处理整个命令行 */
86 while (*next){
87 /* 去除命令字或参数间空格 */
88 while (*next==token){
89 ++next;
90 }
91 /* 处理最后一个参数后接空格的情况 */
92 if (*next==0){
93 break;
94 }
95 argv[argc]=next;
96
97 /* 内层循环处理命令行中的每个命令字及参数 */
98 while (*next && *next!=token){ // 在字符串结束前找单词分隔符
99 ++next;
100 }
101
102 /* 如果未结束(是token字符),使token变成0 */
103 if (*next){
104 *next++=0;
105 }
106
107 /* argv数组越界则返回-1 */
108 if (argc>MAX_ARG_NR){
109 return -1;
110 }
111 ++argc;
112 }
113 return argc;
114 }
115
116 char* argv[MAX_ARG_NR]; // argv必须为全局变量
117 int32_t argc=-1;
118
119 /* 简单的shell */
120 void my_shell(void){
121 cwd_cache[0]='/';
122 while (1){
123 print_prompt();
124 memset(final_path,0,MAX_PATH_LEN);
125 memset(cmd_line,0,MAX_PATH_LEN);
126 readline(cmd_line,MAX_PATH_LEN);
127 if (cmd_line[0]==0){ // 只键入回车
128 continue;
129 }
130 argc=-1;
131 argc=cmd_parse(cmd_line,argv,' ');
132 if (argc==-1){
133 printf("num of arguments exceed %d\n",MAX_ARG_NR);
134 continue;
135 }
136
137 int32_t arg_idx=0;
138 while (arg_idx<argc){
139 printf("%s ",argv[arg_idx]);
140 ++arg_idx;
141 }
142 printf("\n");
143 }
144 PANIC("my_shell: should not be here");
145 }
②shell/shell.h:

1 #ifndef __KERNEL_SHELL_H
2 #define __KERNEL_SHELL_H
3 #include "fs.h"
4 void print_prompt(void);
5 void my_shell(void);
6 extern char final_path[MAX_PATH_LEN];
7 #endif
③device/keyboard,添加处理Ctrl+'u'和Ctrl+'l'的逻辑:

1 /* 键盘中断处理程序 */
2 static void intr_keyboard_handler(void){
3
4 /* 这次中断发生前的上一次中断,以下任意三个键是否有按下 */
5 bool ctrl_down_last=ctrl_status;
6 bool shift_down_last=shift_status;
7 bool caps_lock_last=caps_lock_status;
8
9 bool break_code;
10 uint16_t scancode=inb(KBD_BUF_PORT);
11
12 /* 若扫描码scancode是e0开头的,表示此键的按下将产生多个扫描码,
13 * 所以马上结束此次中断处理函数,等待下一个扫描码进来 */
14 if (scancode==0xe0){
15 ext_scancode=true; // 打开e0标记
16 return;
17 }
18
19 /* 如果上次是以0xe0开头的,将扫描码合并 */
20 if (ext_scancode){
21 scancode=((0xe000) | scancode);
22 ext_scancode=false; // 关闭e0标记
23 }
24
25 break_code=((scancode & 0x0080)!=0); // 获取break_code
26
27 if(break_code){ // 若是断码(按键弹起时产生的扫描码)
28
29 uint16_t make_code=(scancode &= 0xff7f); //多字节扫描码暂不处理
30
31 /* 若是以下三个键弹起了,将状态设为false */
32 if (make_code==ctrl_l_make || make_code==ctrl_r_make){
33 ctrl_status=false;
34 }
35 else if (make_code==shift_l_make || make_code==shift_r_make){
36 shift_status=false;
37 }
38 else if (make_code==alt_l_make || make_code==alt_r_make){
39 alt_status=false;
40 }
41
42 return; // 直接返回结束此次中断处理程序
43
44 }
45 /* 若为通码,只处理数组中定义的键以及alt_right和ctrl键,全是make_code */
46 else if ((scancode>0x00 && scancode<0x3b) || (scancode==alt_r_make) || (scancode==ctrl_r_make)){
47 bool shift=false; // 判断是否与shift结合,用来在一维数组中索引对应的字符,先默认设置成false
48 if ((scancode<0x0e) || (scancode==0x29) || (scancode==0x1a) || \
49 (scancode==0x1b) || (scancode==0x2b) || (scancode==0x27) || \
50 (scancode==0x28) || (scancode==0x33) || (scancode==0x34) || \
51 (scancode==0x35)){
52 if (shift_down_last){ // 如果同时按下了shift键
53 shift=true;
54 }
55 }
56 else{ // 默认为字母键
57 if (shift_down_last && caps_lock_last){ // 同时按下shift和capslock
58 shift=false;
59 }
60 else if (shift_down_last || caps_lock_last){ // 按下shift或capslock
61 shift=true;
62 }
63 else{
64 shift=false;
65 }
66 }
67
68 uint8_t index=(scancode &= 0x00ff);
69 char cur_char=keymap[index][shift]; // 在数组中找到对应的字符
70
71 /* 只处理ASCII码不为0的键 */
72 if (cur_char){
73 if ((ctrl_down_last && cur_char=='l') || (ctrl_down_last && cur_char=='u')){
74 cur_char-='a';
75 }
76
77 if (!ioq_full(&kbd_buf)){
78 ioq_putchar(&kbd_buf,cur_char);
79 }
80 return;
81 }
82
83 /* 记录本次是否按下了下面几类控制键之一,供下次键入时判断组合键 */
84 if (scancode==ctrl_l_make || scancode==ctrl_r_make){
85 ctrl_status=true;
86 }
87 else if (scancode==shift_l_make || scancode==shift_r_make){
88 shift_status=true;
89 }
90 else if (scancode==alt_l_make || scancode==alt_r_make){
91 alt_status=true;
92 }
93 else if (scancode==caps_lock_make){
94 caps_lock_status=!caps_lock_status;
95 }
96 else{
97 put_str("unknown key\n");
98 }
99 }
100 return;
101 }
④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
18 void init(void);
19
20 int main(void){
21 put_str("Welcome,\nI am kernel!\n");
22 init_all();
23 intr_enable();
24 cls_screen();
25 console_put_str("[zbb@LAPTOP //]$ ");
26 while(1);
27 return 0;
28 }
29
30 /* init进程 */
31 void init(void){
32 uint32_t ret_pid=fork();
33 if (ret_pid){ // 父进程
34 printf("parent...\n");
35 while (1);
36 }else { // 子进程
37 printf("son...\n");
38 my_shell();
39 }
40 PANIC("init: should not be here.");
41 }
运行结果如图:
键入Ctrl+l成功清屏:
为fs中的"sys_"添加系统调用。
①lib/user/syscall.h,赋系统调用号、函数声明:

1 #ifndef __LIB_USER_SYSCALL_H
2 #define __LIB_USER_SYSCALL_H
3 #include "stdint.h"
4 #include "fs.h"
5
6 enum SYSCALL_NR{
7 SYS_GETPID,
8 SYS_WRITE,
9 SYS_MALLOC,
10 SYS_FREE,
11 SYS_FORK,
12 SYS_READ,
13 SYS_PUTCHAR,
14 SYS_CLEAR,
15 SYS_GETCWD,
16 SYS_OPEN,
17 SYS_CLOSE,
18 SYS_LSEEK,
19 SYS_UNLINK,
20 SYS_MKDIR,
21 SYS_OPENDIR,
22 SYS_CLOSEDIR,
23 SYS_CHDIR,
24 SYS_RMDIR,
25 SYS_READDIR,
26 SYS_REWINDDIR,
27 SYS_STAT,
28 SYS_PS
29 };
30 uint32_t getpid(void);
31 uint32_t write(int32_t fd,const void* buf,uint32_t count);
32 void* malloc(uint32_t size);
33 void free(void* ptr);
34 int16_t fork(void);
35 int32_t read(int32_t fd,void* buf,uint32_t count);
36 void putchar(char char_asci);
37 void clear(void);
38 char* getcwd(char* buf,uint32_t size);
39 int32_t open(char* pathname,uint8_t flag);
40 int32_t close(int32_t fd);
41 int32_t lseek(int32_t fd,int32_t offset,uint8_t whence);
42 int32_t unlink(const char* pathname);
43 int32_t mkdir(const char* pathname);
44 struct dir* opendir(const char* name);
45 int32_t closedir(struct dir* dir);
46 int32_t rmdir(const char* pathname);
47 struct dir_entry* readdir(struct dir* dir);
48 void rewinddir(struct dir* dir);
49 int32_t stat(const char* path,struct stat* buf);
50 int32_t chdir(const char* path);
51 void ps(void);
52 #endif
②lib/user/syscall.c,新增系统调用实现:

1 #include "syscall.h"
2 #include "thread.h"
3
4 /* 无参数的系统调用 */
5 #define _syscall0(NUMBER) ({ \
6 int retval; \
7 asm volatile ( \
8 "int $0x80" \
9 : "=a"(retval) \
10 : "a"(NUMBER) \
11 : "memory"); \
12 retval; \
13 })
14
15 /* 一个参数的系统调用 */
16 #define _syscall1(NUMBER,ARG1) ({ \
17 int retval; \
18 asm volatile ( \
19 "int $0x80" \
20 : "=a"(retval) \
21 : "a"(NUMBER),"b"(ARG1) \
22 : "memory"); \
23 retval; \
24 })
25
26 #define _syscall2(NUMBER,ARG1,ARG2) ({ \
27 int retval; \
28 asm volatile ( \
29 "int $0x80" \
30 : "=a"(retval) \
31 : "a"(NUMBER),"b"(ARG1),"c"(ARG2) \
32 : "memory"); \
33 retval; \
34 })
35
36 #define _syscall3(NUMBER,ARG1,ARG2,ARG3)({ \
37 int retval; \
38 asm volatile ( \
39 "int $0x80" \
40 : "=a"(retval) \
41 : "a"(NUMBER),"b"(ARG1),"c"(ARG2),"d"(ARG3) \
42 : "memory"); \
43 retval; \
44 })
45
46 /* 获取任务pid */
47 uint32_t getpid(void){
48 return _syscall0(SYS_GETPID);
49 }
50
51 /* 把buf中count个字符写入文件描述符fd */
52 uint32_t write(int32_t fd,const void* buf,uint32_t count){
53 return _syscall3(SYS_WRITE,fd,buf,count);
54 }
55
56 /* 申请size字节大小的内存,并返回结果 */
57 void* malloc(uint32_t size){
58 return (void*)_syscall1(SYS_MALLOC,size);
59 }
60
61 /* 释放ptr指向的内存 */
62 void free(void* ptr){
63 _syscall1(SYS_FREE,ptr);
64 }
65
66 /* 派生子进程,返回子进程pid */
67 pid_t fork(void){
68 return _syscall0(SYS_FORK);
69 }
70
71 /* 从文件描述符fd中读取count个字节到buf */
72 int32_t read(int32_t fd,void* buf,uint32_t count){
73 return _syscall3(SYS_READ,fd,buf,count);
74 }
75
76 /* 输出一个字符 */
77 void putchar(char char_asci){
78 _syscall1(SYS_PUTCHAR,char_asci);
79 }
80
81 /* 清空屏幕 */
82 void clear(void){
83 _syscall0(SYS_CLEAR);
84 }
85
86 /* 获取当前工作目录 */
87 char* getcwd(char* buf,uint32_t size){
88 return (char*)_syscall2(SYS_GETCWD,buf,size);
89 }
90
91 /* 以flag方式打开文件pathname */
92 int32_t open(char* pathname,uint8_t flag){
93 return _syscall2(SYS_OPEN,pathname,flag);
94 }
95
96 /* 关闭文件fd */
97 int32_t close(int32_t fd){
98 return _syscall1(SYS_CLOSE,fd);
99 }
100
101 /* 设置文件偏移量 */
102 int32_t lseek(int32_t fd,int32_t offset,uint8_t whence){
103 return _syscall3(SYS_LSEEK,fd,offset,whence);
104 }
105
106 /* 删除文件pathname */
107 int32_t unlink(const char* pathname){
108 return _syscall1(SYS_UNLINK,pathname);
109 }
110
111 /* 创建目录pathname */
112 int32_t mkdir(const char* pathname){
113 return _syscall1(SYS_MKDIR,pathname);
114 }
115
116 /* 打开目录name */
117 struct dir* opendir(const char* name){
118 return (struct dir*)_syscall1(SYS_OPENDIR,name);
119 }
120
121 /* 关闭目录dir */
122 int32_t closedir(struct dir* dir){
123 return _syscall1(SYS_CLOSEDIR,dir);
124 }
125
126 /* 删除目录pathname */
127 int32_t rmdir(const char* pathname){
128 return _syscall1(SYS_RMDIR,pathname);
129 }
130
131 /* 读取目录dir */
132 struct dir_entry* readdir(struct dir* dir){
133 return (struct dir_entry*)_syscall1(SYS_READDIR,dir);
134 }
135
136 /* 重置目录指针 */
137 void rewinddir(struct dir* dir){
138 _syscall1(SYS_REWINDDIR,dir);
139 }
140
141 /* 获取path属性到buf中 */
142 int32_t stat(const char* path,struct stat* buf){
143 return _syscall2(SYS_STAT,path,buf);
144 }
145
146 /* 改变工作目录path */
147 int32_t chdir(const char* path){
148 return _syscall1(SYS_CHDIR,path);
149 }
150
151 /* 显示任务列表 */
152 void ps(void){
153 _syscall0(SYS_PS);
154 }
③userprog/syscall-init.c,注册系统调用:

1 #include "syscall-init.h"
2 #include "syscall.h"
3 #include "stdint.h"
4 #include "print.h"
5 #include "thread.h"
6 #include "console.h"
7 #include "string.h"
8 #include "memory.h"
9 #include "fs.h"
10 #include "fork.h"
11
12 #define syscall_nr 32
13 typedef void* syscall;
14 syscall syscall_table[syscall_nr];
15
16 /* 返回当前任务的pid */
17 uint32_t sys_getpid(void){
18 return running_thread()->pid;
19 }
20
21 /* 初始化系统调用 */
22 void syscall_init(void){
23 put_str("syscall_init start\n");
24 syscall_table[SYS_GETPID]=sys_getpid;
25 syscall_table[SYS_WRITE]=sys_write;
26 syscall_table[SYS_MALLOC]=sys_malloc;
27 syscall_table[SYS_FREE]=sys_free;
28 syscall_table[SYS_FORK]=sys_fork;
29 syscall_table[SYS_READ]=sys_read;
30 syscall_table[SYS_PUTCHAR]=sys_putchar;
31 syscall_table[SYS_CLEAR]=cls_screen;
32 syscall_table[SYS_GETCWD]=sys_getcwd;
33 syscall_table[SYS_OPEN]=sys_open;
34 syscall_table[SYS_CLOSE]=sys_close;
35 syscall_table[SYS_LSEEK]=sys_lseek;
36 syscall_table[SYS_UNLINK]=sys_unlink;
37 syscall_table[SYS_MKDIR]=sys_mkdir;
38 syscall_table[SYS_OPENDIR]=sys_opendir;
39 syscall_table[SYS_CLOSEDIR]=sys_closedir;
40 syscall_table[SYS_CHDIR]=sys_chdir;
41 syscall_table[SYS_RMDIR]=sys_rmdir;
42 syscall_table[SYS_READDIR]=sys_readdir;
43 syscall_table[SYS_REWINDDIR]=sys_rewinddir;
44 syscall_table[SYS_STAT]=sys_stat;
45 syscall_table[SYS_PS]=sys_ps;
46 put_str("syscall_init done\n");
47 }
④thread/thread.c,实现打印进程信息的sys_ps()的有关函数:
1 #include "stdio.h"
2 #include "file.h"
3 #include "fs.h"
4
5 /* 以填充空格的方式输出buf */
6 static void pad_print(char* buf,int32_t buf_len,void* ptr,char format){
7 memset(buf,0,buf_len);
8 uint8_t out_pad_0idx=0;
9 switch (format){
10 case 's':
11 out_pad_0idx=sprintf(buf,"%s",ptr);
12 break;
13 case 'd':
14 out_pad_0idx=sprintf(buf,"%d",*((int16_t*)ptr));
15 case 'x':
16 out_pad_0idx=sprintf(buf,"%x",*((int16_t*)ptr));
17 }
18 while (out_pad_0idx<buf_len){ // 空格填充
19 buf[out_pad_0idx]=' ';
20 ++out_pad_0idx;
21 }
22 sys_write(stdout_no,buf,buf_len-1);
23 }
24
25 /* 用于在list_traversal函数中的回调函数,用于针对线程队列的处理 */
26 static bool elem2thread_info(struct list_elem* pelem,int arg){
27 struct task_struct* pthread=elem2entry(struct task_struct,all_list_tag,pelem);
28 char out_pad[16]={0};
29
30 pad_print(out_pad,16,&pthread->pid,'d'); // 打印pid
31
32 if (pthread->parent_pid==-1){ // 打印parent_pid
33 pad_print(out_pad,16,"NULL",'s');
34 }else {
35 pad_print(out_pad,16,&pthread->parent_pid,'d');
36 }
37
38 switch (pthread->status){ // 打印状态
39 case 0:
40 pad_print(out_pad,16,"RUNNING",'s');
41 break;
42 case 1:
43 pad_print(out_pad,16,"READY",'s');
44 break;
45 case 2:
46 pad_print(out_pad,16,"BLOCKED",'s');
47 break;
48 case 3:
49 pad_print(out_pad,16,"WAITING",'s');
50 break;
51 case 4:
52 pad_print(out_pad,16,"HANGING",'s');
53 break;
54 case 5:
55 pad_print(out_pad,16,"DIED",'s');
56 }
57 pad_print(out_pad,16,&pthread->elapsed_ticks,'x'); // 打印运行时间片
58
59 memset(out_pad,0,16);
60 ASSERT(strlen(pthread->name)<17);
61 memcpy(out_pad,pthread->name,strlen(pthread->name));
62 strcat(out_pad,"\n");
63 sys_write(stdout_no,out_pad,strlen(out_pad)); // 打印进程名
64 return false; // 返回false是为了迎合主调函数list_traversal,只有一直返回false才会一直遍历thread_all_list
65 }
66
67 /* 打印任务列表 */
68 void sys_ps(void){
69 char* ps_title="PID PPID STAT TICKS COMMAND\n";
70 sys_write(stdout_no,ps_title,strlen(ps_title));
71 list_traversal(&thread_all_list,elem2thread_info,0);
72 }
⑤thread/thread.h,添加一句函数声明:
void sys_ps(void);
路径解析转换,也就是将我们输入的相对路径(含“..”和“.”)在用户态程序中转换为绝对路径(不含“..”和“.”),再将绝对路径提交给内核态下的系统调用中断。
①新创建的shell/buildin_cmd.c:

1 #include "buildin_cmd.h"
2 #include "syscall.h"
3 #include "stdio.h"
4 #include "string.h"
5 #include "fs.h"
6 #include "global.h"
7 #include "dir.h"
8 #include "shell.h"
9 #include "debug.h"
10
11 /* 将路径old_abs_path中的..和.转换为实际路径后存入new_abs_path */
12 static void wash_path(char* old_abs_path,char* new_abs_path){
13 ASSERT(old_abs_path[0]=='/');
14 char name[MAX_FILE_NAME_LEN]={0};
15 char* sub_path=old_abs_path;
16 sub_path=path_parse(sub_path,name);
17 if (name[0]==0){ // 若只键入了"/",直接将"/"存入new_abs_path后返回
18 new_abs_path[0]='/';
19 new_abs_path[1]=0;
20 return;
21 }
22 new_abs_path[0]=0; // 避免传给new_abs_path的缓冲区不干净
23 strcat(new_abs_path,"/");
24 while (name[0]){
25 /* 如果是上一级目录".." */
26 if (!strcmp("..",name)){
27 char* slash_ptr=strrchr(new_abs_path,'/');
28 /* 如果未到new_abs_path中的顶层目录,就将最右边的'/'换成0,
29 * 这样便去除了new_abs_path中最后一层路径,相当于到了上一级目录 */
30 if (slash_ptr!=new_abs_path){
31 *slash_ptr=0;
32 }else {
33 /* 若new_abs_path中只有一个'/',即表示已经到了顶层目录,
34 * 就将下一个字符置为结束符0. */
35 *(slash_ptr+1)=0;
36 }
37 }else if (strcmp(".",name)){
38 if (strcmp(new_abs_path,"/")){
39 strcat(new_abs_path,"/");
40 }
41 strcat(new_abs_path,name);
42 } // 若name为当前目录".",无需处理new_abs_path
43
44 /* 继续遍历下一层路径 */
45 memset(name,0,MAX_FILE_NAME_LEN);
46 if (sub_path){
47 sub_path=path_parse(sub_path,name);
48 }
49 }
50 }
51
52 /* 将path处理成不含..和.的绝对路径,存储在final_path */
53 void make_clear_abs_path(char* path,char* final_path){
54 char abs_path[MAX_PATH_LEN]={0};
55 /* 先判断输入的是否为绝对路径 */
56 if (path[0]!='/'){ // 若输入的不是绝对路径,就拼接成绝对路径
57 memset(abs_path,0,MAX_PATH_LEN);
58 if (getcwd(abs_path,MAX_PATH_LEN)!=NULL){
59 if (!((abs_path[0]=='/') && (abs_path[1]==0))){
60 strcat(abs_path,"/");
61 }
62 }
63 }
64 strcat(abs_path,path);
65 wash_path(abs_path,final_path);
66 }
②shell/buildin_cmd.h:

#ifndef __SHELL_BUILDIN_CMD_H
#define __SHELL_BUILDIN_CMD_H
#include "stdint.h"
void make_clear_abs_path(char* path,char* wash_buf);
#endif
③shell/shell.c,修改my_shell():

1 /* 简单的shell */
2 void my_shell(void){
3 cwd_cache[0]='/';
4 cwd_cache[1]=0;
5 while (1){
6 print_prompt();
7 memset(final_path,0,MAX_PATH_LEN);
8 memset(cmd_line,0,MAX_PATH_LEN);
9 readline(cmd_line,MAX_PATH_LEN);
10 if (cmd_line[0]==0){ // 只键入回车
11 continue;
12 }
13 argc=-1;
14 argc=cmd_parse(cmd_line,argv,' ');
15 if (argc==-1){
16 printf("num of arguments exceed %d\n",MAX_ARG_NR);
17 continue;
18 }
19
20 char buf[MAX_PATH_LEN]={0};
21 int32_t arg_idx=0;
22 while (arg_idx<argc){
23 make_clear_abs_path(argv[arg_idx],buf);
24 printf("%s -> %s\n",argv[arg_idx],buf);
25 ++arg_idx;
26 }
27 printf("\n");
28 }
29 PANIC("my_shell: should not be here");
30 }
④shell/shell.h,添加头文件:
#include "buildin_cmd.h"
⑤还要修改fs/fs.c和fs/fs.h,将path_parse()修改为非静态函数:
char* path_parse(char* pathname,char* name_store){...
以及makefile。
测试结果如图所示:
还要实现ls、cd、mkdir、ps、rm等命令
①shell/buildin_cmd.c,添加一系列内建函数:

1 #include "buildin_cmd.h"
2 #include "syscall.h"
3 #include "stdio.h"
4 #include "string.h"
5 #include "fs.h"
6 #include "global.h"
7 #include "dir.h"
8 #include "shell.h"
9 #include "debug.h"
10
11 /* 将路径old_abs_path中的..和.转换为实际路径后存入new_abs_path */
12 static void wash_path(char* old_abs_path,char* new_abs_path){
13 ASSERT(old_abs_path[0]=='/');
14 char name[MAX_FILE_NAME_LEN]={0};
15 char* sub_path=old_abs_path;
16 sub_path=path_parse(sub_path,name);
17 if (name[0]==0){ // 若只键入了"/",直接将"/"存入new_abs_path后返回
18 new_abs_path[0]='/';
19 new_abs_path[1]=0;
20 return;
21 }
22 new_abs_path[0]=0; // 避免传给new_abs_path的缓冲区不干净
23 strcat(new_abs_path,"/");
24 while (name[0]){
25 /* 如果是上一级目录".." */
26 if (!strcmp("..",name)){
27 char* slash_ptr=strrchr(new_abs_path,'/');
28 /* 如果未到new_abs_path中的顶层目录,就将最右边的'/'换成0,
29 * 这样便去除了new_abs_path中最后一层路径,相当于到了上一级目录 */
30 if (slash_ptr!=new_abs_path){
31 *slash_ptr=0;
32 }else {
33 /* 若new_abs_path中只有一个'/',即表示已经到了顶层目录,
34 * 就将下一个字符置为结束符0. */
35 *(slash_ptr+1)=0;
36 }
37 }else if (strcmp(".",name)){
38 if (strcmp(new_abs_path,"/")){
39 strcat(new_abs_path,"/");
40 }
41 strcat(new_abs_path,name);
42 } // 若name为当前目录".",无需处理new_abs_path
43
44 /* 继续遍历下一层路径 */
45 memset(name,0,MAX_FILE_NAME_LEN);
46 if (sub_path){
47 sub_path=path_parse(sub_path,name);
48 }
49 }
50 }
51
52 /* 将path处理成不含..和.的绝对路径,存储在final_path */
53 void make_clear_abs_path(char* path,char* final_path){
54 char abs_path[MAX_PATH_LEN]={0};
55 /* 先判断输入的是否为绝对路径 */
56 if (path[0]!='/'){ // 若输入的不是绝对路径,就拼接成绝对路径
57 memset(abs_path,0,MAX_PATH_LEN);
58 if (getcwd(abs_path,MAX_PATH_LEN)!=NULL){
59 if (!((abs_path[0]=='/') && (abs_path[1]==0))){
60 strcat(abs_path,"/");
61 }
62 }
63 }
64 strcat(abs_path,path);
65 wash_path(abs_path,final_path);
66 }
67
68 /* pwd命令的内建函数 */
69 void buildin_pwd(uint32_t argc,char** argv){
70 if (argc!=1){
71 printf("pwd: no argument support!\n");
72 return;
73 }else {
74 if (NULL!=getcwd(final_path,MAX_PATH_LEN)){
75 printf("%s\n",final_path);
76 }else {
77 printf("pwd: get current work directory failed.\n");
78 }
79 }
80 }
81
82 /* cd命令的内建函数 */
83 char* buildin_cd(uint32_t argc,char** argv){
84 if (argc>2){
85 printf("cd: only support 1 argument!\n");
86 return NULL;
87 }
88
89 /* 若是只键入cd而无参数,直接返回到根目录 */
90 if (argc==1){
91 final_path[0]='/';
92 final_path[1]=0;
93 }else {
94 make_clear_abs_path(argv[1],final_path);
95 }
96
97 if (chdir(final_path)==-1){
98 printf("cd: no such directory %s.\n",final_path);
99 return NULL;
100 }
101 return final_path;
102 }
103
104 /* ls命令的内建函数 */
105 void buildin_ls(uint32_t argc,char** argv){
106 char* pathname=NULL;
107 struct stat file_stat;
108 memset(&file_stat,0,sizeof(struct stat));
109 bool long_info=false;
110 uint32_t arg_path_nr=0;
111 uint32_t arg_idx=1;
112 while (arg_idx<argc){
113 if (argv[arg_idx][0]=='-'){
114 if (!strcmp("-l",argv[arg_idx])){ // 如果是参数 -l
115 long_info=true;
116 }else if (!strcmp("-h",argv[arg_idx])){ // 如果是参数 -h
117 printf("usage: -l list all infomation about the file.\n-h for help\nlist all files in the current directory if no option\n");
118 return;
119 }else { // 只支持-l -h
120 printf("ls: invalid option %s\nTry 'ls -h' for more infomation.\n",argv[arg_idx]);
121 return;
122 }
123 }else { // ls的路径参数
124 if (arg_path_nr==0){
125 pathname=argv[arg_idx];
126 arg_path_nr=1;
127 }else {
128 printf("ls: only support one path\n");
129 return;
130 }
131 }
132 ++arg_idx;
133 }
134
135 if (pathname==NULL){ // 若只输入了ls或ls -l没有输入操作路径,则默认以当前路径作为操作路径
136 if (NULL!=getcwd(final_path,MAX_PATH_LEN)){
137 pathname=final_path;
138 }else {
139 printf("ls: getcwd for default path failed.\n");
140 return;
141 }
142 }else {
143 make_clear_abs_path(pathname,final_path);
144 pathname=final_path;
145 }
146
147 if (stat(pathname,&file_stat)==-1){
148 printf("ls: cannot access %s: No such file or directory\n",pathname);
149 return;
150 }
151 if (file_stat.st_filetype==FT_DIRECTORY){
152 struct dir* dir=opendir(pathname);
153 struct dir_entry* dir_e=NULL;
154 char sub_pathname[MAX_PATH_LEN]={0};
155 uint32_t pathname_len=strlen(pathname);
156 uint32_t last_char_idx=pathname_len-1;
157 memcpy(sub_pathname,pathname,pathname_len);
158 if (sub_pathname[last_char_idx]!='/'){
159 sub_pathname[pathname_len]='/';
160 ++pathname_len;
161 }
162 rewinddir(dir);
163 if (long_info){
164 char ftype;
165 printf("total: %d\n",file_stat.st_size);
166 while ((dir_e==readdir(dir))){
167 ftype='d';
168 if (dir_e->f_type==FT_REGULAR){
169 ftype='-';
170 }
171 sub_pathname[pathname_len]=0;
172 strcat(sub_pathname,dir_e->filename);
173 memset(&file_stat,0,sizeof(struct stat));
174 if (stat(sub_pathname,&file_stat)==-1){
175 printf("ls: cannot access %s: No such file or directory\n",dir_e->filename);
176 return;
177 }
178 printf("%c %d %d %s\n",ftype,dir_e->i_no,file_stat.st_size,dir_e->filename);
179 }
180 }else {
181 while ((dir_e==readdir(dir))){
182 printf("%s ",dir_e->filename);
183 }
184 printf("\n");
185 }
186 closedir(dir);
187 }else {
188 if (long_info){
189 printf("- %d %d %s\n",file_stat.st_ino,file_stat.st_size,pathname);
190 }else {
191 printf("%s\n",pathname);
192 }
193 }
194 }
195
196 /* ps命令内建函数 */
197 void buildin_ps(uint32_t argc,char** argv){
198 if (argc!=1){
199 printf("ps: no argument support!\n");
200 return;
201 }
202 ps();
203 }
204
205 /* clear命令内建函数 */
206 void buildin_clear(uint32_t argc,char** argv){
207 if (argc!=1){
208 printf("clear: no argument support!\n");
209 return;
210 }
211 clear();
212 }
213
214 /* mkdir命令内建函数 */
215 int32_t buildin_mkdir(uint32_t argc,char** argv){
216 int32_t ret=-1;
217 if (argc!=2){
218 printf("mkdir: only support 1 argument!\n");
219 }else {
220 make_clear_abs_path(argv[1],final_path);
221 /* 若创建的不是根目录 */
222 if (strcmp("/",final_path)){
223 if (mkdir(final_path)==0){
224 ret=0;
225 }else {
226 printf("mkdir: create directory %s failed.\n",argv[1]);
227 }
228 }
229 }
230 return ret;
231 }
232
233 /* rmdir命令的内建函数 */
234 int32_t buildin_rmdir(uint32_t argc,char** argv){
235 int32_t ret=-1;
236 if (argc!=2){
237 printf("rmdir: only suppory 1 argument!\n");
238 }else {
239 make_clear_abs_path(argv[1],final_path);
240 /* 若删除的不是根目录 */
241 if (strcmp("/",final_path)){
242 if (rmdir(final_path)==0){
243 ret=0;
244 }else {
245 printf("rmdir: remove %s failed.\n",argv[1]);
246 }
247 }
248 }
249 return ret;
250 }
251
252 /* rm命令内建函数 */
253 int32_t buildin_rm(uint32_t argc,char** argv){
254 int32_t ret=-1;
255 if (argc!=2){
256 printf("rm: only support 1 argument!\n");
257 }else {
258 make_clear_abs_path(argv[1],final_path);
259 /* 若删除的不是根目录 */
260 if (strcmp("/",final_path)){
261 if (unlink(final_path)==0){
262 ret=0;
263 }else {
264 printf("rm: delete %s failed.\n",argv[1]);
265 }
266 }
267 }
268 return ret;
269 }
②shell/buildin_cmd.h:

1 #ifndef __SHELL_BUILDIN_CMD_H
2 #define __SHELL_BUILDIN_CMD_H
3 #include "stdint.h"
4 void make_clear_abs_path(char* path,char* wash_buf);
5 void buildin_ls(uint32_t argc,char** argv);
6 char* buildin_cd(uint32_t argc,char** argv);
7 int32_t buildin_mkdir(uint32_t argc,char** argv);
8 int32_t buildin_rmdir(uint32_t argc,char** argv);
9 int32_t buildin_rm(uint32_t argc,char** argv);
10 void buildin_pwd(uint32_t argc,char** argv);
11 void buildin_ps(uint32_t argc,char** argv);
12 void buildin_clear(uint32_t argc,char** argv);
13 #endif
③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 printf("external command\n");
39 }
40 }
41 PANIC("my_shell: should not be here");
42 }
④makefile:

1 BUILD_DIR = ./build
2 ENTRY_POINT = 0xc0001500
3 AS = nasm
4 CC = gcc
5 LD = ld
6 LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/ -I userprog/ -I fs/ -I shell/
7 ASFLAGS = -f elf
8 CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
9 LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
10 OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
11 $(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
12 $(BUILD_DIR)/switch.o $(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o \
13 $(BUILD_DIR)/memory.o $(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o \
14 $(BUILD_DIR)/list.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o \
15 $(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o \
16 $(BUILD_DIR)/process.o $(BUILD_DIR)/syscall.o $(BUILD_DIR)/syscall-init.o \
17 $(BUILD_DIR)/stdio.o $(BUILD_DIR)/stdio-kernel.o $(BUILD_DIR)/ide.o \
18 $(BUILD_DIR)/fs.o $(BUILD_DIR)/inode.o $(BUILD_DIR)/file.o \
19 $(BUILD_DIR)/dir.o $(BUILD_DIR)/fork.o $(BUILD_DIR)/shell.o \
20 $(BUILD_DIR)/buildin_cmd.o
21 ############## c代码编译 ###############
22 $(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
23 lib/stdint.h kernel/init.h lib/string.h kernel/memory.h \
24 thread/thread.h kernel/interrupt.h device/console.h \
25 device/keyboard.h device/ioqueue.h userprog/process.h \
26 lib/user/syscall.h userprog/syscall-init.h lib/stdio.h \
27 lib/kernel/stdio-kernel.o fs/fs.o lib/string.h fs/file.h \
28 shell/shell.o kernel/debug.h
29 $(CC) $(CFLAGS) $< -o $@
30
31 $(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
32 lib/stdint.h kernel/interrupt.h device/timer.h kernel/memory.h \
33 thread/thread.h device/console.h device/keyboard.h userprog/tss.h \
34 userprog/syscall-init.h device/ide.h fs/fs.h
35 $(CC) $(CFLAGS) $< -o $@
36
37 $(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
38 lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
39 $(CC) $(CFLAGS) $< -o $@
40
41 $(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/io.h \
42 lib/kernel/print.h kernel/interrupt.h thread/thread.h kernel/debug.h
43 $(CC) $(CFLAGS) $< -o $@
44
45 $(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
46 lib/kernel/print.h lib/stdint.h kernel/interrupt.h
47 $(CC) $(CFLAGS) $< -o $@
48
49 $(BUILD_DIR)/string.o: lib/string.c lib/string.h \
50 kernel/debug.h kernel/global.h
51 $(CC) $(CFLAGS) $< -o $@
52
53 $(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
54 lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h \
55 thread/sync.h thread/thread.h lib/kernel/list.h kernel/interrupt.h
56 $(CC) $(CFLAGS) $< -o $@
57
58 $(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h kernel/global.h \
59 lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
60 $(CC) $(CFLAGS) $< -o $@
61
62 $(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
63 lib/stdint.h lib/string.h kernel/global.h kernel/memory.h \
64 kernel/debug.h kernel/interrupt.h lib/kernel/print.h userprog/process.h \
65 lib/stdio.h fs/file.h fs/fs.h
66 $(CC) $(CFLAGS) $< -o $@
67
68 $(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \
69 kernel/interrupt.h lib/stdint.h kernel/debug.h
70 $(CC) $(CFLAGS) $< -o $@
71
72 $(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
73 lib/stdint.h thread/thread.h kernel/debug.h kernel/interrupt.h
74 $(CC) $(CFLAGS) $< -o $@
75
76 $(BUILD_DIR)/console.o: device/console.c device/console.h \
77 lib/kernel/print.h thread/sync.h
78 $(CC) $(CFLAGS) $< -o $@
79
80 $(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
81 lib/kernel/print.h lib/kernel/io.h kernel/interrupt.h \
82 kernel/global.h lib/stdint.h device/ioqueue.h
83 $(CC) $(CFLAGS) $< -o $@
84
85 $(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \
86 kernel/interrupt.h kernel/global.h kernel/debug.h
87 $(CC) $(CFLAGS) $< -o $@
88
89 $(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \
90 kernel/global.h thread/thread.h lib/kernel/print.h
91 $(CC) $(CFLAGS) $< -o $@
92
93 $(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \
94 lib/string.h kernel/global.h kernel/memory.h lib/kernel/print.h \
95 thread/thread.h kernel/interrupt.h kernel/debug.h device/console.h
96 $(CC) $(CFLAGS) $< -o $@
97
98 $(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h \
99 lib/stdint.h thread/thread.h
100 $(CC) $(CFLAGS) $< -o $@
101
102 $(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \
103 lib/user/syscall.h lib/stdint.h lib/kernel/print.h device/console.h \
104 thread/thread.h lib/string.h kernel/memory.h fs/fs.h userprog/fork.h
105 $(CC) $(CFLAGS) $< -o $@
106
107 $(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h lib/stdint.h lib/string.h \
108 lib/user/syscall.h
109 $(CC) $(CFLAGS) $< -o $@
110
111 $(BUILD_DIR)/stdio-kernel.o: lib/kernel/stdio-kernel.c lib/kernel/stdio-kernel.h \
112 lib/stdio.h device/console.h
113 $(CC) $(CFLAGS) $< -o $@
114
115 $(BUILD_DIR)/ide.o: device/ide.c device/ide.h lib/stdint.h thread/sync.h \
116 lib/kernel/list.h kernel/global.h thread/thread.h lib/kernel/bitmap.h \
117 kernel/memory.h lib/kernel/io.h lib/stdio.h lib/stdint.h \
118 lib/kernel/stdio-kernel.h kernel/interrupt.h kernel/debug.h \
119 device/console.h device/timer.h lib/string.h
120 $(CC) $(CFLAGS) $< -o $@
121
122 $(BUILD_DIR)/fs.o: fs/fs.c fs/fs.h fs/super_block.h fs/inode.h fs/dir.h \
123 lib/stdint.h lib/kernel/stdio-kernel.h lib/kernel/list.h lib/string.h \
124 device/ide.h kernel/global.h kernel/debug.h kernel/memory.h \
125 fs/file.h
126 $(CC) $(CFLAGS) $< -o $@
127
128 $(BUILD_DIR)/inode.o: fs/inode.c fs/inode.h lib/stdint.h lib/kernel/list.h \
129 kernel/global.h fs/fs.h device/ide.h thread/sync.h thread/thread.h \
130 lib/kernel/bitmap.h kernel/memory.h fs/file.h kernel/debug.h \
131 kernel/interrupt.h lib/kernel/stdio-kernel.h
132 $(CC) $(CFLAGS) $< -o $@
133
134 $(BUILD_DIR)/file.o: fs/file.c fs/file.h lib/stdint.h device/ide.h thread/sync.h \
135 lib/kernel/list.h kernel/global.h thread/thread.h lib/kernel/bitmap.h \
136 kernel/memory.h fs/fs.h fs/inode.h fs/dir.h lib/kernel/stdio-kernel.h \
137 kernel/debug.h kernel/interrupt.h
138 $(CC) $(CFLAGS) $< -o $@
139
140 $(BUILD_DIR)/dir.o: fs/dir.c fs/dir.h lib/stdint.h fs/inode.h lib/kernel/list.h \
141 kernel/global.h device/ide.h thread/sync.h thread/thread.h \
142 lib/kernel/bitmap.h kernel/memory.h fs/fs.h fs/file.h \
143 lib/kernel/stdio-kernel.h kernel/debug.h kernel/interrupt.h
144 $(CC) $(CFLAGS) $< -o $@
145
146 $(BUILD_DIR)/fork.o: userprog/fork.c userprog/fork.h thread/thread.h \
147 userprog/process.h kernel/memory.h kernel/interrupt.h kernel/debug.h \
148 lib/string.h fs/file.h
149 $(CC) $(CFLAGS) $< -o $@
150
151 $(BUILD_DIR)/shell.o: shell/shell.c shell/shell.h lib/stdint.h fs/fs.h \
152 lib/user/syscall.h lib/stdio.h lib/stdint.h kernel/global.h \
153 kernel/debug.h fs/fs.h shell/buildin_cmd.h
154 $(CC) $(CFLAGS) $< -o $@
155
156 $(BUILD_DIR)/buildin_cmd.o: shell/buildin_cmd.c shell/buildin_cmd.h lib/stdint.h \
157 lib/user/syscall.h lib/stdio.h lib/string.h kernel/global.h \
158 kernel/debug.h fs/fs.h fs/dir.h shell/shell.h
159 $(CC) $(CFLAGS) $< -o $@
160
161 ############## 汇编代码编译 ###############
162 $(BUILD_DIR)/kernel.o: kernel/kernel.S
163 $(AS) $(ASFLAGS) $< -o $@
164
165 $(BUILD_DIR)/print.o: lib/kernel/print.S
166 $(AS) $(ASFLAGS) $< -o $@
167
168 $(BUILD_DIR)/switch.o: thread/switch.S
169 $(AS) $(ASFLAGS) $< -o $@
170
171 ############## 链接所有目标文件 #############
172 $(BUILD_DIR)/kernel.bin: $(OBJS)
173 $(LD) $(LDFLAGS) $^ -o $@
174
175 .PHONY : mk_dir hd clean all
176
177 mk_dir:
178 if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
179
180 hd:
181 dd if=$(BUILD_DIR)/kernel.bin \
182 of=/home/zbb/bochs/hd60M.img \
183 bs=512 count=200 seek=9 conv=notrunc
184
185 clean:
186 cd $(BUILD_DIR) && rm -f ./*
187
188 build: $(BUILD_DIR)/kernel.bin
189
190 all: mk_dir build hd
得到结果:
well,尝试了一下,确实搞不懂到底哪儿出了问题导致每次在根目录下创建dir,都会赋no=15,哎算了,感觉也无伤大雅,希望后面不会有坑。
本章我们来看看fork(),准确说是sys_fork()的实现过程:
1.得到当前线程的PCB,并在内核空间开辟一页内存,存放fork出的子进程的PCB。
2.执行copy_process()——子进程拷贝父进程的资源:
因为内核空间是共享的,所以在内核空间开辟一页,作为数据从父进程用户空间复制到到子进程用户空间的中转。
接下来有五步:
(1)子进程复制父进程的PCB、虚拟地址位图、内核——copy_pcb_vaddrbitmap_stack0()——PCB的信息当然都存在于PCB中,只要一句memcpy()就搞定了,不过要把pid、status、parent_pid等部分单独修改一下;虚拟地址位图则需要在内核空间开辟一页将父进程的复制过来。
(2)为子进程创建页目录表——create_page_dir()。
(3)复制父进程进程体及用户栈给子进程——copy_body_stack3()——这时就要用到之前的中转页了:数据将从父进程用户空间 -> 内核中转页 -> 子进程用户空间。
(4)为子进程构建thread stack和修改返回值——build_child_stack()——子进程返回值位于intr stack的eax中,故eax=0;构建thread stack时ret_addr赋值为intr_exit,这样子进程稍后就可以假装从中断返回,获得上下文,像父进程一样从fork()的下一行开始执行。
(5)更新文件inode的打开数——update_inode_open_cnts()。
3.将进程添加到就绪队列和全局队列中。
4.将子进程的pid return给父进程。
释放此前的中转页。
参考博客:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库