当前位置: 首页 > Linux

从零开始编写OS内核——运行shell

时间:2023-04-06 11:16:30 Linux

系列目录前言准备工作BIOS启动到实模式GDT和保护模式虚拟内存加载并进入内核显示打印全局描述符表GDT中断处理虚拟内存完美实现heap和malloc是第一个运行多线程并切换锁和多线程同时进入用户态进程的内核线程。系统调用简单的文件系统加载可执行程序键盘驱动运行shellshell命令行这是本系列的最后一篇,为这个OS添加一个用户界面shell,算是Linux编程最入门的经典教科书项目,还有很多小教程也可以在互联网上找到。这里不浪费多少时间,只展示它的核心部分:voidprint_shell(){printf("bash>");}while(1){print_shell();while(1){int32c=read_char();如果(c=='\n'){run_program();休息;}elseif(c<128){printf(c);}}shell本质上只是一个shell,正如它的名字一样,它提供了一个和用户交互的命令行界面,等待用户输入字符并打印出来;一旦用户按下回车键,就意味着需要运行之前输入的命令行,在run_program函数中实现:voidrun_program(){//解析cmd并获取program和args。//..//(fork+exec)新程序。int32pid=fork();if(pid<0){printf("分叉失败");}elseif(pid>0){//父int32状态;等待(pid,&状态);}else{//childint32ret=exec(program,args_index,(char**)args);退出(退役);}}这里首先解析用户在回车之前输入的命令行字符串,解析得到可执行程序的名称和参数。然后是经典的fork+exec组合,运行程序。程序名和参数将传递给exec系统调用的处理函数process_exec,从磁盘中读取用户可执行文件并执行。这里命令行输入的程序名很简单,没有路径的概念,因为我们使用的naive_fs只有一层结构,所有文件都在最顶层,所以直接用文件名即可。fork后的父进程会调用wait系统调用阻塞等待子进程结束。关于wait和exit的系统调用,我在这个系列中没有详细展开,读者可以自行阅读源码。内核启动任务我们先来看看内核启动过程,后台启动了哪些任务,最后是如何进入shell界面的。这部分的代码在src/task/scheduler.c中。首先启动内核主进程/线程,它是最原始的祖先进程,会做如下事情:创建一个内核资源清理线程kernel_clean_thread,它是一个后台线程,我用它来进行进程/线程的最后回收资源。平时处于休眠状态,只有当某个进程/线程死亡需要清理时才会醒来;创建一个init进程和一个线程kernel_init_thread,它将成为第一个用户进程并运行用户程序init;在init程序中,我创建了一个shell进程,然后init进程进入阻塞状态;在实际的Linux系统中,真正的init进程应该也是作为后台任务,负责等待接管和回收所有孤儿进程(OrphanProcess),这里就不实现了,有兴趣的同学可以查资料学习;以上两个任务完成后,原来的线程就变成了cpu_idle线程。所谓cpuidle就是一条hlt指令,只有在系统确实没有任务运行时才会运行,它会让CPU进入低功耗运行状态;当然,以上只是我个人实现的启动内核任务,和linux有点类似但又不完全相同;这个其实很随意,毕竟是自己写的一个玩具OS而已,linux之道没有标准答案。它只需要让系统的重要任务顺利运行并被调度即可。