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。

下面进入glibc的源码,查看函数_IO_new_file_fopen来验证上面的结论

  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 ‘\0‘:
  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占用的内存。

来自为知笔记(Wiz)

时间: 2024-10-22 22:56:24

Linux内核解析之标准I/O库的相关文章

决Ubuntu使用`make menuconfig`配置Linux 内核时,出现缺少&#39;ncurses-devel&#39;库支持。

*** Unable to find the ncurses libraries or the *** required header files. *** 'make menuconfig' requires the ncurses libraries. *** *** Install ncurses (ncurses-devel) and try again. ***  1. 问题状况 一般情况下使用系统自带的软件管理器apt-get就可以安装了(`sudo apt-get install 

linux内核系统调用和标准C库函数的关系分析

今天研究了一下系统调用和标准库函数的区别和联系,从网上搜集的资料如下: 1.系统调用是为了方便应用使用操作系统的接口,而库函数是为了方便人们编写应用程序而引出的,比如你自己编写一个函数其实也可以说就是一个库函数. 2.系统调用可以理解为内核提供给我们在用户态用的接口函数,可以认为是某种内核的库函数. 3.read就是系统调用,而fread就是C标准库函数. 4.很多c函数库中的函数名与系统调用的名称一样是因为该函数本身其实就是调用的系统调用,放到c函数库就是为了用户态的使用 5.写程序直接使用的

Linux内核解析:进程间通信:管道

管道的定义管道的用途管道的操作管道非法read与write内核实现解析管道通信原理及其亲戚通信解析父子进程通信解析亲缘关系的进程管道通信解析管道的注意事项及其性质管道有以下三条性质shell管道的实现与shell命令进行通信system函数与popen函数区别 管道的定义 管道是第一个广泛应用的进程间通信手段.日常在终端执行shell命令时,会大量用到管道.但管道的缺陷在于只能在有亲缘关系(有共同的祖先)的进程之间使用.为了突破这个限制,后来引入了命名管道. 管道的用途 管道是最早出现的进程间通

Linux内核剖析 之 进程简介

1.概念 1.1  什么是进程? 进程是程序执行的一个实例,可以看作充分描述程序已经执行到何种程度的数据结构的汇集. 从内核观点看,进程的目的就是担当分配系统资源(CPU时间,内存等)的实体. 我们熟悉的fork()库函数,它有两种用法: (1).一个父进程希望复制自己,使父子进程执行不同的代码段,常用于网络服务程序. (2).一个进程要执行一个不同的程序,fork()后立即exec(),如shell. 1.2  什么是线程? 有时候,一个进程希望有多个执行流,如一款麻将游戏,三个由电脑控制的人

Linux内核剖析 之 进程简单介绍

1.概念 1.1  什么是进程? 进程是程序运行的一个实例.能够看作充分描写叙述程序已经运行到何种程度的数据结构的汇集. 从内核观点看.进程的目的就是担当分配系统资源(CPU时间,内存等)的实体. 我们熟悉的fork()库函数,它有两种使用方法: (1).一个父进程希望复制自己,使父子进程运行不同的代码段.经常使用于网络服务程序. (2).一个进程要运行一个不同的程序,fork()后马上exec(),如shell. 1.2  什么是线程? 有时候,一个进程希望有多个运行流,如一款麻将游戏,三个由

《Linux内核设计与实现》读书笔记——第一二章

<Linux内核设计与实现>读书笔记——第一二章 第一章 Linux内核简介 1.1 Unix的历史 简洁:仅提供系统调用并有一个非常明确的设计目的. 抽象:Unix中绝大部分东西都被当做文件,这种抽象使对数据和对设备的操作是通过一套相同的系统调用接口来进行的(open().read().write().lseek().close()). 可移植:使用C语言编写,使其在各种硬件体系架构面前都具备令人惊异的移植能力. 进程创建迅速:有独特的fork()系统调用,一次执行保质保量地完成一个任务.简

Linux C 文件操作 -- 系统调用(open(),read()...) 和 标准I/O库(fopen(),fread()...)

一.什么是文件 在讲述文件操作之前,我们首先要知道什么是文件.看到这个问题你可能会感觉到可笑,因为对于用过计算机的人来说,文件是最简单不过的概念了,例如一个文本是一个文件,一个work文档是一个文件等.但是在Linux中,文件的概念还远不止于这些,在Linux中,一切(或几乎一切)都是文件.文件包括很多的内容,例如:大家知道的普通文件是文件,目录也是一个文件,设备也是一个文件,管道也是一个文件等等.对于目录.设备这些的操作也可以完全等同于对纯文本文件的操作,这也是Linux非常成功的特性之一吧.

linux 内核头文件及内核库文件

与用户空间应用不同的是,内核不能调用标准C库或其它任何不属于内核的库.主要原因是,速度及大小的限制. 标准C库对内核而言实在是太大了. 许多常用的的 libc 函数已经在内核中实现了.内核中的库文件是在内核的根目录下 lib 目录下存放. 如常用的字符串操作函数是在 lib/string.c 中定义的,调用时主需要将头文件 <linux/string.h>包含进即可. 关于内核的头文件,内核是不能包含内核之外的头文件的,如同内核不能调用内核之外的库一样. 1)基本的头文件放在内核的 inclu

深入解析Linux内核I/O剖析(open,write实现)

Linux内核将一切视为文件,那么Linux的文件是什么呢?其既可以是事实上的真正的物理文件,也可以是设备.管道,甚至还可以是一块内存.狭义的文件是指文件系统中的物理文件,而广义的文件则可以是Linux管理的所有对象.这些广义的文件利用VFS机制,以文件系统的形式挂载在Linux内核中,对外提供一致的文件操作接口. 从数值上看,文件描述符是一个非负整数,其本质就是一个句柄,所以也可以认为文件描述符就是一个文件句柄.那么何为句柄呢?一切对于用户透明的返回值,即可视为句柄.用户空间利用文件描述符与内