IO
- 文件再次理解
- 系统接口文件操作
- 理解文件描述符 fd
文件再次理解
文件 = 文件内容 + 文件属性
其中文件属性也是数据–>即便你创建一个空文件,其也是要占据磁盘攻坚的。
文件操作 = 文件内容的操作 + 文件属性的操作
有可能在操作文件的过程中即改变文件的内容,又改变文件的属性。
我们在打开文件的时候,系统会将文件的属性加载到内存中
因为根据冯诺依曼体系结构,CPU需要对文件进行读写的话,需要对内存进行读写。
系统中的文件大致可以分为两类
打开的文件是内存文件,没有打开的文件在磁盘上静静的躺着可以称为磁盘文件。
通常我们所说的打开文件,访问文件关闭文件,是谁在进行相关操作?
一般是fopen,fclose,fread,fwrite...
这是我们的代码,然后将代码编译了二进制可执行程序 ,只有文件运行起来的时候,才会执行相应的代码,然后才会正真的对文件进行相关操作。所以是真正对文件进行操作的是进程。
什么是当前路径?
看下列代码
{
5
6 FILE *fp = fopen("log.txt", "w");//写入
7 if(fp == NULL) {
8 perror("fopen");
9 return 1;
10 }
11 printf("mypid: %d\n", getpid());
12 while(1) {
13 sleep(1);
14 }
15 //fprintf 将特定的数据格式化显示到文件流中
16 const char *msg = "hello zjt";
17 int cnt = 1;
18 while(cnt < 20) {
19 fprintf(fp, "%s: %d\n", msg, cnt++);
20 }
21 fclose(fp);
22 printf("为什么不能创建文纪念\n");
23 return 0;
24 }
运行后发现
此时如果chdir更改当前的进程路径
chdir("/home/zjt");//更改当前进程的工作路径
所以当前路径并不是指源代码所在路径下,实际上指的是当前进程所在的路径下,也就是进程的工作路径,只不过默认情况下进程的工作路径是他当前自己所处的路径,不过这是可以改的。
小结一下 open打开方式
a:追加重定向,不断向文件中新增内容
w:以w方式打开文件时,准备写入的时候,其实文件已经被清空了
所以我们可以推导出,所有语言都对系统接口做了封装,C语言也对系统接口做了封装,对于其他语言而言,对于不同的系统接口的封装就类似于多态,也就是说,在最上层的接口调用都一样的,但是底层对于不同系统而言,其调用的结果是不一样的。C语言对系统接口的封装采用的是最直接的穷举所有的底层接口 + 条件编译做到的
为什么要封装?
- 因为原生系统接口,对于普通程序员而言,使用的成本比较高
- 而且如果直接使用系统接口的话,语言不具有跨平台性,不同系统的接口实现都是不一样的。
我们为什么要学习文件级别的系统接口?
因为万变不离其中,所有的语言,只要是文件操作,其最底层都会调用系统接口,只要我们将最本质的系统接口学习了,那么学习其他的语言最上层的调用就会得心应手了!
系统接口文件操作
open函数
O_RDONLY:以只读模式打开文件。
O_WRONLY:以只写模式打开文件。
O_RDWR:以读写模式打开文件。
上述三个宏如果文件不存在就会报错
O_CREAT:如果指定的文件不存在,则创建一个新的文件。如果文件已经存在,则不执行任何操作。需要指定文件的访问权限,
O_APPEND:在文件末尾追加;
O_TRUNC:将文件阶段清空
1 #include<stdio.h>
2 #define PRINT_A 0x1 // 0001
3 #define PRINT_B 0x2 // 0010
4 #define PRINT_C 0x4 // 0100
5 #define PRINT_D 0x8 // 1000
6 #define PRINT_DFL 0x0
7
8 //类比open函数
9 void Show(int flags)
10 {
11 if(flags & PRINT_A) printf("hello 我是A\n");
12 if(flags & PRINT_B) printf("hello 我是B\n");
13 if(flags & PRINT_C) printf("hello 我是C\n");
14 if(flags & PRINT_D) printf("hello 我是D\n");
15 if(flags == PRINT_DFL) printf("hello 我是Default\n");
16 }
17 int main()
18 {
19
20 printf("打印default\n");
21 Show(PRINT_DFL);
22 printf("打印A\n");
23 Show(PRINT_A);
24 printf("打印B\n");
25 Show(PRINT_B);
26 printf("打印A和B\n");
27 Show(PRINT_A | PRINT_B);
28 printf("打印C和D\n");
29 Show(PRINT_C | PRINT_D);
30 return 0;
31 }
~
这就是传宏标志的一种好的做法。
打开文件的系统调用接口有多重,但向文件写入的接口只有write
打开不存在的文件只能用三个参数的open
第三个参数表示文件的权限,打开已存在的文件用两个参数的open
#include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/stat.h>
5 #include<fcntl.h>
6 #include<string.h>
7
8 int main()
9 {
10
11 umask(0);//umask设置为0,以确定的方式打开文件
12 int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);//设置文件的基本权限
13 if(fd < 0) {
14 perror("open error");
15 return 1;
16 }
17 printf("fd: %d \n",fd);
18 int cnt = 0;
19 const char *str = "我是一个即将要被写入的缓冲区\n";
20 while(cnt < 5) {
21 write(fd, str, strlen(str));//此时strlen不用加1,因为\0是C语言独有的标志,与系统无关
22 ++cnt;
23 }
24 close(fd);
25 return 0;
将上述代码只改一部分运行后显示下面的情况
说明系统调用的W与C语言中的"w"是不一样的,C语言fopen的"w"
对应着系统调用的三个接口O_WRONLY | O_CREAT | O_TRUNC
eg
因此还是C语言较方便,同时C语言还有一些fopen打开方式是以格式化写入等所以系统接口的调用非常麻烦
read函数
umask(0);//umask设置为0,以确定的方式打开文件
12 // int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//设置文件的基本权限
13 int fd = open("log.txt", O_RDONLY);
14 if(fd < 0) {
15 perror("open error");
16 return 1;
17 }
18 printf("fd: %d \n",fd);
19 char buffer[128];
20 //因为read读取文件的第二个参数是不关心数据是什么类型的
21 //但是我们还是得把它当做字符串类型,所以在最后加一个'\0'
22 ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
23 if(s > 0) {
24 buffer[s] = '\0';
25 printf("%s\n",buffer);
26 }
理解文件描述符 fd
umask(0);//umask设置为0,以确定的方式打开文件
12
13 int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//设置文件的基本权限
14 int fda = open("loga.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//设置文件的基本权限
15 int fdb = open("logb.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//设置文件的基本权限
16 int fdc = open("logc.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//设置文件的基本权限
17 int fdd = open("logd.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//设置文件的基本权限
18 printf("fd: %5d\n",fd);
19 printf("fda: %5d\n",fda);
20 printf("fdb: %5d\n",fdb);
21 printf("fdc: %5d\n",fdc);
22 printf("fdd: %5d\n",fdd);
此时运行
两个问题
- 为什么从3开始,0,1,2呢?
下面我们验证一下文件描述符0,1,2就是标准输入标准输出标准错误。
//FILE*是结构体指针,在结构体指针一定能找到文件描述符
14 printf("stdin: %d\n",stdin->_fileno);
15 printf("stdout:%d\n",stdout->_fileno);
16 printf("stderr:%d\n",stderr->_fileno);
总结:C语言的接口一定封装了系统接口
2. 0,1,2,3,4,5…你见过什么数据是这个样子的?
数组下标
因为用的都是系统接口,所以这些数组下标也就是操作系统的返回值。
一个进程是可以打开多个文件的,所以进程 :文件 = 1:n
所以在系统中,有可能会存在大量的被打开的文件,OS需要对这些文件做管理,所以先描述在组织
此时又产生了一个问题:
stdin,stdout,stderr对应的分别是键盘,显示器,显示器这些都是硬件,也用上述的struct file来标识对应的文件吗?
我们可以在源码中证明上述体系结构的存在
所以所为的数组下标实在内核当中为该进程建立和对应文件的一种映射关系的一种方式