Printk函数简单解析

来源:互联网 发布:咸鱼质量问题淘宝介入 编辑:程序博客网 时间:2024/06/02 07:05
  1. C语言函数可变参数的原理

  2. 可变参数函数原型

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

    程序清单 1.1

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

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

  3. 函数参数传递方式

    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。

  4. 可变参数操作宏

    假设有一段代码如程序清单 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是一个空的宏。

  5. Vsprintf函数解析

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

    程序清单 2.1

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

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

  • 通过args获得可变参数列表
  • 根据解析fmt中控制字符,比如%d,%s等,将args指向位置的参数转换成字符放入buf中
  • 更新args,重复第二步,直到全部解析完毕为止
  1. 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缓冲区中的内容会被覆盖掉。

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 高跟鞋响声太大怎么办 高跟鞋有声音怎么办 高跟鞋走路晃动怎么办 做不了俯卧撑怎么办 体温37.3怎么办 体温37.8怎么办 伙食费没有发票怎么办 阳气太重怎么办 wif打不开怎么办 手机信号显示h怎么办 脚大怎么办 征文抄袭怎么办 胃不舒服打嗝怎么办 上火嗓子出血怎么办 打靶之后耳鸣怎么办 打靶耳鸣怎么办 射击后耳鸣怎么办 射击耳鸣怎么办 打靶后耳鸣怎么办 二代身份证怎么办 魔方鱼头之后怎么办 魔方三轮换怎么办 电梯抱闸打不开怎么办 洗衣服泡沫太多怎么办 全自动洗衣机e30怎么办 洗衣机波轮掉了怎么办 洗衣机出现故障怎么办 屋里被褥潮湿怎么办 回南天衣服不干怎么办 鞋子湿透了怎么办 衣服洗完有臭味怎么办 塑料瓶盖太紧拧不开怎么办 玻璃瓶玻璃盖打不开怎么办 盖子宁滑丝了怎么办 proteus没有电阻怎么办 proteus产生nan怎么办 数码管显示乱码怎么办 有语言障碍怎么办 excel出现ref怎么办 单片机外围电路怎么办 水弹泡不大怎么办