Linux:基础IO(上)

基础IO

回顾下c语言的文件操作

文件打开(创建)

  #include<stdio.h>
  #include<string.h>
  #include<assert.h>
  
   int main()
  {
     FILE* fp = fopen("log.txt", "w");
     assert(fp);
     (void)fp;
  
    const char* msg = "hello world!\n";                                        
    int count = 5;
    while (count--)
    {
      fwrite(msg, strlen(msg), 1, fp);
    }
  
    fclose(fp);
    return 0;
  }

在这里插入图片描述

  • 注意这里的调用fwrite()时,传的长度参数不要考虑加上 ‘\0’,也就直接传strlen(msg),不要+1,因为’\0’只是在c语言中的规定,文件并不管’\0’

读文件操作

  #include<stdio.h>
  #include<string.h>
  int main()
  {
     FILE* fp = fopen("log.txt", "r");
     if (fp == NULL)
     {
       perror("fopen");
     }
  
    char buf[128];                                                             
    const char* msg = "hello bit\n";
    while (1)
    {
      size_t s = fread(buf, sizeof(char), strlen(msg), fp);
      if (s > 0)
      {
        buf[s] = 0;
        printf("%s", buf);
      }
      if (feof(fp))
      break;
    }
    fclose(fp);
    return 0;
  }

对刚刚创建log.txt的文件进行读操作
在这里插入图片描述

系统的文件I/O

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:

   #include<stdio.h>
   #include<sys/types.h>
   #include<sys/stat.h>
   #include<fcntl.h>
   #include<string.h>
   #include<unistd.h>
   int main()
   {
     umask(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, "0666");
    if (fd < 0)
    {
      perror("open");
    }                                                                          
  
    int count = 5;
    const char* msg = "hello world\n";
    int size = strlen(msg);
  
    while (count--)
    {
      write(fd, msg, size);
    }
    close(fd);
    return 0;
  }

仔细一看是不是突然发现很陌生,包含的头文件非常多且陌生,open、write、close都是系统调用接口,先来看下open
open:在这里插入图片描述
这里有两个open函数,类似于C++的函数重载,先看看mode_t mode参数,mode_t是Umask变量,是一个无符号八进制数,文件创建时,根据传入的mode &(~umask)来确定文件的权限,umask一般是0002,上面我设置成了0000,创建出的文件权限就应该是 -rw-rw-rw-
在这里插入图片描述
再来看flag
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
O_TRUNC: 每次打开清空,再写入

O_WRONLY | O_CREAT | O_TRUNC == “w”
O_WRONLY | O_CREAT | O_APPEND == “a”
这样的传参方式实际是对flag的比特位做修改,把每一个比特位看做成有无,对应的选项做变化

open的返回值:
如果成功创建:返回的是新打开的文件描述符(>= 0)
如果创建失败:返回-1

系统的IO函数于C语言的IO的关系

上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。而 open close read write 都属于系统提供的接口,称之为系统调用接口。

在这里插入图片描述
上下层关系

简单的来说,其实fopen封装了open()
在这里插入图片描述

文件描述符

上面讲到open返回值是文件描述符(整数类)

   #include<stdio.h>
   #include<sys/types.h>
   #include<sys/stat.h>
   #include<fcntl.h>
   #include<unistd.h>
   
  int main()
  {
     int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
     printf("log.txt的文件描述符:%d\n", fd);                                                                  
     close(fd);
     return 0;
  }

在这里插入图片描述
为什么log.txt的文件描述符为3呢?而不是为0,或1 、2呢?
其实Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2
标准输入对应的物理设备是键盘,而标准输出,标准错误对应的物理设备是键盘
这样就能解释为什么第一个打开的文件的文件描述符是3了,因为0,1,2都被占用了

当启动代码时,会变成进程,OS会为我们进程创建struct task_struct结构体,在这结构体中有一个结构体指针数组struct file_struct* files;OS会初始化指针,这个指针指向的是struct file_struct,这个结构体里有一个struct file* fd_array[]指针数组,一个进程会默认打开三个文件,三个文件对应着标准输入,标准输出和标准错误,会为该三个文件组织三个结构体struct file,而第一个struct file会链接第二个struct file,一直链接完,最后一个指向NULL,而struct file_struct* files[]存储的就是struct file的指针,最后返回file_struct最后一个储存的有效file*的下标,此时这个下标就是文件描述符。
在这里插入图片描述
简便图:
在这里插入图片描述

所以,当我们打开新的文件返回的文件描述符为3,文件描述符就是该数组的下标。所以,只要拿着文件
描述符,就可以找到对应的文件

在这里可以解释为什么在Linux下一切皆文件这个概念
在这里插入图片描述
这些硬件实际上是通过驱动提供的接口来进行文件操作,这也是为什么要下载安装硬件驱动,这样OS才能通过接口调动这些硬件

文件描述符的分配规则

将标注输入关闭之后,再打开新的文件,此时的fd为0
在这里插入图片描述
文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符

重定向

在这里插入图片描述
从上面的现象中,本hello world应打印输出在显示器上,用输出重定向到log.txt中.

实际上原理就是把1号标注输出文件改成log.txt
来动手试试:

   #include<stdio.h>
   #include<sys/types.h>
   #include<sys/stat.h>
   #include<fcntl.h>
   #include<unistd.h>
   
   int main()
   {
     close(1);
    int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC);
    printf("fd:%d\n", fd);
  
    fflush(stdout);                                                                                          
    close(fd);
    return 0;
   }

在这里插入图片描述
模拟了输出重定向

  • 有没有注意到 上面的代码printf后面还跟着一句fflush(stdout),这时因为此时数据还存在stdout的缓冲区中,而stdout对应的文件描述符1是关闭的,所以要手动刷新stdout中的缓冲区

dup2系统调用

这样关闭文件,再打开文件是不是又点矬
来,了解dup2这个系统调用,可以帮助我们一键修改
在这里插入图片描述
newfd是oldfd的一份拷贝,拷贝的不是fd,而是拷贝fd对应数组中的内容,所以oldfd指向新创建的普通文件

在这里插入图片描述

缓冲区

缓冲区本质是一块C语言提供的内存空间,这块内存区用来缓存待处理的数据。
在这里插入图片描述

当我们输入数据并要打印出数据,并不是向OS直接输入数据,而是先存入C库定义的缓冲区存入数据,等程序结束时,缓冲区的数据才会向显示器打印。
可是为什么要这样做呢?
实际I/O会使的程序运行变慢,这是因为要访问外设,而当进程等待输入或者输出资源时,会进入阻塞状态,频繁的进行I/O,会效率变低,但是有了缓冲区就不一样了,每次输入数据到缓冲区,根据C库刷新策略再向显示器输出数据,这样从一次I到O,变成了多次I到O。

缓冲区刷新策略:

立即刷新。(IO次数多,效率低)
行刷新 (\n)。
全刷新(全缓冲),即写满缓冲区再刷新。(效率最高)

特殊刷新策略:

调用fflush(强制刷新)
进程退出

不同文件的不同刷新策略:

显示器对应的刷新策略为行缓冲 (为了兼顾用户体验)
而磁盘文件倾向为全缓冲的刷新策略。
其实所有的设备都倾向于全缓冲,因为全缓冲这种刷新策略可以减少IO的次数,也就是减少外设的访问次数,这种方式效率是最高的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值