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 // 00013 #define PRINT_B 0x2 // 00104 #define PRINT_C 0x4 // 01005 #define PRINT_D 0x8 // 10006 #define PRINT_DFL 0x07 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来标识对应的文件吗?
我们可以在源码中证明上述体系结构的存在