无论做什么事情都需要交流,我们的内核也不例外,我们可不想内核一直不声不响地工作,我们需要和它交流,有着良好的互动,做起事情来才会更加的顺畅。大多数时候人们总是喜欢听顺心话,我们和内核交流也希望它按照我们的要求来。就如在前面的异常处理例程的输出,我们希望打印十六进制的表述,而不是二进制的表示,我想没有人希望一连串的01输出。可是在计算机的世界里只有01,我们需要告诉它这些的01应该怎样的显示出来,而不是直接的打印一长串的01。
如果你是一名C程序员,在开发程序的过程中时常需要打印一些反馈信息,使用最多的应该是printf函数,我们给它准备一个的格式化字符串和若干的参数,然后告诉它,我需要你按照指定的格式把参数显示来。printf函数很淡定的说,没问题。然后你在屏幕上看到了期望的输出。记得刚学C语言的时候一直都感觉这是一个神奇的东西,它看到%d就知道需要输出一个十进制整数,看到%s就知道需要输出一个字符串。那个时候还是一个循规蹈矩的大一新生,没有太多的想法,也没有去寻找背后的原因。
有时候看似神秘的事情,其实并没有想象中的那么遥不可及。当你你去尝试努力揭开它那害羞的面纱的时候,想必会另有一番风景。对于格式化字符串输出,也不会有太多神秘的地方,我可以负责任的告诉你,你需要明白只是C语言函数调用过程中参数入栈的顺序就可以去实现自己的格式化输出函数,在C语言中默认的是后面的参数先入栈(也就是说后面的参数位于高地址),调用者清理栈。在这里我不想介绍太多的关于调用约定的东西,教科书式的说教是最让人厌烦的事情。实践是检验真理的唯一标准,看一看真实的状况,想必对理解会更有的帮助,当我们调用printf(“%s,%d,%c\n”,str,100,'c')时,栈的情形大概是这样的:
|————|高地值
|'c' | 参数4
|————|
|100 |参数3
|————|
|str | 参数2
|————|
|fmt | 参数1
|————|
|返回地址|
|————|低地址
| |
|————|
对于格式化字符串fmt可以通过ptr=&fmt获得其地址,显然ptr+4就是存放str的地址,那么在fmt中看到%s时候就可以通过ptr找到需要输出字符串,以此类推,在fmt中看到%d可以找到需要输出的整数为100,在fmt中遇到%c时可以找到输出的字符为'c'。
看完上面的解释,也许你对内核中printk实现已经胸有成竹了,可是我还是要告诉你实现一个功能强大的printk函数仍旧是比较困难的事情,当然我们并不期望实现一个可以和printf媲美的格式化输出函数,如果你确信你可以很好的做到,那么当你写好后可以和我分享以下,分享代码是一件快乐的事情,共同进步,感觉编程的乐趣,真实的感觉。我们不是吹毛求疵的理论者,在刚开始的时候,我们不期望它可以完美无暇,只要它可以工作,可以满足我们的需求,那么就足够了。正如文章标题标示的那样,此时的printk版本相对于真实版本的printk,功能上的确弱了很多,可是它暂时可以满足需求,这就足够了,shabby version,自嘲一下,o(∩_∩)o...哈哈。
1 /* 2 * This file handle simple kernel printk. 3 * A shabby version of printf(fmt,...). 4 */ 5 6 #include "const.h" 7 #include "i386.h" 8 9 /* 10 * @var hex 定义多有可能用到的输出字符。 11 */ 12 static const char hex[]="0123456789ABCDEF"; 13 14 /* 15 * @var disp_buf 输出缓存 16 */ 17 static char disp_buf[4096]; 18 19 /* 20 * 该函数实现了将整数转化为字符串。 21 * 22 * @param outbuf 存放输出字符串的缓存 23 * @param num 要求转化的整数 24 * @return 返回字符串的长度 25 * @warning 该函数不会检查字符串缓存是否有足够的空间来容纳输出。 26 */ 27 int int2str(char *outbuf,int num) 28 { 29 char *start=outbuf; 30 char *cur=outbuf; 31 char *end=NULL; 32 char ch; 33 if(num<0) 34 { 35 *cur++='-'; 36 num=-num; 37 start=cur; 38 } 39 do 40 { 41 *cur++=hex[num%10]; 42 num=num/10; 43 }while(num); 44 end=cur--; 45 while(start<cur) 46 { 47 ch=*start; 48 *start=*cur; 49 *cur=ch; 50 start++; 51 cur--; 52 } 53 return end-outbuf; 54} 55 /* 56 * 该函数实现了将整数转化为十六进制 57 * 58 * @param buf 输出缓存 59 * @param num 需要转化的数值 60 * @return 字符串的长度 61 * @warning 该函数不会检查字符串缓存是否有足够的空间来容纳输出。 62 */ 63 int int2hex(char *buf,int num) 64 { 65 int i=0; 66 int tag=0; 67 int index=0; 68 int size=0; 69 for(i=28;i>=0;i-=4) 70 { 71 index=(num>>i)& 0x0F; 72 if(index>0) 73 tag=1; 74 if(tag==1 ) 75 { 76 *buf++=hex[index]; 77 size++; 78 } 79 } 80 if(tag==0) 81 { 82 *buf++='0'; 83 size++; 84 } 85 return size; 86 } 87 /* 88 * 该函数主要实现了将输入参数按照格式化字符串输出. 89 * 90 * @param fmt 格式化输出列表 91 * @param outbuf 输出缓存 92 * @param args 参数列表指针 93 * @warning 该函数不会检查字符串缓存是否有足够的空间来容纳输出。 94 */ 95 int vsprintf(const char *fmt,char *outbuf,char *args) 96 { 97 char *cur=outbuf; 98 char *str=NULL; 99 while(*fmt) 100 { 101 if(*fmt!='%') 102 { 103 *cur++=*fmt++; 104 continue; 105 } 106 fmt++; 107 switch(*fmt) 108 { 109 case 'd': 110 cur+=int2str(cur,*(int *)args); 111 args+=4; 112 fmt++; 113 break; 114 case 'x': 115 cur+=int2hex(cur,*(int *)args); 116 args+=4; 117 fmt++; 118 break; 119 case 'c': 120 *cur++=*args; 121 args+=4; 122 fmt++; 123 break; 124 case 's': 125 str=*(char **)args; 126 while(*cur++=*str++) 127 { 128 ; 129 } 130 cur--; 131 fmt++; 132 args+=4; 133 break; 134 case 'p': 135 *cur++='0'; 136 *cur++='x'; 137 cur+=int2hex(cur,*(int *)args); 138 fmt++; 139 args+=4; 140 break; 141 default: 142 *cur++='%'; 143 break; 144 } 145 } 146 *cur='\0'; 147 return cur-outbuf; 148 } 149 150 void con_write_char(const char); 151 152 /* 153 * 该函数实现了将字符串写入到显存中。 154 * @param str 待写入显存的字符串. 155 */ 156 void printstr(const char *str) 157 { 158 while(*str) 159 { 160 con_write_char(*str);/*调用在console.c中的w_char函数*/ 161 str++; 162 } 163 } 164 165 int printk(const char *fmt,...) 166 { 167 int len; 168 len=vsprintf(fmt,disp_buf,(char *)&fmt+4); 169 printstr(disp_buf); 170 return len; 171 }
一百多行的代码,简单的printk实现,它可以很好的工作。没有什么新颖的地方,希望你可以容忍我凌乱的代码和表述。不知道你是否发现,格式化后的字符串保存在一个全局的字符数组中,而不是作为一个局部数组,在本版本的内核中必须要这个做(似乎其他版本的内核也需要这么做),至于具体的原因在后面的文章中我会给出详细的解释,。当然,我们可以把它声明为静态局部变量,good,我们可以这样做,只是和全局变量差别不大,我没有那么做。
又是一篇冗长的文章,good night!