新闻中心

EEPW首页 > 嵌入式系统 > 设计应用 > glibc中的printf如何输出到串口

glibc中的printf如何输出到串口

作者: 时间:2016-11-22 来源:网络 收藏
内核版本:2.6.14

glibc版本:2.3.6

本文引用地址:https://www.eepw.com.cn/article/201611/319993.htm

CPU平台:arm

printf输出不一定是串口,也可以是LCD,甚至是文件等,这里仅以输出到串口为例。本文分析了printf和文件描述符0、1和2以及stdout、stdin和stderr的关系,通过这篇文章可以知道文件描述符0、1和2为什么对应着stdout、stdin和stderr,因为glibc就是这么定义的!!!

首先看glibc中printf函数的定义(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);//主要是这个函数
  11. va_end(arg);
  12. returndone;
  13. }
  14. #undef_IO_printf
  15. /*Thisisforlibg++.*/
  16. strong_alias(printf,_IO_printf);

strong_alias,即取别名。网上有人提及这个strong alias好像是为了防止c库符号被其他库符号覆盖掉而使用的,如果printf被覆盖了,还有_IO_printf可以用。跟踪vfprintf函数(),我们先给出该函数的声明,如下(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函数是通过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
继续跟踪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_;
在继续分析_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;//这个就是linux内核中文件描述符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函数跳转表
  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}
其中,FD赋值给了_fileno。我们回到vfprintf的分析,vfprintf的具体实现本文就不详细讲解,主要原理是格式化字符串,最后将字符串输出到文件中,也就是stdout中。至于如何输出,则和_IO_file_jumps关系密切,_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. };
至于怎么跳转到这些函数,以及如何跳转到linux内核,由于涉及到glibc的一些细节,这里简单介绍一下进入内核后的情况:进入linux内核后,调用write(),在write之前所有的代码都是C库的代码,可以说是和平台无关的。而涉及到具体输出,就要调用操作系统提供给的接口。调用write()后,通过系统调用进入内核空间,首先是sys_write(),这个函数代码位于fs/read_write.c中。一进入sys_write(),就要根据传进来的fd描述符找到相应的file结构。对于标准输出,fd= 1,每个进程的进程控制块都有一个打开文件的数组files。file结构就是根据fd在这个数组中查找到相应的结构。找到结构后,就会调用file->write()来向外输出。具体输出到哪里,就要看file结构对应的设备驱动是什么。

通过本文可以理解:文件描述符0、1和2和stdout、stdin和stderr对应,如果要修改linux内核中文件描述符相关代码,一定要注意文件描述符0、1和2的分配和回收,否则会导致终端没有输出信息,也无法和内核输入信息。



关键词: glibcprintf输出串

评论


技术专区

关闭