1.在系统角度理解文件
文件=内容+属性
1.1文件的所有操作:1.对内容操作 2.对属性操作;
C/C++程序,会默认打开三个文件流:标准输入、标准输出、标准错误;
Linux下一切皆文件
键盘、显示器可以被看作文件,但是我从来没有打开过键盘和显示器,依旧能够进行scanf、fgets、printf、cout………(映照了C/C++程序,会默认打开三个文件流:标准输入、标准输出、标准错误;)
标准输入(stdin 默认设备键盘)、标准输出(stdout、默认设备显示器)、标准错误(stderr、默认设备显示器);
2. 文件在磁盘(硬件)上放着,我们访问文件,先写代码->编译->exe->运行->访问文件:本质是谁在访问文件呢 答:进程在访问文件(进程是需要接口的)
2.1需要向硬件写入,只有操作系统有权利;普通用户也想写入,就必须让OS提供文件类的系统接口(系统接口比较难,所以语言上对这些接口做了封装,为了让接口更好的使用;导致了不同的语言,有了不同语言级别的文件访问接口(都不一样)但是,封装的系统接口是一样的)
2.2跨平台问题:基本上所有的语言都是跨平台的;如果语言不提供对文件的系统接口的封装,是不是所有的访问文件的操作,都必须直接使用OS的接口;答:是的
一旦使用系统接口,编写所谓的文件代码,就无法在其他平台上直接运行了,是不具备跨平台性的;语言级别上把所有平台的代码,都实现一遍,再条件编译,动态裁剪;来实现跨平台;
3.显示器是硬件吗?为什么往显示器上打印,不觉得奇怪?
其实,本质上也是一种写入,和往磁盘写入文件没有本质区别,只不过能直观看到罢了
4.文件:站在系统的角度,能够被input读取,或者output写出的设备叫做文件;
5.复习C语言所学的文件操作
5.1当每一个进程运行起来的时候,每个进程都会记录自己所处的工作路径;
5.2当前路径是怎样形成的:进程会使用cwd(current work directory)在底层做一个拼接,拼接想要创建(或打开的)文件名,形成当前路径
- #include
- #include
-
- int main(int argc,char* argv[]) //如果此时把这个文件改成mycat,就变成了一个命令行参数,打印其中的内容;
- {
- if (argc != 2)
- {
- printf("argv error!\n");
- return 1;
- }
- //FILE* fp = fopen("log.txt", "w"); //"w" 先把文件清空,再写入;
- 第一个参数可以带路径,不带路径就是当前路径 //第二个参数为要进行的方式“r”"w" "a"等
- //if (fp == NULL)
- //{
- // perror("fopen");//当返回失败时,打印函数的错误信息;
- // return 2;
- //}
- 进行操作;
- //const char* s1 = "hello fwrite\n";//要不要把\0写入(要不要+1) 答:不加1,\0结尾是c语言的规定,文件是不需要遵守的
- // //文件要保存的是有效数据 /0只是标定结尾的标识符,不算有效数据;不是字符串有效的内容
- //fwrite(s1,strlen(s1),1, fp);
-
- //const char* s2 = "hello fprintf\n";
- //fprintf(fp,"%s", s2);
-
- //const char* s3 = "hello fputs\n";
- //fputs(s3, fp);
-
- //fclose(fp);
-
- //FILE* fp = fopen("log.txt", "a"); //append 追加;不断往里面追加内容;
- //if (fp == NULL)
- //{
- // perror("fopen");
- // return 1;
- //}
- 进行操作;
- //const char* s1 = "hello fwrite\n";
- //fwrite(s1, strlen(s1), 1, fp);
-
- //const char* s2 = "hello fprintf\n";
- //fprintf(fp, "%s", s2);
-
- //const char* s3 = "hello fputs\n";
- //fputs(s3, fp);
-
- //fclose(fp);
-
- FILE* fp = fopen("argv[1]", "r"); //读
- if (fp == NULL)
- {
- perror("fopen");
- return 1;
- }
- //按行读取;
- char line[64];
- //fgets 是C语言提供的接口,s是string的缩写,会自动在字符结尾添加\0
-
- while (fgets(line, sizeof line - 1, fp) != NULL)
- {
- //printf("%s\n", line);
- fprintf(stdout, "%s", line); //stdout是啥;
- }
-
- fclose(fp);
- return 0;
- }
5.3复习C语言
fopen 以w方式打开文件,默认先清空文件(注意,在fwrite之前,就清空了)
fopen 以a方式打开文件,追加,不断向文件中新增内容;
演示了文件读和写的一般操作;
5.4 三个标准输入,输出流是什么? 答:标准输入对应的设备是键盘;标准输出对应的设备是显示器;标准错误对应的设备是显示器;
6.学习系统接口
6.1 open\close \read \write
6.2引子:
- #include<stdio.h>
- //#include<unistd.h>
- #include<string.h>
-
- #define ONE 0x1 //0000 0001
- #define TWO 0X2 //0000 0010
- #define THREE 0x4 //0000 0100
-
- void show(int flags)
- {
- if(flags & ONE) printf("hello one\n");
-
- if(flags & TWO) printf("hello two\n");
-
- if(flags & THREE) printf("hello three\n");
- }
- 使用类似位图的方式,来解决此问题
- int main()
- {
- show(ONE);
- show(TWO);
- show(ONE | TWO);
- show(ONE | TWO | THREE);
-
- return 0;
- }
6.3 open函数
- int open(const char *pathname, int flags);
-
- 由此flags的内部,为啥是各种宏定义就说明白了
- 返回值是int类型——返回的是 file descriptor;失败了返回-1;
file descriptor ——文件描述符;
这几个常量被定义在头文件fcntl.h中
fcntl
是 "file control" 的缩写。它是由 "file"(文件)和 "control"(控制)两个单词组合而成的。
int open(const char *pathname, int flags);
在介绍这几个常量之前,要先介绍一下open函数,因为这几个常量是用在open函数里面的
其中 pathname 是要打开的文件的路径名,
flags 用于指定打开文件的方式和行为,上面这几个常量就是用在这里的
mode 是一个 mode_t 类型的参数,用于指定创建新文件时的权限
介绍这几个常量:
O_CREAT:在文件打开过程中创建新文件
O_RDONLY:以只读方式打开文件。
O_WRONLY:以只写方式打开文件。
O_RDWR:以读写方式打开文件。
O_APPEND:在文件末尾追加数据,而不是覆盖现有内容。
O_TRUNC:如果文件已经存在,将其里面内容全部删干净。
O_EXCL:与 O_CREAT 一起使用时,如果文件已经存在,则 open() 调用将失败。
O_SYNC:使文件写操作变为同步写入,即将数据立即写入磁盘。
O_NONBLOCK:以非阻塞方式打开文件,即使无法立即进行读写操作也不会被阻塞。
//在应用层看到的一个很简单的动作,在系统接口层面甚至是OS层面,可能要做非常多的动作;
- #include<sys/types.h>
- #include<sys/stat.h>
- #include<fcntl.h>
-
- #include<stdio.h>
- #include<string.h>
-
- int main()
- {
- umask(0); //设置系统权限;
- //int fd = open("log.txt", O_WRONLY|O_CREAT,666); //三个参数,专门用来创建的
- //如果文件已经存在,那就只打开就行;
- int fd = open("log.txt", O_RDONLY);
- if (fd < 0)
- {
- perror("open");
- return 1;
- }
- //打开成功
- printf("open sucess,fd:%d\n", fd);
-
- //const char* s = "hello write\n";
- //write(fd, s, strlen(s));
- char buffer[64];
- memset(buffer, '\0', sizeof(buffer));
- read(fd, buffer, sizeof(buffer));
- printf("%s", buffer);
-
- close(fd);
- return 0;
- }
7.分析系统接口的细节,引入fd(文件描述符) ——理论为主;
- #include
- #include
- #include
-
- #include
- #include
-
- int main()
- {
- int fd1 = open("log1.txt", O_WRONLY | O_CREAT, 666);
- printf("open sucess fd: %d\n", fd);
- int fd2 = open("log2.txt", O_WRONLY | O_CREAT, 666);
- printf("open sucess fd: %d\n", fd);
- int fd3 = open("log3.txt", O_WRONLY | O_CREAT, 666);
- printf("open sucess fd: %d\n", fd);
- int fd4 = open("log4.txt", O_WRONLY | O_CREAT, 666);
- printf("open sucess fd: %d\n", fd);
-
- close(fd1);
- close(fd2);
- close(fd3);
- close(fd4);
-
- //验证三个标准流
- fprintf(stdout, "hello stdout\n"); //输出
- const char*s= "hello 1\n";
- write(1, s, strlen(s));
-
- int a = 10;
- fscanf(stdin, "%d", &a);
- printf("%d\n", a);
-
- char input[16];
- ssize_t s = read(0, input, sizeof(input));
- if (s > 0)
- {
- input[s] = '\0';
- printf("%s\n", input);
- }
-
- //证明
- printf("stdin:%d\n", stdin->_fileno);
- printf("stdout:%d\n", stdout->_fileno);
- printf("stderr:%d\n", stderr->_fileno);
-
- return 0;
- }
以上代码运行后,会产生几个问题?
7.1 0,1,2 去了哪里 答:C语言默认打开三个流 stdin(0)、stdout(1)、stderr(2)——默认是FILE*类型;—>内部绝对有fd—>怎样证明?
FILE 是一个结构体类型;C语言设计提供的结构体;结构体一般内部会有各种成员
C文件 库函数内部,一定会调用系统调用;——在系统角度认FILE?还是fd? 答:系统只认fd!所以FILE结构体里,必定封装了fd;
7.2 为什么是这样的数据
8.1进程要访问文件,必须要打开文件;一个进程可以打开多个文件;一般而言,进程:打开的文件 = 1:n;文件要被访问,前提是加载到内存中,才能被访问!如果是多个进程都打开自己的文件,系统中就会存在大量的被打开的文件!所以OS必须会把如此之多的文件管理起来(先描述,在组织;在内核中,如何看待打开的文件?OS内部要为了管理每一个被打开的文件,会构建 struct file结构体对象,充当一个被打开的文件;结构体中包含了一个被打开文件的几乎所有内容(不仅仅包含属性);)如果有很多,再用双链表组织起来;
8.2进程和文件的对应关系 答:PCB中有一个struct file* array[32] 指针数组,他指向了结构体struct file结构体对象;利用了哈希索引的原理,进程找文件变成了 PCB找数组,数组找结构体struct file对象,再进行操作;(Linux中PCB(task_struct进程控制块里存在一个结构体指针 struct files_struct* files 里面的变量有一个指针数组,指向了struct file结构体对象(那个文件)))
8.3 fd在内核层面上,本质上就是一个数组下标;可以看源代码来证明;
9.未来,如果是多平台跑,肯定是要使用语言级别的文件调用;但是学习系统级别的调用,可以帮助我们更好的理解这个语言级别的调用接口,更多的是为网络调用打好基础;因为网络调用时需要系统级别的文件调用;C语言/C++是没有网络接口的;
10文件:磁盘文件(没有被打开)内存文件(被进程在内存中打开)