From 607ae040a4d75370d62abdb0e4cd1afe1fc2072b Mon Sep 17 00:00:00 2001 From: Liu Aleaxander Date: Sun, 14 Mar 2010 00:24:30 +0800 Subject: [PATCH] Add a new chapter lib Signed-off-by: Liu Aleaxander --- doc/lib | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 doc/lib diff --git a/doc/lib b/doc/lib new file mode 100644 index 0000000..ded1925 --- /dev/null +++ b/doc/lib @@ -0,0 +1,132 @@ +Chapter 4 简单的库输出函数 + +4.1 简单的hello world again. +此时, 我们已经跳转到了init.c文件的init函数(是的, 不是所有的C程序都是从main开始执行的, 我们也可以不用main, 就像这里一样)。 为了验证之前的努力有没成功, 我们可以写个很简单的puts函数输出些信息表示我们的操作系统已经成功的运行到这。 puts函数简写如下: + +static void puts(const char *str) +{ + static char * vga_mem = (char *) 0xb8000; + static unsigned long pos = 0; + while (*str) { + *(vga_mem + pos) = *str++; + pos += 2; + } +} + +因此, init将可以简写如下: +void init(void) +{ + puts("Hello world again"); +} +是的, 就这么简单。 虽然很简单, 但是有几点得注意, 第一, 在开发操作系统的过程中, 是没有任何现成的库函数可以用的, 即使是标准的C库。 所以, 连最常用的printf, strcpy之类的函数你也得自己实现。为了简单起见, 这里只实现了一个很简单的puts函数, 功能很简单, 就是把字符串str打印到屏幕上。 第二, 在第1M内存区中, 有几段很特殊的内存空间, 比如第1K字节是由BIOS所有, 0xb8000是VGA的一段内存映射, 也就是说往这段写了数据就会在屏幕上显示出来。不过, 那段内存是用两个字节的空间来存储一个打印字符:第一个字节存储着打印字符的ASCII值, 第二个字节存储着相应的属性, 如字体的颜色, 背景颜色之类的。这也就是为什么pos += 2, 而不是1的原因。 + +4.2 printk的实现 +就如刚才说所, 开发操作系统没有任何的库函数可用, 所以, 我们得实现一个自己的printf函数, printk。 实现printk的关键就是得弄懂函数的栈结构, 因为printf是变参形函数。 现已一个简单的例子来讲解函数调用的是栈结构。 拿一个简单的sum函数来说: +static int sum(int a, int b) +{ + return a + b; +} + +int main(void) +{ + sum(1, 2); + return 0; +} + +------------ Stack TOP +| +------------ +| 参数 2 (b) +------------ +| 参数 1 (a) +------------- +| 返回地址 +------------ +| EBP +------------ <---- 刚进入sum函数时ESP所处的位置 +| .... +----------- + +所以, 对应于一个灰似于这样的函数调用 printf("The sum of %d and %d is %d\n", a, b, a + b) 的栈空间大概如下: + +------------- <----- Stack TOP +| +------------- +| 参数 4 (a + b) +------------ +| 参数 3 (b) +------------ +| 参数 2 (a) +------------ +| 参数 1 (字符串 "The sum of %d and %d is %d\n" 的内存地址) +------------- +| 返回地址 +------------ +| EBP +------------ <---- 刚进入sum函数时ESP所处的位置 +| .... +----------- + +而我们的printk函数应该是这样: +int printk(const char *fmt, ...) +{ + char *var = &fmt + 4; /* 栈 4 字节对齐 */ + char buf[512]; + char *p = buf; + + /* + * 此时, 我们就得到了第二个参数的地址。 类似的可以第到第2, 3 ... N + * 个参数的地址, 这也就是为什么变参函数行的通的道理。 + * + * 接下来要做的就是对fmt字符串做出分析, 每遇到一个%, 就取一个后面的 + * 参数, 直到没有%,此时也应该没有没处理到的参数 + */ + while (*fmt) { + if (*fmt != '%') { + *p++ = *fmt++; + continue; + } + fmt++; + switch (*fmt) { + case 'd': + /* + * my_itoa: 把整数转成字符串, 存到p处, 并返回 + * 更新后的p, 使p指向字符串的末尾。 + * + * 注: 该函数也得自己实现。 + */ + p = my_itoa(*(int *)var, p); + var += 4; /* 使var指向下一下参数 */ + case 's': + ...; + case 'x': + ....; + ...... + } + } + + *p = 0; + puts(buf); + + return p - buf; /* 返回打印的字符数 */ +} + +当然, 这仅是printf函数的一个简单实现。 有了它之后, 我们将很方便的输出与记录我们想要的信息! + +所以, 我们可以改写init成如下所示: +void init(void) +{ + printk("The system is saying %s", "Hello world again"); +} +是的, 我们已经有了自己的printk函数。 +相 +4.3 小结 +这里我们简单地实现了两个重要的函数, 第一个是puts,相于将一字符串输出到屏幕上, 第二个是printk, 格式化输出, 用法和printf一样。 + +当然, 我们可以自己实现更多的常用函数, 如strcpy: +void strcpy(char *dst, const char *src) +{ + while (*src) + *dst++ = *src++; +} +等等。介于本章不是整个C库的实现, 所以,就例举到此。 关于更多的库函数的实现, 可参考glibc或是相应的书籍, 如 The Standard C Library. -- 2.11.4.GIT