Linux0.12内核分析之十二——printk实现( shabby version)

    在上一篇中简要介绍了异常流处理,我们实现了十分蹩脚的异常处理例程,事实上它除了在屏幕上打印堆栈中的一些内容外,剩下就是死循环了,不能做任何的事情。也许你现在已经磨拳擦掌,准备深入到异常处理中,充实我们的异常处理例程,使它成为真正意义上的异常处理例程,毕竟,没有人会想做有名无实的人。可是我在这要给你说,稍安勿躁,在开始我们异常处理例程之前,我们仍旧缺少一个可以反馈信息的工具,我们要做好十足的准备才能开始下一步,这样我们才能事半功倍。



    无论做什么事情都需要交流,我们的内核也不例外,我们可不想内核一直不声不响地工作,我们需要和它交流,有着良好的互动,做起事情来才会更加的顺畅。大多数时候人们总是喜欢听顺心话,我们和内核交流也希望它按照我们的要求来。就如在前面的异常处理例程的输出,我们希望打印十六进制的表述,而不是二进制的表示,我想没有人希望一连串的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!

猜你喜欢

转载自xiaohui-p.iteye.com/blog/1175731