[Linux]基础I/O

  • 重新谈论文件
  1. 空文件,也要在磁盘占用空间
  2. 文件=文件内容+文件属性
  3. 文件操作=文件内容操作,文件属性操作或内容和属性操作。
  4. 标定一个文件必须使用文件路径+文件名,因为它具有唯一性。
  5. 没有指明对应的文件路径,一般默认在当前路径(默认是进程当前的路径)进行文件访问。
  6. 当我们把fopen,fclose,fread,fwrite,等接口写完后,代码编译后,形成二进制可执行程序之后,但是没运行,文件对应的操作没有被执行。

对文件的操作,本质是进程对文件操作。

一个文件没有被打开,不能直接对文件进行访问。一个文件要被访问,必须先被打开。

打开文件需要用户进程+操作系统,用户进程用来调用接口,操作系统实现文件的打开。

一个文件要被访问,必须先被打开。不是所有文件都被系统打开。

进程的操作本质是研究进程和被打开的文件之间的关系。

  • 重新谈论文件操作

文件在磁盘里,磁盘是硬件,需要操作系统访问。想访问磁盘,不能绕过操作系统,必须使用操作系统提供的接口,提供文件级别的操作系统调用接口。

但操作系统只有一个,所以上层语言无论如何变化,库函数底层必须调用系统调用接口,库函数可以千变万化,但是底层不变,那么如何降低学习成本呢?

a+是追加写入加读取。

以w的方式单纯的打开文件,c会自动清空内部数据。

 读写是把文件内容清空后写入,追加是直接在内容后面写入。

 

把特定的内容格式化写入到特定的格式流中。 

试着写一下

#include<stdio.h>
#include<unistd.h>
#define FILE_NAME "log.txt"
int main()
{
  FILE* f=fopen(FILE_NAME,"w");
  if(f==NULL)
  {
    perror("fopen");
    return 1;
  }
    int cnt=5;
    while(cnt)
    {
      fprintf(f,"%s:%d\n","hello wprld:",cnt--);
    }
}

 以行为单位,从stream流中读取数据,放入到s中。s中大小为size。

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define FILE_NAME "log.txt"
int main()
{
  FILE* f=fopen(FILE_NAME,"r");
  if(f==NULL)
  {
    perror("fopen");
    return 1;
  }
   //bin int cnt=5;
   // while(cnt)
   // {
   //bin   fprintf(f,"%s:%d\n","hello wprld:",cnt--);
   // }
   //
   char buffer[64];
   while(fgets(buffer,sizeof(buffer)-1,f)!=NULL)
  {
  buffer[strlen(buffer)-1]=0;//puts打印时自动加上了/n,原先文本中还有个/n,这里就去掉原先文本中的/n
  
  puts(buffer);
  }
  fclose(f);
}


  • 系统调用接口 

用fopen打开文件,底层调用的是系统调用接口open

mode代表创建文件时的权限,pathname代表文件路径,open打开文件成功后,返回文件描述符。

是个整数。失败返回-1,并会说明什么原因失败。


flags有很多选项,例如如下三个。

 具体是什么下面讲解。

大写的让我们想到了,这些就是宏。

我们C语言中传标记位,一般一个整数传一个标记位,但是这样有10个标记位,就要有10个整数,

所以我们选择通过32个比特位来传递选项。

  • 操作系统如何通过比特位传递选项。

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

不能有3,因为只能一个位置为1. 

每一个宏对应的数值,只有一个比特位位置是1,彼此位置不重叠。

也可以这样写

 main函数中这样写,然后就能打印对应的数值了。

 

 这叫标记位传参。

这些就是通过不同的标记位传参的。不同标记位表示不同功能。

上面这样运行,如果之前这个文件没有被创建,会报文件不存在,因为此时文件没有被创建 ,只写标记位不能像fopen一样自动创建文件。

我们需要加上创建标记位。

但此时文件权限会是乱码。之前使用的fopen是语言中内部写的接口,默认权限已经写好。

我们调用系统接口,必须先写文件创建的默认权限。

这样权限就对了,此时是664,因为有个掩码umask为2.

  

 我们也可以更改umask掩码

 但此时在命令行查看uamsk依然会是0002,因为umask是在子进程执行,改的是子进程的文件权限,改的时候不影响shell中的umask。


 打开之后要写入,下面是写入函数。

fd参数是想写入文件的文件描述符。 

buf是指从哪里输入。

count是指写入多少字节。

返回值一般和cout相同,代表传了几个值。

读写文件有两种读写方案

1文本类

2二进制类

语言本身为我们提供的文件读取的分类。但在操作系统看来,都是二进制。

操作系统只看传过来的字节是几,至于是图片还是文字是由C语言等各种软件决定的。


这些接口将特定内容格式化到字符指针中 。

一下代码演示了使用系统调用接口的例子。

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<assert.h>
#include<string.h>
#define FILE_NAME "log.txt"
int main()
{
  umask(0);
  int fd=open(FILE_NAME,O_WRONLY|O_CREAT,0666);
  if(fd<0)
  {
    perror("open");
    return 1;
  
  }
  int cnt=5;
  char letter[64];
  while(cnt)
  {
    sprintf(letter,"%s|%d\n","hello:",cnt--);
    write(fd,letter,strlen(letter)+1);
  }
  printf("%d\n",fd);
}

 但是这么写用vim打开文件后,出现^@乱码

 原因在于这里多加个1

 因为文件要的是字符串的有效内容,文件中字符串不以反斜杠0结尾,不需要写入/0输入到文件中什么内容,文件就存什么内容。

去掉+1就行了。注意,改完后,原先的log.txt要rm删掉后,再运行一下这个代码文件,再vim打开log.txt。


现在这样改一下

 会发现,log.txt变成了这样

 可以看到,之前的数据没有被完全清除。

因为我们自己调用的open这个系统系接口,需要我们自己写一下清空数据的代码。


想要实现追加,可以这样用。



 举一个读取文件的例子

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<assert.h>
#include<string.h>
#define FILE_NAME "log.txt"
int main()
{
  umask(0);
  int fd=open(FILE_NAME,O_RDONLY,0666);
  if(fd<0)
  {
    perror("open");
    return 1;
  
  }
  char buffer[10124];
  ssize_t num=read(fd,buffer,sizeof(buffer)-1);
  if(num>0)
    buffer[num]=0;
  printf("%s",buffer);
 //bin int cnt=5;
 // char letter[64];
 // while(cnt)
 //bin {
  //  sprintf(letter,"%s|%d\n","hello:",cnt--);
  //  write(fd,letter,strlen(letter));
 //usr }
  printf("%d\n",fd);
}

  是为了printf以%s打印时能找到结尾。


文件操作的本质,进程和被打开文件的关系。

进程可以打开多个文件,系统中一定会存在大量被打开的文件,被打开的文件要被操作系统管理。

那么如何管理?

先描述,再组织。

操作系统要管理对应的打开文件,必定要为文件创建对应的内核数据结构来标识文件。

这个结构是struct file {}结构体,这个结构体包含了文件大部分属性。和FILE没有关系。


进程和被打开文件之间的关系是怎么维护的?

在下面这部分会讲到。

这个意思是讲number里面的值变成字符后直接加在字符串的后面。 

比如number是1,那么文件名就是log.txt1

 上面代码的结果如下

数字为什么从0,1,2开始?

这几个是默认被打开的三个标准输入输出流。

 FILE *fp=fopen()中的FILE是个结构体。

fopen底层open访问文件时必须用系统调用接口,系统调用接口访问文件必须用文件描述符。

这个FILE结构体必定有一个字段,是文件描述符。

我们来看一下

 

可见,0,1,2三个文件描述符被三个默认被打开的三个标准输入输出流的FILE结构体中的文件描述符字段所占用。

  • 文件描述符是什么?

系统启动时,键盘,显示器三个文件默认被打开。每个文件从磁盘被调用到内存时,会有自己的struct file结构体,struct file描述了大部分文件属性。每一个文件都有一个struct file,

进程的PCB中保存了一个指针,struct  files_struct *files,指向struct files_struct结构体,结构体里面有一个数组,struct file*fd_array[](指针数组),指向struct file这个描述文件属性的结构体。

文件描述符的本质就是数组下标。struct file*fd_array[]叫做进程描述符表。

 源码。


 

 

 

从小到大,按照顺序寻找最小的且没有被占用的fd,这是fd的分配规则。

而当我们这样写时,程序却打印不出结果。

 所谓的close(1)是不再让文件描述符表file*fd_array[1]指向标准输出流(显示器)。

当我们open后,系统给我们打开了一个文件,系统在fd中找最小的,指向新打开的文件,上述程序即是1号下标指向此文件,此时open返回给fd的也是1。

我们向显示器中打印默认是想stdout中打印,stdout里对应的fd是1。

printf函数也是向stdout中打印,并且认为stdout的fd是1,此时,因为fd=1的数组位置已经指向新打开的文件,所以实际是向新打开的文件中输入内容。

而当我们用cat log.txt指令查看文件内容时,会找不到,这和缓冲区有关。

 我们需要加上如下代码。

当我们close(1)后,运行程序后不会打印fd,需要在文件中才能查看fd,此时fd为1.

当我们去掉close(1)后。

 运行程序,会直接打印fd。

上述过程中,当把fd=1,close后,printf实际是向文件中打印,我们把这个现象叫重定向。

重定向的本质是上层用的fd值不变,在内核中更改文件描述符表struct file*的内容。

为了更好的重定向,我们可以使用dug2函数。

失败返回-1。

 我们写一个输入重定向。

此时,当我们直接运行此程序时,会直接打印log,txt中的内容。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南种北李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值