1、实现fork系统调用
fork创建子进程。通过fork的返回值来判断是父进程还是子进程。
当fork()返回值会返回进程的id,当进程发现pid==0,就知道了自己是fork出来的子进程;而如果pid > 0,则知道了自己是父进程。
fork系统调用实现:
- 在系统调用表sys_table中添加系统调用接口
- 分配子进程运行所需的任务控制块task_t
- 初始化task_t:关键之处在于要让该结构体中的内容,除tss->eax以外,其它内核寄存器的值要与父进程中的相同,这样才能完整的复制父进程调用fork前的状态
- 需要单独为子进程分配相应的内容空间:即内核页表及相应的物理页。这样就可以使子进程拥有独立的运行内存空间。
2、实现exec系统调用
前面的fork只能实现从一个现有的进程克隆出一个子进程。因此下面实现加载全新应用程序的功能:exec()即创建一个独立编译的应用程序
1)建立独立的 不和操作系统代码文件一起编译的文件,比如建立main.c文件,添加main函数
int main (int argc, char **argv) {for (;;) {msleep(1000);}
}
2)添加main函数启动之前的C运行环境的代码,放在crt0.s和cstart.c中
_start:
# 设置各数据段的选择子,由于应用任务都是用tss恢复的,所以
# 实际不必设置,但为安全起见,还是设置一下
mov %ss, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs# 进入主函数执行,注意此时栈没有变,参数仍然在其中
# 所以调用cstart时,仍然可以看到参数
jmp cstart
void cstart (int argc, char ** argv) {main(argc, argv);
}
3)告诉编译器应当将程序编译为在那个地方开始运行:通过链接脚本实现(使用0x81000000 )
4)修改工程cmakelist.txt文件,添加针对该应用程序的编译选项
project(shell LANGUAGES C) # 使用自定义的链接器
# 加入相应的库
set(LIBS_FLAGS "-L ${CMAKE_BINARY_DIR}/source/applib/ -lapp")
set(CMAKE_EXE_LINKER_FLAGS "-m elf_i386 -T ${PROJECT_SOURCE_DIR}/link.lds ${LIBS_FLAGS}")
set(CMAKE_C_LINK_EXECUTABLE "${LINKER_TOOL} <OBJECTS> ${CMAKE_EXE_LINKER_FLAGS} -o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf")include_directories(
${PROJECT_SOURCE_DIR}/../applib/
)# 将所有的汇编、C文件加入工程
# 注意保证start.asm在最前头
file(GLOB C_LIST "*.c" "*.h" "*.S")
add_executable(${PROJECT_NAME} ${C_LIST})# 不带调试信息的elf生成,何种更小,写入到image目录下
add_custom_command(TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND ${OBJCOPY_TOOL} -S ${PROJECT_NAME}.elf ${CMAKE_SOURCE_DIR}/../../image/${PROJECT_NAME}.elf
COMMAND ${OBJDUMP_TOOL} -x -d -S -m i386 ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf > ${PROJECT_NAME}_dis.txt
COMMAND ${READELF_TOOL} -a ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.elf > ${PROJECT_NAME}_elf.txt
相关知识补充
添加虚拟文件访问接口
int sys_open(const char *name, int flags, ...);
int sys_read(int file, char *ptr, int len);
int sys_write(int file, char *ptr, int len);
int sys_lseek(int file, int ptr, int dir);
int sys_close(int file);
在这些接口中,只实现对5000扇区处的shell.elf加载。首先将shell.elf加载到内存,然后将直接从内存中直接进行elf文件解析
创建完成就是增加exec()系统调用,与实现fork()系统调用过程一样,其功能就是使用sys_exec加载应用程序
3、添加sys_yield()系统调用
功能:这个系统调用来实现进程主动放弃CPU并切换到其他进程运行过程。
首先,需要为系统调用预留接口并添加相应的ID,然后在库函数中实现该系统调用的接口。接着,需要处理系统调用的映射表,调用task.c中的函数实现进程主动放弃CPU的功能。