《操作系统真象还原》第十五章
本篇对应书籍第十五章15.1–15.4的内容
本篇介绍了fork的原理和实现,使用fork实现了init进程和简单的shell,同时实现了部分系统调用
fork 的原理与实现
fork是什么?
fork相当于同一个程序多次加载,在内存中产生多个同名程序
fork的作用是克隆进程,子进程是在fork函数返回之后开始执行,执行的是fork之后的代码
也就是fork返回之后,主进程拥有了子进程,子进程拥有了父进程
fork的实现
fork利用老进程克隆出一个新进程并使新进程执行,新进程之所以能够执行,本质上是它具备程序体,这其中包括代码和数据等资源。因此fork就是把某个进程的全部资源复制了一份,然后让处理器的cs:eip寄存器指向新进程的指令部分。 故:实现fork也要分两步,先复制进程资源,然后再跳过去执行。
进程的资源:
- 进程的pcb, 即task_struct, 这是让任务有 “存在感” 的身份证。
- 程序体, 即代码段数据段等,这是进程的实体。
- 用户栈,不用说了,编译器会把局部变量在栈中创建,并且函数调用也离不了栈。
- 内核栈,进入内核态时,一方面要用它来保存上下文环境,另一方面的作用同用户栈一样。
- 虚拟地址池,每个进程拥有独立的内存空间, 其 虚拟地址是用虚拟地址池来管理的。
- 页表,让进程拥有独立的内存空间。
只要将新进程加入到就绪队列中就可以执行了。
需要先做一些基础设施,PCB结构中添加parent_pid成员:
thread/thread.h
PCB结构中添加:
uint32_t cwd_inode_nr; // 进程所在工作目录的inode编号
int16_t parent_pid; // 父进程 pid
uint32_t stack_magic; // 栈的边界标记, 用于检测栈的溢出
/* fork 进程时为其分配 pid, 因为 allocate_pid 已经是静态的, 别的文件无法调用.
不想改变函数定义了, 故定义 fork_pid 函数来封装一下。
*/
pid_t fork_pid(void);
thread/thread.c
/* fork 进程时为其分配 pid, 因为 allocate_pid 已经是静态的, 别的文件无法调用.
不想改变函数定义了, 故定义 fork_pid 函数来封装一下。
*/
pid_t fork_pid(void) {
return allocate_pid();
}
init_thread函数中添加:
pthread->cwd_inode_nr = 0;
pthread->parent_pid = -1; // -1表示没有父进程
pthread->stack_magic = 0x19870916; // 自定义魔数
-1
表示没有父进程,
kernel/memory.c
新增函数:
/* 安装 1 页大小的 vaddr, 专门针对 fork 时虚拟地址位图无须操作的情况 */
void* get_a_page_without_opvaddrbitmap(enum pool_flags pf, uint32_t vaddr) {
struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
lock_acquire(&mem_pool->lock);
void* page_phyaddr = palloc(mem_pool);
if(page_phyaddr == NULL) {
lock_release(&mem_pool->lock);
return NULL;
}
page_table_add((void*) vaddr, page_phyaddr);
lock_release(&mem_pool->lock);
return (void*) vaddr;
}
功能与get_a_page
类似,但无需从虚拟地址内存池中设置位图
得到1页大小内存并复制到页表中 专门针对fork时虚拟地址位图无需操作
因为位图我们后面会复制父进程的 所以当然不用继续对虚拟位图操作了
userprog/fork.h
#ifndef __USERPROG_FORK_H
#define __USERPROG_FORK_H
#include "../thread/thread.h"
/* fork子进程, 只能由用户进程通过系统调用 fork 调用,
* 内核线程不可直接调用, 原因是要从0级栈中获得 esp3 等
*/
pid_t sys_fork(void);
#endif
userprog/fork.c 上
#include "fork.h"
#include "process.h"
#include "memory.h"
#include "interrupt.h"
#include "debug.h"
#include "../thread/thread.h"
#include "string.h"
#include "../fs/file.h"
extern void intr_exit(void);
/* 将父进程的 pcb、虚拟地址位图拷贝给子进程 */
static int32_t copy_pcb_vaddrbitmap_stack0(struct task_struct* child_thread, struct task_struct* parent_thread) {
// a. 复制 pcb 所在的整个页, 里面包含进程 pcb 信息及特级0极的栈, 里面包含了返回地址, 然后再单独修改个别部分
memcpy(child_thread, parent_thread, PG_SIZE);
// 单独修改
child_thread->pid = fork_pid();
child_thread->elapsed_ticks = 0;
child_thread->status = TASK_READY;
// 为新进程把时间片充满
child_thread->ticks = child_thread->priority;
child_thread->parent_pid = parent_thread->pid;
child_thread->general_tag.prev = child_thread->general_tag.next = NULL;
child_thread->all_list_tag.prev = child_thread->all_list_tag.next = NULL;
// 初始化子进程内存块描述符
block_desc_init(child_thread->u_block_desc);
// b. 复制父进程的虚拟地址池的位图
uint32_t bitmap_pg_cnt = DIV_ROUND_UP((0xc0000000 - USER_VADDR_START) / PG_SIZE / 8 , PG_SIZE);
void* vaddr_btmp = get_kernel_pages(bitmap_pg_cnt);
if(vaddr_btmp == NULL) {
return -1;
}
// 此时 child_thread->userprog_vaddr.vaddr_bitmap.bits 还是指向父进程虚拟地址的位图地址
// 下面将 child_thread->userprog_vaddr.vaddr_bitmap.bits 指向自己的位图 vaddr_btmp
// 复制父进程的虚拟内存位图 并把自己刚分配好的独立位图给我们的子进程赋值
memcpy(vaddr_btmp, child_thread->userprog_vaddr.vaddr_bitmap.bits, bitmap_pg_cnt * PG_SIZE);
child_thread->userprog_vaddr.vaddr_bitmap.bits = vaddr_btmp;
// 进程后面加个名字
ASSERT(strlen(child_thread->name) < 11);
strcat(child_thread->name, "_fork");
return 0;
}
/* 复制子进程的进程体(代码和数据)及用户栈 */
static void copy_body_stack3(struct task_struct* child_thread, struct task_struct* parent_thread, void* buf_page) {
uint8_t* vaddr_btmp = parent_thread->userprog_vaddr.vaddr_bitmap.bits;
uint32_t btmp_bytes_len = parent_thread->userprog_vaddr.vaddr_bitmap.btmp_bytes_len;
uint32_t vaddr_start = parent_thread->userprog_vaddr.vaddr_start;
uint32_t idx_byte = 0;
uint32_t idx_bit = 0;
uint32_t prog_vaddr = 0;
// 在父进程的用户空间中查找已有数据的页
while(idx_byte < btmp_bytes_len) {
if(vaddr_btmp[idx_byte]) {
idx_bit = 0;
while(idx_bit < 8) {
if ((BITMAP_MASK << idx_bit) & vaddr_btmp[idx_byte]) {
prog_vaddr = (idx_byte * 8 + idx_bit) * PG_SIZE + vaddr_start;
// 下面的操作是将父进程用户空间中的数据通过内核空间做中转, 最终复制到子进程的用户空间
// a. 将父进程在用户空间中的数据复制到内核缓冲区 buf_page
// 目的是下面切换到子进程的页表后, 还能访问到父进程的数据
memcpy(buf_page, (void*) prog_vaddr, PG_SIZE);
// b. 将页表切换到子进程, 目的是避免下面申请内存的函数将 pte 及 pde 安装在父进程的页表中
page_dir_activate(child_thread);
// c. 申请虚拟地址 prog_vaddr
get_a_page_without_opvaddrbitmap(PF_USER, prog_vaddr);
// d. 从内核缓冲区中将父进程数据复制到子进程的用户空间
memcpy((void*) prog_vaddr, buf_page, PG_SIZE);
// e. 恢复父进程页表
page_dir_activate(parent_thread);
}
idx_bit++;
}
}
idx_byte++;
}
}
copy_pcb_vaddrbitmap_stack0
函数的功能是将父进程的PCB,虚拟地址位图和0级栈拷贝给子进程
- 将父进程的PCB那一内存页直接复制给子进程的PCB,然后再进行修改即可
- 计算父进程虚拟地址位图大小,然后在子进程申请这个大小的空间并把父进程的虚拟地址位图复制过来
copy_body_stack
3 函数的功能是复制子进程的进程体(代码和数据)及用户栈
- 只要把用户空间中的有效部分拷贝出来就行,不同进程之间拷贝需要经过共用的内核空间进行中转
- 这里的策略是,遍历父进程虚拟内存池,每找到1位有效空间就复制到
内核空间缓冲区buf_page
中,缓冲区只有1页大小,复制过去之后,切换页表为子进程的页表,然后申请虚拟地址(不申请位图空间),拷贝到子进程,然后再把页表切换为父进程
userprog/fork.c 下
/* 为子进程构建 thread_stack 和修改返回值 */
static int32_t build_child_stack(struct task_struct* child_thread) {
// a. 使子进程 pid 返回值为 0
// 获取子进程 0 级栈栈顶
struct intr_stack* intr_0_stack = (struct intr_stack*) ((uint32_t) child_thread + PG_SIZE - sizeof(struct intr_stack));
// 修改子进程的返回值为 0
intr_0_stack->eax = 0;
// b. 为switch_to 构建 struct thread_stack, 将其构建在紧临 intr_stack 之下的空间
uint32_t* ret_addr_in_thread_stack = (uint32_t*) intr_0_stack - 1;
//这里我把其结构体搬过来了
/*struct thread_stack
{
uint32_t ebp;
uint32_t ebx;
uint32_t edi;
uint32_t esi;
void (*eip) (thread_func* func,void* func_arg); //和下面的相互照应 以ret 汇编代码进入kernel_thread函数调用
void (*unused_retaddr); //占位数 在栈顶站住了返回地址的位置 因为是汇编ret
thread_func* function; //进入kernel_thread要调用的函数地址
void* func_arg; //参数指针
};*/
//返回地址是高地址
/*** 这三行不是必要的,只是为了梳理thread_stack中的关系 ***/
uint32_t* esi_ptr_in_thread_stack = (uint32_t*) intr_0_stack - 2;
uint32_t* edi_ptr_in_thread_stack = (uint32_t*) intr_0_stack - 3;
uint32_t* ebx_ptr_in_thread_stack = (uint32_t*) intr_0_stack - 4;
/**********************************************************/
// ebp 在 thread_stack 中的地址便是当时的 esp(0级栈的栈顶),
// 即 esp 为 "(uint32_t*) intr_0_stack - 5" */
uint32_t* ebp_ptr_in_thread_stack = (uint32_t*) intr_0_stack - 5;
// switch_to 的返回地址更新为 intr_exit, 直接从中断返回
*ret_addr_in_thread_stack = (uint32_t) intr_exit;
// 下面这两行赋值只是为了使构建的 thread_stack 更加清晰, 其实也不需要
// 因为在进入 intr_exit 后一系列的 pop 会把寄存器中的数据覆盖
*ebp_ptr_in_thread_stack = *ebx_ptr_in_thread_stack = 0;
*edi_ptr_in_thread_stack = *esi_ptr_in_thread_stack = 0;
// 把构建的 thread_stack 的栈顶做为 switch_to 恢复数据时的栈顶
child_thread->self_kstack = ebp_ptr_in_thread_stack;
return 0;
}
/* 更新 inode 打开数 */
static void update_inode_open_cnts(struct task_struct* thread) {
int32_t local_fd = 3, global_fd = 0;
while (local_fd < MAX_FILES_OPEN_PER_PROC) {
global_fd = thread->fd_table[local_fd];
ASSERT(global_fd < MAX_FILE_OPEN);
if (global_fd != -1) {
file_table[global_fd].fd_inode->i_open_cnts++;
}
local_fd++;
}
}
/* 拷贝父进程本身所占资源给子进程 */
static int32_t copy_process(struct task_struct* child_thread, struct task_struct* parent_thread) {
// 内核缓冲区, 作为父进程用户空间的数据复制到子进程用户空间的中转
void* buf_page = get_kernel_pages(1);
if(buf_page == NULL) {
return -1;
}
// a. 复制父进程的 pcb、虚拟地址位图、内核栈到子进程
if(copy_pcb_vaddrbitmap_stack0(child_thread, parent_thread) == -1) {
return -1;
}
// b. 为子进程创建页表, 此页表仅包括内核空间
child_thread->pgdir = create_page_dir();
if(child_thread->pgdir == NULL) {
return -1;
}
// c. 复制父进程进程体及用户栈给子进程
copy_body_stack3(child_thread, parent_thread, buf_page);
// d. 构建子进程 thread_stack 和修改返回值 pid
build_child_stack(child_thread);
// e. 更新文件 inode 的打开数
update_inode_open_cnts(child_thread);
mfree_page(PF_KERNEL, buf_page, 1);
return 0;
}
/* fork 子进程, 内核线程不可直接调用 */
pid_t sys_fork(void) {
struct task_struct* parent_thread = running_thread();
// 为子进程创建 pcb(task_struct结构)
struct task_struct* child_thread = get_kernel_pages(1);
if(child_thread == NULL) {
return -1;
}
ASSERT(INTR_OFF == intr_get_status() && parent_thread->pgdir != NULL);
if(copy_process(child_thread, parent_thread) == -1) {
return -1;
}
// 添加到就绪线程队列和所有线程队列, 子进程由调试器安排运行
ASSERT(!elem_find(&thread_ready_list, &child_thread->general_tag));
list_append(&thread_ready_list, &child_thread->general_tag);
ASSERT(!elem_find(&thread_all_list, &child_thread->all_list_tag));
list_append(&thread_all_list, &child_thread->all_list_tag);
// 父进程返回子进程的 pid
return child_thread->pid;
}
build_child_stack 函数:为子进程构建thread_stack和修改返回值
- fork父进程可以通过中断返回回到之前的代码
- 子进程则需要自行构造中断栈手动从中断返回才能继续执行fork之后的代码
update_inode_open_cnts 函数:更新inode打开数
- 将该进程打开的文件inode信息中的inode打开数+1
copy_process 函数:拷贝父进程本身所占资源给子进程
- 是前面函数的封装
sys_fork 函数:fork子进程,内核线程不可直接调用
添加 fork 系统调用和实现init进程
在 Linux 中,init 是用户级进程,它是第一个启动的程序,因此它的 pid是 1,后续的所有进程都是它的孩子,故 init 是所有进程的父进程,所以它还负责所有子进程的资源回收
init 要主动调用 fork 才能派生出子子孙孙,所以需要先完成 fork系统调用:
- 在 syscall.h 中的 enum SYSCALL_NR 结构中添加 SYS_FORK。
- 在 syscall.c 中添加 fork(),原型是pid_t fork(void),实现是
“return _syscallO(SYS _FORK);"
。 - 在 syscall-init.c 中的函数 syscall_init 中,添加代码
"syscall_table[SYS_FORK]= sys_fork;"
。
接下来看init的实现,init定义在main中:
kernel/main.c
#include "print.h"
#include "init.h"
#include "debug.h"
#include "memory.h"
#include "../thread/thread.h"
#include "interrupt.h"
#include "../device/console.h"
#include "../userprog/process.h"
#include "../userprog/syscall-init.h"
#include "../lib/user/syscall.h"
#include "../lib/stdio.h"
#include "../fs/fs.h"
#include "../fs/dir.h"
#include "../lib/string.h"
void init(void);
int main() {
put_str("I am kernel\n");
init_all();
while(1);
return 0;
}
/* init进程 */
void init(void) {
uint32_t ret_pid = fork();
if(ret_pid) {
printf("i am father, my pid is %d, child pid is %d\n", getpid(), ret_pid);
} else {
printf("i am child, my pid is %d, ret pid is %d\n", getpid(), ret_pid);
}
while(1);
}
为了让init进程获得pid为1,需要最早让init进程得到加载执行,需要在thread.c这里修改
thread/thread.c
extern void init(void);
/* 初始化线程环境 */
void thread_init(void) {
put_str("thread_init start\n");
list_init(&thread_ready_list);
list_init(&thread_all_list);
lock_init(&pid_lock);
// 先创建第一个用户进程:init
// 放在第一个初始化, 这是第一个进程, init进程的 pid 为 1
process_execute(init, "init");
// 将当前 main 函数创建为线程
make_main_thread();
// 创建 idle 线程
idle_thread = thread_start("idle", 10, idle, NULL);
put_str("thread_init done\n");
}
运行 Bochs
编译,运行:
BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
$(BUILD_DIR)/debug.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/string.o \
$(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \
$(BUILD_DIR)/switch.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o \
$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o \
$(BUILD_DIR)/process.o $(BUILD_DIR)/syscall-init.o $(BUILD_DIR)/syscall.o \
$(BUILD_DIR)/stdio.o $(BUILD_DIR)/stdio-kernel.o $(BUILD_DIR)/ide.o \
$(BUILD_DIR)/fs.o $(BUILD_DIR)/inode.o $(BUILD_DIR)/file.o $(BUILD_DIR)/dir.o \
$(BUILD_DIR)/fork.o
############ C 代码编译 ##############
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
lib/stdint.h kernel/init.h lib/string.h \
lib/kernel/stdio-kernel.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
lib/stdint.h kernel/interrupt.h device/timer.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h \
lib/kernel/io.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
lib/kernel/print.h lib/stdint.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/string.o: lib/string.c lib/string.h \
kernel/debug.h kernel/global.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \
lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
lib/stdint.h lib/string.h kernel/global.h kernel/memory.h \
kernel/debug.h kernel/interrupt.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \
kernel/interrupt.h lib/stdint.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
lib/stdint.h thread/thread.h kernel/debug.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/console.o: device/console.c device/console.h \
lib/kernel/print.h thread/sync.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
lib/kernel/print.h lib/kernel/io.h kernel/interrupt.h \
kernel/global.h lib/stdint.h device/ioqueue.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \
kernel/interrupt.h kernel/global.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \
kernel/global.h thread/thread.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \
lib/string.h kernel/global.h kernel/memory.h lib/kernel/print.h \
thread/thread.h kernel/interrupt.h kernel/debug.h device/console.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \
lib/user/syscall.h lib/stdint.h lib/kernel/print.h \
kernel/interrupt.h thread/thread.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h lib/stdint.h lib/string.h lib/user/syscall.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/stdio-kernel.o : lib/kernel/stdio-kernel.c lib/kernel/stdio-kernel.h \
lib/stdio.h device/console.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/ide.o: device/ide.c device/ide.h lib/stdint.h kernel/debug.h \
lib/kernel/stdio-kernel.h lib/stdio.h kernel/global.h thread/sync.h \
lib/kernel/io.h device/timer.h kernel/interrupt.h lib/kernel/list.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/fs.o: fs/fs.c fs/fs.h lib/stdint.h kernel/global.h device/ide.h fs/inode.h fs/dir.h \
fs/super_block.h lib/kernel/stdio-kernel.h lib/string.h kernel/debug.h lib/kernel/list.h \
fs/file.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/inode.o: fs/inode.c fs/inode.h device/ide.h kernel/debug.h thread/thread.h \
kernel/memory.h lib/string.h lib/kernel/list.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/file.o: fs/file.c fs/file.h lib/kernel/stdio-kernel.h thread/thread.h device/ide.h \
fs/file.h kernel/global.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/dir.o: fs/dir.c fs/dir.h device/ide.h fs/fs.h fs/inode.h kernel/memory.h lib/string.h lib/stdint.h \
lib/kernel/stdio-kernel.h kernel/debug.h fs/file.h kernel/memory.h lib/string.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/fork.o: userprog/fork.c userprog/fork.h kernel/global.h lib/stdint.h lib/string.h \
kernel/memory.h kernel/interrupt.h thread/sync.h thread/thread.h kernel/debug.h userprog/process.h \
lib/kernel/stdio-kernel.h fs/file.h lib/kernel/list.h
$(CC) $(CFLAGS) $< -o $@
############## 汇编代码编译 ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.asm
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/print.o: lib/kernel/print.asm
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/switch.o: thread/switch.asm
$(AS) $(ASFLAGS) $< -o $@
############## 链接所有目标文件 #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
$(LD) $(LDFLAGS) $^ -o $@
.PHONY: mk_dir hd clean all
mk_dir:
if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
hd:
sudo dd if=$(BUILD_DIR)/kernel.bin \
of=/home/steven/source/os/bochs/hd60M.img \
bs=512 count=200 seek=9 conv=notrunc
clean:
cd $(BUILD_DIR) && rm -f ./*
build: $(BUILD_DIR)/kernel.bin
all: mk_dir build hd
符合预期
添加 read 系统调用,获取键盘输入
原来的 sys_read 是从文件中读取,现在要支持键盘读取,需要进行改进一下:
fs/fs.c
/* 从文件描述符 fd 指向的文件中读取 count 个字节到 buf, 若成功则返回读出的字节数, 到文件尾则返回 -1 */
int32_t sys_read(int32_t fd, void* buf, uint32_t count) {
ASSERT(buf != NULL);
int32_t ret = -1;
if (fd < 0 || fd == stdout_no || fd == stderr_no) {
printk("sys_read: fd error\n");
} else if(fd == stdin_no) {
// 标准输入
char* buffer = buf;
uint32_t bytes_read = 0;
while(bytes_read < count) {
*buffer = ioq_getchar(&kbd_buf);
bytes_read++;
buffer++;
}
ret = (bytes_read == 0 ? -1 : (int32_t)bytes_read);
} else {
// 读入文件
uint32_t _fd = fd_local2global(fd);
ret = file_read(&file_table[_fd], buf, count);
}
return ret;
}
这里从键盘的环形输入缓冲区一次获取一个字符,直到获取完count个字符为止
接下来添加系统调用
lib/user/syscall.c
/* 从文件描述符 fd 中读取 count 个字节到 buf */
int32_t read(int32_t fd, void* buf, uint32_t count) {
return _syscall3(SYS_READ, fd, buf, count);
}
在syscall-init里添加sys_read,在syscall.h里添加SYS_READ
添加 putchar、clear系统调用
实现clear内核实现:
lib/kernel/print.S
; 清屏
global cls_screen
cls_screen:
pushad
;;;;;;;;;;;;;;;
; 由于用户程序的 cpl 为 3, 显存段的 dpl 为 0, 故用于显存段的选择子 gs 在低于自己特权的环境中为 0,
; 导致用户程序再次进入中断后, gs 为 0, 故直接在 put_str 中每次都为 gs 赋值.
mov ax, SELECTOR_VIDEO ; 不能直接把立即数送入 gs, 须由 ax 中转
mov gs, ax
mov ebx, 0
mov ecx, 80 * 25
.cls:
mov word [gs:ebx], 0x0720 ; 0x0720是黑底白字的空格键
add ebx, 2
loop .cls
mov ebx, 0
.set_cursor: ; 直接把 set_cursor 搬过来用, 省事
; 1. 先设置高8位 ;;;;;;;;
mov dx, 0x03d4 ; 索引寄存器
mov al, 0x0e ; 用于提供光标位置的高8位
out dx, al
mov dx, 0x03d5 ; 通过读写数据端口 0x3d5 来获得或设置光标位置
mov al, bh
out dx, al
; 2. 再设置低8位
mov dx, 0x03d4
mov al, 0x0f
out dx, al
mov dx, 0x03d5
mov al, bl
out dx, al
popad
ret
添加系统调用:
lib/user/syscall.c
/* 输出一个字符 */
void putchar(char char_asci) {
_syscall1(SYS_PUTCHAR, char_asci);
}
/* 清空屏幕 */
void clear(void) {
_syscall0(SYS_CLEAR);
}
fs.c
/* 向屏幕输出一个字符 */
void sys_putchar(char char_asci) {
console_put_char(char_asci);
}
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;
put_str("syscall_init done\n");
}
实现一个简单的shell
shell 雏形
shell/shell.c
#include "shell.h"
#include "stdint.h"
#include "../fs/fs.h"
#include "../fs/file.h"
#include "../lib/user/syscall.h"
#include "stdio.h"
#include "global.h"
#include "../lib/user/assert.h"
#include "string.h"
#define cmd_len 128 // 最大支持 128 字符输入
#define MAX_ARG_NR 16 // 加上命令名外, 最多支持 15 个参数
/* 存储输入的命令 */
static char cmd_line[cmd_len] = {0};
/* 用来记录当前目录, 是当前目录的缓存, 每次执行 cd 命令时会更新此内容 */
char cwd_cache[64] = {0};
/* 输出提示符 */
void print_prompt() {
printf("[steven@localhost %s]$ ", cwd_cache);
}
/* 从键盘缓冲区中最多读入 count 个字节到 buf */
static void readline(char* buf, int32_t count) {
assert(buf != NULL && count > 0);
char* pos = buf;
while(read(stdin_no, pos, 1) != -1 && (pos - buf) < count) {
// 在不出错情况下, 直到找到回车符才返回
switch(*pos) {
// 找到回车或换行符后认为键入的命令结束, 直接返回
case '\n':
case '\r':
*pos = 0; // 添加 cmd_line 的终止字符 0
putchar('\n');
return;
case '\b':
// 阻止删除非本次输入的信息
if(buf[0] != '\b') {
--pos; // 退回到缓冲区 cmd_line 中上一个字符
putchar('\b');
}
break;
default:
// 非控制键则输出字符
putchar(*pos);
pos++;
}
}
printf("readline: can`t find enter_key in the cmd_line, max num of char is 128\n");
}
/* 简单的 shell */
void my_shell(void) {
cwd_cache[0] = '/';
while(1) {
print_prompt();
memset(cmd_line, 0, cmd_len);
readline(cmd_line, cmd_len);
if(cmd_line[0] == 0) {
// 若只键入了一个回车
continue;
}
}
panic("my_shell: should not be here");
}
这里这个my_shell函数实现的shell仅仅是输入字符,然后显示字符,按下回车后,重新接受输入,然后显示字符,还没实际功能,但看起来已经挺唬人的了
kernel/main.c
#include "print.h"
#include "init.h"
#include "debug.h"
#include "memory.h"
#include "../thread/thread.h"
#include "interrupt.h"
#include "../device/console.h"
#include "../userprog/process.h"
#include "../userprog/syscall-init.h"
#include "../lib/user/syscall.h"
#include "../lib/stdio.h"
#include "../fs/fs.h"
#include "../fs/dir.h"
#include "../lib/string.h"
#include "../lib/user/assert.h"
#include "../shell/shell.h"
void init(void);
int main() {
put_str("I am kernel\n");
init_all();
cls_screen();
console_put_str("[steven@localhost /]$ ");
while(1);
return 0;
}
/* init进程 */
void init(void) {
uint32_t ret_pid = fork();
if(ret_pid) {
// 父进程
while(1);
} else {
// 子进程
my_shell();
}
panic("init: should not be here");
}
运行 Bochs
编译,运行:
BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
$(BUILD_DIR)/debug.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/string.o \
$(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \
$(BUILD_DIR)/switch.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o \
$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o \
$(BUILD_DIR)/process.o $(BUILD_DIR)/syscall-init.o $(BUILD_DIR)/syscall.o \
$(BUILD_DIR)/stdio.o $(BUILD_DIR)/stdio-kernel.o $(BUILD_DIR)/ide.o \
$(BUILD_DIR)/fs.o $(BUILD_DIR)/inode.o $(BUILD_DIR)/file.o $(BUILD_DIR)/dir.o \
$(BUILD_DIR)/fork.o $(BUILD_DIR)/shell.o
############ C 代码编译 ##############
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
lib/stdint.h kernel/init.h lib/string.h \
lib/kernel/stdio-kernel.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
lib/stdint.h kernel/interrupt.h device/timer.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h \
lib/kernel/io.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
lib/kernel/print.h lib/stdint.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/string.o: lib/string.c lib/string.h \
kernel/debug.h kernel/global.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \
lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
lib/stdint.h lib/string.h kernel/global.h kernel/memory.h \
kernel/debug.h kernel/interrupt.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \
kernel/interrupt.h lib/stdint.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
lib/stdint.h thread/thread.h kernel/debug.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/console.o: device/console.c device/console.h \
lib/kernel/print.h thread/sync.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
lib/kernel/print.h lib/kernel/io.h kernel/interrupt.h \
kernel/global.h lib/stdint.h device/ioqueue.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \
kernel/interrupt.h kernel/global.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \
kernel/global.h thread/thread.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \
lib/string.h kernel/global.h kernel/memory.h lib/kernel/print.h \
thread/thread.h kernel/interrupt.h kernel/debug.h device/console.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \
lib/user/syscall.h lib/stdint.h lib/kernel/print.h \
kernel/interrupt.h thread/thread.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h lib/stdint.h lib/string.h lib/user/syscall.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/stdio-kernel.o : lib/kernel/stdio-kernel.c lib/kernel/stdio-kernel.h \
lib/stdio.h device/console.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/ide.o: device/ide.c device/ide.h lib/stdint.h kernel/debug.h \
lib/kernel/stdio-kernel.h lib/stdio.h kernel/global.h thread/sync.h \
lib/kernel/io.h device/timer.h kernel/interrupt.h lib/kernel/list.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/fs.o: fs/fs.c fs/fs.h lib/stdint.h kernel/global.h device/ide.h fs/inode.h fs/dir.h \
fs/super_block.h lib/kernel/stdio-kernel.h lib/string.h kernel/debug.h lib/kernel/list.h \
fs/file.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/inode.o: fs/inode.c fs/inode.h device/ide.h kernel/debug.h thread/thread.h \
kernel/memory.h lib/string.h lib/kernel/list.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/file.o: fs/file.c fs/file.h lib/kernel/stdio-kernel.h thread/thread.h device/ide.h \
fs/file.h kernel/global.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/dir.o: fs/dir.c fs/dir.h device/ide.h fs/fs.h fs/inode.h kernel/memory.h lib/string.h lib/stdint.h \
lib/kernel/stdio-kernel.h kernel/debug.h fs/file.h kernel/memory.h lib/string.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/fork.o: userprog/fork.c userprog/fork.h thread/thread.h lib/stdint.h \
lib/kernel/list.h kernel/global.h lib/kernel/bitmap.h kernel/memory.h \
userprog/process.h kernel/interrupt.h kernel/debug.h \
lib/kernel/stdio-kernel.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/shell.o: shell/shell.c shell/shell.h lib/stdint.h fs/fs.h \
lib/user/syscall.h lib/stdio.h lib/stdint.h kernel/global.h lib/user/assert.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/assert.o: lib/user/assert.c lib/user/assert.h lib/stdio.h lib/stdint.h
$(CC) $(CFLAGS) $< -o $@
############## 汇编代码编译 ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.asm
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/print.o: lib/kernel/print.asm
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/switch.o: thread/switch.asm
$(AS) $(ASFLAGS) $< -o $@
############## 链接所有目标文件 #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
$(LD) $(LDFLAGS) $^ -o $@
.PHONY: mk_dir hd clean all
mk_dir:
if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
hd:
sudo dd if=$(BUILD_DIR)/kernel.bin \
of=/home/steven/source/os/bochs/hd60M.img \
bs=512 count=200 seek=9 conv=notrunc
clean:
cd $(BUILD_DIR) && rm -f ./*
build: $(BUILD_DIR)/kernel.bin
all: mk_dir build hd
添加 Ctrl+u 和 Ctrl+l 快捷键
本小节介绍了实现快捷键功能的原理
Ctrl+u
是清除输入,清除本行的输入
Ctrl+l
是清空屏幕,相当于clear
命令,但不会清除本次输入
在kerboard.c里我们写过伏笔:
// 只处理ascii码不为0的键
if (cur_char) {
/***************** 快捷键ctrl+l和ctrl+u的处理 *********************
* 下面是把ctrl+l和ctrl+u这两种组合键产生的字符置为:
* cur_char的asc码-字符a的asc码, 此差值比较小,
* 属于asc码表中不可见的字符部分.故不会产生可见字符.
* 我们在shell中将ascii值为l-a和u-a的分别处理为清屏和删除输入的快捷键*/
if ((ctrl_down_last && cur_char == 'l') || (ctrl_down_last && cur_char == 'u')) {
cur_char -= 'a';
}
/****************************************************************/
/* 若kbd_buf中未满并且待加入的cur_char不为0,
* 则将其加入到缓冲区kbd_buf中 */
if(!ioq_full(&kbd_buf)) {
//put_char(cur_char);
ioq_putchar(&kbd_buf, cur_char);
}
当输入组合键Ctrl+u和+l
的时候,就用u和l的ascii减掉a的ascii,成为控制字符,这样一来,这个组合键就可以变成一个ASCII码来进行处理了
shell/shell.c
/* 从键盘缓冲区中最多读入 count 个字节到 buf */
static void readline(char* buf, int32_t count) {
assert(buf != NULL && count > 0);
char* pos = buf;
while(read(stdin_no, pos, 1) != -1 && (pos - buf) < count) {
// 在不出错情况下, 直到找到回车符才返回
switch(*pos) {
// 找到回车或换行符后认为键入的命令结束, 直接返回
case '\n':
case '\r':
*pos = 0; // 添加 cmd_line 的终止字符 0
putchar('\n');
return;
case '\b':
// 阻止删除非本次输入的信息
if(buf[0] != '\b') {
--pos; // 退回到缓冲区 cmd_line 中上一个字符
putchar('\b');
}
break;
// ctrl + l 清屏
case 'l' - 'a':
// 1. 先将当前的字符 'l'-'a' 置为 0
*pos = 0;
// 2. 再将屏幕清空
clear();
// 3. 打印提示符
print_prompt();
// 4. 将之前键入的内容再次打印
printf("%s", buf);
break;
// ctrl + u 清掉输入
case 'u' - 'a':
while(buf != pos) {
// 连续退格
putchar('\b');
*(pos--) = 0;
}
break;
default:
// 非控制键则输出字符
putchar(*pos);
pos++;
}
}
printf("readline: can`t find enter_key in the cmd_line, max num of char is 128\n");
}
按键处理新增组合键的内容
解析键入字符
给shell增加命令解析的功能:
shell/shell.c
/* 分析字符串 cmd_str 中以 token 为分隔符的单词, 将各单词的指针存入 argv 数组 */
static int32_t cmd_parse(char* cmd_str, char** argv, char token) {
assert(cmd_str != NULL);
int32_t arg_idx = 0;
while(arg_idx < MAX_ARG_NR) {
argv[arg_idx] = NULL;
arg_idx++;
}
char* next = cmd_str;
int32_t argc = 0;
// 外层循环处理整个命令行
while(*next) {
// 去除命令字或参数之间的空格
while(*next == token) {
next++;
}
// 处理最后一个参数后接空格的情况, 如 "ls dir2 "
if(*next == 0) {
break;
}
argv[argc] = next;
// 内层循环处理命令行中的每个命令字及参数
while(*next && *next != token) {
// 在字符串结束前找单词分隔符
next++;
}
// 如果未结束(是token字符), 使 tocken 变成 0
if(*next) {
// 将 token 字符替换为字符串结束符 0, 做为一个单词的结束, 并将字符指针 next 指向下一个字符
*next++ = 0;
}
// 避免 argv 数组访问越界, 参数过多则返回 0
if(argc > MAX_ARG_NR) {
return -1;
}
argc++;
}
return argc;
}
// argv 必须为全局变量, 为了以后 exec 的程序可访问参数
char* argv[MAX_ARG_NR];
int32_t argc = -1;
/* 简单的 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;
}
int32_t arg_idx = 0;
while (arg_idx < argc) {
printf("%s ", argv[arg_idx]);
arg_idx++;
}
printf("\n");
}
panic("my_shell: should not be here");
}
cmd_parse 函数:分析字符串cmd_str中以token为分隔符的单词,将各单词的指针存入argv数组
my_shell 函数:将解析到的单词依次打印出来
解析命令就是把输入的字符接收进来,然后按空格拆分成单词,存入数组中
这里为了测试,就是把接收进来的单词再打印出来
运行 Bochs 功能验证
编译,运行:
符合预期
添加系统调用
lib/user/syscall.h
#ifndef __LIB_USER_SYSCALL_H
#define __LIB_USER_SYSCALL_H
#include "stdint.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
};
uint32_t getpid(void);
uint32_t write(int32_t fd, const void* buf, uint32_t count);
void* malloc(uint32_t size);
void free(void* ptr);
int16_t fork(void);
int32_t read(int32_t fd, void* buf, uint32_t count);
void putchar(char char_asci);
void clear(void);
char* getcwd(char* buf, uint32_t size);
int32_t open(char* pathname, uint8_t flag);
int32_t close(int32_t fd);
int32_t lseek(int32_t fd, int32_t offset, uint8_t whence);
int32_t unlink(const char* pathname);
int32_t mkdir(const char* pathname);
struct dir* opendir(const char* name);
int32_t closedir(struct dir* dir);
int32_t rmdir(const char* pathname);
struct dir_entry* readdir(struct dir* dir);
void rewinddir(struct dir* dir);
int32_t stat(const char* path, struct stat* buf);
int32_t chdir(const char* path);
void ps(void);
#endif
lib/user/syscall.c
#include "syscall.h"
#include "../../thread/thread.h"
/* 无参数的系统调用 */
#define _syscall0(NUMBER) ({ \
int retval; \
asm volatile( \
"int $0x80" \
: "=a"(retval) \
: "a"(NUMBER) \
: "memory" \
); \
retval; \
})
/* 一个参数的系统调用 */
#define _syscall1(NUMBER, ARG1) ({ \
int retval; \
asm volatile ( \
"int $0x80" \
: "=a"(retval) \
: "a"(NUMBER), "b" (ARG1) \
: "memory" \
); \
retval; \
})
/* 两个参数的系统调用 */
#define _syscall2(NUMBER, ARG1, ARG2) ({ \
int retval; \
asm volatile ( \
"int $0x80" \
: "=a"(retval) \
: "a"(NUMBER), "b" (ARG1), "c" (ARG2) \
: "memory" \
); \
retval; \
})
/* 三个参数的系统调用 */
#define _syscall3(NUMBER, ARG1, ARG2, ARG3) ({ \
int retval; \
asm volatile( \
"int $0x80" \
: "=a"(retval) \
: "a"(NUMBER), "b"(ARG1), "c"(ARG2), "d"(ARG3) \
: "memory" \
); \
retval; \
})
/* 返回当前任务 pid */
uint32_t getpid() {
return _syscall0(SYS_GETPID);
}
/* 打印字符串 str */
uint32_t write(int32_t fd, const void* buf, uint32_t count) {
return _syscall3(SYS_WRITE, fd, buf, count);
}
/* 申请 size 字节大小的内存, 并返回结果 */
void* malloc(uint32_t size) {
return (void*) _syscall1(SYS_MALLOC, size);
}
/* 释放 ptr 指向的内存 */
void free(void* ptr) {
_syscall1(SYS_FREE, ptr);
}
/* 派生子进程, 返回子进程 pid */
pid_t fork(void) {
return _syscall0(SYS_FORK);
}
/* 从文件描述符 fd 中读取 count 个字节到 buf */
int32_t read(int32_t fd, void* buf, uint32_t count) {
return _syscall3(SYS_READ, fd, buf, count);
}
/* 输出一个字符 */
void putchar(char char_asci) {
_syscall1(SYS_PUTCHAR, char_asci);
}
/* 清空屏幕 */
void clear(void) {
_syscall0(SYS_CLEAR);
}
/* 获取当前工作目录 */
char* getcwd(char* buf, uint32_t size) {
return (char*) _syscall2(SYS_GETCWD, buf, size);
}
/* 以 flag 方式打开文件 pathname */
int32_t open(char* pathname, uint8_t flag) {
return _syscall2(SYS_OPEN, pathname, flag);
}
/* 关闭文件 fd */
int32_t close(int32_t fd) {
return _syscall1(SYS_CLOSE, fd);
}
/* 设置文件偏移量 */
int32_t lseek(int32_t fd, int32_t offset, uint8_t whence) {
return _syscall3(SYS_LSEEK, fd, offset, whence);
}
/* 删除文件 pathname */
int32_t unlink(const char* pathname) {
return _syscall1(SYS_UNLINK, pathname);
}
/* 创建目录 pathname */
int32_t mkdir(const char* pathname) {
return _syscall1(SYS_MKDIR, pathname);
}
/* 打开目录 name */
struct dir* opendir(const char* name) {
return (struct dir*) _syscall1(SYS_OPENDIR, name);
}
/* 关闭目录 dir */
int32_t closedir(struct dir* dir) {
return _syscall1(SYS_CLOSEDIR, dir);
}
/* 删除目录 pathname */
int32_t rmdir(const char* pathname) {
return _syscall1(SYS_RMDIR, pathname);
}
/* 读取目录 dir */
struct dir_entry* readdir(struct dir* dir) {
return (struct dir_entry*) _syscall1(SYS_READDIR, dir);
}
/* 回归目录指针 */
void rewinddir(struct dir* dir) {
_syscall1(SYS_REWINDDIR, dir);
}
/* 获取 path 属性到 buf中 */
int32_t stat(const char* path, struct stat* buf) {
return _syscall2(SYS_STAT, path, buf);
}
/* 改变工作目录为 path */
int32_t chdir(const char* path) {
return _syscall1(SYS_CHDIR, path);
}
/* 显示任务列表 */
void ps(void) {
_syscall0(SYS_PS);
}
userprog/syscall-init.c
#include "syscall-init.h"
#include "../lib/user/syscall.h"
#include "stdint.h"
#include "print.h"
#include "../thread/thread.h"
#include "../device/console.h"
#include "string.h"
#include "../kernel/memory.h"
#include "../fs/fs.h"
#include "fork.h"
#define syscall_nr 32 // 最大支持的系统子功能调用数
typedef void* syscall;
syscall syscall_table[syscall_nr];
/* 返回当前任务的 pid */
uint32_t sys_getpid(void) {
return running_thread()->pid;
}
/* 初始化系统调用 */
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;
put_str("syscall_init done\n");
}
这一堆系统调用都是之前实现过的,除了ps:
thread/thread.c
/* 以填充空格的方式输出 buf */
static void pad_print(char* buf, int32_t buf_len, void* ptr, char format) {
memset(buf, 0, buf_len);
uint8_t out_pad_0idx = 0;
switch(format) {
case 's':
out_pad_0idx = sprintf(buf, "%s", ptr);
break;
case 'd':
out_pad_0idx = sprintf(buf, "%d", *((uint32_t*) ptr));
break;
case 'x':
out_pad_0idx = sprintf(buf, "%x", *((uint32_t*) ptr));
break;
}
while(out_pad_0idx < buf_len) {
// 以空格填充
buf[out_pad_0idx] = ' ';
out_pad_0idx++;
}
sys_write(stdout_no, buf, buf_len - 1);
}
/* 用于在 list_traversal 函数中的回调函数, 用于针对线程队列的处理 */
static bool elem2thread_info(struct list_elem* pelem, int arg UNUSED) {
struct task_struct* pthread = elem2entry(struct task_struct, all_list_tag, pelem);
char out_pad[16] = {0};
pad_print(out_pad, 16, &pthread->pid, 'd');
if (pthread->parent_pid == -1) {
pad_print(out_pad, 16, "NULL", 's');
} else {
pad_print(out_pad, 16, &pthread->parent_pid, 'd');
}
switch(pthread->status) {
case 0:
pad_print(out_pad, 16, "RUNNING", 's');
break;
case 1:
pad_print(out_pad, 16, "READY", 's');
break;
case 2:
pad_print(out_pad, 16, "BLOCKED", 's');
break;
case 3:
pad_print(out_pad, 16, "WAITING", 's');
break;
case 4:
pad_print(out_pad, 16, "HANGING", 's');
break;
case 5:
pad_print(out_pad, 16, "DIED", 's');
}
pad_print(out_pad, 16, &pthread->elapsed_ticks, 'x');
memset(out_pad, 0, 16);
ASSERT(strlen(pthread->name) < 17);
memcpy(out_pad, pthread->name, strlen(pthread->name));
strcat(out_pad, "\n");
sys_write(stdout_no, out_pad, strlen(out_pad));
// 此处返回 false 是为了迎合主调函数 list_traversal, 只有回调函数返回 false 时才会继续调用此函数
return false;
}
/* 打印任务列表 */
void sys_ps(void) {
char* ps_title = "PID PPID STAT TICKS COMMAND\n";
sys_write(stdout_no, ps_title, strlen(ps_title));
list_traversal(&thread_all_list, elem2thread_info, 0);
}
功能就是将线程列表中各个线程的信息打印出来
路径解析转换
当前路径+相对路径=绝对路径
路径解析也主要是把路径中的 “ …“和“ .“替换成实际的目录,将用户键入的路径,无论是绝对路径,还是相对路径, 一 律转换成不含 “ .” 和 “ … "的绝对路径,然后再把转换后的路径作为命令的参数,最后再对某些有默认参数的命令做针对性处理就好了。
shell/buildin_cmd.c
#include "buildin_cmd.h"
#include "../lib/user/syscall.h"
#include "stdio.h"
#include "string.h"
#include "../fs/fs.h"
#include "global.h"
#include "../fs/dir.h"
#include "shell.h"
#include "assert.h"
/* 将路径 old_abs_path 中的 ..和. 转换为实际路径后存入 new_abs_path */
static void wash_path(char* old_abs_path, char* new_abs_path) {
assert(old_abs_path[0] == '/');
char name[MAX_FILE_NAME_LEN] = {0};
char* sub_path = old_abs_path;
sub_path = path_parse(sub_path, name);
if (name[0] == 0) {
// 若只键入了"/", 直接将"/"存入 new_abs_path 后返回
new_abs_path[0] = '/';
new_abs_path[1] = 0;
return;
}
// 避免传给 new_abs_path 的缓冲区不干净
new_abs_path[0] = 0;
strcat(new_abs_path, "/");
while(name[0]) {
// 如果是上一级目录 “..”
if(!strcmp("..", name)) {
char* slash_ptr = strrchr(new_abs_path, '/');
// 如果未到 new_abs_path 中的顶层目录, 就将最右边的'/'替换为 0
// 这样便去除了 new_abs_path 中最后一层路径, 相当于到了上一级目录
if (slash_ptr != new_abs_path) {
// 如 new_abs_path 为 “/a/b”, ".." 之后则变为 “/a”
*slash_ptr = 0;
} else {
// 如 new_abs_path 为 "/a", ".." 之后则变为 "/"
// 若 new_abs_path 中只有1个'/', 即表示已经到了顶层目录
// 就将下一个字符置为结束符 0
*(slash_ptr + 1) = 0;
}
} else if(strcmp(".", name)) {
// 如果路径不是 ‘.’, 就将 name 拼接到 new_abs_path
if (strcmp(new_abs_path, "/")) {
// 如果 new_abs_path 不是"/", 就拼接一个"/", 此处的判断是为了避免路径开头变成这样 "//"
strcat(new_abs_path, "/");
}
strcat(new_abs_path, name);
}
// 若 name 为当前目录".", 无须处理 new_abs_path
// 继续遍历下一层路径
memset(name, 0, MAX_FILE_NAME_LEN);
if (sub_path) {
sub_path = path_parse(sub_path, name);
}
}
}
/* 将 path 处理成不含 ..和. 的绝对路径, 存储在 final_path */
void make_clear_abs_path(char* path, char* final_path) {
char abs_path[MAX_PATH_LEN] = {0};
// 先判断是否输入的是绝对路径
if(path[0] != '/') {
// 若输入的不是绝对路径, 就拼接成绝对路径
memset(abs_path, 0, MAX_PATH_LEN);
if (getcwd(abs_path, MAX_PATH_LEN) != NULL) {
if (!((abs_path[0] == '/') && (abs_path[1] == 0))) {
// 若 abs_path 表示的当前目录不是根目录 /
strcat(abs_path, "/");
}
}
}
strcat(abs_path, path);
wash_path(abs_path, final_path);
}
shell/shell.c
/* 简单的 shell */
void my_shell(void) {
cwd_cache[0] = '/';
cwd_cache[1] = 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;
}
char buf[MAX_PATH_LEN] = {0};
int32_t arg_idx = 0;
while (arg_idx < argc) {
make_clear_abs_path(argv[arg_idx], buf);
printf("%s -> %s\n", argv[arg_idx], buf);
arg_idx++;
}
}
panic("my_shell: should not be here");
}
运行 Bochs 功能验证
编译,运行:
BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
$(BUILD_DIR)/debug.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/string.o \
$(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \
$(BUILD_DIR)/switch.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o \
$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o \
$(BUILD_DIR)/process.o $(BUILD_DIR)/syscall-init.o $(BUILD_DIR)/syscall.o \
$(BUILD_DIR)/stdio.o $(BUILD_DIR)/stdio-kernel.o $(BUILD_DIR)/ide.o \
$(BUILD_DIR)/fs.o $(BUILD_DIR)/inode.o $(BUILD_DIR)/file.o $(BUILD_DIR)/dir.o \
$(BUILD_DIR)/fork.o $(BUILD_DIR)/shell.o $(BUILD_DIR)/buildin_cmd.o
############ C 代码编译 ##############
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
lib/stdint.h kernel/init.h lib/string.h \
lib/kernel/stdio-kernel.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
lib/stdint.h kernel/interrupt.h device/timer.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h \
lib/kernel/io.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
lib/kernel/print.h lib/stdint.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/string.o: lib/string.c lib/string.h \
kernel/debug.h kernel/global.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \
lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
lib/stdint.h lib/string.h kernel/global.h kernel/memory.h \
kernel/debug.h kernel/interrupt.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \
kernel/interrupt.h lib/stdint.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
lib/stdint.h thread/thread.h kernel/debug.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/console.o: device/console.c device/console.h \
lib/kernel/print.h thread/sync.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
lib/kernel/print.h lib/kernel/io.h kernel/interrupt.h \
kernel/global.h lib/stdint.h device/ioqueue.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \
kernel/interrupt.h kernel/global.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \
kernel/global.h thread/thread.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \
lib/string.h kernel/global.h kernel/memory.h lib/kernel/print.h \
thread/thread.h kernel/interrupt.h kernel/debug.h device/console.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \
lib/user/syscall.h lib/stdint.h lib/kernel/print.h \
kernel/interrupt.h thread/thread.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h lib/stdint.h lib/string.h lib/user/syscall.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/stdio-kernel.o : lib/kernel/stdio-kernel.c lib/kernel/stdio-kernel.h \
lib/stdio.h device/console.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/ide.o: device/ide.c device/ide.h lib/stdint.h kernel/debug.h \
lib/kernel/stdio-kernel.h lib/stdio.h kernel/global.h thread/sync.h \
lib/kernel/io.h device/timer.h kernel/interrupt.h lib/kernel/list.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/fs.o: fs/fs.c fs/fs.h lib/stdint.h kernel/global.h device/ide.h fs/inode.h fs/dir.h \
fs/super_block.h lib/kernel/stdio-kernel.h lib/string.h kernel/debug.h lib/kernel/list.h \
fs/file.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/inode.o: fs/inode.c fs/inode.h device/ide.h kernel/debug.h thread/thread.h \
kernel/memory.h lib/string.h lib/kernel/list.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/file.o: fs/file.c fs/file.h lib/kernel/stdio-kernel.h thread/thread.h device/ide.h \
fs/file.h kernel/global.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/dir.o: fs/dir.c fs/dir.h device/ide.h fs/fs.h fs/inode.h kernel/memory.h lib/string.h lib/stdint.h \
lib/kernel/stdio-kernel.h kernel/debug.h fs/file.h kernel/memory.h lib/string.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/fork.o: userprog/fork.c userprog/fork.h thread/thread.h lib/stdint.h \
lib/kernel/list.h kernel/global.h lib/kernel/bitmap.h kernel/memory.h \
userprog/process.h kernel/interrupt.h kernel/debug.h \
lib/kernel/stdio-kernel.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/shell.o: shell/shell.c shell/shell.h lib/stdint.h fs/fs.h \
lib/user/syscall.h lib/stdio.h lib/stdint.h kernel/global.h lib/user/assert.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/assert.o: lib/user/assert.c lib/user/assert.h lib/stdio.h lib/stdint.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/buildin_cmd.o: shell/buildin_cmd.c shell/buildin_cmd.h fs/file.h fs/fs.h kernel/debug.h \
lib/string.h lib/user/syscall.h fs/dir.h fs/fs.h lib/stdio.h shell/shell.h
$(CC) $(CFLAGS) $< -o $@
############## 汇编代码编译 ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.asm
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/print.o: lib/kernel/print.asm
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/switch.o: thread/switch.asm
$(AS) $(ASFLAGS) $< -o $@
############## 链接所有目标文件 #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
$(LD) $(LDFLAGS) $^ -o $@
.PHONY: mk_dir hd clean all
mk_dir:
if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
hd:
sudo dd if=$(BUILD_DIR)/kernel.bin \
of=/home/steven/source/os/bochs/hd60M.img \
bs=512 count=200 seek=9 conv=notrunc
clean:
cd $(BUILD_DIR) && rm -f ./*
build: $(BUILD_DIR)/kernel.bin
all: mk_dir build hd
实现 ls、cd、mkdir、ps、rm 等命令
命令分为外部命令和内部命令,外部命令是我们的程序执行的,内部命令是系统内核提供的功能
内部命令的编写规则:
- 内部命令都以前缀
"buildin_" + “命令名”
的形式命名, 如cd命令的函数是buildin_cd。 - 形参均是 argc 和 argv, argc 是参数数组 argv中参数的个数 。
- 函数实现是调用同功能的系统调用实现的,如函数buildin_cd是调用系统调用chdir完成的。
- 在进行系统调用前调用函数 make_clear_ abs_path 把路径转换为绝对路径。
功能都是系统调用实现的,通过解析键入字符和路径,获取输入的命令,根据命令判断来去调用系统调用从而实现了简单的控制功能
buildin_cmd.c
pwd
/* pwd 命令的内建函数 */
void buildin_pwd(uint32_t argc, char** argv UNUSED) {
if(argc != 1) {
printf("pwd: no argument support!\n");
return;
} else {
if(NULL != getcwd(final_path, MAX_PATH_LEN)) {
printf("%s\n", final_path);
} else {
printf("pwd: get current work directory failed.\n");
}
}
}
cd
/* cd命令的内建函数 */
char* buildin_cd(uint32_t argc, char** argv) {
if(argc > 2) {
printf("cd: only support 1 argument!\n");
return NULL;
}
// 若是只键入 cd 而无参数, 直接返回到根目录
if(argc == 1) {
final_path[0] = '/';
final_path[1] = 0;
} else {
make_clear_abs_path(argv[1], final_path);
}
if(chdir(final_path) == -1) {
printf("cd: no such directory %s\n", final_path);
return NULL;
}
return final_path;
}
ls
/* ls 命令的内建函数 */
void buildin_ls(uint32_t argc, char** argv) {
char* pathname = NULL;
struct stat file_stat;
memset(&file_stat, 0, sizeof(struct stat));
bool long_info = false;
uint32_t arg_path_nr = 0;
// 跨过argv[0], argv[0]是字符串 “ls”
uint32_t arg_idx = 1;
while (arg_idx < argc) {
if (argv[arg_idx][0] == '-') {
// 如果是选项, 单词的首字符是-
if (!strcmp("-l", argv[arg_idx])) {
// 如果是参数 -l
long_info = true;
} else if (!strcmp("-h", argv[arg_idx])) {
// 参数 -h
printf("usage: -l list all infomation about the file.\n-h for help\nlist all files in the current dirctory if no option\n");
return;
} else {
// 只支持 -h -l 两个选项
printf("ls: invalid option %s\nTry `ls -h' for more information.\n", argv[arg_idx]);
return;
}
} else {
// ls的路径参数
if (arg_path_nr == 0) {
pathname = argv[arg_idx];
arg_path_nr = 1;
} else {
printf("ls: only support one path\n");
return;
}
}
arg_idx++;
}
if (pathname == NULL) {
// 若只输入了ls 或 ls -l, 没有输入操作路径, 默认以当前路径的绝对路径为参数.
if (NULL != getcwd(final_path, MAX_PATH_LEN)) {
pathname = final_path;
} else {
printf("ls: getcwd for default path failed\n");
return;
}
} else {
make_clear_abs_path(pathname, final_path);
pathname = final_path;
}
if (stat(pathname, &file_stat) == -1) {
printf("ls: cannot access %s: No such file or directory\n", pathname);
return;
}
if (file_stat.st_filetype == FT_DIRECTORY) {
struct dir* dir = opendir(pathname);
struct dir_entry* dir_e = NULL;
char sub_pathname[MAX_PATH_LEN] = { 0 };
uint32_t pathname_len = strlen(pathname);
uint32_t last_char_idx = pathname_len - 1;
memcpy(sub_pathname, pathname, pathname_len);
if (sub_pathname[last_char_idx] != '/') {
sub_pathname[pathname_len] = '/';
pathname_len++;
}
rewinddir(dir);
if (long_info) {
char ftype;
printf("total: %d\n", file_stat.st_size);
while ((dir_e = readdir(dir))) {
ftype = 'd';
if (dir_e->f_type == FT_REGULAR) {
ftype = '-';
}
sub_pathname[pathname_len] = 0;
strcat(sub_pathname, dir_e->filename);
memset(&file_stat, 0, sizeof(struct stat));
if (stat(sub_pathname, &file_stat) == -1) {
printf("ls: cannot access %s: No such file or directory\n", dir_e->filename);
return;
}
printf("%c %d %d %s\n", ftype, dir_e->i_no, file_stat.st_size, dir_e->filename);
}
} else {
while ((dir_e = readdir(dir))) {
printf("%s ", dir_e->filename);
}
printf("\n");
}
closedir(dir);
} else {
if (long_info) {
printf("- %d %d %s\n", file_stat.st_ino, file_stat.st_size, pathname);
} else {
printf("%s\n", pathname);
}
}
}
ps
/* ps命令内建函数 */
void buildin_ps(uint32_t argc, char** argv UNUSED) {
if(argc != 1) {
printf("ps: no argument support!\n");
return;
}
ps();
}
clear
/* clear 命令内建函数 */
void buildin_clear(uint32_t argc, char** argv UNUSED) {
if(argc != 1) {
printf("clear: no argument support!\n");
return NULL;
}
clear();
}
mkdir
/* mkdir 命令内建函数 */
int32_t buildin_mkdir(uint32_t argc, char** argv) {
int32_t ret = -1;
if(argc != 2) {
printf("mkdir: only support 1 argument!\n");
} else {
make_clear_abs_path(argv[1], final_path);
// 若创建的不是根目录
if(strcmp("/", final_path)) {
if(mkdir(final_path) == 0) {
ret = 0;
} else {
printf("mkdir: create directory %s failed.\n", argv[1]);
}
}
}
return ret;
}
rmdir
/* rmdir 命令内建函数 */
int32_t buildin_rmdir(uint32_t argc, char** argv) {
int32_t ret = -1;
if (argc != 2) {
printf("rmdir: only support 1 argument!\n");
} else {
make_clear_abs_path(argv[1], final_path);
// 若删除的不是根目录
if (strcmp("/", final_path)) {
if (rmdir(final_path) == 0) {
ret = 0;
} else {
printf("rmdir: remove %s failed.\n", argv[1]);
}
}
}
return ret;
}
rm
/* rm 命令内建函数 */
int32_t buildin_rm(uint32_t argc, char** argv) {
int32_t ret = -1;
if (argc != 2) {
printf("rm: only support 1 argument!\n");
} else {
make_clear_abs_path(argv[1], final_path);
// 若删除的不是根目录
if (strcmp("/", final_path)) {
if (unlink(final_path) == 0) {
ret = 0;
} else {
printf("rm: delete %s failed.\n", argv[1]);
}
}
}
return ret;
}
help
/* 显示内建命令列表 */
void buildin_help(uint32_t argc UNUSED, char** argv UNUSED) {
help();
}
shell/shell.c
/* 简单的 shell */
void my_shell(void) {
cwd_cache[0] = '/';
cwd_cache[1] = 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 {
printf("external command\n");
}
}
panic("my_shell: should not be here");
}
运行 Bochs 功能验证
编译,运行: