Printk函数原型如程序清单 1.1所示:linux
程序清单 1.1函数
int printk(const char *fmt, ...);
从printk函数原型可知,printk除了接收一个固定参数fmt外,后面的参数用...表示。在C/C++语言中,...表示能够接收可变数量的参数(0或0个以上参数)。spa
Printk的参数经过栈来传递,在C/C++中,函数默认调用方式是_cdecl,表示由调用者管理参数入栈操做,且入栈顺序为从右至左,入栈方向为从高地址到低地址。所以,从第n个到第1个参数被放在地址递减的栈中。指针
假设如今有一段代码如程序清单 1.2所示:code
程序清单 1.2字符串
int a = 0x12345678; char b = 2; char *c = "hello"; printk("print %d, %d, %s\n", a, b, c);
调用printk时参数在栈中的分布如图 1.1所示:原型
图 1.1 Printk参数在栈中的分布源码
这里假设"print %d, %d, %s\n"字符串的首地址是0x20000000,"hello"字符串的首地址是0x10000000。从图 1.1中还能看出一个有意思的地方,那就是参数b虽然是1个字节,可是压栈时被扩展为4字节数据,高位补0。也就是说每次压栈的数据最少为4字节,不足4字节的数据补0。it
假设有一段代码如程序清单 1.3所示:asm
程序清单 1.3
int printk(const char *fmt, ...) { va_list args; va_start(args, fmt); i=vsprintf(buf,fmt,args); va_end(args); }
va_list类型的定义如程序清单 1.4所示,可见va_list其实就是一个char型指针。
程序清单 1.4
typedef char *va_list;
va_start宏定义如程序清单 1.5所示:
程序清单 1.5
#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)))
AP表示argument pointer,是参数指针的意思,其实就是va_list类型变量;LASTARG表示last argument,其实就是printk的第一个参数fmt,之因此叫last argument,是由于这个参数是最后一个压栈的。
__va_rounded_size的做用是按int类型的倍数计算TYPE变量在栈中的大小,假设TYPE变量是5字节大小,则__va_rounded_size(TYPE)值为8,由于每次压栈的数据大小都是int类型数据大小的倍数。
(char *) &(LASTARG)表示将fmt变量的地址转为char *指针,这样加上__va_rounded_size (LASTARG)后的值就是第一个可变参数的地址。如图 1.2所示:
图 1.2 va_list args移动示意图
因而可知,va_start宏的做用就是将指针args跳过fmt参数,指向第一个要解析的可变参数。
va_arg宏定义如程序清单 1.6所示:
程序清单 1.6
#define va_arg(AP, TYPE) \ (AP += __va_rounded_size (TYPE), \ *((TYPE *) (AP - __va_rounded_size (TYPE))))
AP += __va_rounded_size (TYPE),通过这个表达式运算后,args指向了下一个参数;
*((TYPE *) (AP - __va_rounded_size (TYPE)))表示取原来args位置处的变量值,如图 1.3所示:
图 1.3 va_arg做用
va_end是一个空的宏。
函数原型如程序清单 2.1所示:
程序清单 2.1
int vsprintf(char *buf, const char *fmt, va_list args);
该函数的主要工做过程以下:
a.经过args得到可变参数列表
b.根据解析fmt中控制字符,好比%d,%s等,将args指向位置的参数转换成字符放入buf中
c.更新args,重复第二步,直到所有解析完毕为止
#include <stdarg.h> #include <stddef.h> #include <linux/kernel.h> static char buf[1024]; extern int vsprintf(char * buf, const char * fmt, va_list args); int printk(const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i=vsprintf(buf,fmt,args); va_end(args); __asm__("push %%fs\n\t" "push %%ds\n\t" "pop %%fs\n\t" "pushl %0\n\t" "pushl $_buf\n\t" "pushl $0\n\t" "call _tty_write\n\t" "addl $8,%%esp\n\t" "popl %0\n\t" "pop %%fs" ::"r" (i):"ax","cx","dx"); return i; }
能够看出,在调用vsprintf对可变参数解析完毕后,全部要输出的字符信息是存放在buf缓冲区中的,最终将字符信息输出到终端上是经过调用tty_write来实现的。
从这里也能够看出这里的printk是不可重入的,由于若是printk函数没有执行完毕,又被调用时,以前buf缓冲区中的内容会被覆盖掉。