5 Linux文件管理

文件及文件系统的引入 #

信息存储问题及要求

  • 进程运行时内存空间有相应一定量的信息
    • 进程终止时,有关信息将随之消失
    • 一方面有关信息受到内存空间大小的限制
    • 另一方面部分信息需要长久甚至永久保存
  • 常常会有多个进程需要存取同样的信息
    • 信息应当独立于进程而存在
  • 信息存储要求
    • 能够存储大量信息
    • 使用信息的进程终止时,有关信息仍旧存在
    • 支持多个进程并发存取有关信息

信息存储解决方案

  • 把信息以某种单元即文件的形式存储在磁盘或其他外部介质上
  • 文件是一个具有名字的存储在磁盘上的一组相关信息的集合体,是磁盘的最小逻辑分配单位
  • 文件系统是操作系统中负责管理和存取文件信息的软件机构
    • 管理文件所需的数据结构
    • 实现文件管理的系统程序
    • 文件操作相关系统调用

ls 命令及文件类型

ls -l hello.c
   -rw-rw-r--  1 zgs zgs      561 Aug 24 07:31 hello.c
  • d 目录
  • l 符号链接文件
  • s 套接字文件
  • b 块设备文件
  • c 字符设备文件
  • p 命名管道文件
  • - 普通文件

硬链接和软链接文件共享

  • 建立 DataFile.txt 的硬链接文件
    • ln DataFile.txt DataFileYLJw
  • 建立 DataFile.txt 的软链接文件
    • ln -s DataFile.txt DataFileRLJ
  • 删除原数据文件 DataFile.txt
    • 硬连接依旧可行
    • 软连接无法继续输出文件内容
lrwxrwxrwx 1 zhaigaoshou2018 zhaigaoshou2018 12 Nov  6 05:49 DataFileRLJ -> DataFile.txt
-rw-rw-r--  2 zhaigaoshou2018 zhaigaoshou2018   15 Nov  6 05:47 DataFile.txt
-rw-rw-r--  2 zhaigaoshou2018 zhaigaoshou2018   15 Nov  6 05:47 DataFileYLJ

文件基本操作与文件系统 #

  • 创建文件 (creat)

    • 分配空间
    • 在目录文件中创建新目录项并设置
  • 打开文件 (open)

    • 写文件 (write)
    • 读文件 (read)
  • 文件检索

    • 文件存放位置
    • 写入/读取指定信息
  • 读写指针重定位 (relocation/fseek)

    • 实际上是个偏移量
    • 不涉及 I/O 操作,只是将 r/w 指针定位到某个位置
  • 删除文件 (delete)

    • 删除目录项
    • 释放文件存储空间
  • 文件内容清空操作 (truncate)

    • 清除文件内容
    • 释放文件存储空间
    • 文件长度为 0

用户与外设间接口

  • 用户可按文件名存取,可用虚拟 I/O 指令,而无需考虑文件如何存储或存储在什么地方
  • 文件系统接口 = 文件操作相关系统调用 + 文件操作相关实用例程
  • 文件系统接口向用户提供的抽象元素
    • 文件(file)
    • 目录(directory)
    • 文件描述符(fd,即 file descriptor)
    • 文件系统(支持多种文件系统时,需指明使用了哪种文件系统)

文件系统的系统解读

  • 统一管理文件的存储空间,实施存储空间的组织、分配与回收
  • 实现文件的按名存取,实现名字空间向存储空间的映射
  • 向用户提供简单方便的使用接口,提供文件系统操作命令以及文件检索与存取操作命令
  • 实现文件信息的共享,并提供文件的保护和保密措施
  • 系统维护及向用户提供有关信息
  • 提供与 I/O 机构的统一接口

文件系统层次模型

磁盘、分区和文件系统

  • 磁盘扇区为 512 字节,但输入输出则以更大的盘块为单位
    • 早期 Unix 文件普遍偏小,故设置盘块大小为 1024 字节
    • 4.3BSD 文件系统:一个文件若无索引块,即由 i-addr[ ] 直接索引,那么该文件由两种大小的盘块组成;大块如 8KB、小块如 1KB;文件最后一块为小块大小的整数倍,其余用大块;fileL = n*block 大 + m* block 小

Unix/Linux 文件系统体系结构

UNIX 文件系统概述

  • 文件系统特点
    • 采用引入索引结点的分级树型目录组织结构
    • 文件物理结构为混合索引式文件结构
    • 采用成组链接法管理空闲盘块
    • 设立有根目录,并把文件看成字符流集合
    • 支持多种文件系统且用户接口抽象元素相同
  • 文件系统资源管理
    • 目录项、磁盘索引结点项及若干盘块
    • 内存索引结点项
    • (系统打开)文件表项、用户文件描述符表项
  • 打开文件的系统调用
    • fd = open(path, mode);
    • fd 通常是一个小整数,为进程打开文件表项的指针/索引

文件描述符表与文件表

文件物理结构类型 #

文件的逻辑结构和物理结构

  • 文件的逻辑结构
    • 包括字符流式文件和记录式文件
    • 描述文件的逻辑结构时应包含对文件的存取方法的定义
  • 文件的物理结构
    • 连续文件
    • 链接文件
    • 索引文件
    • 散列文件

文件物理结构——连续文件

  • 文件物理结构——连续文件
  • 文件物理结构——链接文件
  • 文件物理结构——索引文件

文件的结构组成

  • 一个文件由文件属性描述和文件体组成
    • 文件属性描述⬄文件控制块 FCB⬄目录项
  • Unix 内存索引结点 vnode =>Linux 通用 inode

混合索引文件结构与寻址方式

混合分配方式 (UNIX 系统)

  • 直接寻址
    • 直接地址项存放对应文件数据的盘块的盘块号
    • 盘块大小 4KB、盘块号占 4B,则支持长度在 4KB×10 = 40KB 以内的文件
  • 一次间接寻址
    • i.addr(10) 指向对应文件的一级索引块
    • 一级索引块可含 4KB/4B = 1K 个盘块号,故支持长度在 (4KB×1K=4MB)+40KB 以内的文件
  • 多次间接寻址
    • i.addr(11)、 i.addr(12) 分别指向对应文件的两级索引块和三级索引块,所以支持文件长度可达 (4KB×1K×1K ×1K=4TB)+(4KB×1K×1K=4GB)+4MB+40KB

UNIX 文件操作地址转换过程

  • 将字节偏移量转换为文件逻辑盘块号

    • LogicBlk# = Offset / SizeOfBlk
  • 将逻辑盘块号转换为物理盘块号

    • 确定物理盘块号所在地址项或索引盘块
    • 确定物理盘块号所在索引盘块及位置
  • 举例说明

    • Offset 9000B => LogicBlk# 8 => i.addr(8)
    • Offset 14000B => LogicBlk# 13 => 对应 i.addr(10) 的索引盘块中的第 3 个盘块号

索引结点管理

  • 超级块
    • 记录磁盘盘块和磁盘索引结点使用情况
    • 含空闲盘块号栈、空闲磁盘索引结点号栈及相应数目与锁字段、修改标志与时间等
  • 磁盘索引结点分配与回收
    • 分配过程 ialloc
    • 回收过程 ifree
  • 内存索引结点分配与回收
    • 分配过程 iget
    • 回收过程 iput

UNIX 文件卷组织结构

  • 系统引导块 0#
    • 用于系统引导或空闲
  • 超级块 1#
    • 文件系统结构信息 (盘块及磁盘索引结点)
  • 磁盘索引结点块 2# ~ K#
    • 存放磁盘索引结点
  • 文件数据块 (K+1)# ~ N#
    • 存放文件数据
假设盘块大小为1KB,索引结点占64字节,则讲索引结
点应位于的物理盘块号为:
i*64B/1KB + 2 = [i/16](向下取整) + 2

虚拟文件系统 #

Linux VFS

Linux VFS 实现机制

面向对象的框架

  • 基于 C 语言及其结构体类型实现
  • 每个对象包括数据及指向数据操作功能函数的指针两部分组成

Linux VFS 主要对象类型

  • 超级块对象:表示特定挂载的文件系统
    • 描述了特定文件系统的信息
      • 挂载文件系统的设备、文件系统基本块大小、脏标志、文件系统类型、访问权限、指向文件系统目录根的指针、已打开文件列表、用于控制文件系统的信号量、超级块操作功能函数指针列表
    • 通常对应存放在磁盘上特定扇区的文件系统超级块或文件系统控制块
    • read_inode、write_inode、put_inode、delete_inode、notify_change、put_super、write_super、statfs、remount_fs、clear_inode
  • 索引结点对象:表示特定文件
    • 描述了每个文件的除文件名和文件所含实际内容之外的所有关联信息
    • 基本信息组成
      • 所有者、用户组、访问权限、文件访问时间、所含数据量多少、链接数、索引结点操作功能函数指针列表
      • create:为普通文件创建索引结点以关联某目录的目录项对象
      • lookup:搜索目录以查找对应文件的索引结点
      • mkdir:创建新的目录型索引结点以关联某目录的目录项对象
  • 目录项对象:表示特定的目录项
    • 描述了文件路径中的特定部件,可为目录名、也可为文件名
      • 可用于目录项缓冲中以方便对文件和目录的访问
    • 基本信息组成
      • 指向索引结点和超级块的指针、指向父目录项的指针、指向各子目录项的指针
      • d_hash、d_compare、d_delete、d_iput、d_dname、d_release、d_init
  • 文件对象:表示与某进程关联的一个打开的文件
    • 描述了进程打开的一个文件
      • 该对象在执行 open() 系统调用时被创建,在执行 close() 系统调用时被销毁
    • 基本信息组成
      • 与文件关联的目录项对象、包含对应文件的文件系统、文件对象引用计数、用户的用户标识符、用户的组标识符、文件操作指针、文件对象操作功能函数指针列表
      • read、write、open、release、lock

目录管理及文件共享与保护 #

目录文件由对应目录下所有文件的目录项组成

文件控制块 = 目录项(文件名 +i 结点编号)+ i-node

特殊目录及路径名

  • 根目录
    • 目录层次结构最顶层;无父目录;由根用户拥有
  • 主目录(缩写方式“~”)
    • 用户根目录、登陆时进入;由系统管理员创建
  • 工作目录(缩写方式“.”)
    • 当前目录,是用户会话过程所处的目录
  • 绝对路径名与相对路径名
    • link 机制 => 一个文件可以对应多个绝对路径名
    • 文件合法命名规则
    • 文件操作时通配符:“?”、“[…]”、“*”

目录管理

  • 构造目录过程
    • creat => makenode ( ialloc => wdir )
  • 删除目录过程
    • link & unlink
  • 检索目录过程 namei
    • 根据指定路径名顺序搜索各级目录
    • 任一目录文件所有盘块均须查找
    • 找到对应目录文件名后,应继续调入其盘块
    • 检索成功与失败判断

文件共享 - 绕弯路法

基于基本文件目录实现文件共享 ext2

文件保护

  • 指根据不同的用户或进程对文件进行存取权限控制和保密控制

    • 支持合法用户执行所授权操作
    • 禁止用户执行非授权操作
    • 防止冒充其它用户非法存取文件
    • 防止合法用户误操作/错误使用文件
  • 关键在于访问控制描述及检查验证机制

    • 存放位置(集中专区、基本文件目录表项)
    • 访问控制矩阵或其简化

访问矩阵的简化策略

  • 必要性与可行性
    • 访问矩阵存储开销及其稀疏性特征
  • 简化对策
    • 访问控制表
    • 访问权限表
    • 兼有式实现机制
  • 横向级别
    • 文件主,同组用户,其他用户

文件内存映像及系统实现 #

UNIX/Linux 文件卷组织结构 #

  • 系统引导块 0#
    • 用于系统引导或空闲
  • 超级块 1#【struct super_block】
    • 文件系统结构信息(分区总容量、空闲盘块数及索引结点数等)
  • 磁盘索引结点块区 2# ~ K#
    • 存放磁盘索引结点【struct ext2_inode】
  • 文件数据块区 (K+1)# ~ N#
    • 存放文件数据

UNIX/Linux 文件系统数据结构 #

UNIX 超级块结构 filsys

struct filsys
{
 int s-isize;		 //inode区占用盘块数
 int s-fsize;		 //磁盘块数
 int s-nfree;		 //直接管理的一般存储盘块空闲块数
 int s-free[100];		 //空闲盘块索引表暨空闲盘块号栈
 int s-ninode;		 //直接管理的空闲索引结点inode数
 int s-inode[100];	 //空闲索引结点inode的索引表暨空闲索引结点号栈
 char s-flock;		 //空闲盘块号栈的上锁标志
 char s-ilock;		 //空闲索引结点号栈的上锁标志
 char sfmod; 		//本信息块已被修改标志
 char s-ronly;		 //本文件系统只能读出的标志
 int s-time[2];		 //最近一次更新时间
 int pad[50];
}; 

Linux 文件系统信息查看命令 #

mount

文件系统信息查看命令2-2B——考试着重考计算

8192*256/4096
= 2^13 * 2^8 / 2^12
= 2^9
= 512
盘块组数:
12M/32768
= 12*2^20 / 2^15
= 12*2^5
= 384

Linux 目录项及索引结点结构 #

  • 目录项【struct dentry】
    • 文件名、索引结点指针
  • 外存索引结点【struct ext2_inode】
    • 文件主标识符、存取权限、访问时间、链接计数、物理盘块数、物理盘块号数组(混合索引结构)
  • 内存索引结点
    • 存取权限、访问时间、设备号、等等【struct inode】
    • 外存索引结点的内容【struct ext2_inode_info】

Linux 进程控制块与文件打开表 #

进程控制块【struct task_struct】

  • 文件打开表指针、……

文件打开表【struct files_struct】

  • 文件描述符表、文件描述符表指针、文件控制块指针数组、……

文件描述符表【 struct fd_struct】

  • 文件控制块二级指针

文件控制块【 struct file】

  • 文件名、索引结点指针、存取权限、文件读写指针、……
//zgs/linux-4.8.8/include/linux/sched.h
//进程控制块

struct task_struct {
	volatile long state;
	atomic_t usage;
	unsigned int flags;
	unsigned int ptrace;
……
	struct fs_struct *fs;
	struct files_struct *files;
……
};
//zgs/linux-4.8.8/include/linux/fdtable.h
//每进程文件打开表

struct files_struct {
……
	struct fdtable __rcu *fdt;
	struct fdtable fdtab;
……
	int next_fd;
	unsigned long close_on_exec_init[1];
	unsigned long open_fds_init[1];	
	unsigned long full_fds_bits_init[1];
	struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
//zgs/linux-4.8.8/include/linux/fdtable.h
//文件描述符表

struct fdtable {
	unsigned int max_fds;
	struct file __rcu **fd; 
	unsigned long *close_on_exec;
	unsigned long *open_fds;
	unsigned long *full_fds_bits;
	struct rcu_head rcu;
};
//zgs/linux-4.8.8/include/linux/fs.h //文件控制块结构
struct file {
	union {
		struct llist_node	fu_llist;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
	struct inode		*f_inode;
	const struct file_operations	*f_op;
 	spinlock_t		f_lock;
	atomic_long_t		f_count;
	unsigned int 		f_flags;
	fmode_t			f_mode;
	struct mutex		f_pos_lock;
	loff_t			f_pos;
	struct fown_struct	f_owner;
	const struct cred	*f_cred;
	struct file_ra_state	f_ra;
	u64			f_version;
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
……
	struct address_space	*f_mapping;
} __attribute__((aligned(4)));

Linux 虚拟文件系统实现概要 #

基于 C 的面向对象实现方法

Linux 文件操作相关系统调用 #

文件创建系统调用例程

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

int main()
{
 int fd = creat("FileCreated.txt", 0777);
 return 0;
}

//文件FileCreated.txt存取权限:-rwxrwxr-x
// <= 0777 & ~0002

creat 系统调用基本用法

  • int creat(const char *pathName, mode_t mode);
  • 返回值为文件描述符 fd(一般 >=3;失败 -1)
  • mode 为所创建文件的存取权限期望设置

文件实际的存取权限 = (~mask & mode)

  • mask 是由 umask 命令规定的文件掩码
  • mode 一共 12 个二进制位,前三位分别表示 SUID(设置用户标识符位)、SGID(设置用户组标识符位)、sticky(粘附位)
  • 适用于特殊可执行文件(譬如密码修改程序/usr/bin/passwd)针对非授权用户临时提权执行的场合。若对文件设置了 SUID 或 SGID,则运行此文件的进程将拥有与文件所有者/属组相同的操作权限

进程的真实/有效用户标识符

  • 进程控制块
struct task_struct {
 ……
 const struct cred __rcu *real_cred;
 const struct cred __rcu *cred;
 …… 
 struct nsproxy *nsproxy;
 ……
};
struct cred {
 ......
 kuid_t	uid;
 kgid_t	gid;
 kuid_t	suid;
 kgid_t	sgid;
 kuid_t	euid;
 kgid_t	egid;
 kuid_t	fsuid;
 kgid_t	fsgid;
 ......
 struct user_struct *user;
 struct user_namespace *user_ns;  
 ......
};

umask 命令及使用效果

  • 无参方式用于显示当前掩码
    • 一个三位八进制数
    • 加选项 -S 则显示创建后所取得的操作权限
  • 有参方式用于设置掩码
    • umask [三位八进制数]
  • 缺省未指定存取权限下的掩码效果
    • 所创建目录(mkdir):~mask
    • 所创建文件(touch):~mask 并移除所有执行权

文件打开/关闭 - 系统调用

  • 关于文件打开的系统调用
    • 存取模式:O_RDONLY、O_WRONLY、O_RDWR
    • 文件创建标志:O_CLOEXEC、O_DIRECTORY、O_CREAT、O_EXCL、O_NOCTTY、O_NOFOLLOW、O_TMPFILE、O_TRUNC
    • 文件状态标志:O_APPEND
    • 没有指定任何模式,文件创建采用缺省存取模式,即 110 100 100 000
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
  • 关于文件关闭的系统调用
    • 如果文件不存在,创建该文件并使用 mode 参数来限定文件的实际访问权限: ~umask & mode
#include <unistd.h>
int close(int fd);

文件硬链接 - 系统调用

  • 关于文件硬链接的系统调用
#include <unistd.h>
int link(const char *pathnameS, const char *pathnameD);
  • 关于文件链接移除的系统调用
#include <unistd.h>
int unlink(int fd);

关于文件读/写的系统调用

#include <unistd.h>

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

文件读写指针移动 - 系统调用

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

SEEK_SET  0
SEEK_CUR  1
SEEK_END  2

Linux 管道通信相关系统调用 #

UNIX/Linux 管道通信

  • UNIX 提供的进程通信方式

    • 核心态进程之间:使用 sleep/wakeup 实现同步
    • 同一用户的进程之间:使用信号量 (P/V)、消息缓冲区、邮箱、共享存储区实现进程间通信
    • 父子进程之间:通过系统调用 wait/exit 实现同步
    • 任意两个进程之间:管道提供大量信息传输同步
  • 管道通信

系统调用 pipe 及系统命令 mknod

  • 用于创建无名管道的系统调用
#include <unistd.h>
int pipe(int pipefd[2]);
pipefd[0]用于读管道,pipefd[1]用于写管道
成功返回0,出错返回-1
  • 用于创建管道文件的系统命令
    • mknod 用于创建字符设备、块设备等特殊文件;p 则指定其创建 FIFO 管道文件。
mknod <管道文件名> p
mknod zgsPipe p
  • 管道文件读写同步与互斥
    • 为了协调双方的通信,管道通信机制必须提供同步、互斥和判断对方是否存在等三方面的能力
    • 从原理上讲,进程之间通信如果使用同一文件描述符,通信过程应由用户协调;但如果使用两个文件描述符,则通信过程可由系统协调

0777文件权限的解释 - 掘金

关于linux下0666和0777权限所代表的意思_权限0777-CSDN博客

//有名管道例程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
int main() 
{
 char zPipeName[10] = "zgs-pipe";
 char zFileName[15] = "DataFile.txt";
 char buf[PIPE_BUF + 1];
 int fdPipe, fdData, zRead, zWrite, zSend = 0;
 if (access(zPipeName, F_OK) == -1)
 {if (mkfifo (zPipeName, S_IFIFO|0666) == -1)
  {
   printf("Failed to create pipe[%s] by mkfifo()!\n", zPipeName);
   exit(0);
 }}
fdPipe = open(zPipeName, O_WRONLY);
 fdData = open(zFileName, O_RDONLY);
 printf("Process[%d] open pipe[%s] in WriteOnly mode\n", getpid(), zPipeName);
 if (fdPipe!=-1 && fdData!=-1)
 {while ((zRead = read(fdData, buf, PIPE_BUF))>0)
  {buf[zRead] = '\0';
   if ((zWrite = write(fdPipe, buf, zRead))==-1)
   {printf("Failed to write pipe[%s]!\n", zPipeName);
    exit(0);
   }
   zSend += zWrite;
  }
  close(fdPipe);   close(fdData);
 }
 printf("Process[%d] finished writting %d bytes.\n", getpid(), zSend);
 return 0;
}

int main() 
{char zPipeName[10] = "zgs-pipe";
 char buf[PIPE_BUF + 1];
 int fdPipe, zRead, zReceive = 0;
 memset(buf, '\0', sizeof(buf));
 fdPipe = open(zPipeName, O_RDONLY);
 printf("Process[%d] open pipe[%s] in ReadOnly mode\n", getpid(), zPipeName);
 printf("The following are contents that Process[%d] have read:\n", getpid());
 if (fdPipe!=-1)
 {
   while ((zRead = read(fdPipe, buf, PIPE_BUF))>0)
  {   buf[zRead] = '\0';    printf("%s", buf);    zReceive += zRead;   }
  close(fdPipe);
 }
 printf("Process[%d] finished reading %d bytes.\n", getpid(), zReceive);
 return 0;
}