1.文件
1.1文件属性
当我们创建文件时,文件就有了对应的属性,可以用mkdir创建目录,touch创建普通文件。用ls -al查看文件属性。
从上图可以看出目录或者文件的所有者,所属组,其他人权限,创建时间等信息。由此我们便可以得到,一个结论,文件创建出来,即使我们没有写入任何的数据,他也是有大小的。使用stat可以查看文件的详细信息。
stat test.c
这里大小显示为0并不是没有占内存,而是大小不足一字节,所以显示为0.
文件=文件内容+属性
1.2 文件存储位置
当文件没有被打开使用时,如果把他放他被放置在系统的磁盘中,但当文件打开时,他就会被加载到内存中。CPU只会与内存进行数据处理,使用文件前必须打开文件说的也就是把文件从磁盘加载到内存中,让CPU进行数据处理。
1.3 文件与进程关系
当我们在修改文件时,在Windows中会默认打开记事本,在Linux中使用vim或者nano。实际上我们不会之间修改文件,而是通过软件修改,其实就是通过进程修改文件。我们要学习文件管理就是在学习OS进程管理与文件管理之间的关系。
1.4如何管理文件
一个进程可以打开多个文件,多个进程可以打开很多文件,在OS中,这些文件分布在内存的各个地方,为了管理这些文件,Liunx必定会 先描述,在组织。即在使用struct结构体包含文件信息,然后再组织管理起来。如下图
在Liunx中学习文件操作,就是学习进程管理与文件管理之间的关系。
2.C语言文件操作
在C语言中,可以使用fopen打开文件,fwrite,fputs,fprintf等写入文件。
2.1 fopen
path参数是文件地址,可以采用绝对地址,也可以采用想对地址。第二个参数是文件打开方式。函数失败返回空指针。
2.1.1 文件打开方式
文件打开方式主要有6种,a,w,r及其附加类
- r 以只读方式打开文件,(不可以修改文件)从文件开头读取。
- r+ 以读写方式打开文件,从文件开头读取。
- w 以写方式打开文件,文件存在清除,不存在创建新文件,从文件开头写入
- w+ 以写方式打开文件,文件存在清除,不存在创建新文件,可以读取文件内容,从文件开头写入
- a 以追加写入方式打开文件,文件不存在则创建,从文件结尾写入
- a+ 以追加读取写入方式打开文件,文件不存在则创建,从文件结尾写入。但读取从文件开头开始读取
总而言之,C语言提供了丰富的文件接口使用。不同场景下使用合适的方式。
#include<stdio.h>
int main()
{
FILE * fp=fopen("hello.txt","w");//不加路径则默认为当前路径下
fprintf(fp,"%s","hello files!\n");
fclose(fp);
return 0;
}
运行上述程序后,会在当前目录下生成hello.txt文件。
2.2 fclose
使用起来相对简单,只需要把文件指针传入即可。
在打开文件后,必须要关闭文件,当我们在修改文件时,文件数据是在内存中的,如果直接结束进程,内存中的数据被释放,文件修改后的数据就可能不存在了。关闭文件实际上就是把在内存中的文件写入到磁盘当中去。
2.3 默认打开文件
当C语言程序运行时,就会默认打开三个文件。
- stdin 标准输入 (一般为键盘)
- stdout 标准输出 (一般为显示器)
- stderr 标准错误 (一般为显示器)
2.3.1 键盘与显示器为什么是文件
OS是软硬件资源的管理者,对上要提供良好的开发环境,对下要管理好硬件。实际上OS要管理好硬件,就是要做好对于数据的IO操作,从硬件读取数据,或者是向硬件写入数据。
对于硬件来说,他会有许多,OS为了管理硬件,一定会先描述在组织,对下管理硬件资源。创建hardware_struct,里面有设备名称,硬件编号,以及函数指针等。其中的函数指针就是为了读写操作准备的。
由此,当我们想要向硬件写入的时候,不必关注底层硬件设计,只需要使用write函数即可,剩下的交给硬件驱动。这就是Linux下的一切皆文件的实现方式。
实际上驱动程序主要就是IO操作相关的函数,所以向电脑插入一个设备的时候,电脑首先就会查看有没有相关的驱动程序,如果没有就运行不了。我们之前学习的C语言函数就是对应上图的函数库操作。
3.系统文件操作
根据之前的操作系统图,C语言函数一定是封装了系统调用接口的。OS不允许用户越过自己直接访问硬件。(当然单片机除外,简单的单片机没有OS)
3.1 open
open就是操作系统提供的打开文件操作。
int open(const char *pathname, int flags);该函数第一个参数是文件路径,第二个参数是标记位,可以选择对文件如何操作。有如下主要选项。
O_RDONLY
:以只读方式打开文件。O_WRONLY
:以只写方式打开文件。O_RDWR
:以读写方式打开文件。O_CREAT
:如果文件不存在,则创建该文件。使用此标志时,需要提供第三个参数mode
来指定文件的权限。O_TRUNC
:如果文件存在且以可写方式打开,则将文件截断为零长度。O_APPEND
:以追加方式打开文件,每次写操作都会将数据追加到文件末尾。
上述标记位可以组合使用,用 | 分隔开。例如O_WRONLY | O_CREAT | O_TRUNC 就可以达到C语言fopen中W的效果,或者说C语言a,r,w就是open不同选项封装的。
3.1.1 返回值
返回值是int,没有看错,返回值就是朴实无华的int。在Linux进程中我们必定要管理文件,但是一个int类型怎么指向一个文件?怎么根据int确定是那个文件。
运行下述代码。
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int f1=open("text1",O_WRONLY|O_CREAT);
int f2=open("text2",O_WRONLY|O_CREAT);
int f3=open("text3",O_WRONLY|O_CREAT);
int f4=open("text4",O_WRONLY|O_CREAT);
int f5=open("text5",O_WRONLY|O_CREAT);
printf("f1:%d f2:%d f3:%d f4:%d f5:%d",f1,f2,f3,f4,f5);
return 0;
}
我们可以发现如果我们连续打开文件,这是一连串的数字从3开始。计算机中有什么结构有如此规整的数字么?答案显然是数组,至于为什么从3开始,因为我们每个进程默认打开三个文件,标准输入,标准输出,标准错误。
实际上进程就是使用数组来管理文件的,如下图。
3.1.2 标记位如何实现
实际上flags就是个32位的位图,每个O_RDONLY,O_WRONLY都是宏定义,且是某个比特位为1的特殊值。如下图
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
printf("%d %d %d %d\n",O_RDONLY,O_WRONLY, O_CREAT,O_TRUNC);
return 0;
}
|实际上就是按位或,有1为1,这样在传入参数后,可以根据每个比特位的情况来实现不同的功能,而避免写过多的函数。
3.1.3 open
在 C 语言里并没有像 C++ 那样的函数重载机制,C 语言要求函数名必须唯一。不过在 Linux 系统中,
open
函数存在两种不同参数列表的形式,这并非是通过函数重载实现的,而是借助预处理器和编译器的协作来达成的。
int open(const char *pathname, int flags, mode_t mode);前两个参数与之前一样,第三个参数是设置文件的权限。
这里使用数字,实际上也是运用位图的思想,每个选项对应一个比特位。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
open("hello.txt", O_TRUNC | O_WRONLY|O_CREAT, 0777);
//这里0777表示8进制,二进制为 0 111 111 111
return 0;
}
运行上述程序,得到如下结果。
但看起来与我们设置的777不符合,为0775,这是因为还有掩码umask作用,真实权限是将默认权限值与 umask 值按位取反的结果进行按位与运算。简单记位减去掩码就是真正的权限。(权限=open设置-掩码)
111 111 111 (默认权限)
111 111 101 (取反后的掩码位)
---------------
111 111 101 (计算结果)
3.2 close
这个是Linux系统提供的接口,可以关闭文件,他的参数比较特殊是fd,就是文件在进程管理数组中的下标。关闭成功返回0,失败返回-1.
标准输入,标准输出,标准错误也是文件,在数组下标为0,1,2.一定也可以被关闭。如下代码。
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(2);
int f1=open("text1",O_WRONLY|O_CREAT);
int f2=open("text2",O_WRONLY|O_CREAT);
int f3=open("text3",O_WRONLY|O_CREAT);
int f4=open("text4",O_WRONLY|O_CREAT);
int f5=open("text5",O_WRONLY|O_CREAT);
printf("f1:%d f2:%d f3:%d f4:%d f5:%d\n",f1,f2,f3,f4,f5);
return 0;
}
可以发现下标从2开始。在运行下述程序,关闭0号文件。
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
int f1=open("text1",O_WRONLY|O_CREAT);
int f2=open("text2",O_WRONLY|O_CREAT);
int f3=open("text3",O_WRONLY|O_CREAT);
int f4=open("text4",O_WRONLY|O_CREAT);
int f5=open("text5",O_WRONLY|O_CREAT);
printf("f1:%d f2:%d f3:%d f4:%d f5:%d\n",f1,f2,f3,f4,f5);
return 0;
}
此时下标从1开始,由此我们可以得出下标的分配是从最小的没开始用的数组下标开始。