printf和標準輸出

printf和標準輸出
 
 

printf和標準輸出

    上次寫到main函數的參數傳遞.現在繼續往下進行.最近忙實驗室的事情,看了一週
的文章,也沒啥進展,週末寫點技術貼,放鬆一下:-)

    進入main函數後,就要調用printf("Hello World!
");了.順便將C語言參數傳遞提
一下.字符串"Hello World!
"編譯器是當作字符串常量來處理的,雖然printf是在main
內部調用,但"Hello World!
"可不是放在main的棧中,字符串常量至少是放到.data段的
,準確說是放在只讀數據段.rodata,這個我在工作站上驗證了一把.假如編輯的文件名是
hello.c,首先編譯生成elf格式二進制文件gcc hello.c -o hello然後用命令
objdump -s hello -s參數會將所有段信息dump出來.你會看到"Hello World!
"位於
.rodata段.

    printf()是個標準C庫函數.雖然功能簡單,但實現起來卻不容易.這是個和平臺相關的
函數.在pc上,printf輸出是輸出到終端屏幕,在嵌入式設備上,一般printf()是輸出到串口.
同是調用printf(),最終輸出的設備卻不同,從直覺的肯定是感覺printf()底層和平臺是相
關的.那麼printf()是怎樣實現的呢?

    可以看一下C庫程序的代碼,這裏以uClibc爲例.
    int printf(const char * __restrict format, ...)
    {
    va_list arg;
    int rv;

    va_start(arg, format);
    rv = vfprintf(stdout, format, arg);
    va_end(arg);

    return rv;
    }
    printf支持字符串格式化輸出,具體參數處理這裏不提.可以看到printf()調用了
vfprintf(),vfprintf()第一個參數是stdout是標準輸出設備.標準輸出設備是個結構體,
最重要的成員就是他的描述符,其值爲1.

    跟進vfprintf()函數看,裏面是複雜的參數處理,因爲printf()的參數形式很靈活,
所以在vfprintf()裏面要對傳進來的參數進行解析處理,形成最終的輸出格式.有興趣的
可以看一下,藉助這個可以讓你在一個沒有操作系統的平臺上實現你自己的printf()
函數.這樣你在裸機上調程序時輸出調試信息就更方便些(實際上uClinux的printk就是
這麼幹的).

    vfprintf()在參數處理之後,就是輸出了,輸出調用的是putc(),進入putc()然後
再跟進幾層函數,發現調用了linux系統調用write()。呵呵,是的,輸出就是藉助操作
系統代碼完成的。在write之前所有的代碼都是C庫的代碼,可以說是和平臺無關的。
而涉及到具體輸出,就要調用操作系統提供給你的接口。系統調用的原理就是通過一定
手段(一般是trap陷阱)進入操作系統的內核空間,調用操作系統代碼來完成某些任務。

    Linux系統調用針對不同平臺有不同的實現方式。這個以後再講。調用write()後,
進入內核空間,首先來到的就是sys_write(),這個函數代碼位於fs/read_write.c中。
一進入sys_write(),就要根據傳進來的fd描述符找到相應的file結構。對於標準輸出,
fd=1,每個進程的進程控制塊都有一個打開文件的數組。file結構就是根據fd在這個
數組中查找到相應的結構。找到結構後,就會調用file->write()來向外輸出。具體輸出
到哪裏,就要看file結構對應的設備驅動是什麼。一般嵌入式系統可以從串口將信息輸
出,那麼file->write()最底層就是調用的串口驅動的類似transmit_char的函數。

    有關linux的設備驅動有很多書介紹,整個驅動的結構很複雜,我這裏也沒必要提了.
至於終端設備怎樣掛在驅動隊列裏面,怎麼根據標準輸出的描述符找到相應的驅動結構
有興趣的莊printf()函數看,裏面是複雜的參數處理,因爲printf()的參數形式很靈活,
所以在vfprintf()裏面要對傳進來的參數進行解析處理,形成最終的輸出格式.有興趣的
可以看一下,藉助這個可以讓你在一個沒有操作系統的平臺上實現你自己的printf()
函數.這樣你在裸機上調程序時輸出調試信息就更方便些(實際上uClinux的printk就是
這麼幹的).

    vfprintf()在參數處理之後,就是輸出了,輸出調用的是putc(),進入putc()然後
再跟進幾層函數,發現調用了linux系統調用write()。呵呵,是的,輸出就是藉助操作
系統代碼完成的。在write之前所有的代碼都是C庫的代碼,可以說是和平臺無關的。
而涉及到具體輸出,就要調用操作系統提供給你的接口。系統調用的原理就是通過一定
手段(一般是trap陷阱)進入操作系統的內核空間,調用操作系統代碼來完成某些任務。

    Linux系統調用針對不同平臺有不同的實現方式。這個以後再講。調用write()後,
進入內核空間,首先來到的就是sys_write(),這個函數代碼位於fs/read_write.c中。
一進入sys_write(),就要根據傳進來的fd描述符找到相應的file結構。對於標準輸出,
fd=1,每個進程的進程控制塊都有一個打開文件的數組。file結構就是根據fd在這個
數組中查找到相應的結構。找到結構後,就會調用file->write()來向外輸出。具體輸出
到哪裏,就要看file結構對應的設備驅動是什麼。一般嵌入式系統可以從串口將信息輸
出,那麼file->write()最底層就是調用的串口驅動的類似transmit_char的函數。

    有關linux的設備驅動有很多書介紹,整個驅動的結構很複雜,我這裏也沒必要提了.
至於終端設備怎樣掛在驅動隊列裏面,怎麼根據標準輸出的描述符找到相應的驅動結構
有興趣的請查閱相關資料.
--


手段(一般是trap陷阱)進入操作系統的內核空間,調用操作系統代碼來完成某些任務。

    Linux系統調用針對不同平臺有不同的實現方式。這個以後再講。調用write()後,
進入內核空間,首先來到的就是sys_write(),這個函數代碼位於fs/read_write.c中。
一進入sys_write(),就要根據傳進來的fd描述符找到相應的file結構。對於標準輸出,
fd=1,每個進程的進程控制塊都有一個打開文件的數組。file結構就是根據fd在這個
數組中查找到相應的結構。找到結構後,就會調用file->write()來向外輸出。具體輸出
到哪裏,就要看file結構對應的設備驅動是什麼。一般嵌入式系統可以從串口將信息輸
出,那麼file->write()最底層就是調用的串口驅動的類似transmit_char的函數。

    有關linux的設備驅動有很多書介紹,整個驅動的結構很複雜,我這裏也沒必要提了.
至於終端設備怎樣掛在驅動隊列裏面,怎麼根據標準輸出的描述符找到相應的驅動結構
有興趣的請查閱相關資料.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章