【linux】基础IO

news2024/11/27 19:51:08

文章目录

  • 一、复习文件相关知识
  • 二、复习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+可读可写,读取内容可以在文件任意地方读取,写入只能是追加在末尾写入
wbb是二进制的意思,以二进制的方式写入
rbb是二进制的意思,以二进制的方式读取

这里就说这么多,其他的操作有兴趣可以深入了解

操作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语言库函数系统接口调用
fopenopen
fcloseclose
fwritewrite
freadread
fseeklseek

任何语言的文件操作库函数都是对底层的系统调用接口进行封装

四、文件描述符

我们前面知道了
文件操作的本质:进程和被打开文件的关系

那么进程可以打开多个文件吗?肯定是可以的
既然进程可以打开多个文件,那么系统一定会存在大量被打开的文件
被打开的文件要不要被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就是虚拟文件系统在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/143049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】stack、queue和deque

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《吃透西嘎嘎》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;stack 的…

密集单目 SLAM 的概率体积融合

点击进入—>3D视觉工坊学习交流群笔者简述&#xff1a;这篇论文主要还是在于深度估计这块&#xff0c;深度估计由于硬件设备的不同是有很多方法的&#xff0c;双目&#xff0c;RGBD&#xff0c;激光雷达&#xff0c;单目&#xff0c;其中最难大概就是单目了。在该论文中作者…

Flutter不常用组件(四)

Offstage 创建一个在视觉上隐藏其子项的小部件。隐藏后不占空间。 该组件有以下几个属性&#xff1a; Key? key&#xff1a;标识键bool offstage true&#xff1a;是否隐藏。默认为trueWidget? child&#xff1a;子组件 Center(child: Column(mainAxisAlignment: MainAx…

【设计篇】36 # 如何理解可视化设计原则?

说明 【跟月影学可视化】学习笔记。 可视化设计的四个基本原则 基本原则一&#xff1a;简单清晰原则 我们可以看下面一张图&#xff1a;国际茶叶委员会制作的全球茶叶消费排行榜图表&#xff0c;目的是想说明喝茶最多的不是中国人 我们可以用更简单的直方图方式去表现 基本…

c++11 标准模板(STL)(std::deque)(九)

定义于头文件 <deque> std::deque 修改器 移除末元素 std::deque<T,Allocator>::pop_back void pop_back(); 移除容器的最末元素。 在空容器上调用 pop_back 是未定义的。 指向被擦除元素的迭代器和引用被非法化。尾后迭代器是否被非法化是未指定的。其他迭代…

YOLOv5-common.py文件

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 目录一、任务说明二、导入相关包和配置1.基本组件1.1 autopad1.2 Conv1.3 Focus1.4 Bottleneck1.5 BottleneckCSP1.6 C31.7 SPP1.8 Concat1.…

C3P0数据库连接池详解 及连接JDBC步骤

目录 一、基本定义 二、使用C3P0&#xff08;数据库连接池&#xff09;的必要性 1.JDBC传统模式开发存在的主要问题 三、数据库连接池的详细说明 四、使用连接池的明显优势 1.资源的高效利用 2.更快的系统反应速度 3.减少了资源独占的风险 4.统一的连接管理&#xff0c…

SQL优化实战-0001:SQL查找是否存在,不要再使用count

文章目录1.需求分析与普遍SQL写法2.问题分析3.优化方案4.总结5.补充5.1 还有什么时候可以使用LIMIT 15.2 什么时候没必要使用LIMIT 11.需求分析与普遍SQL写法 业务代码中&#xff0c;需要根据一个或多个条件&#xff0c;查询是否存在记录而不关心有多少条记录。普遍的SQL及代码…

大战谷歌!微软Bing引入ChatGPT;羊了个羊高·薪招纳技术人才;Debian彻底移除Python2;GitHub今日热榜 | ShowMeAI资讯日报

&#x1f440;日报合辑 | &#x1f3a1;AI应用与工具大全 | &#x1f514;公众号资料下载 | &#x1f369;韩信子 &#x1f3a1; 『微软Bing』将引入 ChatGPT&#xff0c;与 Google 一场大战难免 微软计划2023年3月底之前推出 Bing 搜索引擎的新版本&#xff0c;使用 ChatGPT …

Lua中self 、自索引及其面向对象应用代码示例

一、Lua表的self标识 在lua中&#xff0c;表拥有一个标识&#xff1a;self。self类似于c中的this指针和python中的self。在lua中&#xff0c;提供了冒号操作符来隐藏这个参数&#xff0c;例如&#xff1a; t1 {id 1, name "panda",addr "beijing" }-…

快过年了,看我使用python制作一个灯笼的小程序

哈喽呀&#xff0c;最近一直在写一些关于新年的的python小东西&#xff0c;今天也不例外&#xff0c;我把那些都放到了一个专辑里面了&#xff0c;感兴趣的朋友们可以去看看一下哦 目录 前言 画外轮廓 画灯笼的提线 画灯笼盖 画一下各种柱子 小小的外壳来一下 其他的小装饰…

Python打包(问题记录,待解决)

引言 文章用于测试在Python3.8的版本&#xff0c;打包Obspy地震包&#xff0c;最后集成到PyQt5上。 部署或冻结应用程序是 Python 项目的重要组成部分&#xff0c; 这意味着捆绑所有必需的资源&#xff0c;以便应用程序找到它需要的一切 能够在客户端计算机上运行。 但是&…

基于Python Unet的医学影像分割系统源码,含皮肤病的数据及皮肤病分割的模型,用户输入图像,模型可以自动分割去皮肤病的区域

手把手教你用Unet做医学图像分割 我们用Unet来做医学图像分割。我们将会以皮肤病的数据作为示范&#xff0c;训练一个皮肤病分割的模型出来&#xff0c;用户输入图像&#xff0c;模型可以自动分割去皮肤病的区域和正常的区域。废话不多说&#xff0c;先上效果&#xff0c;左侧…

JAVA语言基础语法——JVM虚拟机默认处理异常的方式,try...catch捕获异常

1.JVM默认的处理方式 a&#xff0e;把异常的名称&#xff0c;异常原因及异常出现的位置等信息输出在了控制台 运行结果如下&#xff1a; b&#xff0e;程序停止执行&#xff0c;异常下面的代码不会再执行了 2.try...catch捕获异常&#xff08;自己处理&#xff0c;捕获异常&am…

数学建模-相关性分析(Matlab)

注意&#xff1a;代码文件仅供参考&#xff0c;一定不要直接用于自己的数模论文中 国赛对于论文的查重要求非常严格&#xff0c;代码雷同也算作抄袭 如何修改代码避免查重的方法&#xff1a;https://www.bilibili.com/video/av59423231 //清风数学建模 一、基础知识 1.皮尔逊…

Qt之Json操作demo

一、JSON简介&#xff1a; JSON(JavaScript Object Notation)是一种轻量级的数据交换格式&#xff0c;使用JavaScript语法来描述数据对象&#xff0c;但是JSON仍然独立于语言和平台。JSON解析器和JSON库支持许多不同的编程语言&#xff0c;被广泛用于Internet上的数据交换格式。…

多线程高级(线程状态、线程池、volatile、原子性、并发工具)

1.线程池 1.1 线程状态介绍 当线程被创建并启动以后&#xff0c;它既不是一启动就进入了执行状态&#xff0c;也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢&#xff1f;Java中的线程 状态被定义在了java.lang.Thread.State…

Java程序:jstack

前言 如果有一天&#xff0c;你的Java程序长时间停顿&#xff0c;也许是它病了&#xff0c;需要用jstack拍个片子分析分析&#xff0c;才能诊断具体什么病症&#xff0c;是死锁综合征&#xff0c;还是死循环等其他病症&#xff0c;本文我们一起来学习jstack命令~ jstack 的功能…

阳康后的第一篇博客,先来几道恶心二进制编程题

目录 一、统计二进制中1的个数 二、打印整数二进制的奇数位和偶数位 三、两个整数二进制位不同个数 一、统计二进制中1的个数 这是一道牛客网OJ题&#xff0c;感兴趣的话可以先做一遍再看解析哦 -> 牛客网的OJ链接 注意&#xff1a;上面的牛客网是接口型&#xff0c;不需…

Vagrant管理已存在的虚拟机

起因 某天打开VirtualBox后&#xff0c;发现之前创建的虚拟机都没了&#xff0c;后将虚拟机从本地磁盘又重新导入&#xff0c;但是发现使用 vagrant up 会创建新的虚拟机&#xff0c;而我要用vagrant管理已存在的虚拟机&#xff0c;就是 vagrant up的时候&#xff0c;我需要启动…