目录
一. 如何在系统层面上理解文件
二. 语言层面上的文件IO函数
三. Linux操作系统提供的IO接口
3.1 open接口 -- 打开文件
3.2 close接口 -- 关闭文件
3.3 write接口 -- 向文件中写内容
3.4 read接口 -- 从文件中读取内容
四. 总结
在Linux操作系统层面,可以认为,只要能进行input写入或output读取的任何的任何设备,都可以被理解为文件,我们可以从狭义和广义两个方面认识文件的概念:
换句话说就是:Linux下一切皆文件。
文件 = 内容 + 属性,对文件的操作无外乎就是对内容的操作和对属性的操作。因此,就算一个文件的内容为空,那么它的属性信息也要存储在磁盘中,也要占用存储空间。
通过Linux操作系统的ls -l (可简写为ll) 指令,可以查看文件的属性信息,包括文件的类型、权限信息、链接数、拥有者、所属组、文件内容占用空间大小、最近一次修改的时间、文件名。
本质上,只有操作系统有权利对文件进行读写操作。用户对于文件的任何操作,其本质是都是调用操作系统的相关IO接口来完成的,只不过为了方便不同用户的使用,操作系统及各种编程语言,会通过图形化界面、库函数等方式,封装文件的IO接口。
如果我们希望通过代码来访问文件,那么操作流程依次为:写代码 -> 编译源代码 -> 载入内存运行,创建进程 -> 访问文件。因此,我们还可以认为,访问文件的操作本质是由进程来完成的。
C/C++均提供了相应的库函数/类,来实现的文件得IO操作
这些语言层面的IO函数,在底层都封装了操作系统提供的IO接口。那么,既然操作系统已经提供了IO接口,为什么各种语言语言还要提供自己的IO函数呢?这可以从以下两个方面来理解:
提问:库函数怎样针对不同的OS平台,实现不同版本的IO函数呢?答:一般采用条件编译#ifdef/#endif的方法来实现,在库函数中将全部平台下的IO接口都进行封装,实现不同底层原理的IO函数,再根据运行的平台,来对库函数代码选取一部分进行编译。
不同的语言,会提供不同的IO接口函数,但是,在同一操作系统平台下,系统IO的接口都是一样的,无论语言层面的IO接口再怎么变化,在底层都需要封装系统IO接口。
用于打开文件的系统接口(可能创建文件),有下面两种格式:
使用open函数,要包含三个头文件:
其中,file的意义为文件打开的路径,flag为打开方式的标识方法,mode为如果要创建文件的话文件的起始权限。
关于file,就是带有路径的文件的文件名。flag类似于C语言的open函数中"w"、"a"、"r"等打开方式控制的标识,主要的flag和对应的功能以下几种:
如果我们同时需要两种flag的功能,那么应该使用按位或操作符,来同时对两个位置进行标记。如:我们希望open以只写的方式打开某个文件的同时在文件不存在的时候创建它,那么flag的传参方式为O_WRONLY|O_CREAT。
mode为文件创建的起始权限,以8进制的方式传参,如果我们希望创建文件的起始权限为拥有者、所属组和其它人都是rw-,那么在调用open时就应该给mode传0666。
但是,实际创建文件的起始权限,要经过系统的文件权限掩码过滤相应权限。Linux系统默认的文件掩码一般为0002,即:其它人不能拥有写文件的权限。那么,我们就可以在创建的进程的文件中,使用umask(0000),将该进程中的文件掩码暂时改为0000,不对任何权限进行过滤,这样就可以按照希望的方式给定文件的起始权限。
open接口的返回值:a.如果文件打开成功,就返回被打开的文件的文件描述符。b.如果文件打开失败,那么就返回-1。
再使用open之后,应当通过if判断open函数的返回值,再确保文件打开成功之后,再使用文件。
代码3.1:文件打开
#include
#include
#include
#include
#include
int main()
{
int fd1 = open("log1.txt", O_WRONLY|O_CREAT); //以只写的方式打开log1.txt文件,如果文件不存在就创建
if(fd1 < 0) //判断打开是否成功
{
perror("open fd1");
return 1;
}
//下面省略打开文件是否成功的判断
int fd2 = open("log2.txt", O_RDONLY); //以只读方式打开log2.txt文件
int fd3 = open("log3.txt", O_WRONLY|O_APPEND); //以追加写的方式打开log3.txt文件
int fd4 = open("log4.txt", O_WRONLY|O_TRUNC); //清空文件原有内容再写入
umask(0000); //在此之后,权限掩码为0000
int fd5 = open("log5.txt", O_WRONLY|O_CREAT, 0666); //给定权限0666创建文件
close(fd1);
close(fd2);
close(fd3);
close(fd4);
close(fd5);
return 0;
}
close用于关闭某个被open接口打开的文件,其函数原型为:
使用close函数需要包含的头文件:
close一般配合open的返回值使用,如果传入错误的文件描述符,那么close就会失败。
使用write函数需要包含的头文件:
代码3.2:写文件
#include
#include
#include
#include
#include
#include
int main()
{
int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_TRUNC);
if(fd1 < 0)
{
perror("open fd1");
return 1;
}
const char* s1 = "hello world\n";
//这里不需要strlen(s) + 1
//字符串末尾以\0结尾是语言层面的规则,与文件无关
write(fd1, s1, strlen(s1));
write(fd1, s1, strlen(s1));
write(fd1, s1, strlen(s1));
int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND|O_TRUNC);
if(fd2 < 0)
{
perror("open fd2");
return 2;
}
const char* s2 = "hello Linux\n";
write(fd2, s2, strlen(s2));
write(fd2, s2, strlen(s2));
write(fd2, s2, strlen(s2));
close(fd1);
close(fd2);
return 0;
}
代码3.3:read读取文件内容
#include
#include
#include
#include
#include
#include
int main()
{
int fd = open("log1.txt", O_RDONLY); //只读方式打开文件
if(fd < 0) //检查文件是否打开成功
{
perror("open");
return 1;
}
char buff[100]; //记录存储从文件中读取到的内容
memset(buff, '\0', 100); //空间内容初始化
ssize_t ret = read(fd, buff, 100); //读取100字节的内容到buff中
printf("ret = %d\n", ret);
printf("buff:%s\n", buff);
close(fd);
return 0;
}