在上一篇中简要介绍了异常流处理,我们实现了十分蹩脚的异常处理例程,事实上它除了在屏幕上打印堆栈中的一些内容外,剩下就是死循环了,不能做任何的事情。也许你现在已经磨拳擦掌,准备深入到异常处理中,充实我们的异常处理例程,使它成为真正意义上的异常处理例程,毕竟,没有人会想做有名无实的人。可是我在这要给你说,稍安勿躁,在开始我们异常处理例程之前,我们仍旧缺少一个可以反馈信息的工具,我们要做好十足的准备才能开始下一步,这样我们才能事半功倍。
无论做什么事情都需要交流,我们的内核也不例外,我们可不想内核一直不声不响地工作,我们需要和它交流,有着良好的互动,做起事情来才会更加的顺畅。大多数时候人们总是喜欢听顺心话,我们和内核交流也希望它按照我们的要求来。就如在前面的异常处理例程的输出,我们希望打印十六进制的表述,而不是二进制的表示,我想没有人希望一连串的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!
分享到:
相关推荐
Linux内核的console和printk是两个紧密关联的重要组成部分。printk是Linux内核中的一种日志记录机制,用于记录内核消息,而console指的是内核通过哪个输出设备将这些消息显示出来。8250串口驱动是Linux内核中用于...
在这个源码包中,我们发现了"printk.c"这个文件,它揭示了Linux内核中的一个重要组件——printk函数的工作方式。 printk函数是Linux内核用于输出日志信息的关键工具,它扮演着系统调试和故障排查的重要角色。在早期...
在Linux操作系统中,内核模块(Kernel Modules)是可加载的代码片段,它们可以扩展内核的功能,而无需重新启动系统。这些模块对于系统管理员和开发者来说是非常有用的,因为它们允许在需要时添加或移除特定的硬件...
《Linux内核源代码分析》是一本深入探讨Linux操作系统核心机制的专业书籍。它不仅涵盖了Linux内核的基础架构,还详细解析了其工作原理,旨在帮助读者深入理解这一开源操作系统的精髓。通过阅读这本书,读者可以了解...
Linux内核编程是进行Linux系统开发的核心部分,涉及对操作系统底层的控制和定制。文档中提及的内容广泛,包括了内核模块编程的基本概念、内核编程中常用的宏定义、Makefile的编写、以及设备驱动开发的一些要点。下面...
### Linux内核修炼之道知识点概览 #### 一、前言与背景介绍 - **Linux内核的历史背景**:本书开篇介绍了Linux内核的发展历程,涵盖了从1991年至2009年的关键历史事件。这不仅有助于读者理解Linux内核的发展脉络,...
通过《Linux内核修炼之道》,读者不仅能够了解Linux的历史,还能掌握内核分析和驱动开发的技术,从而在Linux世界中游刃有余。这本书不仅是初学者的指南,也是资深开发者的参考资料,对于任何希望深入Linux内核的人来...
在内核调试技术之中,简单的是printk的使用了,它的用法和C语言应用程序中的printf使用类似,在应用程序中依靠的是stdio.h中的库,而在linux内核中没有这个库,所以在linux内核中,使用这个printk要对内核的实现...
10. **性能优化**:学习如何分析和优化Linux内核及应用程序的性能,利用工具如perf、strace等。 "Linux内核篇内核编程.txt"可能包含内核编程的实践指南,涵盖了编写内核模块、调试技巧和最佳实践等内容。通过阅读和...
Kprobes的使用大幅简化了内核调试过程,让开发者可以在不必频繁编译和重启内核的情况下,实现对内核运行时状态的观察和分析。 除了上述提到的几种调试技术,Linux内核还提供了一些其他的监视内核代码和错误跟踪的...
《LINUX内核源代码情景分析(下)》是一本深度探讨Linux内核源代码的专著,旨在帮助读者理解并掌握Linux操作系统的核心机制。在本书的下半部分,作者将引领我们深入到内核的各个关键模块,揭示其运行原理和设计思路...
Linux内核模块编程入门可以分为几个步骤:首先,需要编写模块的源代码,包括头文件、Makefile文件和模块的实现代码。其次,需要使用gcc编译器和ld链接器编译和链接模块。最后,需要使用insmod命令加载模块到内核中...
2 printk函数脆弱之处 3 LOG等级 4 记录缓冲区 5 syslogd/klogd 6 dmesg 7 注意 8 内核printk和日志系统的总体结构 9 动态调试 六 内存调试工具 1 MEMWATCH 2 YAMD 3 Electric Fence 七 strace 八 OOPS 1 ksymoops ...
《Linux内核设计与实现(第2版英文版)》是深入理解Linux内核的经典著作,作者Robert Love通过详实的讲解和清晰的代码分析,为读者揭示了Linux内核的工作原理和设计思想。这本书是Linux内核开发的绝佳入门教程,适合...
levels.h中定义),不写则默认为级别4(MESSAGE_LOGLEVEL_DEFAULT),级别高于设定的输出级别才可以显示(可在include/linux/printk.h修改宏CONSOLE_LOGLEVEL_DEFAULT设定输出级别,默认为7): 定义 级别 含义 ...
———————————————————————— 第二部分 Linux内核模块编程指南 第1章 Hello, World 第2章 字符设备文件 第3章 /proc文件系统 158 第4章 把/proc用于输入 162 第5章 把设备文件用于输入 170 第...