Gos —— shell程序

fork原理

fork的作用主要是利用老进程克隆出来一个新进程并使新进程执行。所以fork可以简单的分为两步:

  • 复制进程资源
  • 跳过去执行
/**
 * @brief fork子进程
 * 
 * @return pid_t 成功,父进程返回子进程的pid;失败返回-1
 */
pid_t sys_fork(void);

当然,作为新进程其首先肯定是要在内存中有自己的空间的,所以我么首先要做的就是在内存中给他分配内存空间。

pid_t sys_fork(void)
{
    struct task_struct *parent_thread = running_thread();
    //为进程创建pcb
    struct task_struct *child_thread = get_kernel_pages(1);
    ...
	//复制进程体
	copy_process(child_thread, parent_thread);
	...
}

既然要复制资源,就必须明确进程有哪些资源:

  • 进程的PCB
  • 程序体,也就是代码段、数据段等等
  • 用户栈
  • 内核栈,在进入内核态时用其来保存上下文环境
  • 虚拟地址池,用于管理虚拟地址
  • 页表

上面这些过程就在函数copy_process中调用,其函数原型如下:

/**
 * @brief 给子进程拷贝父进程的资源
 * 
 * @param child_thread 子进程指针
 * @param parent_thread 父进程指针
 * @return uint32_t 成功返回0,失败返回-1 
 */
static uint32_t copy_process(struct task_struct *child_thread, struct task_struct *parent_thread);

其主要会执行以下的步骤:

  1. 复制父进程的pcb、虚拟地址位图、内核栈。这个函数会先复制父进程的PCB到子进程自己的内核空间,之后把pid、priority以及ticks等信息修改为自己的。之后,复制父进程虚拟地址池的位图
    //# 1.复制父进程的pcb、虚拟地址位图、内核栈
    if (copy_pcb_vaddrbitmap_stack0(child_thread, parent_thread) == -1)
    {
        //这种情况基本没有
        return -1;
    }
  1. 为子进程创建页表,此页表仅包括内核空间
    child_thread->pgdir = create_page_dir();
    if (child_thread->pgdir == NULL)
    {
        return -1;
    }
  1. 复制父进程的进程体给子进程。这一步是在父进程的空间中查找所有有数据的页,之后要做的就是把父进程的页拷贝到内核,再激活子进程的页表,再把这个页拷贝给子进程,之后再转回父进程,循环这个过程。
    copy_body_stack3(child_thread, parent_thread, buf_page);
  1. 构建子进程thread_stack和修改返回值。这里会通过子进程中断栈的eax寄存器先修改进程的返回值pid为0,再为switch_to函数构建线程栈,最后把构建thread_stack的栈顶作为switch_to恢复数据时的栈顶
    build_child_stack(child_thread);
  1. 更新文件的inode打开数。当子进程fork父进程的时候,它也会打开这些文件,所以这里主要是更新内核文件表的打开进程数加一。
    update_inode_open_cnts(child_thread);

实现Init进程

在操作系统中,所有的其他进程都是init进程的子进程。其实现比较简单:

  1. 调用fork派生子进程
  2. 父进程中打印自己的pid以及fork的返回值
/* init进程 */
void init(void)
{
    uint32_t ret_pid = fork();
    if (ret_pid)
    { // 父进程
        while (1)
            ;
    }
    else
    { // 子进程
        my_shell();
    }
    panic("init: should not be here");
}

为了保证init进程的pid为1,需要在main_thread之前建立,所以在初始线程的时候我们创建init线程。

/*
 * @brief 线程模块初始化
 * @note 主要是初始化线程队列和线程就绪队列
 * @note 为main函数创建线程
 */
void thread_init(void)
{
    put_str("thread init start...\n");
    list_init(&thread_ready_list);
    list_init(&thread_all_list);

    lock_init(&pid_lock);

    //创建第一个用户进程
    create_process(init, "init");

    //为main函数创建线程
    make_main_thread();

    idle_thread = thread_start("idle", 10, idle, NULL);
    put_str("thread init done!\n");
}

shell进程

我们可以看到在init进程中我们的子进程创建了shell程序。shell程序主要是获取用户输入,之后根据用户的输入执行相应的逻辑代码,这里篇幅有限只列举几个命令的实现原理:

/**
 * @brief shell程序,从init进程fork出来
 * 
 */
void my_shell(void)
{
    cwd_cache[0] = '/';
    while (1)
    {
        print_tips();
        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("shell: no parma input!\n");
            continue;
        }

        if (!strcmp("pwd", argv[0]))
        {
            in_pwd(argc, argv);
        }
        else if(!strcmp("cd",argv[0]))
        {
            in_cd(argc, argv);
        }
        else if (!strcmp("ls", argv[0]))
        {
            in_ls(argc, argv);
        }
        else if(!strcmp("ps",argv[0]))
        {
            in_ps(argc, argv);
        }
        else if(!strcmp("clear",argv[0]))
        {
            in_clear(argc, argv);
        }
        else if (!strcmp("mkdir", argv[0]))
        {
            in_mkdir(argc, argv);
        }
        else if (!strcmp("rmdir", argv[0]))
        {
            in_rmdir(argc, argv);
        }
        else if (!strcmp("mkfile", argv[0]))
        {
            in_mkfile(argc, argv);
        }
        else if (!strcmp("rm", argv[0]))
        {
            in_rm(argc, argv);
        }
    }
    panic("my_shell: should not be here");
}

pwd命令实现原理

pwd命令的作用是获取当前的路径,这个函数的主体实现在sys_getcwd函数中:

/**
 * @brief 得到当前工作路径并放到buf中
 * 
 * @param buf 存放路径的缓冲区,如果其为null,则内核自己分配一个空间
 * @param size buf的大小
 * @return char* 当buf为空的时候,内核分配的空间的地址。成功返回地址,失败返回null
 */
char *sys_getcwd(char *buf, uint32_t size);

这个函数的原理就是首先获取当前线程的工作路径,这是由一个变量cwd_inode_no保存的:

   int32_t child_inode_no = current_thread->cwd_inode_no; //得到当前默认工作路径

这样我们就能得到当前这个目录的文件目录表,文件目录表中保存了父目录的inode节点信息,得到inode节点号之后查表就可以得到父目录的文件名,这样不断的向上回溯,拼接字符串就可以得到。

//从子目录向上遍历,直到找到根目录
    while ((child_inode_no))
    {
        parent_inode_no = get_parent_dir_inode_no(child_inode_no, io_buf);
        if (get_child_dir_name(parent_inode_no, child_inode_no, full_path_reverse, io_buf) == -1)
        {
            //未找到子目录名字,失败退出
            sys_free(io_buf);
            return NULL;
        }
        child_inode_no = parent_inode_no;
    }

cd命令实现原理

cd命令的表现是更改当前进程的工作路径,实际上就是更改上面的变量cwd_inode_no。其主要实现函数原型如下:

/**
 * @brief cd命令,转换当前目录
 * 
 * @param argc 命令参数个数
 * @param argv 路径参数
 * @return char* 成功返回转到的路径名称,失败返回null
 */
char *in_cd(uint32_t argc, char **argv);

cd函数主要做两件事情:

  1. 根据传递进来的相对路径转换为绝对路径,这个过程主要就是查文件目录表。
        //解析当前路径
        make_clear_abs_path(argv[1], final_path);
  1. 更改当前进程的工作路径,这一步是调用函数chdir,其原理就是根据绝对路径找到这个目录的inode号,之后把这个inode号赋值给cwd_inode_no。
    int inode_no = search_file(path, &searched_record);
    if (inode_no != -1)
    {
        if (searched_record.file_type == FT_DIRECTORY)
        {
            running_thread()->cwd_inode_no = inode_no;
            ret = 0;
        }
        else
        {
            printk("sys_chdir: %s is not a directory!\n");
        }
    }
    dir_close(searched_record.parent_dir);

rm命令实现原理

rm命令主要是删除一个文件。其主要实现函数函数原型如下:

/**
 * @brief rm命令 删除文件
 * 
 * @param argc 
 * @param argv 
 * @return int32_t 
 */
int32_t in_rm(uint32_t argc, char **argv);

其要做的事情也是两件:

  1. 相对路径转换成绝对路径
make_clear_abs_path(argv[1], final_path);
  1. 删除文件,这个会调用unlink函数。这个函数会先保证文件存在且未被打开,之后根据文件的绝对路径,定位到这个文件的inode信息。之后调用delete_dir_entry函数在其父目录的目录文件表删除这个目录项信息,最后把这个文件的磁盘空间释放掉。
 //在父目录下删除此文件
 struct dir *parent_dir = searched_record.parent_dir;
 delete_dir_entry(current_partition, parent_dir, inode_no, io_buf);

参考文献

[1] 操作系统真相还原

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shenmingik

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值