【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)

文章目录

    • 什么是当前路径?
    • 重新谈论文件
      • 文件操作
    • 系统文件I/O
      • Open
      • write
    • 文件描述符fd


什么是当前路径?

编写代码并运行:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   while(1)
  7   {
  8     sleep(1);                                                         
  9     printf("我是一个进程: %d\n",getpid());
 10   }
 11   return 0;
 12 }

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第1张图片

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第2张图片
exe代表的是当前进程执行的是磁盘的可执行程序。

cwd叫做当前进程的current working directory,表示当前进程所在的工作目录。

系统中有一个可以更改目录的方法,chdir,谁调用chdir,就更改谁的当前工作目录。
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第3张图片
原目录:

[likaixuan1@VM-4-13-centos 20230404-基础IO]$ pwd
/home/likaixuan1/106/20230404-基础IO
[likaixuan1@VM-4-13-centos 20230404-基础IO]$ 

改改代码:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6  chdir("/home/likaixuan1");                                            
  7   while(1)                     
  8   {          
  9     sleep(1);
 10     printf("我是一个进程: %d\n",getpid());
 11   }          
 12   return 0;  
 13 }    

编译运行:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第4张图片

输入指令:[likaixuan1@VM-4-13-centos 20230404-基础IO]$ ls /proc/32714 -al

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第5张图片
当前进程的cwd变成了/home/likaixuan1,当前工作目录被修改了。

补充问题:
为什么我们自己写的shell,cd的时候,路径没有变化?

fork创建子进程,子进程有自己的工作目录,执行cd更改的是子进程的目录,子进程执行完毕继续用的是父进程即shell,子进程改了和父进程没有关系,我们看到的是父进程。

重新谈论文件

  1. 空文件也要在磁盘中占空间。
  2. 文件 = 内容 + 属性。
  3. 文件操作 = 对内容 + 对属性 or 对内容和属性。
  4. 标定一个文件,必须使用:文件路径 + 文件名。(唯一性)
  5. 如果没有指明对应的文件路径,默认是在当前路径进行文件访问。
  6. 当我们把fopen、fclose、fread、fwrite等接口写完之后,代码编译之后,形成二进制可执行程序之后,但是没运行,文件对应的操作没有执行。对文件的操作本质是:进程对文件的操作
  7. 一个文件没有被打开,不可以直接执行文件访问。(一个文件要被访问,就必须先被打开)被用户进程(调用接口)和操作系统OS(实际的打开文件)打开。还有就是不是所有的磁盘文件都被打开。文件在应用层面可以分两类:1.被打开的文件。2.没有被打开的文件。所以进一步得出结论,文件操作的本质:进程和被打开文件的关系

文件操作

文件在磁盘,磁盘是硬件,只有操作系统可以管理,那么所有的人想要访问磁盘就不能跨过操作系统,必须得使用操作系统提供的文件级别的系统调用的接口。操作系统只有一个,所以上层语言无论如何变化,a.库函数底层必须调用系统调用接口。b.库函数可以千变万化,但底层不变。

r+:读写(不存在就出错)。
w+:读写(不存在就创建)。
a:追加(append),写在文件末尾。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 
  5 #define FILE_NAME "log.txt"
  6 int main()
  7 {
  8   FILE *fp = fopen(FILE_NAME,"w");
  9   if(NULL == fp)
 10   {
 11     perror("fopen");
 12     return 1;
 13   }
 14 
 15   fclose(fp);                                                                
 16 
 17 }

编译运行,我们发现创建了log.txt.文件。
运行前:
在这里插入图片描述
运行后:

在这里插入图片描述

此时文件大小为0,也就证明了w就是写入,但是文件不存在自动会创建。
现在我们想往里面写入5句话,用fprintf()接口。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 
  5 #define FILE_NAME "log.txt"
  6 int main()
  7 {
  8   FILE *fp = fopen(FILE_NAME,"w");
  9   if(NULL == fp)
 10   {
 11     perror("fopen");
 12     return 1;
 13   }
 14 
 15   int cnt = 5;
 16   while(cnt)
 17   {
 18     fprintf(fp,"%s:%d\n","hello                             
 19 }      

运行然后cat,我们就看到文件内容写到文件里了。
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第6张图片

我们也可以以r方式打开文件,这个叫做读取,

修改代码:

    1 #include <stdio.h>
    2 #include <unistd.h>                                                     
    3 
    4 
    5 #define FILE_NAME "log.txt"
    6 int main()
    7 {                                                                       
    8   FILE *fp = fopen(FILE_NAME,"w");
    9   if(NULL == fp)
   10   {
   11     perror("fopen");
   12     return 1;
   13   }                      
   14 
   15   char buffer[64];                            
   16   while(fgets(buffer,sizeof(buffer)-1,fp) != NULL)
   17   {
   18     puts(buffer);//把读的字符串,显示到显示器上。                           
   19   }                                
   20                                                         
   21   fclose(fp);                                   
   22 }   

我们使用fgets,表示以行为单位,从特定的文件当中读取数据,将读到的数据放到s所指向的缓冲区的当中。
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第7张图片
运行程序,就可以按照文本行的方式把文件读上去了:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第8张图片

我们以a方式打开,继续修改代码:

  1 #include <stdio.h>  
  2 #include <unistd.h>  
  5 #define FILE_NAME "log.txt"  
  6 int main()  
  7 {  
 10   FILE *fp = fopen(FILE_NAME,"a");
 11   if(NULL == fp)
 12   {
 13     perror("fopen");
 14     return 1;
 15   }
 23   int cnt = 5;
 24   while(cnt)
 25   {
 26     fprintf(fp,"%s:%d\n","hello file",cnt--);
 27   }                                                                          
 28   fclose(fp);                     
 36 }                                

运行程序:

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第9张图片

这就叫做追加。

注意细节:以w方式单纯打开文件,c语言会自动清空内部的数据

系统文件I/O

Open

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问。
我们在系统当中实际上用fopen打开文件底层调用的系统调用接口是open
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第10张图片
头文件是:

#include 
#include 
#include 

我们先介绍:
int open(const char *pathname, int flags, mode_t mode);

我们创建文件时权限是什么由第三个参数mode_t mode告诉。第一个参数就是当前路径。

返回成功会返回给我们一个文件描述符file descriptor,失败了-1被设置,errno也会被设置。
在这里插入图片描述

第二个参数int flags,它有很多选项:
在这里插入图片描述

O_RDONLY…这些东西叫做宏。
我们一般用C传标记为,一个整数传一个标记位。我们知道一个整数有32个比特位,也就意味着我们可以通过比特位传递选项。

操作系统如何使用比特位传递选项?

一个比特位,一个选项,比特位位置不能重复。

#define ONE 0x1    
#define TWO 0x2    
#define THREE 0x4  
#define FOUR 0x8   

每一个宏对应的数值,只有一个比特位是1,彼此位置不重叠。
有的开源项目是这样写的:

#define ONE (1<<0)    
#define TWO (1<<1)    
#define THREE (1<<2)  
#define FOUR (1<<3)  

代码:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #define FILE_NAME "log.txt"
  4 
  5 //每一个比特位对应的数值,只有一个比特位时1,彼此位置不重叠。
  6 #define ONE (1<<0)
  7 #define TWO (1<<1)
  8 #define THREE (1<<2)
  9 #define FOUR (1<<3)
 10 
 11 void show(int flags)
 12 {
 13   if(flags & ONE) printf("one\n");
 14   if(flags & TWO) printf("two\n");
 15   if(flags & THREE) printf("three\n");
 16   if(flags & FOUR) printf("four\n");
 17 }
 18 
 19 
 20 int main()
 21 { 
 22   show(ONE);
 23   printf("-------------------------\n");
 24   show(TWO);
 25   printf("-------------------------\n");
 26   show(ONE | TWO);
 27   printf("-------------------------\n");
 28   show(ONE | TWO | THREE);
 29   printf("-------------------------\n");
 30   show(ONE | TWO | THREE | FOUR); 
 31   printf("-------------------------\n");     
 32 } 

编译运行:

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第11张图片
上面就是标记位传参。

再次看:int open(const char *pathname, int flags, mode_t mode);
第一个参数是对应的路径起始就是我们要打开的文件。第二个是按标记位传参,O_RDONLY表示只读,O_WRONLY表示只写,O_RDWR表示读写。此时这就表示不同的标记位(宏),这些宏是通过不同的比特位来表示不同的含义的。
补充:O_TRUNC:表示对文件内容做清空。O_APPEND:表示追加。
在这里插入图片描述

下面我们看看int open(const char *pathname, int flags, mode_t mode);怎么用?

close是关闭文件描述符的。

代码:

 #include 
 #include 
 #include 
 #include 
 #include 
 #include  
 #define FILE_NAME "log.txt"
 int main()
 { 
   int fd = open(FILE_NAME,O_WRONLY | O_CREAT,0666);  
   if(fd < 0)
   {
     perror("open");
     return 1;
   }
 
   close(fd);
 }

编译运行:
运行前:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第12张图片
运行后:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第13张图片
此时log.txt的权限是664(-rw-rw-r–)和我们用C语言创建的一样。
因为umask默认是0002所以我们的权限最终是664
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第14张图片
我们可以设置umask为0,默认权限就是666了。
在这里插入图片描述

此时权限就是(-rw-rw-rw-),需要注意的是我们改的是我们自己的文件权限并不影响shell。

write

向文件写入:

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第15张图片

#include 
ssize_t write(int fd, const void *buf, size_t count);

第一个参数fd就是我们想往哪个文件去写,第二个是我们想写的时候对应的缓冲区数据所在地,第三个是缓冲区当中字节个数。返回值是我们所写的字节数。

以前学C的时候我们了解到读写文件两种方案:文本类和二进制类,这里的文件读取分类是语言给我们提供的。传const void *操作系统看来都是二进制位。

在这里插入图片描述
sprintf是将特定的内容格式化,形成到字符串里面。

代码:

 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #define FILE_NAME "log.txt"
 int main()
 { 
   umask(0);
   int fd = open(FILE_NAME,O_WRONLY | O_CREAT | O_TRUNC,0666);
   if(fd < 0)
   {
     perror("open");
     return 1;
   }
 
   int cnt = 5;                                
   char outBuffer[64];                                                                      
   while(cnt)                                                                 
   {                                           
     sprintf(outBuffer, "%s:%d\n","aaaa",cnt--);                                      
     write(fd,outBuffer,strlen(outBuffer));//向文件中写入string的时候 不用+1                                        
   close(fd);
 }

编译运行就完成了写入:
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第16张图片
追加的话:需要使用O_APPEND
修改下面代码:

int fd = open(FILE_NAME,O_WRONLY | O_CREAT | O_APPEND,0666);

编译运行看下面实验结果:

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第17张图片


想要读取文件:得使用O_RSONLY

【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第18张图片
read这个接口表示从一个文件描述符中读取文件,返回ssize_t(有符号整数,可以大于小于等于0)
ssize_t read(int fd, void *buf, size_t count); 从特定的文件(fd)中读到缓冲区里(buf),期望读count个。

代码:

 #include 
 #include   
 #include  
 #include 
 #include  
 #include 
 #include                                            
 #define FILE_NAME "log.txt"                          
 int main()
 {         
   umask(0);
   int fd = open(FILE_NAME,O_RDONLY); 
   if(fd < 0)                         
   {         
     perror("open");
     return 1;      
   }          
   char buffer[1024];
   ssize_t num = read(fd,buffer,sizeof(buffer)-1);
   if(num > 0)                                    
   {          
     buffer[num] = 0;
     printf("%s",buffer);
   }                     
   close(fd);
 }

此时就可以读出文件内容了。

如上,系统调用接口:open / close / write / read对应C语言是库函数接口:fopen / fclose / fwrite / fread

访问文件时必须使用系统调用接口,C库函数是封装了系统调用接口的。

文件描述符fd

进程可以打开多个文件,系统中一定会存在大量被打开的文件,被打开的文件要被操作系统管理。
如何管理?(先描述,再组织)
操作系统为了管理对应的文件,必定要为文件创建对应的内核数据结构标识文件,这个内核数据结构是struct file{},它包含了文件的大部分属性。

三个标准输入输出流:
stdin :键盘
stdout :显示器
stderr :显示器

FILE *fp = fopen();
FILE是一个结构体,我们底层访问文件时必须用系统调用,而系统调用接口访问文件时必须用文件描述符。所以这个结构体里必定有一个字段叫做文件描述符。

输出文件描述符:
在这里插入图片描述

在这里插入图片描述

我们自己打开的文件或文件描述符是从3开始的,原因就是0 1 2 默认被占用,我们C语言FILE类型指针也封装了操作系统内的文件描述符。

为什么数字是0 1 2开始的?

进程执行了一个接口,接口叫做open,系统在启动的时候默认启动了三个文件:键盘、显示器、显示器。操作系统要把log.txt加载到对应的内存里,那么首先并不是把内容直接加载到内存里,而是先描述这个文件,那么log.txt对应的结构叫做struct filestruct file{}保存的是文件的属性。PCB里有一个struct files_struct *file这样的一个指针,这个指针指向了一个属于进程的数据结构对象,叫做struct files_struct这个结构体是专门构建进程和文件对应关系的结构体,这个结构体里面包含了一个数组,这个数组的名字叫struct file* fd_array[]这是一个指针数组,数组里面所有的成员都是struct file*的指针,数组就有下标,分别都可以指向文件,0指向键盘,1指向显示器,2指向显示器,当再打开文件时从上往下找没有被使用的文件标识符了,所以从磁盘中把文件加载进来,把对象构建好,然后把这个对象的地址填到3号文件描述符里,此时3号文件描述符就指向新打开的文件了,然后我们再把3号文件描述符通过系统调用给用户返回,就得到了一个数字就叫做3,所以当一个进程在访问文件时它需要传入3,通过系统调用,操纵系统会找进程的文件描述符表,根据它找到对应的文件,文件找到了就可以对文件进行操作了。

文件描述符的本质:数组的下标
【Linux】基础IO(系统文件I/O Open write 文件描述符fd 什么是当前路径? 重新谈论文件 文件操作)_第19张图片

你可能感兴趣的:(Linux,linux,运维,服务器,开发语言,数据结构)