新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > glibc中的printf如何輸出到串口

glibc中的printf如何輸出到串口

作者: 時(shí)間:2016-11-22 來(lái)源:網(wǎng)絡(luò) 收藏
內(nèi)核版本:2.6.14

glibc版本:2.3.6

本文引用地址:http://m.butianyuan.cn/article/201611/319993.htm

CPU平臺(tái):arm

printf輸出不一定是串口,也可以是LCD,甚至是文件等,這里僅以輸出到串口為例。本文分析了printf和文件描述符0、1和2以及stdout、stdin和stderr的關(guān)系,通過(guò)這篇文章可以知道文件描述符0、1和2為什么對(duì)應(yīng)著stdout、stdin和stderr,因?yàn)間libc就是這么定義的!??!

首先看glibc中printf函數(shù)的定義(glibc-2.3.6/stdio-common/printf.c):

[plain]view plaincopy
print?
  1. #undefprintf
  2. /*WriteformattedoutputtostdoutfromtheformatstringFORMAT.*/
  3. /*VARARGS1*/
  4. int
  5. printf(constchar*format,...)
  6. {
  7. va_listarg;
  8. intdone;
  9. va_start(arg,format);
  10. done=vfprintf(stdout,format,arg);//主要是這個(gè)函數(shù)
  11. va_end(arg);
  12. returndone;
  13. }
  14. #undef_IO_printf
  15. /*Thisisforlibg++.*/
  16. strong_alias(printf,_IO_printf);

strong_alias,即取別名。網(wǎng)上有人提及這個(gè)strong alias好像是為了防止c庫(kù)符號(hào)被其他庫(kù)符號(hào)覆蓋掉而使用的,如果printf被覆蓋了,還有_IO_printf可以用。跟蹤vfprintf函數(shù)(),我們先給出該函數(shù)的聲明,如下(glibc-2.3.6/libio/stdio.h):

[plain]view plaincopy
print?
  1. externintvfprintf(FILE*__restrict__s,__constchar*__restrict__format,
  2. _G_va_list__arg);
printf函數(shù)是通過(guò)vfprintf將format輸出到stdout文件中,stdout是(FILE *)類型。stdout的定義如下(glibc-2.3.6/libio/stdio.h),順被也給出stdin和stderr的定義:

[plain]view plaincopy
print?
  1. /*Standardstreams.*/
  2. externstruct_IO_FILE*stdin;/*Standardinputstream.*/
  3. externstruct_IO_FILE*stdout;/*Standardoutputstream.*/
  4. externstruct_IO_FILE*stderr;/*Standarderroroutputstream.*/
  5. /*C89/C99saytheyremacros.Makethemhappy.*/
  6. #definestdinstdin
  7. #definestdoutstdout
  8. #definestderrstderr
繼續(xù)跟蹤stdout(glibc-2.3.6/libio/stdio.c):

[plain]view plaincopy
print?
  1. _IO_FILE*stdin=(FILE*)&_IO_2_1_stdin_;
  2. _IO_FILE*stdout=(FILE*)&_IO_2_1_stdout_;
  3. _IO_FILE*stderr=(FILE*)&_IO_2_1_stderr_;
在繼續(xù)分析_IO_2_1_stdout_之前,我們先看一下_IO_FILE(FILE和_IO_FILE是一回事,#define FILE _IO_FILE)的定義(glibc-2.3.6/libio/libio.h):

[plain]view plaincopy
print?
  1. struct_IO_FILE{
  2. int_flags;/*High-orderwordis_IO_MAGIC;restisflags.*/
  3. #define_IO_file_flags_flags
  4. /*ThefollowingpointerscorrespondtotheC++streambufprotocol.*/
  5. /*Note:Tkusesthe_IO_read_ptrand_IO_read_endfieldsdirectly.*/
  6. char*_IO_read_ptr;/*Currentreadpointer*/
  7. char*_IO_read_end;/*Endofgetarea.*/
  8. char*_IO_read_base;/*Startofputback+getarea.*/
  9. char*_IO_write_base;/*Startofputarea.*/
  10. char*_IO_write_ptr;/*Currentputpointer.*/
  11. char*_IO_write_end;/*Endofputarea.*/
  12. char*_IO_buf_base;/*Startofreservearea.*/
  13. char*_IO_buf_end;/*Endofreservearea.*/
  14. /*Thefollowingfieldsareusedtosupportbackingupandundo.*/
  15. char*_IO_save_base;/*Pointertostartofnon-currentgetarea.*/
  16. char*_IO_backup_base;/*Pointertofirstvalidcharacterofbackuparea*/
  17. char*_IO_save_end;/*Pointertoendofnon-currentgetarea.*/
  18. struct_IO_marker*_markers;
  19. struct_IO_FILE*_chain;
  20. int_fileno;//這個(gè)就是linux內(nèi)核中文件描述符fd
  21. #if0
  22. int_blksize;
  23. #else
  24. int_flags2;
  25. #endif
  26. _IO_off_t_old_offset;/*Thisusedtobe_offsetbutitstoosmall.*/
  27. #define__HAVE_COLUMN/*temporary*/
  28. /*1+columnnumberofpbase();0isunknown.*/
  29. unsignedshort_cur_column;
  30. signedchar_vtable_offset;
  31. char_shortbuf[1];
  32. /*char*_save_gptr;char*_save_egptr;*/
  33. _IO_lock_t*_lock;
  34. #ifdef_IO_USE_OLD_IO_FILE
  35. };
  36. struct_IO_FILE_plus
  37. {
  38. _IO_FILEfile;
  39. conststruct_IO_jump_t*vtable;//IO函數(shù)跳轉(zhuǎn)表
  40. };

下面我們看看_IO_2_1_stdout_的定義(glibc-2.3.6/libio/stdfiles.c),順便給出_IO_2_1_stdin_和_IO_2_1_stderr_的定義:

[plain]view plaincopy
print?
  1. #defineDEF_STDFILE(NAME,FD,CHAIN,FLAGS)
  2. struct_IO_FILE_plusNAME
  3. ={FILEBUF_LITERAL(CHAIN,FLAGS,FD,NULL),
  4. &_IO_file_jumps};
  5. DEF_STDFILE(_IO_2_1_stdin_,0,0,_IO_NO_WRITES);
  6. DEF_STDFILE(_IO_2_1_stdout_,1,&_IO_2_1_stdin_,_IO_NO_READS);
  7. DEF_STDFILE(_IO_2_1_stderr_,2,&_IO_2_1_stdout_,_IO_NO_READS+_IO_UNBUFFERED);

從這里我們可以看到,_IO_2_1_stdout_的FD = 0、_IO_2_1_stdin_的FD = 1、_IO_2_1_stderr_的FD = 2。FILEBUF_LITERAL用于初始化_IO_FILE,定義如下(glibc-2.3.6/libio/libioP.h):

[plain]view plaincopy
print?
  1. #defineFILEBUF_LITERAL(CHAIN,FLAGS,FD,WDP)
  2. {_IO_MAGIC+_IO_LINKED+_IO_IS_FILEBUF+FLAGS,
  3. 0,0,0,0,0,0,0,0,0,0,0,0,(_IO_FILE*)CHAIN,FD,
  4. 0,_IO_pos_BAD,0,0,{0},0,_IO_pos_BAD,
  5. 0}
其中,F(xiàn)D賦值給了_fileno。我們回到vfprintf的分析,vfprintf的具體實(shí)現(xiàn)本文就不詳細(xì)講解,主要原理是格式化字符串,最后將字符串輸出到文件中,也就是stdout中。至于如何輸出,則和_IO_file_jumps關(guān)系密切,_IO_file_jumps的定義如下:

[plain]view plaincopy
print?
  1. conststruct_IO_jump_t_IO_file_jumps=
  2. {
  3. JUMP_INIT_DUMMY,
  4. JUMP_INIT(finish,INTUSE(_IO_file_finish)),
  5. JUMP_INIT(overflow,INTUSE(_IO_file_overflow)),
  6. JUMP_INIT(underflow,INTUSE(_IO_file_underflow)),
  7. JUMP_INIT(uflow,INTUSE(_IO_default_uflow)),
  8. JUMP_INIT(pbackfail,INTUSE(_IO_default_pbackfail)),
  9. JUMP_INIT(xsputn,INTUSE(_IO_file_xsputn)),
  10. JUMP_INIT(xsgetn,INTUSE(_IO_file_xsgetn)),
  11. JUMP_INIT(seekoff,_IO_new_file_seekoff),
  12. JUMP_INIT(seekpos,_IO_default_seekpos),
  13. JUMP_INIT(setbuf,_IO_new_file_setbuf),
  14. JUMP_INIT(sync,_IO_new_file_sync),
  15. JUMP_INIT(doallocate,INTUSE(_IO_file_doallocate)),
  16. JUMP_INIT(read,INTUSE(_IO_file_read)),
  17. JUMP_INIT(write,_IO_new_file_write),
  18. JUMP_INIT(seek,INTUSE(_IO_file_seek)),
  19. JUMP_INIT(close,INTUSE(_IO_file_close)),
  20. JUMP_INIT(stat,INTUSE(_IO_file_stat)),
  21. JUMP_INIT(showmanyc,_IO_default_showmanyc),
  22. JUMP_INIT(imbue,_IO_default_imbue)
  23. };
至于怎么跳轉(zhuǎn)到這些函數(shù),以及如何跳轉(zhuǎn)到linux內(nèi)核,由于涉及到glibc的一些細(xì)節(jié),這里簡(jiǎn)單介紹一下進(jìn)入內(nèi)核后的情況:進(jìn)入linux內(nèi)核后,調(diào)用write(),在write之前所有的代碼都是C庫(kù)的代碼,可以說(shuō)是和平臺(tái)無(wú)關(guān)的。而涉及到具體輸出,就要調(diào)用操作系統(tǒng)提供給的接口。調(diào)用write()后,通過(guò)系統(tǒng)調(diào)用進(jìn)入內(nèi)核空間,首先是sys_write(),這個(gè)函數(shù)代碼位于fs/read_write.c中。一進(jìn)入sys_write(),就要根據(jù)傳進(jìn)來(lái)的fd描述符找到相應(yīng)的file結(jié)構(gòu)。對(duì)于標(biāo)準(zhǔn)輸出,fd= 1,每個(gè)進(jìn)程的進(jìn)程控制塊都有一個(gè)打開(kāi)文件的數(shù)組files。file結(jié)構(gòu)就是根據(jù)fd在這個(gè)數(shù)組中查找到相應(yīng)的結(jié)構(gòu)。找到結(jié)構(gòu)后,就會(huì)調(diào)用file->write()來(lái)向外輸出。具體輸出到哪里,就要看file結(jié)構(gòu)對(duì)應(yīng)的設(shè)備驅(qū)動(dòng)是什么。

通過(guò)本文可以理解:文件描述符0、1和2和stdout、stdin和stderr對(duì)應(yīng),如果要修改linux內(nèi)核中文件描述符相關(guān)代碼,一定要注意文件描述符0、1和2的分配和回收,否則會(huì)導(dǎo)致終端沒(méi)有輸出信息,也無(wú)法和內(nèi)核輸入信息。



關(guān)鍵詞: glibcprintf輸出串

評(píng)論


技術(shù)專區(qū)

關(guān)閉