zoukankan      html  css  js  c++  java
  • Linux内核解析之标准I/O库

    当Linux创建一个进程时,会自动创建3个文件描述符0,1,2,分别对应标准输入,标准输出,错误输出。C库中与文件描述符对应的是文件指针。查看C库头文件stdio.h中的源码
    1. typedef struct _IO_FILE FILE; //文件流类型
    2. extern struct _IO_FILE *stdin; /* 标准输入流 */
    3. extern struct _IO_FILE *stdout; /* 标准输出流 */
    4. extern struct _IO_FILE *stderr; /* 错误流 */
    5. #ifdef __STDC__
    6. /* C89/C99 say they're macros. Make them happy. */
    7. #define stdin stdin
    8. #define stdout stdout
    9. #define stderr stderr
    10. #endif
    从上面源码看stdin,stdout,stderr是文件流指针,看看stdin,stdout,stderr是如何定义的
    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_stdin_的定义
    1. DEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES);
    2. DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS);
    3. DEF_STDFILE(_IO_2_1_stderr_, 2, &_IO_2_1_stdout_, _IO_NO_READS+_IO_UNBUFFERED);
    DEF_STDFILE是一个宏定义,用于初始化C库中的FILE结构,_IO_2_1_stdin__IO_2_1_stdout__IO_2_1_stderr_分别用于0,1,2的初始化,这样c库的文件指针跟系统的文件描述符关联起来了,另外注意后面的标志位,stdin是不可写,stdout不可读,stderr不可读不可写没缓冲
    通过上面分析可以得知stdin,stdout,stderr是file类的文件指针


    I/O缓冲引出的有趣问题。
        C库的I/O接口对文件I/O进行了封装为了提高性能,其引入了缓存机制,共有3种缓存机制:全缓存,行缓存及无缓存
        全缓存一般用于访问真正的磁盘文件。C库为文件访问申请一块内存,只有当文件内容将缓存填满或者执行flush时,C库才会将缓存内容写入到内核中。
        行缓存一般用于访问终端,当遇到一个换行符时,就会引发真正的I/O操作。
        无缓存那就不用多说了


    C库的fopen用于打开文件,其内部实现必然要使用open系统调用。那么fopen的各个标志位又对应open的哪些标志位呢?请看表2-1。
     
    1. _IO_FILE *
    2. _IO_new_file_fopen (fp, filename, mode, is32not64)
    3. _IO_FILE *fp;
    4. const char *filename;
    5. const char *mode;
    6. int is32not64;
    7. {
    8. int oflags = 0, omode;
    9. int read_write;
    10. int oprot = 0666;
    11. int i;
    12. _IO_FILE *result;
    13. #ifdef _LIBC
    14. const char *cs;
    15. const char *last_recognized;
    16. #endif
    17. if (_IO_file_is_open (fp))
    18. return 0;
    19. switch (*mode)
    20. {
    21. case 'r':
    22. omode = O_RDONLY;
    23. read_write = _IO_NO_WRITES;
    24. break;
    25. case 'w':
    26. omode = O_WRONLY;
    27. oflags = O_CREAT|O_TRUNC;
    28. read_write = _IO_NO_READS;
    29. break;
    30. case 'a':
    31. omode = O_WRONLY;
    32. oflags = O_CREAT|O_APPEND;
    33. read_write = _IO_NO_READS|_IO_IS_APPENDING;
    34. break;
    35. default:
    36. __set_errno (EINVAL);
    37. return NULL;
    38. }
    39. #ifdef _LIBC
    40. last_recognized = mode;
    41. #endif
    42. for (i = 1; i < 7; ++i)
    43. {
    44. switch (*++mode)
    45. {
    46. case '':
    47. break;
    48. case '+':
    49. omode = O_RDWR;
    50. read_write &= _IO_IS_APPENDING;
    51. #ifdef _LIBC
    52. last_recognized = mode;
    53. #endif
    54. continue;
    55. case 'x':
    56. oflags |= O_EXCL;
    57. #ifdef _LIBC
    58. last_recognized = mode;
    59. #endif
    60. continue;
    61. case 'b':
    62. #ifdef _LIBC
    63. last_recognized = mode;
    64. #endif
    65. continue;
    66. case 'm':
    67. fp->_flags2 |= _IO_FLAGS2_MMAP;
    68. continue;
    69. case 'c':
    70. fp->_flags2 |= _IO_FLAGS2_NOTCANCEL;
    71. continue;
    72. case 'e':
    73. #ifdef O_CLOEXEC
    74. oflags |= O_CLOEXEC;
    75. #endif
    76. fp->_flags2 |= _IO_FLAGS2_CLOEXEC;
    77. continue;
    78. default:
    79. /* Ignore. */
    80. continue;
    81. }
    82. break;
    83. }
    84. result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
    85. is32not64);


    fdopen与fileno
    Linux提供了文件描述符,而C库又提供了文件流。在平时的工作中,有时候需要在两者之间进行切换,因此C库提供了两个API:
    1. #include <stdio.h>
    2. FILE *fdopen(int fd, const char *mode);
    3. int fileno(FILE *stream);
    fdopen用于从文件描述fd中生成一个file指针,而fileno则用于从文件指针中得到对应的文件描述符

    查看fdopen的实现,其基本工作是创建一个新的文件流FILE,并建立文件流FILE与描述符的对应关系。我们以fileno的简单实现,来了解文件流FILE与文件描述符fd的关系。——因为该函数代码较长,在此就不罗列C库的代码了。代码如下:
    1. int fileno (_IO_FILE* fp)
    2. {
    3. CHECK_FILE (fp, EOF);
    4. if (!(fp->_flags & _IO_IS_FILEBUF) || _IO_fileno (fp) < 0)
    5. {
    6. __set_errno (EBADF);
    7. return -1;
    8. }
    9. return _IO_fileno (fp);
    10. }
    11. #define _IO_fileno(FP) ((FP)->_fileno)
    从fileno的实现基本上就可以得知文件流与文件描述符的对应关系。文件流FILE保存了文件描述符的值。当从文件流转换到文件描述符时,可以直接通过当前FILE保存的值_fileno得到fd。而从文件描述符转换到文件流时,C库返回的都是一个重新申请的文件流FILE,且这个FILE的_fileno保存了文件描述符。
    因此无论是fdopen还是fileno,关闭文件时,都要使用fclose来关闭文件,而不是用close。因为只有采用此方式,fclose作为C库函数,才会释放文件流FILE占用的内存。












  • 相关阅读:
    C++ 将对象写入文件 并读取
    IronPython fail to add reference to WebDriver.dll
    How to Capture and Decrypt Lync Server 2010 TLS Traffic Using Microsoft Tools
    .net code injection
    数学系学生应该知道的十个学术网站
    Difference Between Currency Swap and FX Swap
    Swift开源parser
    谈谈我对证券公司一些部门的理解(前、中、后台)[z]
    JDK8记FullGC时候Metaspace内存不会被垃圾回收
    JVM源码分析之JDK8下的僵尸(无法回收)类加载器[z]
  • 原文地址:https://www.cnblogs.com/zengyiwen/p/5755187.html
Copyright © 2011-2022 走看看