文件描述符的目的是什么?

4

我的理解是,fopen()open()都可以用于打开文件。 open()返回一个文件描述符。但是它们在获取要写入或读取的文件方面应该是等效的。定义文件描述符的目的是什么?从维基页面上不清楚。

https://en.wikipedia.org/wiki/File_descriptor

2个回答

2

fopen 返回一个 FILE *,它是文件描述符的包装器(我将忽略“规范不需要这样做”的方面,因为我不知道有实现不这样做)。在高层次上,它看起来像这样:

最初的回答:

fopen 函数返回一个 FILE * 类型的值,它是对文件描述符的封装(虽然规范并不要求这么做,但实际上所有的实现都这么做了)。简单来说,它的作用就是把文件描述符进行包装,使其更易于操作。

application --FILE *--> libc --file descriptor--> kernel

Shell脚本直接操作文件描述符,主要是因为它们正在执行其他程序,而您无法修改其他程序的FILE *对象。但是,在启动时(即在forkexec之间),可以使用dup系统调用来修改其他程序的文件描述符。例如:


/bin/cat > foo.txt

这行命令告诉shell执行/bin/cat程序,但首先将标准输出(文件描述符#1)重定向到它打开的文件中。伪代码实现如下:

(最初的回答)

if (fork() == 0) {
    int fd = open("foo.txt");
    dup2(fd, 1);
    exec("/bin/cat");
}

最接近使用FILE *的方法是调用freopen,但是与文件描述符不同,在使用exec时不会保留这个方法。但为什么我们需要FILE *呢?因为它是围绕文件描述符的一种封装,其中一个主要的好处是拥有预读缓存。例如,考虑fgets。它最终会在与传入的FILE *相关联的文件描述符上调用read系统调用。但是它怎么知道应该读多少呢?内核没有选项可以说“给我一行”(除了行缓冲的ttys)。如果你在第一个read中读取超过一行,下次调用fgets时你可能只会得到下一行的一部分,因为内核已经在前一个read系统调用中给出了第一部分。另一个选择是逐个字符地调用read,这对性能来说非常糟糕。那么libc做了什么呢?它一次读取一堆字符,然后将额外的字符存储在FILE *对象的内部缓冲区中。下次调用fgets时,它可以使用内部缓冲区。这个缓冲区也与fread等函数共享,因此你可以交替调用fgetsfread而不会丢失数据。

大部分是正确的,尽管FILE不一定是缓冲的。但这些都没有真正回答所提出的问题。POSIX 可以将与文件描述符相关的大多数内容定义为(指向)FILE对象。观察到它没有这样做并不能解释为什么它没有这样做。 - John Bollinger

1
这两个函数处于不同的层面:
  • open() 是一个底层的 POSIX 函数,用于打开文件。它返回一个独特的整数来标识和启用对已打开文件的访问。这个整数是一个“文件描述符”。
  • fopen() 是一个高级、可移植的 C 标准库函数,用于打开文件。
在 POSIX 系统上,可移植的 fopen() 可能会调用不可移植的 open(),但这是一个实现细节。
如果不确定,应优先选择使用 fopen()
有关更多信息,在 Linux 系统上,请参阅 man 2 read。POSIX 的 read() 函数通过由 open() 返回的文件描述符读取数据。

Shell主要使用文件描述符。他们如何使其可移植? - user1424739
据我所知,如果系统支持基于POSIX的shell,如Bash——据我所知,除了MS Windows之外的几乎所有系统都支持它,也许现在的Windows也支持——那么该系统将支持POSIX。因此,open()将在POSIX可移植的范围内可移植。但是,从被要求提供每个标准托管C安装的库的角度来看,它并不具备可移植性。这回答了你的问题吗? - thb
1
可以通过C标准库的fread()fwrite()函数(使用从fopen()获取的FILE *)进行二进制I/O。此外,如果需要缓冲I/O,则可以自动获得它。您能否更具体地说明为什么您认为基于文件描述符的I/O比流I/O更适合这些任务? - John Bollinger
@JohnBollinger,您教会了我一些我不知道的东西。我已经使用open()达到这个目的20年了。鉴于您的建议,我已删除有关二进制和fopen()的段落。 - thb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接