3 4.1 简单的hello world again.
4 此时, 我们已经跳转到了init.c文件的init函数(是的, 不是所有的C程序都是从main开始执行的, 我们也可以不用main, 就像这里一样)。 为了验证之前的努力有没成功, 我们可以写个很简单的puts函数输出些信息表示我们的操作系统已经成功的运行到这。 puts函数简写如下:
6 static void puts(const char *str)
8 static char * vga_mem = (char *) 0xb8000;
9 static unsigned long pos = 0;
11 *(vga_mem + pos) = *str++;
19 puts("Hello world again");
21 是的, 就这么简单。 虽然很简单, 但是有几点得注意, 第一, 在开发操作系统的过程中, 是没有任何现成的库函数可以用的, 即使是标准的C库。 所以, 连最常用的printf, strcpy之类的函数你也得自己实现。为了简单起见, 这里只实现了一个很简单的puts函数, 功能很简单, 就是把字符串str打印到屏幕上。 第二, 在第1M内存区中, 有几段很特殊的内存空间, 比如第1K字节是由BIOS所有, 0xb8000是VGA的一段内存映射, 也就是说往这段写了数据就会在屏幕上显示出来。不过, 那段内存是用两个字节的空间来存储一个打印字符:第一个字节存储着打印字符的ASCII值, 第二个字节存储着相应的属性, 如字体的颜色, 背景颜色之类的。这也就是为什么pos += 2, 而不是1的原因。
24 就如刚才说所, 开发操作系统没有任何的库函数可用, 所以, 我们得实现一个自己的printf函数, printk。 实现printk的关键就是得弄懂函数的栈结构, 因为printf是变参形函数。 现已一个简单的例子来讲解函数调用的是栈结构。 拿一个简单的sum函数来说:
25 static int sum(int a, int b)
36 ------------ Stack TOP
46 ------------ <---- 刚进入sum函数时ESP所处的位置
50 所以, 对应于一个类似于这样的函数调用 printf("The sum of %d and %d is %d\n", a, b, a + b) 的栈空间大概如下:
52 ------------- <----- Stack TOP
61 | 参数 1 (字符串 "The sum of %d and %d is %d\n" 的内存地址)
66 ------------ <---- 刚进入sum函数时ESP所处的位置
71 int printk(const char *fmt, ...)
73 char *var = &fmt + 4; /* 栈 4 字节对齐 */
78 * 此时, 我们就得到了第二个参数的地址。 类似的可以第到第2, 3 ... N
79 * 个参数的地址, 这也就是为什么变参函数行的通的道理。
81 * 接下来要做的就是对fmt字符串做出分析, 每遇到一个%, 就取一个后面的
82 * 参数, 直到没有%,此时也应该没有没处理到的参数
93 * my_itoa: 把整数转成字符串, 存到p处, 并返回
98 p = my_itoa(*(int *)var, p);
99 var += 4; /* 使var指向下一下参数 */
111 return p - buf; /* 返回打印的字符数 */
114 当然, 这仅是printf函数的一个简单实现。 有了它之后, 我们将很方便的输出与记录我们想要的信息!
119 printk("The system is saying %s", "Hello world again");
121 是的, 我们已经有了自己的printk函数。
124 这里我们简单地实现了两个重要的函数, 第一个是puts,将一字符串输出到屏幕上, 第二个是printk, 格式化输出, 用法和printf一样。
126 当然, 我们可以自己实现更多的常用函数, 如strcpy:
127 void strcpy(char *dst, const char *src)
132 等等。介于本章不是整个C库的实现, 所以,就例举到此。 关于更多的库函数的实现, 可参考glibc[Glibc]或是相应的书籍, 如 [Plauger 1991]The Standard C Library.