Printk函數簡單解析

  1. C語言函數可變參數的原理

  2. 可變參數函數原型

    Printk函數原型如程序清單 1.1所示: linux

    程序清單 1.1 函數

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

    從printk函數原型可知,printk除了接收一個固定參數fmt外,後面的參數用...表示。在C/C++語言中,...表示能夠接收可變數量的參數(0或0個以上參數)。 指針

  3. 函數參數傳遞方式

    Printk的參數經過棧來傳遞,在C/C++中,函數默認調用方式是_cdecl,表示由調用者管理參數入棧操做,且入棧順序爲從右至左,入棧方向爲從高地址到低地址。所以,從第n個到第1個參數被放在地址遞減的棧中。 字符串

    假設如今有一段代碼如程序清單 1.2所示: 原型

    程序清單 1.2 源碼

    int a = 0x12345678; it

    char b = 2; asm

    char *c = "hello"; ast

    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緩衝區中的內容會被覆蓋掉。

相關文章
相關標籤/搜索