跟我一起学Linux系统编程004-文件描述符、文件IO、C库IO

Linux系统编程学习004-文件描述符、文件IO、C库IO

我们知道了,在Linux系统上,一切皆文件。如果要在Linux系统上编程操作文件,则一定离不开文件描述符的概念。

关于文件描述符概念的理解,在我的两个浅文《Linux系统编程笔记-文件描述符》和《Linux Shell脚本攻略-重定向理解与补充》都有提及。虽然啰嗦的我都想吐,但为了文意连贯,有助于理解,还不得不次重复一下要点。

关于Linux文件、文件描述符和标准文件描述符

Linux系统编程学习004-文件描述符、文件IO、C库IO

Linux中标准文件描述符

要点如下:

在Linux中,一切都是文件。屏幕是文件,键盘是文件,系统错误也被抽象成了一个文件。当然,各种磁盘文件、各种设备,都是文件。

凡是文件都有其对应的名字,名字是静态的。比如:键盘文件、屏幕文件、系统错误,它们的名字分别是:/dev/stdin、/dev/stdout、/dev/stderr。当然,各种磁盘文件、各种设备,也都有对应的名字。

既然在Linux操作系统中,你将一切都抽象为了文件,那么对于一个打开的文件,我应用程序怎么对应上呢?文件描述符这个概念就应运而生。进程运行时,需要内核去打开某个文件,内核返回一个非负整数,用来指代这个文件,这个非负整数就是文件描述符。所以,文件描述符是动态概念,有进程才能有描述符。

在Linux中,标准文件描述符有三个,分别是0(stdin)、1(stdout)、2(stderr)。stdin其实就是指键盘设备文件,stdout就指的是屏幕设备文件, stderr就是指错误文件。三个之所以称之为“标准”,意思就是说它们是系统预留的,相当于每个进程的全局变量,也就是说每个进程无需刻意打开这三个文件描述符,就可以使用这三个标准文件

除了这三个标准的文件描述符,其它的,你要用,就必须自已open,自已open所得到的文件描述符值都是大于或等于3的整数值。

关于上述标准文件描述符概念,从代码的角度的理解,在我的浅文《Linux系统编程笔记-文件描述符》中有更详细的代码说明。

文件描述符的本质

为了说明文件描述符的本质,我们事先了解与进程相关的三个表。

(1)进程级的文件描述符表

(2)系统级的文件描述符表

(3)文件系统的i-node表

上面这三个表,是内核维护的三个数据结构表。第一张表,进程级的文件描述符表,是针对进程的,每个进程都有一张这样的表。另外两张表都是系统级的,是所有进程共享的(虽然进程不能直接访问。)

Linux系统编程学习004-文件描述符、文件IO、C库IO

文件描述符,本质上它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。因为它是索引值,所以只可能为从0开始的非负整数。

文件IO函数中的参数fd,其实就是文件描述符这个东西。明白这一概念后,系统API中的诸多函数,都非常易懂。

系统API中的open、write、read、lseek、fcntl、pread、pwrite、readv、writev、dup1/2/3、ftruncate、mkstemp,等等,均需要文件描述符fd作参数。一般的步骤是先用open(文件名...),内核打开这个文件之后,把文件描述符fd作参数返回,其它的函数运行时,把文件描述符fd作为参数,传到内核。这等于告诉内核,打开文件表中的哪一项,再找到对应的i-node表,来进行处理。

一个进程刚创建,还未打开任何文件时,它是如下图这样的。

Linux系统编程学习004-文件描述符、文件IO、C库IO

进程创建时的进程文件描述符表

我们看到,即使进程啥也没干,已打打开了3个文件,拥有了3个文件描述符,它们分别是0(stdin)、1(stdout)、2(stderr)。因为这三个标准文件描述符是全局的,也就是说每个进程无需刻意打开这三个文件描述符,就可以使用这三个标准文件。你不需要open(0/1/2,...),就可以直接read(0/1/2,...)或write(0/1/2,...)。下面的代码,看似没有open,直接write,其实编译执行后,就可以将字符串“Out to stdout directly”输出到终端屏幕上来,没有错误。

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <fcntl.h>

int main(int argc,char *argv[])

{

write(1,"Out to stdout directly",sizeof("Out to stdout directly"));

return 0;

}

文件IO/通用IO

在我的浅文《Linux系统编程学习002-一切皆文件、文件系统、IO》中提到了通用IO的概念。现在再提一下:Linux“一切皆是文件”,那么对文件的读写操作也是通用的,我们称之为通用I/O,也可以称之为文件I/O。在Linux平台上,文件I/O是一个通用性的概念,它要借用系统API调用的诸多接口函数(open/read/write...)来实现。

文件IO的通用性在哪里。不用编程示例,我们看几个命令及输出就知道了。

$ cat abc.txt

 This line1 out to stdout

 This line2 out to stdout

 This line3 out to stdout

$ cat abc.txt -n

 1This line1 out to stdout

 2This line2 out to stdout

 3This line3 out to stdout

$ cp abc.txt /dev/stdout

 This line1 out to stdout

 This line2 out to stdout

 This line3 out to stdout

$ cp abc.txt /dev/stdout | cat -n

 1This line1 out to stdout

 2This line2 out to stdout

 3This line3 out to stdout

$ cp abc.txt /dev/stdout | cat -n >out.txt

$ cat out.txt

 1This line1 out to stdout

 2This line2 out to stdout

 3This line3 out to stdout

前两个命令很好理解,如果没有一切皆文件、文件描述符的理解,也就难以理解“ cp abc.txt /dev/stdout”。更不会理解后两个带有重定向、管道的命令操作。

标准C库

既然系统API这么能打,还要标准C库的IO函数干啥?用open/read/write...就可以了,为什么还要fopen/fread/fwrite...呢?

说实话,学习Linux系统编程,我们当然要用系统API,因为这是最直接最好的办法。但是如果编写应用程序,那最好还是用标准C库函数。原因如下。

编程效率上来讲,标准C库其实屏蔽调用了系统API,因为屏蔽了系统API的调用细节,编程、调试的效率都要高得多。如果你是老板,多快好省,是你的必然选择,没谁在意你技术的深度。

从文件IO运行效率上来讲,当数据吞吐量大时,如果顺序访问文件,用标准C库的函数效率要远大于直接用系统API。系统API是低级IO函数,open/read/write等诸多文件IO函数无缓冲。标准C库是高级IO函数,fopen/fread/fwrite等诸多IO函数,都在内存开辟一个“缓冲区”,先读写“缓冲区”,在条件合适时再调用系统API工作。因此进行文件读写操作的时候就减少了用户态和内核态的切换(切换到内核态调用还是需要调用系统调用API:read,write),提高了IO效率。当然如果是,如果随机访问文件则相反。

还有一个,就是可移植性。标准C库函数是平台无关的,系统API则是Unix/Linux平台专用的。标准C库函数在大部分系统上是可移植的,比如fopen与操作系统无关,Linux上的与windows上的都差不多。文件IO的open函数,仅限于UNIX/Linux,因为其它平台上没有文件描述符概念,windows上类似的是CreateFile。

但是,只会标准C,不懂系统编程原理,不了解系统API函数,就无法深度开发。尤其是在遇到系统问題的时候,编程、调试寸步难行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虎哥的世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值