【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)

目录

  • 0.前言
  • 1.文件
    • 1.1如何理解文件?
    • 1.1 文件构成
    • 1.2 文件有哪些
  • 2.文件操作
    • 2.1 C语言文件操作
    • 2.2 系统调用文件操作
    • 2.3 两种操作的关系
  • 3.文件描述符fd
    • 3.1 什么是文件描述符?
    • 3.2 为什么有文件描述符?
    • 3.3 fd使用之重定向
  • 4. I/O与缓冲区
    • 4.1 是什么?
    • 4.2 为什么?
    • 4.3 使用方法与注意事项
  • 5. 文件管理系统
    • 5.1 文件系统是什么
    • 5.2 为什么需要文件系统
    • 5.3 文件系统如何对磁盘进行管理
      • 5.3.1 磁盘数据抽象化
      • 5.3.1 磁盘的分区管理
      • 5.4 我们的目录之中保存的是什么
      • 5.5 对文件操作的流程
      • 5.6 文件软硬链接
  • 6.动静态库
    • 6.1 什么是函数库
    • 6.2 动静态库的差别
    • 6.3 函数库生成
      • 6.3.1 常用命令
      • 6.3.2 函数库需具备条件
      • 6.3.3 静态库生成
      • 6.3.4 动态库生成

0.前言

操作系统是管理资源的,想管理,要先描述,再组织管理

这篇文章前四点着重介绍文件操作使用。
第五点着重介绍文件管理系统是如何做到高效稳定的管理的。

1.文件

1.1如何理解文件?

“Linux下一切皆文件”

1.1 文件构成

首先明确一点,文件的构成,用Windows举例,
文件=内容+属性。
内容:就是里面存储的数据了。
属性:关于这个文件的特征,比如大小,创建时间,修改时间,存储位置等

1.2 文件有哪些

文件其实不止于,.txt .c .cpp .exe 等等。

是不是文件主要看他有没有iNode,这个概念下面提到。

说几个容易被忽略的文件,

显示器(stdout),键盘(stdin),错误码(stderror)。

目录也是文件,软连接也是文件,硬链接不是。

至于里面存储的是什么,且听下面的解释。

2.文件操作

2.1 C语言文件操作

fopen、fwrite、fread、rewind、fseek、fclose。

每个函数都在cpp官网可以找到使用方法和示例,不再赘述。

列出几个本人踩过的坑:

  1. 注意返回值类型size_t,要匹配。
  2. 读写操作要注意数据类型的大小
  3. 文件内部也需要定位,好比电脑光标一样,
  4. rewind是回退到开始位置,
  5. fseek函数可以指定位置,但是要注意fseek函数的第二个参数偏移量,这个参数指定文件的初始位置。
  6. ftell函数可以告诉你当前打开文件的指向位置。
    Linux下示例:

代码截图:
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第1张图片
运行截图
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第2张图片

2.2 系统调用文件操作

open、write、lseek、read、close。

使用方法类比C语言文件操作库函数接口
open接口介绍:
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第3张图片
第一个参数:代表文件名.

第二个参数:代表打开方式,

为什么是int类型,而且可以有多种方式呢?

注意联系两种打开方法中间的操作符就是按位或 “| ” ,每一种方式对应32个比特位上的某位为1。根据一个int参数的比特位来确定打开方式。
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第4张图片
第三个参数:需要创建文件时,设定的文件权限。

注意:并不是你设置了多少就是多少,要考虑与系统给出的umask掩码计算后的结果。
假设你给出的权限是mask,则实际创建的出来的文件权限是:
mask & ~umask
格式: umask 权限值
说明:将现有的存取权限减去权限掩码后,即可产生建立文件时预设权限。超级用户默认掩码值为0022,普通用户默认为0002。

write接口介绍:
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第5张图片

代码截图:
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第6张图片
运行截图:
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第7张图片

2.3 两种操作的关系

其实看名字思考一下就能明白,一个是C语言库函数给出的接口,一个是操作系统给出的接口,而操作系统是设备软硬件资源的直接管理者。

所以,可以认为,f#系列的函数,都是对系统调用的封装调用,方便二次开发。

再来看一个现象,验证一下,
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第8张图片
背景:
open函数的返回值是个整数,这个整数是一个文件指针数组的下标,而操作系统默认打开三个标准文件,标准输入,标准输出,标准错误。分别占用这个文件指针数组的0、1、2。这个下标就是我们下面要讲的文件描述符fd。

现象:
原本应该printf标准输出的fp:1到显示器上,在关闭显示器这个文件后后,重新打开的文件占用了最小的未使用的文件指针数组里的位置。(重定向的一个例子)

原因:
而printf是C语言文件函数接口,它调用了一个系统调用接口write,它只管往这个下标为1 里的文件指针指向写入,不管里面存放的是什么。也证明了,f#系列的函数,都是对系统调用的封装调用。

3.文件描述符fd

3.1 什么是文件描述符?

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第9张图片
这个数组下标就是文件描述符fd。

3.2 为什么有文件描述符?

其实这个问题应该换一种问法,文件描述符是干什么的?

为了方便调用者使用文件管理的操作,又能知道进程打开的文件数目,数组下标式的访问会增加效率,等因素。

所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。

3.3 fd使用之重定向

手动重定向
从上面的示例来看,用close关闭一个文件,其他文件指针的位置不会变化,只有再打开新的文件时,才会把新的fd分配给这个数组的最小的未使用的位置。
分配方式:最小的、未使用的位置。

系统调用重定向:

#include 
int dup2(int oldfd, int newfd);

dup2这个接口是将oldfd拷贝给 newfd,然后向newfd操作,建议调用之前,关闭newfd,更好的进行文件管理。

打开的文件要进行关闭,实际我们的文件描述符是有限的(初始默认32个),无限制的打开而不关闭会造成文件描述符资源泄漏。

注意:一个文件可以被打开多次,有多个fd。但是一个fd只能对应一个文件。
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第10张图片
使用示例:
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第11张图片
现象大家可以自己去验证一下。

4. I/O与缓冲区

4.1 是什么?

代码:想一想,下面的代码会出现什么现象?如果不关闭标准输出1呢?
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第12张图片
现象:为什么fwrite 和 printf会被打印两次呢?
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第13张图片

4.2 为什么?

原因: 注意看,所有的输出操作,都是在fork之前的,fork之前的代码只会执行一次,但是为什么fwrite 和printf会被打印两次,唯独write不会呢?

我们知道,为了提高I/O效率,是有内存缓冲区的。

  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
  • printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
  • 但是进程退出之后,会统一刷新,写入文件当中。
  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
  • write 没有变化,说明没有所谓的缓冲。

综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。

那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

如果有兴趣,可以看看FILE结构体:

typedef struct _IO_FILE FILE;在/usr/include/libio.h

struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

4.3 使用方法与注意事项

我们之前在使用scanf录入数据的时候,经常因为格式问题导致录入失败。
就是因为缓冲区的方式导致的,由于当时是C语言标准库函数scanf,也没有重定向,所以使用行缓冲,一旦遇到空格字符“ ”和 “\n"就会自动刷新缓冲区。
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第14张图片
解决办法:

链接: scanf函数使用注意事项.

5. 文件管理系统

这一部分本人参考搬运了同学文章中的部分,非常感谢,在此贴出该文章的链接。
链接: 进一步了解系统I/O.

5.1 文件系统是什么

文件管理系统是操作系统的重要组成部分之一,为使用文件的用户和程序提供服务。是操作系统用于管理明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构,它降低了磁盘空间的使用难度,将磁盘数据更加形象化的展示给用户。

直白的来讲:文件系统就是,操作系统的一个分支,用来管理二级存储相关资源。
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第15张图片

5.2 为什么需要文件系统

这个问题转化成文件管理系统的功能是什么?

  1. 快速访问文件(索引)
  2. 易于修改
  3. 节约存储空间
  4. 维护简单
  5. 可靠与安全性

那文件系统是如何做到以上的呢?

5.3 文件系统如何对磁盘进行管理

5.3.1 磁盘数据抽象化

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第16张图片

5.3.1 磁盘的分区管理

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第17张图片

5.4 我们的目录之中保存的是什么

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第18张图片

5.5 对文件操作的流程

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第19张图片

5.6 文件软硬链接

文件链接分为硬链接和软链接两种。

  • 硬链接没有独立的inode,只是增加了一个和inode具有映射关系的文件名,并没有创建新的文件

  • 软链接则创建了新的文件,具有独立的inode,这个文件保存着链接文件的路径

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第20张图片

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第21张图片

6.动静态库

6.1 什么是函数库

生活中我们经常听到程序员朋友吐槽,听的最多的就是删库跑路了,跑路好理解,这个删库指什么呢?

这个库指的是库函数,里面存放的是头文件下实现的各个接口组成的二进制文件(即已经预处理、编译、汇编、好的.o文件)。我们使用这些库的时候,只需要包含头文件,最终共同链接即可。

举例说明:
我们在使用printf函数的时候,会包含一个头文件,这个库函数放到了名为libc.so.6的库文件中去了,在没有特别指定的时候,gcc会到系统默认的搜索路径/usr/lib下进行查找,也就是链接到libc.so.6库函数中去,这样就实现了函数,这也是链接的作用;
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第22张图片

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第23张图片

6.2 动静态库的差别

静态链接:在编译链接的时候,将对应的代码拷贝至源文件

  • 缺点:假如有n个程序都调用了库的同一个接口,则都需要拷贝一份,在一定程度上占用资源(硬盘资源和内存资源),

  • 优点:可移植性强,库函数缺失也不影响程序。

动态库:在运行的时候才去链接动态库的代码,整个系统里面可以只有一份,多个程序共享使用库的代码
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第24张图片
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第25张图片

6.3 函数库生成

6.3.1 常用命令

ldd:查看一个可执行文件依赖的库
ar -rc:静态库打包
ar -tv:查看库依赖的文件

6.3.2 函数库需具备条件

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第26张图片

6.3.3 静态库生成

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第27张图片
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第28张图片

6.3.4 动态库生成

【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第29张图片
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第30张图片
【Linux】文件管理系统解析(I/O与缓冲区,文件描述符fd,iNode)_第31张图片

你可能感兴趣的:(linux学习过程,linux,操作系统,系统I/O和文件管理)