Linux系统编程之文件IO

前言

在学习C语言时,我们接触过如fopen、fclose、fseek、fgets、fputs、fread、fwrite等函数,实际上,这些函数是对于底层系统调用的封装。C默认会打开三个输入输出流,分别是stdin,stdout,stderr。执行 man stdin后,会展示如下描述:

   #include <stdio.h>
   extern FILE *stdin;
   extern FILE *stdout;
   extern FILE *stderr;
</stdio.h>

可以看到,这三个流类型都是FILE*,也就是说指向了某个文件。实际上,以上三者分别对应的文件为键盘、显示器、显示器。

那么,操作系统是如何管理文件,并进行文件IO呢?

  1. 文件描述符及基本IO接口介绍

1.1 什么是文件描述符

在第一讲中,我们知道了当进程被创建后,系统会给该进程分配对应的PCB,在Linux中,进程的PCB是task_stuct,里面有一项files_struct——打开文件表。打开文件表的源码如下:

struct files_struct {

    atomic_t count; /* &#x5171;&#x4EAB;&#x8BE5;&#x8868;&#x7684;&#x8FDB;&#x7A0B;&#x6570; */

    rwlock_t file_lock; /* &#x4FDD;&#x62A4;&#x4EE5;&#x4E0B;&#x7684;&#x6240;&#x6709;&#x57DF;,&#x4EE5;&#x514D;&#x5728;tsk->alloc_lock&#x4E2D;&#x7684;&#x5D4C;&#x5957;*/

    int max_fds; /*&#x5F53;&#x524D;&#x6587;&#x4EF6;&#x5BF9;&#x8C61;&#x7684;&#x6700;&#x5927;&#x6570;*/

    int max_fdset; /*&#x5F53;&#x524D;&#x6587;&#x4EF6;&#x63CF;&#x8FF0;&#x7B26;&#x7684;&#x6700;&#x5927;&#x6570;*/

    int next_fd; &#xFF0F;*&#x5DF2;&#x5206;&#x914D;&#x7684;&#x6587;&#x4EF6;&#x63CF;&#x8FF0;&#x7B26;&#x52A0;1*/

    struct file ** fd; /* &#x6307;&#x5411;&#x6587;&#x4EF6;&#x5BF9;&#x8C61;&#x6307;&#x9488;&#x6570;&#x7EC4;&#x7684;&#x6307;&#x9488; */

    fd_set *close_on_exec; /*&#x6307;&#x5411;&#x6267;&#x884C;exec( )&#x65F6;&#x9700;&#x8981;&#x5173;&#x95ED;&#x7684;&#x6587;&#x4EF6;&#x63CF;&#x8FF0;&#x7B26;*/

    fd_set *open_fds; /*&#x6307;&#x5411;&#x6253;&#x5F00;&#x6587;&#x4EF6;&#x63CF;&#x8FF0;&#x7B26;&#x7684;&#x6307;&#x9488;*/

    fd_set close_on_exec_init;/* &#x6267;&#x884C;exec( )&#x65F6;&#x9700;&#x8981;&#x5173;&#x95ED;&#x7684;&#x6587;&#x4EF6;&#x63CF;&#x8FF0;&#x7B26;&#x7684;&#x521D;&#x503C;&#x96C6;&#x5408;*/

    fd_set open_fds_init; /*&#x6587;&#x4EF6;&#x63CF;&#x8FF0;&#x7B26;&#x7684;&#x521D;&#x503C;&#x96C6;&#x5408;*/

    struct file * fd_array[32];/* &#x6587;&#x4EF6;&#x5BF9;&#x8C61;&#x6307;&#x9488;&#x7684;&#x521D;&#x59CB;&#x5316;&#x6570;&#x7EC4;*/

};

进程是通过文件描述符(file descriptors,简称fd)而不是文件名来访问文件的,文件描述符是一个整数。

在打开文件表中,最重要的一项是fd_array[32],这是一个指针数组,通常, fd_array包括32个 文件对象指针,如果进程打开的文件数目多于32,内核就分配一个新的、更大的文件指针数组,并将其地址存放在fd域中,内核同时也更新max_fds域的值。

每当打开一个文件时,系统就会分配fd_array中的某项,将其指向打开的文件结构体。从下图我们可以看出,fd实际上就是fd_array的索引。只要有fd,就可以找到对应文件的位置。

Linux系统编程之文件IO

1.2 基本IO接口

在认识返回值之前,需要先区分两个概念: 系统调用和库函数。 在用户程序中,凡是与资源有关的操作(如存储分配、进行I/O传输及管理文件等),都通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。在执行系统 调用的过程中,操作系统会由用户态进入到内核态。系统为了防止应用程序能随意修改系统数据,只给用户提供接口,用户要使用,那就通过提供的接口来调用。

fopen、fclose、fread、fwrite 都是C标准库当中的函数,我们称之为库函数(libc),而open close read write lseek 都属于系统提供的接口,称之为系统调用接口。实际上,库函数往往是对系统调用接口的进一步封装,方便程序员进行二次开发。

1.2.1 open/close接口

函数原型:

头文件:

#include <sys types.h></sys>
#include <sys stat.h></sys>
#include <fcntl.h></fcntl.h>

接口1: int open(const char *pathname, int flags);:

接口2: int open(const char *pathname, int flags, mode_t mode);

接口3: int close(int fd);

如果打开的文件存在,则使用接口1,如果打开的文件不存在,则使用接口2。

pathname:待打开或创建的文件

flags:以何种方式打开。打开文件时,可以传入多个常量进行”或”运算。这些常量有:

​ 0_RDONLY:只读打开;O_WRONLY:只写打开;O_RDWR:读写打开。这三个常量,必须指定且只能指定一个。

​ O_CREAT:若文件不存在,则创建该文件,需要使用mode选项,来指明新文件的访问权限。

​ O_APPEND:追加写入。

​ O_TRUNC:截断文件(清空文件内容)

O_NONBLOCK :使用非阻塞方式读写设备文件,如果不添加,默认情况下读写为阻塞方式。

以上的选项是按照按位或的方式进行组合的, O_RDWR|O_CREAT|O_APPEND 意思是以读写的方式打开文件,如果文件不存在则创建文件,打开文件后写入方式为追加写入。

mode:当创建一个新文件时,需要给文件设置权限,一般通过传递一个8进制的数字。关于文件权限,请读者自行查阅相关文章。

返回值:

​ 创建成功返回一个文件描述符

​ 创建失败返回-1。

1.2.2 read/wirte接口

ssize_t read(int fd, void *buf, size_t count);

fd:文件描述符

buf:将文件读到buf指向的空间中

count:期望读取的字节数

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

fd:文件描述符

buf:将buf中的内容写到文件当中去

count:期望写入的字节数, size_t被定义为unsigned long

返回值:返回读出或者写入的字节数。需要注意的是read和write的返回值都是有符号数, ssize_t被定义为long,出错的时候返回-1。有趣的是返回一个-1的可能性使得读到或者写入的最大值减小了一半。

通过下例来感受一下上面几个接口的使用:

#include <stdio.h>
#include <sys types.h>
#include <sys stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
char buff[1024];
int main()
{
  int fd = open("wrfile",O_RDWR|O_CREAT|O_APPEND,0644);
  if(fd == -1)
  {
    return 1;
  }
  else{
    const char* str = "Hello world\n";
    strcpy(buff,str);
    write(fd,buff,strlen(buff));
    printf("%d\n",fd);
    close(fd);
  }

  return 0;
}
</string.h></unistd.h></fcntl.h></sys></sys></stdio.h>

执行该段代码后,可以看到如下输出结果

Linux系统编程之文件IO

第一,打开文件后,将返回wrfile的fd,执行write函数,会将buff中的内容写入到wrfile中。

第二,输出wrfile的内容,发现如果只执行一次,输出一行”Hello world”,如果执行两次,输出两行”Hello world”,这是因为我们是以追加的方式打开的文件。

第三, 打开文件后,返回的fd号码是3。为什么会是3?这就与文件描述符的分配规则有关。

1.3 文件描述符的分配规则

根据我们在1.1中知道的,fd是fd_array[ ]的索引。通常,数组的第一个元素(索引为 0)是进程的标准输入文件,数组的第二个元素(索引为 1)是进程的标准输出文件,数组的第三个元素(索引为 2)是进程的标准错误文件,分别对应的键盘,显示器,显示器。在Linux中,万物皆文件,因此各种外设也会当做文件进行处理。

是不是瞬间明白了为什么用户自己打开第一个文件的时候分配的fd是3而不是0?是的,这是因为对于任何进程,标准输入文件、标准输出文件和标准错误文件会被默认打开。当再次分配的时候,操作系统会采用 最小未分配原则——即先分配当前fd_array中未被使用的最小索引。

如果我们先关闭了fd为1的文件,会是什么情况呢?请看下例:

#include <stdio.h>
#include <sys types.h>
#include <sys stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
  close(1);
  int fd = open("./myfile",O_RDWR|O_CREAT,0644);
  if(-1 == fd)
  {
    return -1;
  }else{
      printf("The fd is : %d\n",fd);
      fflush(stdout);
      close(fd);
  }
  return 0;
}
</string.h></unistd.h></fcntl.h></sys></sys></stdio.h>

执行该段代码后,结果如下:

Linux系统编程之文件IO

执行./test后,并没有在屏幕上打印出结果。但是当我们查看myfile文件中的内容时发现,原来是内容都输出在了myfile文件中。我们可以看到,打开myfile的fd为1,这是因为我们之前关闭了fd为1的文件,当打开新的文件时,系统会分配最小未使用的fd——1。

为什么执行printf后内容会输出到myfile中而不是屏幕上,这就需要提到输出重定向。

1.4 输入重定向与输出重定向的本质

1.4.1重定向的原理

printf是格式化输出函数,当调用printf时,数据会被默认输出到缓冲区中,当换行符或者缓冲区满时,会将数据刷新到stdout中。stdout是标准输出设备,其fd默认为1。

在上例中,关闭fd为1的文件后,当打开新的文件,会给其分配最小未使用的fd。此时执行printf函数,会将数据输出到磁盘文件myfile中,我们将这种现象称为输出重定向。常见的重定向有:>, >>,

Original: https://www.cnblogs.com/Grong/p/15522656.html
Author: 乌有先生ii
Title: Linux系统编程之文件IO

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/585759/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Redis从入门到精通:中级篇

    原文链接:http://www.cnblogs.com/xrq730/p/8944539.html,转载请注明出处,谢谢 本文目录 上一篇文章以认识Redis为主,写了Redis系…

    Linux 2023年5月28日
    080
  • Redis的Docker安装及基本使用

    Redis 端口 6379 通过以下命令启动一个简单的Redis容器 docker run –name some-redis -d -p 6379:6379 redis:6.2….

    Linux 2023年5月28日
    076
  • 性能测试

    一.性能测试概述 性能测试概念: 性能测试是指通过特定方式,对被测系统按照一定策略施加压力,获取系响应时间、TPS、资源利用率等性能指标,以期保证生产系统的性能能够满足用户需求的过…

    Linux 2023年6月6日
    079
  • 【Python】**kwargs和takes 1 positional argument but 2 were given

    Python的函数定义中可以在参数里添加kwargs——简单来说目的是允许 添加不定参数名称的参数,并作为 字典传递参数。但前提是——你必须提供 参数名**。 例如下述情况: 1 …

    Linux 2023年6月13日
    096
  • 操作系统实战45讲笔记- 05 CPU工作模式:程序执行的三种模式

    实模式 实模式又称实地址模式,实,即真实,这个真实分为两个方面,一个方面是运行真实的指令,对指令的动作不作区分,直接执行指令的真实功能,另一方面是发往内存的地址是真实的,对任何地址…

    Linux 2023年6月7日
    075
  • VR(虚拟现实)开发资源汇总

    Daydream Gear VR Algorithm ATW Bluetooth Blog Latency Tools Touch Unity Qualcomm EGL Origi…

    Linux 2023年6月7日
    075
  • Java高级

    抽象类和抽象方法 1.定义 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。 类的设计应该保证父类和子类都能够共享特征。 有时候将一个父类设计的非常抽象…

    Linux 2023年6月13日
    0101
  • PHP设计模式—享元模式

    定义: 享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象。 结构: Flyweight:享元抽象类,所有具体享元类的接口,通过这个接口,Flyweight 可…

    Linux 2023年6月7日
    062
  • HTML笔记整理–上节

    一、认识WEB 「网页」主要是由 &#x6587;&#x5B57;、 &#x56FE;&#x50CF;和 &#x8D85;&#x94…

    Linux 2023年6月13日
    088
  • shell中 $() $(()) $[] ${} $[[]] 区别

    $( ) &#x4E0E; (&#x53CD;&#x5F15;&#x53F7;) &#x5728; bash shell &#x4E…

    Linux 2023年5月28日
    082
  • mysql 8.0.20 忘记密码,修改密码

    由于mysql更新较快,8.0对比5.7很多操作有了变化,特别修改密码,和忘记密码这一块已经和以前完全不一样了。 一、 忘记密码 1、 在my.cnf 文件中添加skip-gran…

    Linux 2023年6月6日
    087
  • 无线配置多一个路由器作为家庭wifi的无线热点?

    以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」 https://mp.weixin.qq.com/s/8OcDnY3O6ux41GntesHHcg 手头…

    Linux 2023年6月6日
    097
  • 浅谈kali : aircrack-ng套件

    aircrack-ng 套件包含有: Name Description aircrack-ng 破解WEP以及WPA(字典攻击)密钥 airdecap-ng airmon-ng 将…

    Linux 2023年6月14日
    068
  • haproxy服务部署

    haproxy haproxy 一、haproyx是什么 二、负载均衡类型 三、部署haproxy 1.源码部署haproxy 2.Haproxy搭建http负载均衡 一、hapr…

    Linux 2023年6月6日
    088
  • shell编程学习

    在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash。 ! 告诉系…

    Linux 2023年5月28日
    077
  • 在使用amoeba连接数据库时,报错java.lang.Exception: poolName=slaves, no valid pools

    搭建3台MySQL服务器,完成主从复制,搭建一台amoeba服务器,完成MySQL的读写分离 问题描述: 问题1、 在服务搭建完毕后,利用客户机连接amoeba服务器登录数据库,无…

    Linux 2023年6月13日
    066
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球