手写操作系统(六十二)-实现系统调用wait和exit

代码、内容参考来自于包括《操作系统真象还原》、《一个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:一个操作系统的实现

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇