Linux中,较高级的I/O函数(如printf(),scanf()等)都是通过使用内核提供的系统级Unix I/O函数来实现的。虽然我们用起这些较高级的I/O函数都觉得非常顺手,但是学习系统级I/O函数仍存在许多意义,正如《深入理解计算机系统》一书中写道:
1.了解Unix I/O会帮助我们理解其他的系统概念。系统I/O与其他系统概念经常会形成循环依赖,比如I/O函数在系统进程创建和执行中非常重要,而进程的创建又在不同进程的文件共享中扮演着关键角色;
2.有时除了Unix系统级I/O以外别无选择。在某些重要的情况中,使用高级的I/O函数不太可能,比如:标准I/O库中没有提供读取文件元数据的方式,例如文件大小或文件创建时间。
了解完系统级I/O的重要性,接下来介绍一下本篇博客的主体目录:
- 基本的系统级I/O函数及其相应的参数
- 系统级I/O操作中运用的指令
- 《CSAPP》中推荐的I/O函数
- 总结
下面来进入我们的第一个部分:
1.基本的系统级I/O函数及其相应的参数
“Linux中,一切皆文件”,我的《深入了解计算机系统》的老师如是说道。一个功能完整的机器,所有的设备/文件等等是海量的,那么应当如何单独地描述标记一个文件呢?这时我们就需要引入一个文件描述符(fd,一个非负数)的概念。一个应用程序通过要求内核打开相应的文件时,内核会返回一个非负数,这个非负数就是这个相应的文件的文件标识符fd。
解释完fd是什么,进入正题。Unix I/O中,所有的I/O都由一种“统一而且一致的方式”来执行,各种Unix I/O函数如下:
- 打开文件:每个对文件的I/O操作必须要进行的操作,一个文件的fd也是在这个时候生成,并作为open()的返回值。
- Linux shell中创建时就有的文件:Linux shell作为用户访问内核的界面,创建的每一个进程都要有三个打开的文件:标准输入(fd=0)、标准输出(fd=1)、标准错误(fd=2)。头文件<unistd.h>中定义了三个可以代替三个上述文件描述符的常量:STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。由上述可知,每个自定义文件的fd都只能从3开始。
- 改变当前文件位置:对于每个打开后的文件,内核会保存其文件位置k,初始化为0。应用程序能通过seek操作,设置文件的位置。
- 读写文件:读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后改变文件的位置,从k变成k+n;特殊地,对一个字节大小为m<=k的文件,读操作会触发EOF,应用程序可以检测到。写操作类似。
- 关闭文件:内核会释放该文件所创建的各种数据结构(堆栈、等等),并将fd恢复到可用的描述符中(释放两件东西:数据结构+fd)。
(1).打开文件
open()的函数原型如下:
#include<sys/types.h>
#include<sys/stat.h>
#include<fcnt1.h>
int open(char *filename, int flags, mode_t mode);
/*返回值:若成功则返回文件fd,不成功则为-1。*/
/*函数参数:filename:文件名;flags:进程访问文件的方式对应的参数;mode:访问权限位*/
接下来对flags及mode进行进一步的解析:
i).flags参数
了解完各种flags,我们可以知道它与open()的多种联用:
fd=open("foo.txt",O_WRONLY|O_APPEND,0);
/*打开一个文件,并在后面追加一些数据。*/
fd=open("foo.txt",O_CREAT|O_TRUNC|O_WRONLY,DEF_MODE);
/*若文件名对应的文件不存在,则创建;*/
/*若文件名对应的文件存在,则截断(清空)文件内的原内容,并将权限设置为可写。*/
ii).访问权限位
各种访问权限位在头文件sys/stat.h中定义,以掩码的形式表现出来,如下:
(2).关闭文件
代码如下,理解起来简单,故不作赘述:
#include<unistd.h>
int close(int fd);
/*成功返回0,不成功返回-1.,,x'x*/
(3).读和写文件
系统级的读写主要是通过调用read()和write()实现的,他们的代码如下:;
include<unistd.h>
ssize_t read(int fd, void *buf, size_t n);
/*若成功则返回读的字节数,若EOF则返回0,若失败则返回-1。*/
ssize_t write(int fd, const void *buf, size_t n);
/*若成功则返回写入的字节数,若出错则为-1。*/
/*变量名解释:fd;文件描述符;buf:read()时读入字节的存储位置,write()时被写入字节的来源;n:读写操作字节数数量。*/
了解了这么多系统级的I/O函数,接下来看看在Linux命令符提示窗口下可能会帮助到我们使用系统级I/O的指令。
2.系统级I/O操作中运用的指令/参数
(1).ls指令
ls指令可以查看指定文件的目录结构(包括目录和文件夹),但只限于文件和文件夹的名称。比如接下来我们来用ls指令来查看根目录下的子目录(“/”代表根目录):
(2).cat指令
cat指令用于显示文件中的内容,语法为cat+(选项)+(文件列表参数/路径);
其选项有以下几种:
各自的运行截图如下:
(3).chmod指令
chmod指令用于改变文件的属性(即改变文件的访问权限位),语法为chmod+mode+file;其中mode代表权限设定字串,格式为:[ugoa][±=][rwxX];其中:
- [ugoa]:代表文件的各类操作对象,u:user,用户本身;g:group of user,用户所在的群体;o:前两者之外的人;a:all。
- [±=]:+:代表增加权限;-:代表减少权限;=:代表唯一设定权限;
- [rwxX]:r:可读;w:可写;x:可执行;X:该档案是个子目录,或者该档案已经被设置为可执行。
下面使用上述指令给出例子:
- 将file.txt设置为所有人皆可读取:
chmod ugo+r file.txt
- 将file.txt设置为仅使用者可以执行:
chmod u+x file.txt
(4).cp指令
cp指令用于复制文件或者文件夹,它有两种用法:
- 用法一:一对一(此处目标为文件)复制:
$ cp src-file target-file
- 用法二:多对一(此处目标为文件夹)复制:
$ cp src1 src2 src3 ... target-directory
了解完系统级I/O和相关的Linux指令,我们来看《CSAPP》一书中推荐的I/O函数:
3.《CSAPP》推荐的I/O函数
《CSAPP》中有如下几条关于I/O函数使用的原则:
- 只要有可能就一定使用系统I/O;
- 不要用scanf()或者rio_readlineb()来读取二进制文件:scanf()这样的函数是专门用来读取文本文件的,而二进制文件中经常会散布着许多无意义的0xa,读取时必会产生“诡异莫测”的错误;
- 对网络套接字的I/O使用RIO(健壮的I/O)函数。
4.总结
以上便是我个人对于Linux系统级I/O及相关指令操作文档的汇总,其中包含了系统级I/O函数的汇总,一些十分有用且有趣的Linux指令,以及《CSAPP》一书中对于I/O函数使用的建议。希望这能对大家有所帮助。