printf和標準輸出
上次寫到main函數的參數傳遞.現在繼續往下進行.最近忙實驗室的事情,看了一周的文章,也沒啥進展,周末寫點技術貼,放鬆一下:-)
進入main函數後,就要調用printf("Hello World!/n");了.順便將C語言參數傳遞提一下.字串"Hello World!/n"編譯器是當作字串常量來處理的,雖然printf是在main內部調用,但"Hello World!/n"可不是放在main的棧中,字串常量至少是放到.data段的,準確說是放在唯讀資料區段.rodata,這個我在工作站上驗證了一 把.假如編輯的檔案名稱是hello.c,首先編譯產生elf格式二進位檔案gcc hello.c -o hello然後用命令objdump -s hello -s參數會將所有段資訊dump出來.你會看到"Hello World!/n"位於.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的裝置驅動有很多書介紹,整個驅動的結構很複雜,我這裡也沒必要提了.至於終端裝置怎樣掛在驅動隊列裡面,怎麼根據標準輸出的描述符找到相應的驅動結構有興趣的請查閱相關資料.