×
单片机 > 单片机程序设计 > 详情

glibc中的printf如何输出到串口

发布时间:2020-08-31 发布时间:
|
内核版本:2.6.14

glibc版本:2.3.6

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 plain copy
 
 print?
  1. #undef printf  
  2.   
  3. /* Write formatted output to stdout from the format string FORMAT.  */  
  4. /* VARARGS1 */  
  5. int  
  6. printf (const char *format, ...)  
  7. {  
  8.   va_list arg;  
  9.   int done;  
  10.   
  11.   va_start (arg, format);  
  12.   done = vfprintf (stdout, format, arg);//主要是这个函数  
  13.   va_end (arg);  
  14.   
  15.   return done;  
  16. }  
  17.   
  18. #undef _IO_printf  
  19. /* This is for libg++.  */  
  20. strong_alias (printf, _IO_printf);  

 

strong_alias,即取别名。网上有人提及这个strong alias好像是为了防止c库符号被其他库符号覆盖掉而使用的,如果printf被覆盖了,还有_IO_printf可以用。跟踪vfprintf函数(),我们先给出该函数的声明,如下(glibc-2.3.6/libio/stdio.h):

 

[plain] view plain copy
 
 print?
  1. extern int vfprintf (FILE *__restrict __s, __const char *__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 plain copy
 
 print?
  1. /* Standard streams.  */  
  2. extern struct _IO_FILE *stdin;      /* Standard input stream.  */  
  3. extern struct _IO_FILE *stdout;     /* Standard output stream.  */  
  4. extern struct _IO_FILE *stderr;     /* Standard error output stream.  */  
  5. /* C89/C99 say they're macros.  Make them happy.  */  
  6. #define stdin stdin  
  7. #define stdout stdout  
  8. #define stderr stderr  
继续跟踪stdout(glibc-2.3.6/libio/stdio.c):

 

 

[plain] view plain copy
 
 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 plain copy
 
 print?
  1. struct _IO_FILE {  
  2.   int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */  
  3. #define _IO_file_flags _flags  
  4.   
  5.   /* The following pointers correspond to the C++ streambuf protocol. */  
  6.   /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */  
  7.   char* _IO_read_ptr;   /* Current read pointer */  
  8.   char* _IO_read_end;   /* End of get area. */  
  9.   char* _IO_read_base;  /* Start of putback+get area. */  
  10.   char* _IO_write_base; /* Start of put area. */  
  11.   char* _IO_write_ptr;  /* Current put pointer. */  
  12.   char* _IO_write_end;  /* End of put area. */  
  13.   char* _IO_buf_base;   /* Start of reserve area. */  
  14.   char* _IO_buf_end;    /* End of reserve area. */  
  15.   /* The following fields are used to support backing up and undo. */  
  16.   char *_IO_save_base; /* Pointer to start of non-current get area. */  
  17.   char *_IO_backup_base;  /* Pointer to first valid character of backup area */  
  18.   char *_IO_save_end; /* Pointer to end of non-current get area. */  
  19.   
  20.   struct _IO_marker *_markers;  
  21.   
  22.   struct _IO_FILE *_chain;  
  23.   
  24.   int _fileno;//这个就是linux内核中文件描述符fd  
  25. #if 0  
  26.   int _blksize;  
  27. #else  
  28.   int _flags2;  
  29. #endif  
  30.   _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */  
  31.   
  32. #define __HAVE_COLUMN /* temporary */  
  33.   /* 1+column number of pbase(); 0 is unknown. */  
  34.   unsigned short _cur_column;  
  35.   signed char _vtable_offset;  
  36.   char _shortbuf[1];  
  37.   
  38.   /*  char* _save_gptr;  char* _save_egptr; */  
  39.   
  40.   _IO_lock_t *_lock;  
  41. #ifdef _IO_USE_OLD_IO_FILE  
  42. };  
  43. struct _IO_FILE_plus  
  44. {  
  45.   _IO_FILE file;  
  46.   const struct _IO_jump_t *vtable;//IO函数跳转表  
  47. };  

 

下面我们看看_IO_2_1_stdout_的定义(glibc-2.3.6/libio/stdfiles.c),顺便给出_IO_2_1_stdin_和_IO_2_1_stderr_的定义:

 

[plain] view plain copy
 
 print?
  1. #  define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \  
  2.   struct _IO_FILE_plus NAME \  
  3.     = {FILEBUF_LITERAL(CHAIN, FLAGS, FD, NULL), \  
  4.        &_IO_file_jumps};  
  5.   
  6.   
  7. DEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES);  
  8. DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS);  
  9. 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 plain copy
 
 print?
  1. #   define FILEBUF_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 plain copy
 
 print?
  1. const struct _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的分配和回收,否则会导致终端没有输出信息,也无法和内核输入信息。




『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
51单片机CO2检测显示程序解析