代码、内容参考来自于包括《操作系统真象还原》、《一个64位操作系统的设计与实现》以及《ORANGE’S:一个操作系统的实现》。
当一个父进程创建一个子进程来执行某项任务时,父进程可能需要知道子进程的退出状态。子进程完成其任务后,会将其退出状态保存在pcb中并调用exit退出。此时,子进程的pcb不会被立即回收,因为它包含了子进程的退出状态。只有当父进程通过wait系统调用来查询子进程的状态时,子进程的pcb才会被回收。
孤儿进程:如果一个父进程在其子进程结束之前退出,那么这些子进程将被称为孤儿进程,也就是说没有父进程来回收他们的pcb资源。为了防止资源浪费,这些孤儿进程会被init进程“领养”,即成为init进程的子进程,由init来回收他们的pcb。
僵尸进程:当一个子进程终止,但其父进程没有调用wait来回收其资源时,此时这个子进程也无法过继给init,于是这个子进程就变成了僵尸进程。它们仍然占用pcb,但不执行任何操作。僵尸进程的存在可能会导致资源浪费。
1.修改pcb
pcb增加表示退出状态的成员
修改thread/thread.h
/* 进程或线程的pcb,程序控制块 */
struct task_struct {
uint32_t* self_kstack; // 各内核线程都用自己的内核栈
pid_t pid;
enum task_status status;
char name[TASK_NAME_LEN];
uint8_t priority;
uint8_t ticks; // 每次在处理器上执行的时间嘀嗒数
/* 此任务自上cpu运行后至今占用了多少cpu嘀嗒数,
* 也就是此任务执行了多久*/
uint32_t elapsed_ticks;
/* general_tag的作用是用于线程在一般的队列中的结点 */
struct list_elem general_tag;
/* all_list_tag的作用是用于线程队列thread_all_list中的结点 */
struct list_elem all_list_tag;
uint32_t* pgdir; // 进程自己页表的虚拟地址
struct virtual_addr userprog_vaddr; // 用户进程的虚拟地址
struct mem_block_desc u_block_desc[DESC_CNT]; // 用户进程内存块描述符
int32_t fd_table[MAX_FILES_OPEN_PER_PROC]; // 已打开文件数组
uint32_t cwd_inode_nr; // 进程所在的工作目录的inode编号
pid_t parent_pid; // 父进程pid
int8_t exit_status; // 进程结束时自己调用exit传入的参数
uint32_t stack_magic; // 用这串数字做栈的边界标记,用于检测栈的溢出
};我们需要一个内存释放的函数
修改kernel/memory.c
/* 根据物理页框地址pg_phy_addr在相应的内存池的位图清0,不改动页表*/
void free_a_phy_page(uint32_t pg_phy_addr) {
struct pool* mem_pool;
uint32_t bit_idx = 0;
if (pg_phy_addr >= user_pool.phy_addr_start) {
mem_pool = &user_pool;
bit_idx = (pg_phy_addr - user_pool.phy_addr_start) / PG_SIZE;
} else {
mem_pool = &kernel_pool;
bit_idx = (pg_phy_addr - kernel_pool.phy_addr_start) / PG_SIZE;
}
bitmap_set(&mem_pool->pool_bitmap, bit_idx, 0);
}free_a_phy_page函数根据物理页框地址 pg_phy_addr在相应的内存池的位图清0,此函数并不会改动页表。 判断它所属的物理内存池,算出与物理内存池的起始物理地址的差,使差再除以PG_SIZE,所得的商便是在位图中的索引bit_idx,,最后调用bitmap_set在相应物理内存池中将 bit_idx 置为 0。
修改kernel/memory.h添加函数声明
void free_a_phy_page(uint32_t pg_phy_addr);
修改thread/thread.c
定义了pid池,池中包含了位图,由位图去管理pid的分配与释放。
/* pid的位图,最大支持1024个pid */
uint8_t pid_bitmap_bits[128] = {0};
/* pid池 */
struct pid_pool {
struct bitmap pid_bitmap; // pid位图
uint32_t pid_start; // 起始pid
struct lock pid_lock; // 分配pid锁
}pid_pool;
接着修改thread/thread.c
pid_pool_init是初始化pid_poo,对位图、起始pid、锁进行了具体初始化工作。
allocate_pid函数用于分配pid
release_pid函数用于释放pid。
/* 初始化pid池 */
static void pid_pool_init(void) {
pid_pool.pid_start = 1;
pid_pool.pid_bitmap.bits = pid_bitmap_bits;
pid_pool.pid_bitmap.btmp_bytes_len = 128;
bitmap_init(&pid_pool.pid_bitmap);
lock_init(&pid_pool.pid_lock);
}
/* 分配pid */
static pid_t allocate_pid(void) {
lock_acquire(&pid_pool.pid_lock);
int32_t bit_idx = bitmap_scan(&pid_pool.pid_bitmap, 1);
bitmap_set(&pid_pool.pid_bitmap, bit_idx, 1);
lock_release(&pid_pool.pid_lock);
return (bit_idx + pid_pool.pid_start);
}
/* 释放pid */
void release_pid(pid_t pid) {
lock_acquire(&pid_pool.pid_lock);
int32_t bit_idx = pid - pid_pool.pid_start;
bitmap_set(&pid_pool.pid_bitmap, bit_idx, 0);
lock_release(&pid_pool.pid_lock);
}
同样在thread.c
thread_exit 接受2个参数,待退出的任务thread_over. 是否要调度标记need_schedule,功能是回收 thread_over 的 pcb和页表,并将其从调度队列中去除。
pid_check函数是函数listr_traversal的回调函数,它用于比对任务的pid,找到特定pid的任务就返回。
函数pid2thread接受1个参数,任务的pid,功能是根据pid找pcb,若找到则返回该pcb,否则返回NULL,其原理是调用list_traversal遍历全部队列中的所有任务,通过回调函数pid_check 过滤出特定 pid的任务。
thread_init函数修改调用pid_pool_init()
/* 回收thread_over的pcb和页表,并将其从调度队列中去除 */
void thread_exit(struct task_struct* thread_over, bool need_schedule) {
/* 要保证schedule在关中断情况下调用 */
intr_disable();
thread_over->status = TASK_DIED;
/* 如果thread_over不是当前线程,就有可能还在就绪队列中,将其从中删除 */
if (elem_find(&thread_ready_list, &thread_over->general_tag)) {
list_remove(&thread_over->general_tag);
}
if (thread_over->pgdir) { // 如是进程,回收进程的页表
mfree_page(PF_KERNEL, thread_over->pgdir, 1);
}
/* 从all_thread_list中去掉此任务 */
list_remove(&thread_over->all_list_tag);
/* 回收pcb所在的页,主线程的pcb不在堆中,跨过 */
if (thread_over != main_thread) {
mfree_page(PF_KERNEL, thread_over, 1);
}
/* 归还pid */
release_pid(thread_over->pid);
/* 如果需要下一轮调度则主动调用schedule */
if (need_schedule) {
schedule();
PANIC("thread_exit: should not be here\n");
}
}
/* 比对任务的pid */
static bool pid_check(struct list_elem* pelem, int32_t pid) {
struct task_struct* pthread = elem2entry(struct task_struct, all_list_tag, pelem);
if (pthread->pid == pid) {
return true;
}
return false;
}
/* 根据pid找pcb,若找到则返回该pcb,否则返回NULL */
struct task_struct* pid2thread(int32_t pid) {
struct list_elem* pelem = list_traversal(&thread_all_list, pid_check, pid);
if (pelem == NULL) {
return NULL;
}
struct task_struct* thread = elem2entry(struct task_struct, all_list_tag, pelem);
return thread;
}
/* 初始化线程环境 */
void thread_init(void) {
put_str("thread_init start\n");
list_init(&thread_ready_list);
list_init(&thread_all_list);
pid_pool_init();
/* 先创建第一个用户进程:init */
process_execute(init, "init"); // 放在第一个初始化,这是第一个进程,init进程的pid为1
/* 将当前main函数创建为线程 */
make_main_thread();
/* 创建idle线程 */
idle_thread = thread_start("idle", 10, idle, NULL);
put_str("thread_init done\n");
}
对应thread.h加入函数声明
void thread_exit(struct task_struct* thread_over, bool need_schedule); struct task_struct* pid2thread(int32_t pid); void release_pid(pid_t pid);
2.实现wait和exit
Linux中 exit 的系统调用是_exit,其原型是”void _exit(int status)”,其中status 是返回的状态值,是子进程代入的参数。
wait的原型是”pid_t wait(int *status)”,其中status是父进程传入的地址,该地址空间用于接收子进程返回值,成功则返回子进程的pid,失败则返回-1。
新建/userprog/wait_exit.c
#include "wait_exit.h"
#include "global.h"
#include "debug.h"
#include "thread.h"
#include "list.h"
#include "stdio-kernel.h"
#include "memory.h"
#include "bitmap.h"
#include "fs.h"
/* 释放用户进程资源:
* 1 页表中对应的物理页
* 2 虚拟内存池占物理页框
* 3 关闭打开的文件 */
static void release_prog_resource(struct task_struct* release_thread) {
uint32_t* pgdir_vaddr = release_thread->pgdir;
uint16_t user_pde_nr = 768, pde_idx = 0;
uint32_t pde = 0;
uint32_t* v_pde_ptr = NULL; // v表示var,和函数pde_ptr区分
uint16_t user_pte_nr = 1024, pte_idx = 0;
uint32_t pte = 0;
uint32_t* v_pte_ptr = NULL; // 加个v表示var,和函数pte_ptr区分
uint32_t* first_pte_vaddr_in_pde = NULL; // 用来记录pde中第0个pte的地址
uint32_t pg_phy_addr = 0;
/* 回收页表中用户空间的页框 */
while (pde_idx < user_pde_nr) {
v_pde_ptr = pgdir_vaddr + pde_idx;
pde = *v_pde_ptr;
if (pde & 0x00000001) { // 如果页目录项p位为1,表示该页目录项下可能有页表项
first_pte_vaddr_in_pde = pte_ptr(pde_idx * 0x400000); // 一个页表表示的内存容量是4M,即0x400000
pte_idx = 0;
while (pte_idx < user_pte_nr) {
v_pte_ptr = first_pte_vaddr_in_pde + pte_idx;
pte = *v_pte_ptr;
if (pte & 0x00000001) {
/* 将pte中记录的物理页框直接在相应内存池的位图中清0 */
pg_phy_addr = pte & 0xfffff000;
free_a_phy_page(pg_phy_addr);
}
pte_idx++;
}
/* 将pde中记录的物理页框直接在相应内存池的位图中清0 */
pg_phy_addr = pde & 0xfffff000;
free_a_phy_page(pg_phy_addr);
}
pde_idx++;
}
/* 回收用户虚拟地址池所占的物理内存*/
uint32_t bitmap_pg_cnt = (release_thread->userprog_vaddr.vaddr_bitmap.btmp_bytes_len) / PG_SIZE;
uint8_t* user_vaddr_pool_bitmap = release_thread->userprog_vaddr.vaddr_bitmap.bits;
mfree_page(PF_KERNEL, user_vaddr_pool_bitmap, bitmap_pg_cnt);
/* 关闭进程打开的文件 */
uint8_t fd_idx = 3;
while(fd_idx < MAX_FILES_OPEN_PER_PROC) {
if (release_thread->fd_table[fd_idx] != -1) {
sys_close(fd_idx);
}
fd_idx++;
}
}
/* list_traversal的回调函数,
* 查找pelem的parent_pid是否是ppid,成功返回true,失败则返回false */
static bool find_child(struct list_elem* pelem, int32_t ppid) {
/* elem2entry中间的参数all_list_tag取决于pelem对应的变量名 */
struct task_struct* pthread = elem2entry(struct task_struct, all_list_tag, pelem);
if (pthread->parent_pid == ppid) { // 若该任务的parent_pid为ppid,返回
return true; // list_traversal只有在回调函数返回true时才会停止继续遍历,所以在此返回true
}
return false; // 让list_traversal继续传递下一个元素
}
/* list_traversal的回调函数,
* 查找状态为TASK_HANGING的任务 */
static bool find_hanging_child(struct list_elem* pelem, int32_t ppid) {
struct task_struct* pthread = elem2entry(struct task_struct, all_list_tag, pelem);
if (pthread->parent_pid == ppid && pthread->status == TASK_HANGING) {
return true;
}
return false;
}
/* list_traversal的回调函数,
* 将一个子进程过继给init */
static bool init_adopt_a_child(struct list_elem* pelem, int32_t pid) {
struct task_struct* pthread = elem2entry(struct task_struct, all_list_tag, pelem);
if (pthread->parent_pid == pid) { // 若该进程的parent_pid为pid,返回
pthread->parent_pid = 1;
}
return false; // 让list_traversal继续传递下一个元素
}
/* 等待子进程调用exit,将子进程的退出状态保存到status指向的变量.
* 成功则返回子进程的pid,失败则返回-1 */
pid_t sys_wait(int32_t* status) {
struct task_struct* parent_thread = running_thread();
while(1) {
/* 优先处理已经是挂起状态的任务 */
struct list_elem* child_elem = list_traversal(&thread_all_list, find_hanging_child, parent_thread->pid);
/* 若有挂起的子进程 */
if (child_elem != NULL) {
struct task_struct* child_thread = elem2entry(struct task_struct, all_list_tag, child_elem);
*status = child_thread->exit_status;
/* thread_exit之后,pcb会被回收,因此提前获取pid */
uint16_t child_pid = child_thread->pid;
/* 2 从就绪队列和全部队列中删除进程表项*/
thread_exit(child_thread, false); // 传入false,使thread_exit调用后回到此处
/* 进程表项是进程或线程的最后保留的资源, 至此该进程彻底消失了 */
return child_pid;
}
/* 判断是否有子进程 */
child_elem = list_traversal(&thread_all_list, find_child, parent_thread->pid);
if (child_elem == NULL) { // 若没有子进程则出错返回
return -1;
} else {
/* 若子进程还未运行完,即还未调用exit,则将自己挂起,直到子进程在执行exit时将自己唤醒 */
thread_block(TASK_WAITING);
}
}
}
/* 子进程用来结束自己时调用 */
void sys_exit(int32_t status) {
struct task_struct* child_thread = running_thread();
child_thread->exit_status = status;
if (child_thread->parent_pid == -1) {
PANIC("sys_exit: child_thread->parent_pid is -1\n");
}
/* 将进程child_thread的所有子进程都过继给init */
list_traversal(&thread_all_list, init_adopt_a_child, child_thread->pid);
/* 回收进程child_thread的资源 */
release_prog_resource(child_thread);
/* 如果父进程正在等待子进程退出,将父进程唤醒 */
struct task_struct* parent_thread = pid2thread(child_thread->parent_pid);
if (parent_thread->status == TASK_WAITING) {
thread_unblock(parent_thread);
}
/* 将自己挂起,等待父进程获取其status,并回收其pcb */
thread_block(TASK_HANGING);
}release_prog_resource函数接受1个参数,待释放的任务release_thread,功能是释放任务的资源,资源包括:页表中的物理页、虚拟内存池占物理页、关闭打开的文件。函数中先完成的工作是回收页表中的物理页框,直接遍历页表,如果页表的p位为1,这说明已经分配了物理页框。
uint16_t user_pde_nr = 768, pde_idx = 0的变量user_pde_nr表示用户空间中pde的数量,其值为768,pde_idx表示pde的索引值,从0起。
uint16_t user_pte_nr = 1024, pte_idx = 0的变量user_pte_nr表示每个页表中pte的数据,其值为1024,pte_idx表示pte的索引值,从0起。
uint32_t* first_pte_vaddr_in_pde = NULL的变量first_pte_vaddr_in_pde表示pde中第0个pte的地址,主要是用它来遍历页表中所有pte。
while (pde_idx < user_pde_nr)通过两层while循环回收页表中用户空间的页框,大体上是在外层循环中判断页目录中的pde,如果pde的p位为1,表示该pde中可能会有页表,原因是回收内存空间时,页表中的pte很可能被回收干净了,但该页表所在的pde并不释放,也就是说pde中的页表地址还在。一个页表能表示的内存范围是1024*4KB=4MB,一个pde便表示一个页表,故我们可以根据当前是第几个pde,即pde_idx的值,推算出虚拟地址范围。
first_pte_vaddr_in_pde通过pte_ptr函数获取第pde_idx个页表中第0个pte的虚拟地址。目的是通过虚拟地址遍历页表中的所有pte。内层循环用来遍历每一个pte,如果pte的P位为1,表示已分配了物理页,将其通过free_a_phy_page回收。
接下来是回收用户虚拟地址池所占的物理内存,最后关闭进程打开的文件。
函数find_child是list_traversal的回调函数,功能是查找pelem的parent_pid是否是ppid,成功返回true,失败则返回false。
函数find_hanging_child是专门找状态为TASK_HANGING的子进程。
函数init_adopt_a_child也是 list_traversal的回调函数,功能是将parent_pid等于pid的进程过继给init,使 init 作为该进程的父进程。
sys_wait 函数,等待子进程调用 exit,将子进程的退出状态保存到 status 指向的变量
步骤如下:
- 函数开头先调用running_thread获得当前任务,也就是父进程parent_thread,接着是一个while循环,通过 list_traversal 在全部队列 thread_all_list 中遍历,通过回调函数 find_hanging_child 过滤出 进程pid (parent_pid)为parent_thread->pid,并且status为TASK_HANGING的子进程。此处是为了优先处理已经退出的进程。
- 如果有已退出的子进程,先从子进程的exit_status 中获取子进程的状态到status中,然后获取子进程的pid到child_pid中,随后调用thread_exit把子进程从队列中删除,这里传给thread_exit的第二个参数是false,即表示调用thread_exit后还要回来,
- 如果没有已退出的子进程,这时候再遍历一次查看是否有子进程,如果没有,返回-1,如果有子进程,此时说明它的状态必然不是TASK_HANGING,也就是说子进程尚未调用exi,因此执行”thread_block(TASK_WAITING)”阻塞自己,直到子进程执行exit时把自己唤醒。
sys_exit子进程用来结束自己,退出时的事项:
- 函数开头先调用 running_thread 获得自己的 pcb,即child_thread,随后将status存入自己pcb 的 exit_status 中。
- 当前退出的进程有可能还有子进程,于是在调用list_raversal遍历全部队列thread_all_list,通过回调函数init_adopt_a_child将自己的子进程全部过继给init。
- 调用release_prog_resource释放自己除了pcb以外的资源,pcb中的exit_status父进程还没来收走,因此pcb得由父进程在调用wait获取其状态时再回收了。
- 通过函数pid2thread获得自己的父进程parent_thread,判断父进程是否正在等待子 进程退出,如果父进程正在等待自己,其状态status应该为TASK_WAITING,于是通过 “thread_unblock(parent_thread)”把父进程唤醒。
- 最后通过 thread_block 将自己挂起,并将自己的状态置为 TASK_HANGING,这样父进程便知道子进程已经退出了,可以获取退出状态值,并回收自己的pcb。
对应/userprog/wait_exit.h加入函数声明
#ifndef __USERPROG_WAITEXIT_H #define __USERPROG_WAITEXIT_H #include "thread.h" pid_t sys_wait(int32_t* status); void sys_exit(int32_t status); #endif
然后将sys_wait与sys_exit封装成系统调用
/lib/user/syscall.c
/* 以状态status退出 */
void exit(int32_t status) {
_syscall1(SYS_EXIT, status);
}
/* 等待子进程,子进程状态存储到status */
pid_t wait(int32_t* status) {
return _syscall1(SYS_WAIT, status);
}
/lib/user/syscall.h
enum SYSCALL_NR {
SYS_GETPID,
SYS_WRITE,
SYS_MALLOC,
SYS_FREE,
SYS_FORK,
SYS_READ,
SYS_PUTCHAR,
SYS_CLEAR,
SYS_GETCWD,
SYS_OPEN,
SYS_CLOSE,
SYS_LSEEK,
SYS_UNLINK,
SYS_MKDIR,
SYS_OPENDIR,
SYS_CLOSEDIR,
SYS_CHDIR,
SYS_RMDIR,
SYS_READDIR,
SYS_REWINDDIR,
SYS_STAT,
SYS_PS,
SYS_EXECV,
SYS_EXIT,
SYS_WAIT
};void exit(int32_t status); pid_t wait(int32_t* status);
/userprog/syscall-init.c
/* 初始化系统调用 */
void syscall_init(void) {
put_str("syscall_init start\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
syscall_table[SYS_FORK] = sys_fork;
syscall_table[SYS_READ] = sys_read;
syscall_table[SYS_PUTCHAR] = sys_putchar;
syscall_table[SYS_CLEAR] = cls_screen;
syscall_table[SYS_GETCWD] = sys_getcwd;
syscall_table[SYS_OPEN] = sys_open;
syscall_table[SYS_CLOSE] = sys_close;
syscall_table[SYS_LSEEK] = sys_lseek;
syscall_table[SYS_UNLINK] = sys_unlink;
syscall_table[SYS_MKDIR] = sys_mkdir;
syscall_table[SYS_OPENDIR] = sys_opendir;
syscall_table[SYS_CLOSEDIR] = sys_closedir;
syscall_table[SYS_CHDIR] = sys_chdir;
syscall_table[SYS_RMDIR] = sys_rmdir;
syscall_table[SYS_READDIR] = sys_readdir;
syscall_table[SYS_REWINDDIR] = sys_rewinddir;
syscall_table[SYS_STAT] = sys_stat;
syscall_table[SYS_PS] = sys_ps;
syscall_table[SYS_EXECV] = sys_execv;
syscall_table[SYS_EXIT] = sys_exit;
syscall_table[SYS_WAIT] = sys_wait;
put_str("syscall_init done\n");
}
将exit函数集成到运行库中。这样,即使程序中没有明确调用exit,它也会在程序结束时自动被调用,与_start相同。需要特别注意的是,子进程的退出机制与普通的函数返回机制不同。当子进程终止时,它并不是“返回”给其父进程;相反,它只是简单地结束了自己的执行。父进程和子进程在内存地址空间和执行上下文中是完全独立的,子进程不可能按照常规的函数调用方式“返回”一个值给父进程(做到这点需要其他的进程间通信机制支持)。取而代之的是,子进程提供一个退出状态,来描述其终止的方式或原因。因此,这里的push eax并不是我们在普通函数调用中看到的那种返回值——比如一个指针或某种计算结果。实际上,它代表了子进程的结束状态,就像我们在每个main函数中写的return 0一样。
修改/command/start.S
[bits 32] extern main extern exit section .text global _start _start: ;下面这两个要和execv中load之后指定的寄存器一致 push ebx ;压入argv push ecx ;压入argc call main ;将main的返回值通过栈传给exit,gcc用eax存储返回值,这是ABI规定的 push eax call exit ;exit不会返回
3.实现cat命令
cat命令用于查看文件
/command/cat.c
#include "syscall.h"
#include "stdio.h"
#include "string.h"
int main(int argc, char** argv) {
if (argc > 2 || argc == 1) {
printf("cat: only support 1 argument.\neg: cat filename\n");
exit(-2);
}
int buf_size = 1024;
char abs_path[512] = {0};
void* buf = malloc(buf_size);
if (buf == NULL) {
printf("cat: malloc memory failed\n");
return -1;
}
if (argv[1][0] != '/') {
getcwd(abs_path, 512);
strcat(abs_path, "/");
strcat(abs_path, argv[1]);
} else {
strcpy(abs_path, argv[1]);
}
int fd = open(abs_path, O_RDONLY);
if (fd == -1) {
printf("cat: open: open %s failed\n", argv[1]);
return -1;
}
int read_bytes= 0;
while (1) {
read_bytes = read(fd, buf, buf_size);
if (read_bytes == -1) {
break;
}
write(1, buf, read_bytes);
}
free(buf);
close(fd);
return 66;
}通过malloc从堆中申请了1024字节的内存用作缓冲区buf,512字节的abs_path用于存储参数的绝对路径。后面处理参数文件的路径为绝对路径存入到 abs_buf中。通过open打开参数文件,循环读取文件,然后通过write输出,直到read返回值为-1,也就是一直读到文件尾。最后释放 buf 并关闭参数文件,把 66 作为返回值返回。
接下来就是编译文件了
/command/compile.sh
#!/bin/bash
if [[ ! -d "../lib" || ! -d "../build" ]];then
echo "dependent dir don\`t exist!"
cwd=$(pwd)
cwd=${cwd##*/}
cwd=${cwd%/}
if [[ $cwd != "command" ]];then
echo -e "you\`d better in command dir\n"
fi
exit
fi
CC="gcc"
BIN="cat"
CFLAGS="-Wall -c -fno-builtin -W -Wstrict-prototypes \
-Wmissing-prototypes -Wsystem-headers -m32 -fno-stack-protector"
LIBS="-I ../lib/ -I ../lib/kernel/ -I ../lib/user/ -I \
../kernel/ -I ../device/ -I ../thread/ -I \
../userprog/ -I ../fs/ -I ../shell/"
OBJS="../build/string.o ../build/syscall.o \
../build/stdio.o ../build/assert.o start.o"
DD_IN=$BIN
DD_OUT="/bochs/bin/dreams.img"
nasm -f elf ./start.S -o ./start.o
ar rcs simple_crt.a $OBJS start.o
$CC $CFLAGS $LIBS -o $BIN".o" $BIN".c"
ld $BIN".o" simple_crt.a -o $BIN -m elf_i386
SEC_CNT=$(ls -l $BIN|awk '{printf("%d", ($5+511)/512)}')
if [[ -f $BIN ]];then
dd if=./$DD_IN of=$DD_OUT bs=512 \
count=$SEC_CNT seek=300 conv=notrunc
fi在command 目录下执行,当前 command目录下就生成了cat 命令
修改shell/shell.c
/* 简单的shell */
void my_shell(void) {
cwd_cache[0] = '/';
while (1) {
print_prompt();
memset(final_path, 0, MAX_PATH_LEN);
memset(cmd_line, 0, MAX_PATH_LEN);
readline(cmd_line, MAX_PATH_LEN);
if (cmd_line[0] == 0) { // 若只键入了一个回车
continue;
}
argc = -1;
argc = cmd_parse(cmd_line, argv, ' ');
if (argc == -1) {
printf("num of arguments exceed %d\n", MAX_ARG_NR);
continue;
}
if (!strcmp("ls", argv[0])) {
buildin_ls(argc, argv);
} else if (!strcmp("cd", argv[0])) {
if (buildin_cd(argc, argv) != NULL) {
memset(cwd_cache, 0, MAX_PATH_LEN);
strcpy(cwd_cache, final_path);
}
} else if (!strcmp("pwd", argv[0])) {
buildin_pwd(argc, argv);
} else if (!strcmp("ps", argv[0])) {
buildin_ps(argc, argv);
} else if (!strcmp("clear", argv[0])) {
buildin_clear(argc, argv);
} else if (!strcmp("mkdir", argv[0])){
buildin_mkdir(argc, argv);
} else if (!strcmp("rmdir", argv[0])){
buildin_rmdir(argc, argv);
} else if (!strcmp("rm", argv[0])) {
buildin_rm(argc, argv);
} else { // 如果是外部命令,需要从磁盘上加载
int32_t pid = fork();
if (pid) { // 父进程
int32_t status;
int32_t child_pid = wait(&status); // 此时子进程若没有执行exit,my_shell会被阻塞,不再响应键入的命令
if (child_pid == -1) { // 按理说程序正确的话不会执行到这句,fork出的进程便是shell子进程
panic("my_shell: no child\n");
}
printf("child_pid %d, it's status: %d\n", child_pid, status);
} else { // 子进程
make_clear_abs_path(argv[0], final_path);
argv[0] = final_path;
/* 先判断下文件是否存在 */
struct stat file_stat;
memset(&file_stat, 0, sizeof(struct stat));
if (stat(argv[0], &file_stat) == -1) {
printf("my_shell: cannot access %s: No such file or directory\n", argv[0]);
exit(-1);
} else {
execv(argv[0], argv);
}
}
}
int32_t arg_idx = 0;
while(arg_idx < MAX_ARG_NR) {
argv[arg_idx] = NULL;
arg_idx++;
}
}
panic("my_shell: should not be here");
}修改main文件
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "process.h"
#include "syscall-init.h"
#include "syscall.h"
#include "stdio.h"
#include "memory.h"
#include "dir.h"
#include "fs.h"
#include "assert.h"
#include "shell.h"
#include "ide.h"
#include "stdio-kernel.h"
void init(void);
int main(void) {
put_str("I am kernel\n");
init_all();
/************* 写入应用程序 *************/
uint32_t file_size = 10352;
uint32_t sec_cnt = DIV_ROUND_UP(file_size, 512);
struct disk* sda = &channels[0].devices[0];
void* prog_buf = sys_malloc(file_size);
ide_read(sda, 300, prog_buf, sec_cnt);
int32_t fd = sys_open("/cat", O_CREAT|O_RDWR);
if (fd != -1) {
if(sys_write(fd, prog_buf, file_size) == -1) {
printk("file write error!\n");
while(1);
}
}
/************* 写入应用程序结束 *************/
cls_screen();
console_put_str("[Dreams@localhost /]$ ");
thread_exit(running_thread(), true);
return 0;
}
/* init进程 */
void init(void) {
uint32_t ret_pid = fork();
if(ret_pid) { // 父进程
int status;
int child_pid;
/* init在此处不停的回收僵尸进程 */
while(1) {
child_pid = wait(&status);
printf("I`m init, My pid is 1, I recieve a child, It`s pid is %d, status is %d\n", child_pid, status);
}
} else { // 子进程
my_shell();
}
panic("init: should not be here");
}执行结果

4.参考
郑钢著操作系统真象还原
田宇著一个64位操作系统的设计与实现
丁渊著ORANGE’S:一个操作系统的实现


