内核版别: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):
print?
- #undefprintf
- /*WriteformattedoutputtostdoutfromtheformatstringFORMAT.*/
- /*VARARGS1*/
- int
- printf(constchar*format,…)
- {
- va_listarg;
- intdone;
- va_start(arg,format);
- done=vfprintf(stdout,format,arg);//首要是这个函数
- va_end(arg);
- returndone;
- }
- #undef_IO_printf
- /*Thisisforlibg++.*/
- strong_alias(printf,_IO_printf);
strong_alias,即取别号。网上有人提及这个strong alias如同是为了避免c库符号被其他库符号覆盖掉而运用的,假如printf被覆盖了,还有_IO_printf能够用。盯梢vfprintf函数(),咱们先给出该函数的声明,如下(glibc-2.3.6/libio/stdio.h):
print?
- externintvfprintf(FILE*__restrict__s,__constchar*__restrict__format,
_G_va_list__arg);
printf函数是经过vfprintf将format输出到stdout文件中,stdout是(FILE *)类型。stdout的界说如下(glibc-2.3.6/libio/stdio.h),顺被也给出stdin和stderr的界说:
print?
- /*Standardstreams.*/
- externstruct_IO_FILE*stdin;/*Standardinputstream.*/
- externstruct_IO_FILE*stdout;/*Standardoutputstream.*/
- externstruct_IO_FILE*stderr;/*Standarderroroutputstream.*/
- /*C89/C99saytheyremacros.Makethemhappy.*/
- #definestdinstdin
- #definestdoutstdout
- #definestderrstderr
持续盯梢stdout(glibc-2.3.6/libio/stdio.c):
print?
- _IO_FILE*stdin=(FILE*)&_IO_2_1_stdin_;
- _IO_FILE*stdout=(FILE*)&_IO_2_1_stdout_;
- _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):
print?
- struct_IO_FILE{
- int_flags;/*High-orderwordis_IO_MAGIC;restisflags.*/
- #define_IO_file_flags_flags
- /*ThefollowingpointerscorrespondtotheC++streambufprotocol.*/
- /*Note:Tkusesthe_IO_read_ptrand_IO_read_endfieldsdirectly.*/
- char*_IO_read_ptr;/*Currentreadpointer*/
- char*_IO_read_end;/*Endofgetarea.*/
- char*_IO_read_base;/*Startofputback+getarea.*/
- char*_IO_write_base;/*Startofputarea.*/
- char*_IO_write_ptr;/*Currentputpointer.*/
- char*_IO_write_end;/*Endofputarea.*/
- char*_IO_buf_base;/*Startofreservearea.*/
- char*_IO_buf_end;/*Endofreservearea.*/
- /*Thefollowingfieldsareusedtosupportbackingupandundo.*/
- char*_IO_save_base;/*Pointertostartofnon-currentgetarea.*/
- char*_IO_backup_base;/*Pointertofirstvalidcharacterofbackuparea*/
- char*_IO_save_end;/*Pointertoendofnon-currentgetarea.*/
- struct_IO_marker*_markers;
- struct_IO_FILE*_chain;
- int_fileno;//这个便是linux内核中文件描述符fd
- #if0
- int_blksize;
- #else
- int_flags2;
- #endif
- _IO_off_t_old_offset;/*Thisusedtobe_offsetbutitstoosmall.*/
- #define__HAVE_COLUMN/*temporary*/
- /*1+columnnumberofpbase();0isunknown.*/
- unsignedshort_cur_column;
- signedchar_vtable_offset;
- char_shortbuf[1];
- /*char*_save_gptr;char*_save_egptr;*/
- _IO_lock_t*_lock;
- #ifdef_IO_USE_OLD_IO_FILE
- };
- struct_IO_FILE_plus
- {
- _IO_FILEfile;
- conststruct_IO_jump_t*vtable;//IO函数跳转表
- };
下面咱们看看_IO_2_1_stdout_的界说(glibc-2.3.6/libio/stdfiles.c),趁便给出_IO_2_1_stdin_和_IO_2_1_stderr_的界说:
print?
- #defineDEF_STDFILE(NAME,FD,CHAIN,FLAGS)\
- struct_IO_FILE_plusNAME\
- ={FILEBUF_LITERAL(CHAIN,FLAGS,FD,NULL),\
- &_IO_file_jumps};
- DEF_STDFILE(_IO_2_1_stdin_,0,0,_IO_NO_WRITES);
- DEF_STDFILE(_IO_2_1_stdout_,1,&_IO_2_1_stdin_,_IO_NO_READS);
- 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):
print?
- #defineFILEBUF_LITERAL(CHAIN,FLAGS,FD,WDP)\
- {_IO_MAGIC+_IO_LINKED+_IO_IS_FILEBUF+FLAGS,\
- 0,0,0,0,0,0,0,0,0,0,0,0,(_IO_FILE*)CHAIN,FD,\
- 0,_IO_pos_BAD,0,0,{0},0,_IO_pos_BAD,\
- 0}
其间,FD赋值给了_fileno。咱们回到vfprintf的剖析,vfprintf的详细完成本文就不详细解说,首要原理是格式化字符串,最终将字符串输出到文件中,也便是stdout中。至于怎样输出,则和_IO_file_jumps联系密切,_IO_file_jumps的界说如下:
print?
- conststruct_IO_jump_t_IO_file_jumps=
- {
- JUMP_INIT_DUMMY,
- JUMP_INIT(finish,INTUSE(_IO_file_finish)),
- JUMP_INIT(overflow,INTUSE(_IO_file_overflow)),
- JUMP_INIT(underflow,INTUSE(_IO_file_underflow)),
- JUMP_INIT(uflow,INTUSE(_IO_default_uflow)),
- JUMP_INIT(pbackfail,INTUSE(_IO_default_pbackfail)),
- JUMP_INIT(xsputn,INTUSE(_IO_file_xsputn)),
- JUMP_INIT(xsgetn,INTUSE(_IO_file_xsgetn)),
- JUMP_INIT(seekoff,_IO_new_file_seekoff),
- JUMP_INIT(seekpos,_IO_default_seekpos),
- JUMP_INIT(setbuf,_IO_new_file_setbuf),
- JUMP_INIT(sync,_IO_new_file_sync),
- JUMP_INIT(doallocate,INTUSE(_IO_file_doallocate)),
- JUMP_INIT(read,INTUSE(_IO_file_read)),
- JUMP_INIT(write,_IO_new_file_write),
- JUMP_INIT(seek,INTUSE(_IO_file_seek)),
- JUMP_INIT(close,INTUSE(_IO_file_close)),
- JUMP_INIT(stat,INTUSE(_IO_file_stat)),
- JUMP_INIT(showmanyc,_IO_default_showmanyc),
- JUMP_INIT(imbue,_IO_default_imbue)
- };
至于怎样跳转到这些函数,以及怎样跳转到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的分配和收回,否则会导致终端没有输出信息,也无法和内核输入信息。