文件描述符

在《嗨翻C语言》的学习中,了解到“文件描述符”的概念。有一点点傻傻分不清楚文件描述符表中两列的区别,每每涉及重定向时觉得混乱。

文件描述符

参考 文件描述符-维基百科 中描述:

在 UNIX/Linux 平台上,对于控制台(Console)的标准输入,标准输出,标准错误输出也对应了三个文件描述符。它们分别是 0,1,2。在实际编程中,如果要操作这三个文件描述符时,建议使用 <unistd.h> 头文件中定义的三个宏来表示:STDIN_FILENO, STDOUT_FILENO 以及 STDERR_FILENO

对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用 opencreate 返回的文件描述符标识该文件,将其作为参数传送给 readwrite

FILE* 指针

在介绍 文件描述符和inode关系 一篇文章中描述到:(待验证)

在 UNIX 系统中,用户通过终端登录系统后得到一个 Shell 进程,这个终端成为 称为 Shell 进程的控制终端 (Controlling Terminal),控制终端是保存在 PCB 中的信息,而我们知道 fork 会复制 PCB 中的信息,因此由 Shell 进程启动的其它进程的控制终端也是这个终端。

默认情况 下(没有重定向),每个进程的标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)都指向控制终端,因为在程序启动时(在 main() 函数还 没开始执行之前)会自动把控制终端打开三次,分别赋给三个 FILE * 指 针 stdinstdoutstderr,这三个文件指针是 libc 中定义的全局变量,这三个文件的描述符分别是 0、1、2,保存在相应的 FILE 结构体中。进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。

在了解以上内容之后,需要重新翻阅《嗨翻C语言》中相关章节,确定是否掌握。

两者的区别

  • 文件描述符:在 linux 系统中,设备也是以文件的形式存在,要对该设备进行操作 就必须先打开这个文件,打开文件就会获得文件描述符,它是个很小的正整数。每个进程在 PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。

    • 文件描述符的优点:兼容 POSIX 标准,许多 Linux 和 UNIX 系统调用都依赖于它。
    • 文件描述符的缺点:不能移植到 UNIX 以外的系统上去,也不直观。
  • 文件指针:C 语言中使用的是文件指针而不是文件描述符做为 I/O 的句柄。文件指针指向进程用户区中的一个被称为 FILE 结构的数据结构。FILE 结构包括一个缓冲区和 一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。FILE * 中除了包含了 fd 信息,还包含了 IO 缓冲,是 C 标准形式,所以 FILE *fd 更适合跨平台,应该多用 fopen 在,少用 open

  • C 语言文件指针与文件描述符之间可以相互转换:通过 fdopenfileno 两个函数实现,它们都包含在头文件 stdio.h 中。

查阅文件

参见 Debian8Light 系统 /usr/include/stdio.h 文件:

stdin stdout stderr_IO_FILE 结构体类型,而 _IO_FILEFILE 一致。

参见 Debian8Light 系统 /usr/include/libio.h 文件:(可以看到 _IO_FILE 结构体中封装了 _fileno 属性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];

/* char* _save_gptr; char* _save_egptr; */

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

fprintf(stdout, "Hello world!\n");./test > a.out 重定向之后打印结果到 a.out 文件。

延伸阅读

以下链接,供进一步理解。

  • Linux中的文件描述符与打开文件之间的关系

    两个不同的文件描述符,若指向同一个打开文件句柄,将共享同一文件偏移量。因此,如果通过其中一个文件描述符来修改文件偏移量(由调用 read()write()lseek() 所致),那么从另一个描述符中也会观察到变化,无论这两个文件描述符是否属于不同进程,还是同一个进程,情况都是如此。

  • linux中文件描述符fd和文件指针flip的理解

    每个进程在 PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表,文件描述符就是这个表的索引,文件描述表中每个表项都有一个指向已打开文件的指针,现在我们明确一下:已打开的文件在内核中用 file 结构体表示,文件描述符表中的指针指向 file 结构体。