p2p倒闭 网站开发,企业oa系统价格,智能网站建设公司排名,四平市住房和城乡建设部网站文章目录 前言可变参数的原理实现系统调用write更新syscall.h更新syscall.c更新syscall-init.c 实现printf编写stdio.h编写stdio.c 第一次测试main.cmakefile结果截图 完善printf修改main.c 结语 前言
上部分链接#xff1a;《操作系统真象还原》第十二章#xff08;1#… 文章目录 前言可变参数的原理实现系统调用write更新syscall.h更新syscall.c更新syscall-init.c 实现printf编写stdio.h编写stdio.c 第一次测试main.cmakefile结果截图 完善printf修改main.c 结语 前言
上部分链接《操作系统真象还原》第十二章1——进一步完善内核-CSDN博客。上部分我们是寄存器存参数书的结尾还提到了用栈传递参数实现系统调用我们不使用这种方法。
之前我们屏幕打印是直接操作显存显然用户进程没有这样的权限。这部分完成printf函数让用户进程也能打印信息。 可变参数的原理
这里摘一些我认为比较关键的内容吧。
早期操作系统只能申请静态内存。随着计算机的进步操作系统开始支持堆内存管理堆内存专 门用于程序运行时的内存申请因此编译器也开始支持程序在运行时动态内存申请也就是编译器开始支 持源码中的变长数据结构。
程序中的数据结构终归有个长度此长度要么在编译时确定要么在运行时确 定。编译时确定是指数据结构在源码编译阶段就能确定下来说白了就是编译器必须提前知道数据结构的 长度它为此类数据结构分配的是静态内存也就是程序被操作系统加载时分配的内存。运行时确定是指 数据结构的长度是在程序运行阶段确定下来的编译器为此类数据结构如 C99 中的变长数组在堆中 分配内存已经说过了堆本来就是用于程序运行时的动态内存分配因此可以在运行阶段确定长度。
函数占用的也是静态内存因此也得提前告诉编译器自己占用的内存大小。为了在 编译时获取函数调用时所需要的内存空间这通常是在栈中分配内存单元编译器要求提供函数声明 声明中描述了函数参数的个数及类型编译器用它们来计算参数所占据的栈空间。因此编译器不关心函数 声明中参数的名称它只关心参数个数及类型您懂的函数声明中的参数可以不包括参数名但必须包 括类型编译器用这两个信息才能确定为函数在栈中分配的内存大小。重点来了函数并不是在堆中分 配内存因此它需要提前确定内存空间这通常取决于参数的个数及类型 大小但编译器却允许函数的参数个数不固定可变参数。
其实这种可变仍然是静态的。参数是由调用者压入的调用者当然知道栈中压入了几个参数参数占用了多少空间因 此无论函数的参数个数是否固定采用 C 调用约定调用者都能完好地回收栈空间不必担心栈溢出等 问题。因此看似“动态”的可变参数函数其实也是“静态”“固定”的传入参数的个数是由编译器 在编译阶段就确定下来的。
拿格式化输出函数 printf(char* format, arg1, arg2…)举例比如printf”hello %s!”, ”martin”其中的”hello %s!”便是 format——格式化字符串。通过%占位符就能实现可变参数。
linux通过三个宏定义支持可变参数下面是3个宏的说明。
va_start(ap,v)参数 ap 是用于指向可变参数的指针变量参数v是支持可变参数的函数的第1个 参数如对于printf来说参数v就是字符串format。此宏的功能是使指针ap指向v的地址它的调用 必须先于其他两个宏相当于初始化ap指针的作用。va_arg(ap,t)参数 ap 是用于指向可变参数的指针变量参数t是可变参数的类型此宏的功能是 使指针ap指向栈中下一个参数的地址并返回其值。va_end(ap)将指向可变参数的变量ap置为null也就是清空指针变量ap。
后续我们会实现这三个宏。 实现系统调用write
linux的系统调用write 接受 3个参数其中的fd是文件描述符buf是被 输出数据所在的缓冲区count 是输出的字符数write 的功 能是把buf中count个字符写到文件描述符fd指向的文件中。
我们这里先实现一个简易版本只接受一个参数——待打印字符指针。
我们按三部曲完成简单版write。
更新syscall.h
第一步添加新的子功能号
#ifndef __LIB_USER_SYSCALL_H
#define __LIB_USER_SYSCALL_H
#include ../kernel/stdint.henum SYSCALL_NR
{SYS_GETPID,SYS_WRITE
};
uint32_t getpid(void); // 获取任务pid
uint32_t write(char *str); // 打印字符串并返回字符串长度#endif更新syscall.c
第二步添加系统调用的用户接口
#include ./syscall.h/*从上到下分别是0、1、2、3参数的系统调用结构基本一致*eax是子程序号剩下三个存在ebx、ecx、edx中*//*({ ... })是gcc扩展*将一组语句封装为一个表达式返回最后一个语句的值*/
#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(char *str)
{return _syscall1(SYS_WRITE, str);
}更新syscall-init.c
第三步定义子功能处理函数并在syscall_table中注册 #include ./syscall-init.h
#include ../lib/kernel/stdint.h
#include ../lib/user/syscall.h
#include ../thread/thread.h
#include ../lib/kernel/print.h
#include ../device/console.h
#include ../lib/string.h#define syscall_nr 32 // 最大支持的子功能个数
typedef void *syscall;
syscall syscall_table[syscall_nr];/*返回当前任务的pid*/
uint32_t sys_getpid(void)
{return running_thread()-pid;
}/*打印字符串str*/
uint32_t sys_wirte(char *str)
{console_put_str(str);return strlen(str);
}/*初始化系统调用*/
void syscall_init(void)
{put_str(syscall_init start\n);syscall_table[SYS_GETPID] sys_getpid;syscall_table[SYS_WRITE] sys_wirte;put_str(syscall_init done\n);
}到此我们实现了文件管理系统之前的简化版write。 实现printf
printf是vsprintf和write的封装write已经完成本 节要完成vsprintf、用于可变参数解析的3个宏以及转换函数itoa这些实现后就完成了基本的printf本 节的目标是使printf支持十六进制输出即完成“%x”的功能。
关于linux中的vsprintf函数
此函数的功能是把 ap 指向的可变参数以字符串格式format中的符号’%为替 换标记不修改原格式字符串format将format中除“%类型字符”以外的内容复制到str把“%类型字 符”替换成具体参数后写入str中对应“%类型字符”的位置也就是说函数执行后str的内容相当于格 式字符串format中的“%类型字符”被具体参数替换后的format字符串。vsprintf 执行完成后返回字符串str的长度。
同样我们参考这个函数写我们的vsprintf路径是lib/stdio.c .h
编写stdio.h #ifndef __LIB_STDIO_H
#define __LIB_STDIO_H
#include ./kernel/stdint.h
typedef char *va_list;
uint32_t vsprintf(char *str, const char *format, va_list ap);
uint32_t printf(const char *format, ...);#endif先给出头文件再给出函数实现。
编写stdio.c
这部分最长的代码注释很清楚不再赘述 #include ./stdio.h
#include ./kernel/stdint.h
#include ./string.h
#include ../kernel/debug.h
#include ./user/syscall.h#define va_start(ap, v) ap (va_list) v // ap指向第一个固定参数v
#define va_arg(ap, t) *((t *)(ap 4)) // ap依次指向下一个参数通过解除引用返回其值
#define va_end(ap) ap NULL/*将整型转化为字符ascii*/
/*三个参数依次是带转化数值转化后字符保存的缓冲区转化进制*/
static void itoa(uint32_t value, char **buf_ptr_addr, uint8_t base)
{uint32_t m value % base; // 余数uint32_t i value / base; // 倍数if (i){itoa(i, buf_ptr_addr, base);}if (m 10){// 第一次解引用后是缓冲区地址提供下一个字符的位置// 第二次解引用后是char赋值为对应的字符*((*buf_ptr_addr)) m 0;}else{*((*buf_ptr_addr)) m - 10 A;}
}/*将参数ap按照格式format输出到字符串str并返回替换后str长度*/
uint32_t vsprintf(char *str, const char *format, va_list ap)
{char *buf_ptr str;const char *index_ptr format;char index_char *index_ptr;int32_t arg_int;while (index_char) // 没有到达末尾就一直处理{if (index_char ! %) // 没有遇到%直接复制即可{*buf_ptr index_char;buf_ptr;index_ptr;index_char *index_ptr;continue;}// 以下为遇到%后的处理过程// 先跳过%index_ptr;index_char *index_ptr;// 然后判断占位符是哪种// 目前先实现x代表后面的参数是无符号整形if (index_char x){// 获得第一个参数并且ap指向下一个参数arg_int va_arg(ap, int);// 将无符号整型转化为字符并放到str后面itoa(arg_int, buf_ptr, 16);// 跳过x并且准备好进行后面的处理index_ptr;index_char *index_ptr;}else{PANIC(Undefined placeholder);}}return strlen(str);
}/*格式化输出字符串format即printf*/
/*包含可变参数*/
uint32_t printf(const char *format, ...)
{va_list args; // 可变参数列表va_start(args, format);char buf[1024] {0}; // 最终拼接后字符串储存位置vsprintf(buf, format, args);va_end(args);return write(buf);
}第一次测试
main.c
// 内核的入口函数
#include ../lib/kernel/print.h
#include ./init.h
#include ../thread/thread.h
#include ../device/console.h
#include ./interrupt.h
#include ../userprog/process.h
// 本章测试头文件
#include ../lib/user/syscall.h
#include ../userprog/syscall-init.h
#include ../lib/stdio.hvoid k_thread_a(void *);
void k_thread_b(void *);
void u_prog_a(void);
void u_prog_b(void);
int main(void)
{put_str(HongBais OS kernel\n);init_all(); // 初始化所有模块process_execute(u_prog_a, user_prog_a);process_execute(u_prog_b, user_prog_b);intr_enable();console_put_str( main_pid:0x);console_put_int(sys_getpid());console_put_char(\n);thread_start(k_thread_a, 31, k_thread_a, argA: );thread_start(k_thread_b, 31, k_thread_b, argB: );while (1){};
}void k_thread_a(void *arg)
{char *para arg;console_put_str( thread_a_pid:0x);console_put_int(sys_getpid());console_put_char(\n);while (1){};
}void k_thread_b(void *arg)
{char *para arg;console_put_str( thread_b_pid:0x);console_put_int(sys_getpid());console_put_char(\n);while (1){};
}void u_prog_a(void)
{printf( program_a_pid:0x%x\n, getpid());while (1){};
}void u_prog_b(void)
{printf( program_b_pid:0x%x\n, getpid());while (1){};
}makefile
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)/string.o $(BUILD_DIR)/memory.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################ c代码编译 ##################
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \lib/kernel/stdint.h kernel/init.h kernel/debug.h \kernel/memory.h thread/thread.h kernel/interrupt.h \device/console.h userprog/process.h lib/user/syscall.h \userprog/syscall-init.h lib/stdio.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \lib/kernel/stdint.h kernel/interrupt.h device/timer.h \kernel/memory.h thread/thread.h device/console.h \device/keyboard.h userprog/tss.h userprog/syscall-init.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \lib/kernel/stdint.h kernel/global.h kernel/io.h \lib/kernel/print.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/stdint.h \kernel/io.h lib/kernel/print.h kernel/interrupt.h \thread/thread.h kernel/debug.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \lib/kernel/print.h kernel/interrupt.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/string.o: lib/string.c lib/string.h \kernel/debug.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h \lib/string.h thread/sync.h thread/thread.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/kernel/stdint.h lib/kernel/list.h lib/string.h \kernel/memory.h kernel/interrupt.h kernel/debug.h \lib/kernel/print.h userprog/process.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \lib/kernel/stdint.h kernel/interrupt.h kernel/debug.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \lib/kernel/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 kernel/interrupt.h kernel/io.h \lib/kernel/stdint.h device/ioqueue.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \lib/kernel/stdint.h thread/thread.h thread/sync.h \kernel/interrupt.h kernel/debug.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \lib/kernel/stdint.h thread/thread.h kernel/global.h \lib/kernel/print.h lib/string.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \kernel/global.h lib/kernel/stdint.h thread/thread.h \kernel/debug.h userprog/tss.h device/console.h \lib/string.h kernel/interrupt.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \lib/kernel/stdint.h lib/user/syscall.h thread/thread.h \lib/kernel/print.h$(CC) $(CFLAGS) $ -o $$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h \lib/kernel/stdint.h lib/string.h kernel/debug.h \lib/user/syscall.h$(CC) $(CFLAGS) $ -o $############## 汇编代码编译 ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S$(AS) $(ASFLAGS) $ -o $$(BUILD_DIR)/print.o: lib/kernel/print.S$(AS) $(ASFLAGS) $ -o $$(BUILD_DIR)/switch.o: thread/switch.S$(AS) $(ASFLAGS) $ -o $############## 连接所有目标文件 #############
$(BUILD_DIR)/kernel.bin: $(OBJS)$(LD) $(LDFLAGS) $^ -o $.PHONY : mk_dir hd clean allmk_dir:if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fihd:dd if$(BUILD_DIR)/kernel.bin \of/home/hongbai/bochs/bin/c.img \bs512 count200 seek10 convnotruncclean:cd $(BUILD_DIR) rm -f ./*build: $(BUILD_DIR)/kernel.binall: mk_dir build hd
结果截图 还是非常成功的。 完善printf
一口气实现对“%c”、“%s”和“%d”三种占位符的处理。
对应单个字符字符串int类型。 #include ./stdio.h
#include ./kernel/stdint.h
#include ./string.h
#include ../kernel/debug.h
#include ./user/syscall.h#define va_start(ap, v) ap (va_list) v // ap指向第一个固定参数v
#define va_arg(ap, t) *((t *)(ap 4)) // ap依次指向下一个参数通过解除引用返回其值
#define va_end(ap) ap NULL/*将整型转化为字符ascii*/
/*三个参数依次是带转化数值转化后字符保存的缓冲区转化进制*/
static void itoa(uint32_t value, char **buf_ptr_addr, uint8_t base)
{uint32_t m value % base; // 余数uint32_t i value / base; // 倍数if (i){itoa(i, buf_ptr_addr, base);}if (m 10){// 第一次解引用后是缓冲区地址提供下一个字符的位置// 第二次解引用后是char赋值为对应的字符*((*buf_ptr_addr)) m 0;}else{*((*buf_ptr_addr)) m - 10 A;}
}/*将参数ap按照格式format输出到字符串str并返回替换后str长度*/
uint32_t vsprintf(char *str, const char *format, va_list ap)
{char *buf_ptr str;const char *index_ptr format;char index_char *index_ptr;int32_t arg_int;char *arg_str;while (index_char) // 没有到达末尾就一直处理{if (index_char ! %) // 没有遇到%直接复制即可{*buf_ptr index_char;buf_ptr;index_ptr;index_char *index_ptr;continue;}// 以下为遇到%后的处理过程// 先跳过%index_ptr;index_char *index_ptr;// 然后判断占位符是哪种类型// %x后面的参数是16进制unsigned intif (index_char x){// 获得第一个参数并且ap指向下一个参数arg_int va_arg(ap, int);// 将无符号整型转化为字符并放到str后面itoa(arg_int, buf_ptr, 16);// 跳过x并且准备好进行后面的处理index_ptr;index_char *index_ptr;}// %d后面的参数是intelse if (index_char d){arg_int va_arg(ap, int);// 负数需要进行补码操作转化为正数然后额外输出一个-if (arg_int 0){arg_int 0 - arg_int;*buf_ptr -;buf_ptr;}itoa(arg_int, buf_ptr, 10);index_ptr;index_char *index_ptr;}// %c,后面的参数是charelse if (index_char c){*buf_ptr va_arg(ap, char);buf_ptr;index_ptr;index_char *index_ptr;}// %s,后面的参数是string(char*)else if (index_char s){arg_str va_arg(ap, char *);strcpy(buf_ptr, arg_str);buf_ptr strlen(arg_str);index_ptr;index_char *index_ptr;}else{PANIC(Undefined placeholder);}}return strlen(str);
}/*格式化输出字符串format即printf*/
/*包含可变参数*/
uint32_t printf(const char *format, ...)
{va_list args; // 可变参数列表va_start(args, format);char buf[1024] {0}; // 最终拼接后字符串储存位置vsprintf(buf, format, args);va_end(args);return write(buf);
}uint32_t sprintf(char *buf, const char *format, ...)
{va_list args;uint32_t retval;va_start(args, format);retval vsprintf(buf, format, args);va_end(args);return retval;
}修改main.c
// 内核的入口函数
#include ../lib/kernel/print.h
#include ./init.h
#include ../thread/thread.h
#include ../device/console.h
#include ./interrupt.h
#include ../userprog/process.h
// 本章测试头文件
#include ../lib/user/syscall.h
#include ../userprog/syscall-init.h
#include ../lib/stdio.hvoid k_thread_a(void *);
void k_thread_b(void *);
void u_prog_a(void);
void u_prog_b(void);
int main(void)
{put_str(HongBais OS kernel\n);init_all(); // 初始化所有模块process_execute(u_prog_a, user_prog_a);process_execute(u_prog_b, user_prog_b);intr_enable();printf( main_pid:0x%x\n,getpid());thread_start(k_thread_a, 31, k_thread_a, argA: );thread_start(k_thread_b, 31, k_thread_b, argB: );while (1){};
}void k_thread_a(void *arg)
{char *para arg;printf( thread_a_pid:0x%x\n,getpid());while (1){};
}void k_thread_b(void *arg)
{char *para arg;printf( thread_b_pid:0x%x\n,getpid());while (1){};
}void u_prog_a(void)
{printf(%s%d%c, program_a_pid:,getpid(),\n);while (1){};
}void u_prog_b(void)
{printf(%s%d%c, program_b_pid:,getpid(),\n);while (1){};
}makefile不变结果截图 ok那么四种占位符都测试完毕prints初步实现。 结语
第二部分整体还是比较简单马上就要进入最难的内存部分了郑钢老师提到我们要重构我们的内存管理系统还好我之前梳理过希望下一部分能高效率顺利完成。