文章目录
- 一、复习文件相关知识
- 二、复习C文件相关操作
- 1、复习知识点
- 2、复习操作
- 三、文件的系统调用接口
- 1、open
- 2、write
- 3、read
- 小结
- 四、文件描述符
- 1、初步认识
- 2、两个问题
- 知识点
- 3、文件描述符的分配规则
- 五、重定向
- 1、 dup2函数
- 2、myshell里面实现重定向功能
- 3、知识点
- 六、如何理解linux一切皆文件
一、复习文件相关知识
我们前面已经学习过文件方面的知识了,接下来就进行一下总结:
1、空文件在磁盘也是要占用空间的(空文件也是有属性信息的,只不过空文件内容为0字节罢了)
2、文件=内容+属性
3、文件操作=对内容操作+对属性操作+对内容和属性的操作
4、标定一个文件必须使用:文件路径+文件名,因为具有唯一性
5、没有指明对应文件路径,默认在当前路径进行文件访问。当前路径:进程的工作目录(进程从根目录到当前所在位置的路径)
6、我们前面学习C语言的时候,学习了fopen、fclose、fread、fwrite等接口,当我们写完这些代码之后,进行编译就形成了二进制可执行程序。但是,如果我们没有运行代码,文件对应的操作就没有被执行。所以,对文件的操作,本质上就是:对进程的操作
7、一个文件没有被打开,可以直接进行文件访问吗?肯定是不能的。一个文件要被访问,必须先被打开!(操作就是用户进程+OS)
8、磁盘那么多文件,是不是所有文件都被打开了呢?这也肯定不是的。
文件也就分为两种:1、被打开的文件;2、没有被打开的文件
综合上面几点结论,代码得出一个最终结论:
文件操作的本质:进程和被打开文件的关系
二、复习C文件相关操作
1、复习知识点
我们既然学习了C语言和C++语言,那么应该可以想到,两个语言都有对应的文件操作,但是操作的函数接口不一样。不知如此,java、php、python、go等等语言都有自己对应的文件操作接口,也就是说:每种语言的文件操作都不一样
接下来我们就来仔细分析:
文件在哪里呢?
在磁盘上面
磁盘属于硬件
所以,磁盘是归OS(操作系统)管理的
那么所有人想要访问磁盘/文件就都不能绕过OS
那么,想要对文件进行操作,就必须使用OS提供的接口,也磁盘/文件归OS管理
OS为了方便磁盘/文件被访问,就提高了文件级别的系统调用接口
可是,语言有很多种,但操作系统只有一个。每个语言的底层都是操作系统
所以得出结论:
上层语言不管怎么变化
1、库函数底层必须调用系统调用接口
2、库函数可以千变万化,但是底层不会改变
所以,我们想要学好文件操作,将来快速了解任何一门语言,就要学好底层的知识,我们学习不会改变的知识,以不变应万变
2、复习操作
这里我就先用表格将操作列举出来:
操作符 | 作用 |
---|---|
w | 向文件写入内容,文件不存在会自动创建文件。w单纯的打开文件不写入时,C语言是会清空原文件的全部内容 |
w+ | 可读可写,但是不会创建文件 |
r | 读取文件内容,文件必须存在,否则报错 |
r+ | 可读可写,但是不存在报错 |
a | 在文件原有内容的末尾追加新的内容,文件不存在会自动创建 |
a+ | 可读可写,读取内容可以在文件任意地方读取,写入只能是追加在末尾写入 |
wb | b是二进制的意思,以二进制的方式写入 |
rb | b是二进制的意思,以二进制的方式读取 |
这里就说这么多,其他的操作有兴趣可以深入了解
操作1:“w“创建不存在文件
1 #include <stdio.h>
2 int main()
3 {
4 FILE* fp=fopen("log.txt","w");
5 if(fp==NULL)
6 {
7 perror("fopen");
8 return 1;
9 }
10 fclose(fp); //我没做任何处理,直接关闭
11 return 0;
12 }
操作2:”w“向文件写入
1 #include <stdio.h>
2 int main()
3 {
4 FILE* fp=fopen("log.txt","w");
5 if(fp==NULL)
6 {
7 perror("fopen");
8 return 1;
9 }
10 int cnt=5;
11 while(cnt)
12 {
13 fprintf(fp,"%s:%d\n","hello!",cnt--); //向文件写入
15 }
16 fclose(fp);
17 return 0;
18 }
操作3:”r“读取文件内容
1 #include <stdio.h>
2 #include <string.h>
3 int main()
4 {
5 FILE* fp=fopen("log.txt","r");
6 if(fp==NULL)
7 {
8 perror("fopen");
9 return 1;
10 }
11 char buffer[64];
12 while(fgets(buffer,sizeof(buffer)-1,fp)!=NULL)//因为fgets是C语言的库函数,其中的s表示string字符串,所以文件内容的结尾会有\0,这里-1去掉\0
13 {
14 buffer[strlen(buffer)-1]=0;//这里把buffer的最后换行置0,因为puts可能打印的时候带了\n,而这里再加一个\n就会多空处一行
15 puts(buffer);
16 }
17 }
操作4:”a“向文件追加内容
1 #include <stdio.h>
2 int main()
3 {
4 FILE* fp=fopen("log.txt","a"); //因为log.txt文件存在的原因,这里直接把w改成a就行了
5 if(fp==NULL)
6 {
7 perror("fopen");
8 return 1;
9 }
10 int cnt=5;
11 while(cnt)
12 {
13 fprintf(fp,"%s:%d\n","hello!",cnt--);
15 }
16 fclose(fp);
17 return 0;
18 }
那么,C语言的操作就复习到这里了,接下来我们就学习新内容了
三、文件的系统调用接口
1、open
我们已经知道了,上面C语言使用的库函数接口是封装底层系统调用接口的,那么我们就来学习一下底层的系统调用接口
C语言的fopen就是再open的基础之上进行封装得来的
大部分文件的系统调用接口,所需要的头文件都是这三个
open作用:打开或者创建一个文件设备
三个参数
pathname:文件名/路径->只有文件名就是默认当前路径
flags:文件操作所需要对应的宏操作选项(下面仔细讲),按标记位传参
mode:文件的权限
接下来看看返回值:
这里就要提出新概念:
文件描述符
:文件描述符是一个整数,文件操作成功就返回文件描述符,失败就返回-1
这个下面也会仔细讲
接下来我们就仔细研究一下flags:
上面的三个是flags最常用的三个宏选项,分别是:可读,可写,即可读又可写。下面的宏选项操作我们用到再讲
接下来我们先不用open选项,先来了解一个新内容——标记位
C语言传标记位,是一个int表示一个标记位。但是,int有32个bit位,那么可以通过比特位来传递选项,达到不同的目的
注意:一个比特位对应一个选项。而且比特位的位置不能重复
举例:
7 // 我没有指明路径
8 #define FILE_NAME(number) "log.txt"#number
9
10 // 每一个宏,对应的数值,只有一个比特位是1,彼此位置不重叠
11 #define ONE (1<<0) //0x1 这种注释的16进制宏定义的方法是系统内部实现的方法,而许多人认为这样不如位操作方法方便
12 #define TWO (1<<1) //0x2
13 #define THREE (1<<2) //0x4
14 #define FOUR (1<<3) //0x8
15
16 void show(int flags)
17 {
18 if(flags & ONE) printf("one\n");
19 if(flags & TWO) printf("two\n");
20 if(flags & THREE) printf("three\n");
21 if(flags & FOUR) printf("four\n");
22 }
23 int main()
24 {
25 show(ONE);
26 printf("-----------------------\n");
27 show(TWO);
28 printf("-----------------------\n");
29 show(ONE | TWO);
30 printf("-----------------------\n");
31 show(ONE | TWO | THREE);
32 printf("-----------------------\n");
33 show(ONE | TWO | THREE | FOUR);
34 printf("-----------------------\n");
35 }
接下来就来使用open系统调用接口
我们先采用open的两个参数方法,先不考虑mode参数
1 #include <stdio.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <assert.h>
8 int main()
9 {
10 int fd=open("log.txt",O_WRONLY); //O_WRONLY表示以只写的方式进行文件操作
11 assert(fd!=-1);
12 (void)fd;//防止警告
13 close(fd);
14 return 0;
15 }
我们先删除log.txt文件,然后来看看结果:
我们可以看到,open居然没有创建文件,为什么会这样呢?我们以只写的方式进行文件操作不应该创建文件吗?
其实,底层如果是这样实现的话,是不会创建文件的,C语言能够只读创建是做了封装的!
我们想要系统调用也能够创建文件,就需要加点东西了
1 #include <stdio.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <assert.h>
8 int main()
9 {
10 int fd=open("log.txt",O_WRONLY | O_CREAT);//O_WRONLY表示以只写的方式进行文件操作 | O_CREAT表示文件不存在自动创建
11 assert(fd!=-1);
12 (void)fd;//防止警告
13 close(fd);
14 return 0;
15 }
文件的权限是乱的!!!
所以,我们在创建文件的时候,必须指定文件权限,不然很危险!!!
1 #include <stdio.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <assert.h>
8 int main()
9 {
10 int fd=open("log.txt",O_WRONLY | O_CREAT,0666);//O_WRONLY表示以只写的方式进行文件操作 | O_CREAT表示文件不存在自动创建
11 assert(fd!=-1);
12 (void)fd;//防止警告
13 close(fd);
14 return 0;
15 }
这里我们就进行uamsk的更改:
1 #include <stdio.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <assert.h>
8 int main()
9 {
10 umask(0);
11 int fd=open("log.txt",O_WRONLY | O_CREAT,0666);//O_WRONLY表示以只写的方式进行文件操作 | O_CREAT表示文件不存在自动创建
12 assert(fd!=-1);
13 (void)fd;//防止警告
14 close(fd);
15 return 0;
16 }
晤,做了这么多的准备工作,那么我们就开始用open系统调用接口进行写入了
2、write
终于可以写入了,我们来看看这个函数的文档:
老规矩,三个参数
fd:我们向哪一个文件写
buf:我们写入的时候,缓存区数据在哪里
count:缓存区中,数据的字节个数
返回值其实就是count的值,但是我们这里不考虑返回值,后面的章节再来讲这个ssize_t
注意:buf的返回值是void*类型的。我们前面说的文本类和二进制类是C语言提供给我们的;而系统可不管什么文本,二进制,在操作系统看来,都是二进制!!!
1 #include <stdio.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <assert.h>
8 int main()
9 {
10 umask(0);
11 int fd=open("log.txt",O_WRONLY | O_CREAT,0666);//O_WRONLY表示以只写的方式进行文件操作 | O_CREAT 表示文件不存在自动创建
12 assert(fd!=-1);
13 (void)fd;//防止警告
14 char buffer[64];//我们自己定义的缓存区
15 int cnt=5;
16 while(cnt)
17 {
18 sprintf(buffer,"%s:%d\n","hello:",cnt--);//sprintf把数据格式化成字符串
19 write(fd,buffer,strlen(buffer));//系统调用接口,这里不能+1带上\0!!!我们要都就是字符串有效内容,不需要\0
19 //这是文件,不是C语言,C语言的规则关我文件什么事,这样做会产生乱码等错误
20 }
21 //printf("fd:%d\n",fd);
22 close(fd);
23 return 0;
24 }
我们继续
接下来我们在换内容打印:
打印aaaaa
这里居然还有上次剩下的数据。说明了open函数并没有帮我们清空文件内部,也就是说:C语言不仅仅帮我们创建文件,还帮我们清空了内容
那么我们系统调用函数也想清空内容也得加点内容了:
1 #include <stdio.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <assert.h>
8 int main()
9 {
10 umask(0);
11 int fd=open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
11 //O_WRONLY表示以只写的方式进行文件操 | O_CREAT表示文件不存在自动创建 | O_TRUNC对文件内容做清空处理
12 assert(fd!=-1);
13 (void)fd;//防止警告
14 char buffer[64];//我们自己定义的缓存区
15 int cnt=5;
16 while(cnt)
17 {
18 sprintf(buffer,"%s:%d\n","aaaaa",cnt--);//sprintf把数据格式化成字符串
19 write(fd,buffer,strlen(buffer));
20 }
21 //printf("fd:%d\n",fd);
22 close(fd);
23 return 0;
24 }
所以,我们C语言一个简简单单的”w“选项,底层操作系统做了O_WRONLY | O_CREAT | O_TRUNC等等操作
我们已经完成了写入操作,那么接下来就趁热打铁,学习一下怎么追加内容:
接下来就是读取操作了
3、read
这里的参数就不多介绍了,和上面的write参数意义是一样的
1 #include <stdio.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <assert.h>
8 int main()
9 {
10 umask(0);
11 int fd = open("log.txt","O_RDONLY");
12 assert(fd!=-1);
13 void(fd);
14 char buffer[1024];
15 ssize_t ret = read(fd,buffer,sizeof(buffer)-1);
16 if(ret > 0) buffer[ret]=0;//设置\0
17 printf("%s",buffer);
18 close(fd);
19 return 0;
20 }
小结
C语言库函数 | 系统接口调用 |
---|---|
fopen | open |
fclose | close |
fwrite | write |
fread | read |
fseek | lseek |
… | … |
任何语言的文件操作库函数都是对底层的系统调用接口进行封装
四、文件描述符
我们前面知道了
文件操作的本质:进程和被打开文件的关系
那么进程可以打开多个文件吗?肯定是可以的
既然进程可以打开多个文件,那么系统一定会存在大量被打开的文件
被打开的文件要不要被OS管理呢?也是要的
怎么管理呢?先描述,再组织
所以,OS为了管理对应的打开文件,必须要为打开文件创建对应的内核数据结构,这个内核数据结构就是struct file{}
,这个结构体包含了文件的大部分属性
1、初步认识
前面我们打开一个进程的时候,打印fd的值是3,我们在来仔细研究:
1 #include <stdio.h>
2 #include <string.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <assert.h>
8 int main()
9 {
10 umask(0);
11 int fd0=open("log0.txt",O_WRONLY|O_CREAT);
12 int fd1=open("log1.txt",O_WRONLY|O_CREAT);
13 int fd2=open("log2.txt",O_WRONLY|O_CREAT);
14 int fd3=open("log3.txt",O_WRONLY|O_CREAT);
15 printf("fd0:%d\n",fd0);
16 printf("fd1:%d\n",fd1);
17 printf("fd2:%d\n",fd2);
18 printf("fd3:%d\n",fd3)
19 }
2、两个问题
问题1:文件描述符fd从3开始,一直是连续的小整数,但是0、1、2去哪里了呢?
问题2:文件描述符fd从3开始,一直是连续的小整数,我们学习的知识中,只有数组的下标是连续的小整数,fd是数组下标吗?
我们依次来解决,先来看问题1:为什么fd的值从3开始,而不是0
我们前面学习过,操作系统会打开三个默认的输入输出流:
stdin:标准输入->键盘
stdout:标准输出->显示器
stderr:标准错误->显示器
知识点
我们C语言文件操作用的是FILE*是一个结构体
而底层函数接口调用用的是文件描述符
我们又知道C语言库函数在底层上面
所以得出结论:C语言的FILE*结构体内部一定有一个字段是底层的文件描述符,因为库函数是对系统接口调用的封装,既然系统调用接口用了文件描述符,那么上层一定要调用文件描述符
这三个文件被默认打开,那么会不会是这三个文件占用了0、1、2文件描述符呢?
我们来看看:
printf("stdin->fd: %d\n", stdin->_fileno);//_fileno可以拿到我们想要的文件描述符
printf("stdout->fd: %d\n", stdout->_fileno);
printf("stderr->fd: %d\n", stderr->_fileno);
所以,综上所述:
OS默认打开了三个标准输入输出流文件,这三个文件占用这文件描述符0、1和2。我们在不动这三个文件描述符/文件的前提下,创建任何文件的文件描述符都是从3开始的,一直递增
接下来,我们来解决问题2
解决上面的问题1之后,我们对问题2有了另外的疑惑:为什么文件描述符是从0开始的呢?
我们知道了文件操作是:进程与被打开文件的关系
那么进程是要创建task_struct(进程控制块),所以task_struct里有一个struct files_struct* files
指针,这个指针指向一个struct files_struct
结构体,这个结构体是专门构成进程和结构对应关系的结构体
而struct files_struct结构体里面有一个struct file* fd_array[]
的数组(也叫做:文件描述符表),这个指针数组里面存放着指针,从数组下标0开始,数组的每一个下标位置存放着一个文件的地址。因为标准输入输出流被打开,所以数组下标0、1、2的里面存放的就是这三个流的地址。
这也证明了,文件描述符的本质就是:数组的下标
。所以,我们创建的新文件,默认文件描述符是3,因为3表示struct file* fd_array[]数组下标为3的位置,数组的这个位置就存放着我们新创建的文件。每创建一个文件,文件描述符就对应跟着变化
3、文件描述符的分配规则
我们直接来举几个例子,任何通过例子观察出结论:
正常情况下,就是上面的情况,但是,如果我们关闭0、1、2这三个文件描述符对应的文件,也就是关闭那三个输入输出流会怎么样呢?我们来看看:
所以我们可以总结出文件描述符的分配规则:
文件描述符分配规则:数组从小到大,按照顺序寻找最小的,并且没有被占用的位置(下标)
接下来我们再来看看特殊情况:
这里很好理解:
五、重定向
上面的例子中,本来应该打印到显示器的内容,却打印到了文件里面,这叫做输出重定向
这就是重定向操作,我们接下来就来学习一下重定向操作
在此之前,先来学习/总结前面的知识点
1、重定向的分类:输出重定向(>);输入重定向(<);追加重定向(>>);
2、重定向的本质:上层的文件描述符不变,在内核(文件描述符表)中更改文件描述符对应位置中存放的struct file* 地址
在学习重定向之前,我们先来学习一下一个函数
1、 dup2函数
我们上面先关闭,再创建新文件的操作是不是太麻烦了呢?
dup2函数就是来简化这个操作的!
先来看文档:
makes newfd be the copy of oldfd, closing newfd first if necessary
int dup2(int oldfd, int newfd);
我们结合上面的例子来仔细看看这个函数:
dup2有两个参数,一个oldfd,一个newfd。其中前面的makes newfd be the copy of oldfd这句话的意思是:
newfd拷贝oldfd。结合我们上面的例子来看,我们要想让文件描述符1不指向stdout,而是指向我们的新建文件。所以:fd=1->新建文件,那么就是把fd的内容拷贝到1里面去(注意不能弄反了),所以,oldfd就是fd,而newfd就是1
我们还可以更加简单的理解为:
newfd和oldfd不管谁拷贝谁,最终剩下来的是oldfd。而我们上面的例子中,代码最后的结果就是fd和1的内容都是fd的,1也指向了fd指向的内容。所以oldfd就是上面的fd
更改上面例子的代码,采用dup2函数
这就是输出重定向
接下来我们来分别看看追加重定向和输入重定向
追加重定向:
可以看到,追加重定向和我们的文件描述符没有关系,和文件描述符指向的文件(对象)也没有关系,只是和我们的打开方式有关系
接下来看看输入重定向:
我们前面写过一个myshell,接下来再myshell里面实现我们的重定向功能
2、myshell里面实现重定向功能
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <ctype.h>
6 #include <sys/types.h>
7 #include <sys/wait.h>
8 #include <assert.h>
9 #include <fcntl.h>
10
11 #define NUM 1024
12 #define OPT_NUM 64
13
14 #define NONE_R 0//默认没有重定向
15 #define INPUT_R 1//输入重定向标记
16 #define OUTPUT_R 2
17 #define APPEND_R 3
18
19 int rtype = NONE_R;//默认没有重定向
20 char* rfile = NULL;//重定向文件名称
21
22 char lineCommand[NUM];
23 char *myargv[OPT_NUM]; //指针数组
24 int lastCode = 0;
25 int lastSig = 0;
26
27 #define jumpspace(start) do{ while(isspace(*start)) ++start; }while(0)//这里不带分号,因为这个宏我们可以直接拿来做if语句的判断条件
28 void commandCheak(char* ck)
29 {
30 assert(ck!=NULL);
31 char* start = ck;
32 char* end = ck + strlen(ck);//这里不用在最后加1,假设有4个字符,下标4就是最后的\0
33 while(start!=end)
34 {
35 if(*start == '>')
36 {
37 *start='\0';
38 ++start;
39 if(*start=='>')
40 {
41 rtype=APPEND_R;
42 ++start;//追加重定向要再加一次
43 }
44 else
45 {
46 rtype=OUTPUT_R;
47 }
48 jumpspace(start);
49 rfile=start;
50 break;
51 }
52 else if(*start == '<')
53 {
54 *start='\0';
55 ++start;
56 jumpspace(start);
57 rtype = INPUT_R;
58 rfile = start;
59 break;
60 }
61 else
62 {
63 ++start;
64 }
65 }
66 }
67 int main()
68 {
69 while(1)
70 {
71 rtype=NONE_R;
72 rfile=NULL;
73 // 输出提示符
74 printf("用户名@主机名 当前路径# ");
75 fflush(stdout);
76
77 // 获取用户输入, 输入的时候,输入\n
78 char *s = fgets(lineCommand, sizeof(lineCommand)-1, stdin);
79 assert(s != NULL);
80 (void)s;
81 // 清除最后一个\n , abcd\n
82 lineCommand[strlen(lineCommand)-1] = 0; // ?
83 //printf("test : %s\n", lineCommand);
84
85 // "ls -a -l -i" -> "ls" "-a" "-l" "-i" -> 1->n
86 commandCheak(lineCommand);//将一个字符串分隔为两个字符串
87 // 字符串切割
88 myargv[0] = strtok(lineCommand, " ");
89 int i = 1;
90 if(myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
91 {
92 myargv[i++] = (char*)"--color=auto";
93 }
94
95 // 如果没有子串了,strtok->NULL, myargv[end] = NULL
96 while(myargv[i++] = strtok(NULL, " "));
97
98 // 如果是cd命令,不需要创建子进程,让shell自己执行对应的命令,本质就是执行系统接口
99 // 像这种不需要让我们的子进程来执行,而是让shell自己执行的命令 --- 内建/内置命令
100 if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
101 {
102 if(myargv[1] != NULL) chdir(myargv[1]);
103 continue;
104 }
105 if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
106 {
107 if(strcmp(myargv[1], "$?") == 0)
108 {
109 printf("%d, %d\n", lastCode, lastSig);
110 }
111 else
112 {
113 printf("%s\n", myargv[1]);
114 }
115 continue;
116 }
117 // 测试是否成功, 条件编译
118 #ifdef DEBUG
119 for(int i = 0 ; myargv[i]; i++)
120 {
121 printf("myargv[%d]: %s\n", i, myargv[i]);
122 }
123 #endif
124 // 内建命令 --> echo
125
126 // 执行命令
127 pid_t id = fork();
128 assert(id != -1);
129
130 if(id == 0)
131 {
132 switch(rtype)//进行重定向处理!
133 {
134 case NONE_R:
135 break;
136 case INPUT_R:
137 {
138 int fd = open(rfile,O_RDONLY);
139 assert(fd!=-1);
140 dup2(fd,0);
141 }
142 break;
143 case OUTPUT_R:
144 case APPEND_R:
145 {
146 int flags = O_WRONLY | O_CREAT;
147 if(rtype==APPEND_R) flags |= O_APPEND;
148 else flags |= O_TRUNC;
149 int fd = open(rfile,flags);
150 assert(fd!=-1);
151 dup2(fd,1);
152 }
153 break;
154 default:
155 printf("Is bug!!!\n");
156 break;
157 }
158 execvp(myargv[0], myargv);
159 exit(1);
160 }
161 int status = 0;
162 pid_t ret = waitpid(id, &status, 0);
163 assert(ret > 0);
164 (void)ret;
165 lastCode = ((status>>8) & 0xFF);
166 lastSig = (status & 0x7F);
167 }
168 }
3、知识点
第一点:
因为进程独立性的原因,子进程和父进程的操作互不影响,所以,子进程处理要拷贝父进程的pcb以外,还要拷贝父进程里面的文件描述符表!
然而文件不属于进程,所以子进程拷贝父进程不关文件什么事,因为文件不属于文件,文件属于文件系统。文件是被父子进程共享的!
第二点:
程序进程替换是不会影响曾经进程打开的重定向文件。文件描述符之前指向什么文件,程序进程替换之后还是指向什么文件,因为替换只是替换物理地址上面的代码和数据,与我pcb和files_struct无关。
六、如何理解linux一切皆文件
我们学linux的时候就常常听说linux一切皆文件,那么如何理解呢?我们怎么能够看到细节呢?
接下来我们就来综合前面内容来分析一下:
我们前面学习计算机的软硬件体系结构是时候,知道了计算机内部的结构是什么样的:
那么,今天就通过这张图来研究一下为什么linux一切皆文件
硬件部分有许多的硬件,比如磁盘、网卡、键盘、显示器等等。我们向这些硬件读取数据或者写入数据都方法都不一样,这是肯定的。而我们想要使用硬件就必须调用对应硬件的设备驱动,这个驱动层里面有这对应硬件的结构体,驱动层通过每一个硬件的结构体来管控对应的硬件,从而方便我们操作硬件。这个硬件结构体里面有读取函数和写入函数等对硬件操作的函数,那么我们想要对硬件做任何操作,就要通过驱动层对应的硬件结构体内部的函数来完成我们的操作
在驱动层上面就是操作系统层面了,OS里面有一个struct file
结构体,这个结构体里面存放在各种文件的属性,比如:文件的空间大小,文件描述符…不仅仅如此,这个文件的结构体里面还有驱动层每一个硬件结构体内部操作(read读、write写等等)函数的函数指针,通过struct file内部的函数指针可以直接调用驱动层硬件结构体内部的函数。这也就是说:OS不用管驱动层、硬件层是怎么样实现的,只需要调用OS自己内部struct file中对应的函数指针,就能够调用起来硬件,从而帮助我们进行任何的硬件操作。
所以,OS不管你底层是怎么实现的,他只需要struct file这个文件结构体就能够完成对底层硬件的调用
站在OS的视角来看,驱动设备和硬件都是struct file结构体(因为OS通过该结构体就完成了所有操作),这也就是为什么linux中一切皆文件!
图中的vfs就是
虚拟文件系统