Exercise 1.8
解答:
在这个练习中我们首先要阅读以下三个源文件的代码,弄清楚他们三者之间的关系:
三个文件分别为 kernprintf.c,kernconsole.c, libprintfmt.c
首先大致浏览三个源文件,其中粗略的观察到3点:
1.kernprintf.c中的cprintf,vcprintf子程序调用了libprintfmt.c中的vprintfmt子程序。
2.kernprintf.c中的putch子程序中调用了cputchar,这个程序是定义在kernconsole.c中的。
3.libprintfmt.c中的某些程序也依赖于cputchar子程序
所以得出结论,kernprintf.c,libprintfmt.c两个文件的功能依赖于kernconsole.c的功能。所以我们就先探究一下kernconsole.c。
1.kernconsole.c
这个文件中定义了如何把一个字符显示到console上,即我们的显示屏之上,里面包括很多对IO端口的操作。
其中我们最感兴趣的自然就是cputchar子程序了。下面是这个程序的代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// `High'-level console I/O. Used by readline and cprintf. void cputchar(int c) { cons_putc(c); } // output a character to the console static void cons_putc(int c) { serial_putc(c); lpt_putc(c); cga_putc(c); }
在上面的代码中我发现两点,1.cputchar代码的注释中说:这个程序时最高层的console的IO控制程序,2.cputchar的实现其实是通过调用cons_putc完成的。
cons_putc程序的功能在它的备注中已经被叙述的很清楚了,即输出一个字符到控制台(计算机的屏幕)。所以我们就知道了cputchar的功能也是向屏幕上输出一个字符。
下面我们具体看下cons_putc子程序,这段如果不感兴趣可以略过,直接看对libprintfmt.c文件的分析。
cons_putc子程序中包含3个子程序,我们分别看下,首先是serial_putc子程序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#define COM1 0x3F8 #define COM_TX 0 // Out: Transmit buffer (DLAB=0) #define COM_LSR 5 // In: Line Status Register #define COM_LSR_TXRDY 0x20 // Transmit buffer avail static void serial_putc(int c) { int i; for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i++) delay(); outb(COM1 + COM_TX, c); }
其中包括了一些IO端口程序,通过代码中的宏定义我们知道它是在控制0x3f8端口,这个端口我们在 http://bochs.sourceforge.net/techspec/PORTS.LST 中查询可以看到,它是属于控制计算机中的串口的。我们在观察一下子程序中的inb指令和outb指令,他们分别控制了两个端口,COM1 + COM_LSR = 0x3f8+5 = 0x3fd端口和COM1 + COM_TX = 0x3f8+0 = 0x3f8端口。查询上面的链接看到两个端口的定义:
从上面的图片中我们可以知道,inb指令是读取0x3fd端口,即line status registers,的内容,并且判断它的bit5是否为1,即发送数据缓冲寄存器是否为空。如果为空,则计算机可以发送下一个数据给端口。
而outb指令则是把要发送的数据c,发送给0x3f8,从上图中可见,当0x3f8端口被写入值时,他是作为发送数据缓冲寄存器的,里面存放要发送给串口的数据。
所以serial_putc子程序的功能是把一个字符输出给串口。至于为什么要这么做我还没有想明白。
再考虑下一个子程序,lpt_putc,代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/***** Parallel port output code *****/ // For information on PC parallel port programming, see the class References // page. static void lpt_putc(int c) { int i; for (i = 0; !(inb(0x378+1) & 0x80) && i < 12800; i++) delay(); outb(0x378+0, c); outb(0x378+2, 0x08|0x04|0x01); outb(0x378+2, 0x08); }
它的功能在注释里面已经很清楚了,就是把这个字符输出给并口设备。为什么这样做也不清楚。
最后一个程序,cga_putc:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
static void cga_putc(int c) { // if no attribute given, then use black on white if (!(c & ~0xFF)) c |= 0x0700; switch (c & 0xff) { case '': if (crt_pos > 0) { crt_pos--; crt_buf[crt_pos] = (c & ~0xff) | ' '; } break; case ' ': crt_pos += CRT_COLS; /* fallthru */ case ' ': crt_pos -= (crt_pos % CRT_COLS); break; case ' ': cons_putc(' '); cons_putc(' '); cons_putc(' '); cons_putc(' '); cons_putc(' '); break; default: crt_buf[crt_pos++] = c; /* write the character */ break; } // What is the purpose of this? if (crt_pos >= CRT_SIZE) { int i; memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++) crt_buf[i] = 0x0700 | ' '; crt_pos -= CRT_COLS; } /* move that little blinky thing */ outb(addr_6845, 14); outb(addr_6845 + 1, crt_pos >> 8); outb(addr_6845, 15); outb(addr_6845 + 1, crt_pos); }
这个程序的功能根据名称就能才出来了,肯定是把字符输出到cga设备上面,即计算机的显示屏。至于里面的代码的含义,也比较好理解,它定义了一个缓冲区,缓冲区的当且显示内容的最后一个字符的指针就是crt_pos,所以当你新输入一个字符时,你必须根据字符值的值,来输出正确的内容给这个缓冲区,然后缓冲区的内容才能正确的显示在屏幕上。
比如第8行当c为''时,代表是输入了退格,所以此时要把缓冲区最后一个字节的指针减一,相当于丢弃当前最后一个输入的字符。当c为' '时,我要输出5个空格给缓冲区。如果不是特殊字符,那么就把字符的内容直接输入到缓冲区。
而switch之后的if判断语句的功能应该是保证缓冲区中的最后显示出去的内容的大小不要超过显示的大小界限CRT_SIZE。
最后四句则是把缓冲区的内容输出给显示屏。
以上就是对cputchar子程序和console.c文件的分析。
2.libprintfmt.c
首先看一下这个文件刚开头的注释:
"打印各种样式的字符串的子程序,经常被printf,sprintf,fprintf函数所调用,这些代码是同时被内核和用户程序所使用的。"
通过这个注释我们知道,这个文件中定义的子程序是我们能在编程时直接利用printf函数向屏幕输出信息的关键。
那么我们把目光锁定到被其他文件依赖的vprintfmt子程序,下面的这个版本是我已经加过注释,并且补充了一部分的版本,你还可以在lab/Lab1目录下找到它,名字为printfmt.c,而原来没修改过没备注的在lab/lib目录下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 void 2 vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) 3 { 4 register const char *p; 5 register int ch, err; 6 unsigned long long num; 7 int base, lflag, width, precision, altflag; 8 char padc; 9 10 while (1) { 11 while ((ch = *(unsigned char *) fmt++) != '%') { 12 if (ch == '