printk原理简介

1.C语言函数可变参数的原理

1.1可变参数函数原型

Printk函数原型如程序清单 1.1所示:

程序清单 1.1

int printk(const char *fmt, ...);

从printk函数原型可知,printk除了接收一个固定参数fmt外,后面的参数用...表示。在C/C++语言中,...表示可以接收可变数量的参数(0或0个以上参数)。

1.2函数参数传递方式

Printk的参数通过栈来传递,在C/C++中,函数默认调用方式是_cdecl,表示由调用者管理参数入栈操作,且入栈顺序为从右至左,入栈方向为从高地址到低地址。因此,从第n个到第1个参数被放在地址递减的栈中。

假设现在有一段代码如程序清单 1.2所示:

程序清单 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。

1.3可变参数操作宏

假设有一段代码如程序清单 1.3所示:

程序清单 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.Vsprintf函数解析

函数原型如程序清单 2.1所示:

程序清单 2.1

int vsprintf(char *buf, const char *fmt, va_list args);

该函数的主要工作过程如下:

    a.通过args获得可变参数列表

    b.根据解析fmt中控制字符,比如%d,%s等,将args指向位置的参数转换成字符放入buf中

    c.更新args,重复第二步,直到全部解析完毕为止

3.Linux0.11 printk源码

#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缓冲区中的内容会被覆盖掉。

转载于:https://my.oschina.net/u/3399876/blog/1594271

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值