在内核段要显示,需要printk.c函数集
看看printk这个函数
int printk(const char *fmt, ...)
{
va_list args;
int i;
va_start(args, fmt);
i=vsprintf(buf,fmt,args);
va_end(args);
console_print(buf);
return i;
}
看看其中的变量以及函数
首先printk是一个变参函数,这里深入理解变参函数的构成
这里涉及一个头文件stdarg.h
1、
typedef char *va_list;
这个类型的定义
2、
#ifndef __sparc__
#define va_start(AP, LASTARG) \
(AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#else
#define va_start(AP, LASTARG) \
(__builtin_saveregs (), \
AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#endif
看一看最核心的
#define va_start(AP, LASTARG) \
(AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
是一个宏,
里面又有一个宏
#define __va_rounded_size(TYPE) \
(((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))
不管这个函数是什么实践一下
看一个测试代码
#include <iostream>
using namespace std;
#define __va_rounded_size(TYPE) \
(((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))
#define va_start(AP, LASTARG) \
(AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
int test(const char* fmt,char a)
{
char* args;
char buff;
int cnt;
int size;
va_start(args,fmt);
cnt=__va_rounded_size(fmt);
buff=*args;
size=sizeof(fmt);
return 0;
}
int main()
{
test("test fmt",'a');
return 0;
}
- fmt 0x01087800 “test fmt” const char *
fmt里面是存放的字符串的地址 - &a 0x0025f694 “a” char *
- &fmt 0x0025f690 const char * *
这两个变量是压栈的地址
经过va_start之后 - args 0x0025f694 “a” char *
指向函数下一个参数a的入栈地址
cnt 4 int
那么我们的这个就明白了
实际就是计算参数fmt占用的栈的大小,然后计算后指针指向下一个参数
对于这个宏
__va_rounded_size的作用,
是一个对齐,这里是4字节对齐,不足4字节的按照4字节对齐
打个比方,对于十进制,我们要求值10对齐,也就是
1 ->10 《==(1+9)/10×10
11->20 《== (11+9)/10*10
45->50 《== (45+9)/10×10
如何计算,根据十进制加法满10进1
将这个数加9 然后除10取模,再乘以10.这样就就可以了
同样在这段代码中也是可以的这么去实现的。
由上面的可知
va_start(args, fmt);
就是将指针args指向第二个参数。
下面看下一句代码
i=vsprintf(buf,fmt,args);
首先在printf.c中
static char buf[1024];
一个静态变量
然后用到vsprintf函数,这个函数在vsprintf.c中,很长一段函数代码。一步一步看。
这个函数主体代码块就是一个for循环,对传进来的参数进行扫描一次
展开for循环
第一个代码块
if (*fmt != ‘%’) {
*str++ = *fmt;
continue;
}
寻找第一个%的位置,之前的字符都保存到str指向的缓冲区中,
continue是为了满足if条件时,只执行if语句。%字符不保存到缓冲区里。
检测到%继续向下执行。可想而知,if下面的代码都是对%后面的字符串操作
第二个代码块
/* process flags */
flags = 0;
repeat:
++fmt; /* this also skips first '%' */
switch (*fmt) {
case '-': flags |= LEFT; goto repeat;
case '+': flags |= PLUS; goto repeat;
case ' ': flags |= SPACE; goto repeat;
case '#': flags |= SPECIAL; goto repeat;
case '0': flags |= ZEROPAD; goto repeat;
}
到这是停一下,需要学习一下%格式输出的知识
见笔记【%格式输出】
大致知道其完整格式是这样的,所以猜测linus会分5个代码块来解决这个问题
上面的switch代码块就是检测flags的,只要有这些字符,会repeat执行,然后将这些属性与操作。
属性宏定义为
#define ZEROPAD 1 /* pad with zero */
#define SIGN 2 /* unsigned/signed long */
#define PLUS 4 /* show plus */
#define SPACE 8 /* space if plus */
#define LEFT 16 /* left justified */
#define SPECIAL 32 /* 0x */
第三个代码块,解决width的问题
/* get field width */
field_width = -1;
if (is_digit(*fmt))
field_width = skip_atoi(&fmt);
else if (*fmt == '*') {
/* it's the next argument */
field_width = va_arg(args, int);
if (field_width < 0) {
field_width = -field_width;
flags |= LEFT;
}
}
其中field_width = -1;是将宽度初始化未设置
再分析,
1、#define is_digit(c) ((c) >= '0' && (c) <= '9')
检测是否在字符‘0’到‘9’
2、
static int skip_atoi(const char **s)
{
int i=0;
while (is_digit(**s))
i = i*10 + *((*s)++) - '0';
return i;
}
这是一个静态函数,不允许其他文件访问
这就是一个将字符串数,转换成整数的过程。这个写法也是很规范的,学习一下
#include <iostream>
#include <algorithm>
using namespace std;
#define d(c) ((c>='0')&&(c<='9'))
int a_i(const char* ai)
{
int back=0;
while(d(*ai))
{
back=back*10+((*ai++)-'0');
}
return back;
}
int main()
{
char *b="1890an";
a_i(b);
}
上面是本人利用一级指针写的,当初linus为什么用二级指针写呢
在oldlinux中有人提到过这个问题
http://www.oldlinux.org/oldlinux/viewthread.php?tid=5228
一级指针传递时,原指针没有变化,只是复制了一个指针来操作字符串。而二级指针是直接对原指针操作,在函数结束时,原指针也是向前推进的。这两种方法都能实现同样的字符转整数的功能,但是差别就是在那个地方,
3、
#define va_arg(AP, TYPE) \
(AP += __va_rounded_size (TYPE), \
*((TYPE *) (AP - __va_rounded_size (TYPE))))
调用这个宏时,是检测到‘’,就是宽度由下一个整数参数指明,就是args指向的。
这是一个逗号操作,
下一个代码块
/* get the precision */
precision = -1;
if (*fmt == '.') {
++fmt;
if (is_digit(*fmt))
precision = skip_atoi(&fmt);
else if (*fmt == '*') {
/* it's the next argument */
precision = va_arg(args, int);
}
if (precision < 0)
precision = 0;
}
和上一个模块差不多
下一个代码块
/* get the conversion qualifier */
qualifier = -1;
if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
qualifier = *fmt;
++fmt;
}
switch (*fmt) {
case 'c':
if (!(flags & LEFT))
while (--field_width > 0)
*str++ = ' ';
*str++ = (unsigned char) va_arg(args, int);
while (--field_width > 0)
*str++ = ' ';
break;
case 's':
s = va_arg(args, char *);
len = strlen(s);
if (precision < 0)
precision = len;
else if (len > precision)
len = precision;
if (!(flags & LEFT))
while (len < field_width--)
*str++ = ' ';
for (i = 0; i < len; ++i)
*str++ = *s++;
while (len < field_width--)
*str++ = ' ';
break;
case 'o':
str = number(str, va_arg(args, unsigned long), 8,
field_width, precision, flags);
break;
case 'p':
if (field_width == -1) {
field_width = 8;
flags |= ZEROPAD;
}
str = number(str,
(unsigned long) va_arg(args, void *), 16,
field_width, precision, flags);
break;
case 'x':
flags |= SMALL;
case 'X':
str = number(str, va_arg(args, unsigned long), 16,
field_width, precision, flags);
break;
case 'd':
case 'i':
flags |= SIGN;
case 'u':
str = number(str, va_arg(args, unsigned long), 10,
field_width, precision, flags);
break;
case 'n':
ip = va_arg(args, int *);
*ip = (str - buf);
break;
default:
if (*fmt != '%')
*str++ = '%';
if (*fmt)
*str++ = *fmt;
else
--fmt;
break;
}
}
这里就是常见的输出值了
挑一个c看看
其输出代码为
switch (*fmt) {
case 'c':
if (!(flags & LEFT))
while (--field_width > 0)
*str++ = ' ';
*str++ = (unsigned char) va_arg(args, int);
while (--field_width > 0)
*str++ = ' ';
break;
第一句
if (!(flags & LEFT))
while (–field_width > 0)
*str++ = ’ ‘;
检测是否左对齐,如果不是,根据数据宽度,将对应的数存到缓冲区间里面
第二句
*str++ = (unsigned char) va_arg(args, int);
将变参后面的参数缓存到缓冲区中,这里有一个问题
就是args需要每次缓存后需要指向下一个参数,什么时候args变化的?
搜索之后args只是在va_arg函数中使用。
重新看看这个宏
#define va_arg(AP, TYPE) \
(AP += __va_rounded_size (TYPE), \
*((TYPE *) (AP - __va_rounded_size (TYPE))))
现将AP加一个地址(和TYPE相关),返回原地址取值,最终AP的值是变化的。
vprintf还有最后一个问题
return str-buf;
两个地址相减 ,实际就是相对地址,str为缓冲区的现在指向的指针,buff是基地址。得到缓存的大小(字节)
到此vprintf简单分析完成
printk下一句代码
va_end(args);
没有看到源码。
谷歌上找到
#define va_end(ap) ap = (va_list)0 /* 将ap置空 */
实际就是一个指针释放的作用。
printk最后一个调用函数
console_print(buf);
传入参数就是要显示的缓存内存地址,进入这个函数
console_print函数源代码如下
void console_print(const char * b)
{
int currcons = fg_console;
char c;
while (c = *(b++)) {
if (c == 10) {
cr(currcons);
lf(currcons);
continue;
}
if (c == 13) {
cr(currcons);
continue;
}
if (x>=video_num_columns) {
x -= video_num_columns;
pos -= video_size_row;
lf(currcons);
}
__asm__("movb %2,%%ah\n\t"
"movw %%ax,%1\n\t"
::"a" (c),
"m" (*(short *)pos),
"m" (attr)
:"ax");
pos += 2;
x++;
}
set_cursor(currcons);
}
对此分析分析
看看了关于vga等显示操作,暂不讨论吧。
到此 printk这个变参函数简单讨论结束