Add a new chapter lib
[thunix.git] / doc / lib
blobded19256e5373698e3023636c02d5eb8c8ced1f2
1 Chapter 4  简单的库输出函数
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;
10         while (*str) {
11                 *(vga_mem + pos) = *str++;
12                 pos += 2;
13         }
16 因此, init将可以简写如下:
17 void init(void)
19         puts("Hello world again");
21 是的, 就这么简单。 虽然很简单, 但是有几点得注意, 第一, 在开发操作系统的过程中, 是没有任何现成的库函数可以用的, 即使是标准的C库。 所以, 连最常用的printf, strcpy之类的函数你也得自己实现。为了简单起见, 这里只实现了一个很简单的puts函数, 功能很简单, 就是把字符串str打印到屏幕上。 第二, 在第1M内存区中, 有几段很特殊的内存空间, 比如第1K字节是由BIOS所有, 0xb8000是VGA的一段内存映射, 也就是说往这段写了数据就会在屏幕上显示出来。不过, 那段内存是用两个字节的空间来存储一个打印字符:第一个字节存储着打印字符的ASCII值, 第二个字节存储着相应的属性, 如字体的颜色, 背景颜色之类的。这也就是为什么pos += 2, 而不是1的原因。
23 4.2 printk的实现
24 就如刚才说所, 开发操作系统没有任何的库函数可用, 所以, 我们得实现一个自己的printf函数, printk。 实现printk的关键就是得弄懂函数的栈结构, 因为printf是变参形函数。 现已一个简单的例子来讲解函数调用的是栈结构。 拿一个简单的sum函数来说:
25 static int sum(int a, int b)
27         return a + b;
30 int main(void)
32         sum(1, 2);
33         return 0;
36 ------------   Stack TOP
38 ------------  
39 |  参数 2  (b)
40 ------------
41 |  参数 1  (a)
42 -------------
43 |  返回地址
44 ------------
45 |  EBP
46 ------------ <---- 刚进入sum函数时ESP所处的位置
47 |  ....
48 -----------
50 所以, 对应于一个灰似于这样的函数调用 printf("The sum of %d and %d is %d\n", a, b, a + b) 的栈空间大概如下:
52 -------------  <----- Stack TOP
53
54 ------------- 
55 |  参数 4  (a + b)
56 ------------   
57 |  参数 3  (b)
58 ------------   
59 |  参数 2  (a)
60 ------------
61 |  参数 1  (字符串 "The sum of %d and %d is %d\n" 的内存地址)
62 -------------
63 |  返回地址
64 ------------
65 |  EBP
66 ------------ <---- 刚进入sum函数时ESP所处的位置
67 |  ....
68 -----------
70 而我们的printk函数应该是这样:
71 int printk(const char *fmt, ...)
73         char *var = &fmt + 4;  /* 栈 4 字节对齐 */
74         char buf[512];
75         char *p = buf;
77         /*
78          * 此时, 我们就得到了第二个参数的地址。 类似的可以第到第2, 3 ... N
79          * 个参数的地址, 这也就是为什么变参函数行的通的道理。
80          * 
81          * 接下来要做的就是对fmt字符串做出分析, 每遇到一个%, 就取一个后面的
82          * 参数, 直到没有%,此时也应该没有没处理到的参数
83          */
84         while (*fmt) {
85                 if (*fmt != '%') {
86                         *p++ = *fmt++;
87                         continue;
88                 }
89                 fmt++;
90                 switch (*fmt) {
91                 case 'd':
92                         /*
93                          * my_itoa: 把整数转成字符串, 存到p处, 并返回
94                          * 更新后的p, 使p指向字符串的末尾。
95                          * 
96                          * 注: 该函数也得自己实现。
97                          */
98                         p = my_itoa(*(int *)var, p); 
99                         var += 4;  /* 使var指向下一下参数 */
100                 case 's':
101                         ...;
102                 case 'x':
103                         ....;   
104                 ......
105                 }
106         }
108         *p = 0;
109         puts(buf);
111         return p - buf;  /* 返回打印的字符数 */
114 当然, 这仅是printf函数的一个简单实现。 有了它之后, 我们将很方便的输出与记录我们想要的信息!
116 所以, 我们可以改写init成如下所示:
117 void init(void)
119         printk("The system is saying %s", "Hello world again");
121 是的, 我们已经有了自己的printk函数。
123 4.3 小结
124 这里我们简单地实现了两个重要的函数, 第一个是puts,相于将一字符串输出到屏幕上, 第二个是printk, 格式化输出, 用法和printf一样。 
126 当然, 我们可以自己实现更多的常用函数, 如strcpy:
127 void strcpy(char *dst, const char *src)
129         while (*src) 
130                 *dst++ = *src++;
132 等等。介于本章不是整个C库的实现, 所以,就例举到此。 关于更多的库函数的实现, 可参考glibc或是相应的书籍, 如 The Standard C Library.