现在该为我们的内核编写第一个单独的程序-shell。 它会存储在单独的.elf文件中,并在内核启动时由init进程启动。
这是我们操作系统开发周期中的最后一篇文章。
目录
构建系统(make,gcc,gas)。 初始引导(多次引导)。 启动(qemu)。 C库(strcpy,memcpy,strext)。
C库(sprintf,strcpy,strcmp,strtok,va_list ...)。 以内核模式和用户应用程序模式构建库。
内核系统日志。 显存 输出到终端(kprintf,kpanic,kassert)。
动态内存,堆(kmalloc,kfree)。
内存和中断处理的组织(GDT,IDT,PIC,syscall)。 例外情况
虚拟内存(页面目录和页面表)。
过程。 策划人 多任务处理。 系统调用(kill,exit,ps)。
字符设备驱动程序。 系统调用(ioctl,fopen,fread,fwrite)。 C库(fopen,fclose,fprintf,fscanf)。
内核(initrd),elf及其内部文件系统。 系统调用(执行)。 Shell作为内核的完整程序。用户保护模式(ring3)。 任务状态段(tss)。
Shell作为完整的内核程序
在上一篇文章中,我们研究了字符设备驱动程序,并编写了一个终端驱动程序。
现在,我们拥有创建第一个控制台应用程序所需的一切。
我们将编写控制台应用程序本身,然后将其编译为单独的elf。
void start() { u_int errno; stdio_init(); errno = main(); stdio_deinit(); exit(errno); }
我们将需要初始化标准库,并将控制权转移到熟悉的main函数。
int main() { char cmd[255]; while (1) { printf(prompt); flush(); scanf(cmd); if (!execute_command(cmd)) { break; } } return 0; }
在循环的更深处,我们只需读取该行并执行命令即可。
如果有参数,则通过strtok_r进行Parsim命令。
static bool execute_command(char* cmd) { if (!strcmp(cmd, cmd_ps)) { struct clist_definition_t *task_list; task_list = ps(); printf(" -- process list\n"); clist_for_each(task_list, print_task_info); } else if (!strcmp(cmd, cmd_clear)) { clear(); flush(); } else if (!strncmp(cmd, cmd_kill, strlen(cmd_kill))) { char* save_ptr = null; strtok_r(cmd, " ", &save_ptr); char* str_tid = strtok_r(null, " ", &save_ptr); u_short tid = atou(str_tid); if (!kill(tid)) { printf(" There is no process with pid %u\n", tid); }; } else if (!strncmp(cmd, cmd_exit, strlen(cmd_exit))) { clear(); printf(prompt); flush(); return false; } else if (!strncmp(cmd, cmd_exec, strlen(cmd_exec))) { char* save_ptr = null; strtok_r(cmd, " ", &save_ptr); char* str_file = strtok_r(null, " ", &save_ptr); exec(str_file); } else if (!strncmp(cmd, cmd_dev, strlen(cmd_dev))) { struct clist_definition_t *dev_list; dev_list = devs(); printf(" -- device list\n"); clist_for_each(dev_list, print_dev_info); } else { printf(" There is no such command.\n Available command list:\n"); printf(" %s %s %s <pid> %s <file.elf> %s %s\n", cmd_ps, cmd_exit, cmd_kill, cmd_exec, cmd_clear, cmd_dev); } return true; }
实际上,我们只是在拉系统调用。
让我提醒您有关标准库的初始化。
在上一课中,我们在库中编写了以下函数:
extern void stdio_init() { stdin = fopen(tty_dev_name, MOD_R); stdout = fopen(tty_dev_name, MOD_W); asm_syscall(SYSCALL_IOCTL, stdout, IOCTL_INIT); asm_syscall(SYSCALL_IOCTL, stdin, IOCTL_READ_MODE_LINE); asm_syscall(SYSCALL_IOCTL, stdin, IOCTL_READ_MODE_ECHO); }
它只是打开用于读取和写入的特殊终端驱动程序文件,该文件对应于键盘输入和输出到屏幕。
在用外壳组装精灵之后,必须将其放置在原始内核文件系统(initrd)上。
初始ram磁盘由内核加载程序作为多重引导模块加载,因此我们知道initrd内存中的地址。
根据James Molloy的文章,仍然很容易为initrd组织文件系统。
因此,格式如下:
extern struct initrd_node_t { unsigned char magic; char name[8]; unsigned int offset; unsigned int length; }; extern struct initrd_fs_t { int count; struct initrd_node_t node[INITRD_MAX_FILES]; };
接下来,请记住32位elf的格式。
struct elf_header_t { struct elf_header_ident_t e_ident; u16 e_type; u16 e_machine; u32 e_version; u32 e_entry; u32 e_phoff; u32 e_shoff; u32 e_flags; u16 e_ehsize; u16 e_phentsize; u16 e_phnum; u16 e_shentsize; u16 e_shnum; u16 e_shstrndx; };
在这里,我们对程序头表的入口和地址感兴趣。
代码和数据部分将是第一个标题,而堆栈部分将是第二个标题(根据通过objdump研究elf的结果)。
struct elf_program_header_t { u32 p_type; u32 p_offset; u32 p_vaddr; u32 p_paddr; u32 p_filesz; u32 p_memsz; u32 p_flags; u32 p_align; } attribute(packed);
该信息足以编写elf文件加载器。
我们已经知道如何为自定义流程选择页面。
因此,我们只需要为标题分配足够数量的页面并将内容复制到其中即可。
我们将编写一个函数,该函数将基于已解析的elf文件创建一个进程。
在视频教程中了解如何解析elfik。
我们只需要下载一个包含代码和数据的程序标头,因此我们不会一概而论,而只关注这种情况。
extern void elf_exec(struct elf_header_t* header) { assert(header->e_ident.ei_magic == EI_MAGIC); printf(MSG_KERNEL_ELF_LOADING, header->e_phnum);
这里最有趣的是创建页面目录和页面表。
请注意,首先我们选择物理页面(mm_phys_alloc_pages),然后将它们映射到逻辑页面(mmu_occupy_user_page)。
这里假设物理存储器中的页面是连续分配的。
仅此而已。 现在,您可以为内核实现自己的shell! 观看视频教程并深入研究细节。
结论
我希望您发现这一系列文章有用。
我们尚未考虑过保护环,但是由于该主题的相关性较低且评论不一,我们将继续中断。
我认为您现在已经准备好进行进一步的研究,因为您和我都检查了所有最重要的内容。
因此,束紧腰带,投入战斗! 通过编写自己的操作系统!
我花了大约一个月的时间(如果我们考虑全天工作6-8个小时)来实施您和我从头开始学到的一切。
因此,在2-3个月内,您将能够编写一个具有真实文件系统的成熟OS,而我们没有实现该文件系统。
只是知道qemu不知道如何使用任意格式的initrd并将其剪切为4kb,所以您将需要像在Linux中那样使它成为一个,或者使用borsch代替qemu。
如果您知道如何解决此问题,请写一封私人信件,我将非常感谢您。
仅此而已! 直到新的不再见面!
参考文献
观看
视频教程以获取更多信息。
git存储库中的源代码(您需要lesson9分支)。
参考文献
1.詹姆斯·莫洛伊(James Molloy)。 滚动自己的玩具UNIX克隆操作系统。
2.牙齿。 DOS,Windows,Unix的汇编器
3.卡拉什尼科夫。 汇编程序很简单!
4. Tanenbaum。 操作系统。 实施与开发。
5.罗伯特·洛夫(Robert Love)。 Linux内核 开发过程的描述。