信息安全系统设计与实现:第七、八章学习笔记

信息安全系统设计与实现:第七、八章学习笔记

20191331 lyx

教材学习内容总结

第七章 文件操作

学习目标

学习了解操作系统中的各个操作级别,文件I/O操作,低级文件操作,EXT2/3文件系统。

文件操作级别

(1) 硬件级别
+ fdisk:将硬盘、u盘h或SDC盘分区
+ mkfs:格式化磁盘分区,为系统做好准备
+ fsck:检查和维修系统
+ 碎片整理:压缩文件系统中的文件

(2) 操作系统内核中的文件系统函数

操作系统内核为基本文件操作提供支持。

书上举出的类Unix系统内核的文件操作函数。

kumount(),kumount()                  (mount/umount file systems)
kmkdir(),krmdir()                    (make/remove directory)
kchair(),kgetCwd()                   (change directory,get CWD pathname)
klink(),kunlink()                    (hard link/unlink files)
kchmod(),kchown(),kutime()           (change r|w|x permissions,owner,time)
kcreat(),kopen()                     (create/open file for R,W,RW,APPEND)
kread(),kwrite()                     (read/write opened files)
klseek(),kclose()                    (Lseek/close file descriptors)
keymlink(),kreadlink ()              (create/read symbolic 1ink files)
kstat(),kfstat(),klatat()            (get file status/information)
kopendir(),kreaddir()                (open/read directories)

其作用

  • 用户:主要是普通程序员,通过api的方式从硬盘存取数据,如open、write、read、lseek。

  • 便捷性:

    • 易用性:如果要满足大众化的需求,存储和获取api的学习成本要足够低,什么二进制、扇区号不要在应用层出现。

    • 高效性:三个方面,一是单次操作的响应时间要足够短;二个是大批量文件存储的吞吐量要足够的大,但前提是在一定的硬件条件下,希望把硬件能力尽可能的发挥出来;三是存储空间的高效利用,如果1G的硬盘只能存储500M文件,50%的有效利用率是不会被接受的。

  • 安全性:需要在各种情况下(如掉电)保证一致性,即对于write success的信息需要保保证落盘,重启时需要可见;对于在write过程中发生意外情况的请求,重启时能自动恢复,不能损坏已有数据。

  • 适配各种标准的磁盘:用户可能根据价格、性能、容量、可靠性选择不同类型的磁盘,但用户是希望统一的操作界面,没有额外的学习成本。

抽象分层

(3) 系统调用

用户模式程序可以通过系统调用来访问内核函数。

通过man -k flie | grep 2 可以查看与文件操作有关的系统调用。

(4) I/O库函数

常用的I/O库函数。

功能 函数名 适用于
字符输入函数 getchar 标准输入流
字符输出函数 putchar 标准输出流
字符输入函数 fgetc 所有输入流
字符输出函数 fputc 所有输出流
文本行输入函数 fgtes 所有输入流
文本行输出函数 fputs 所有输出流
格式化输入函数 scanf 标准输入流
格式化输出函数 printf 标准输出流
格式化输入函数 fscanf 所有输入流
格式化输出函数 fprintf 所有输出流
二进制输入 fread 文件
二进制输出 fwrite 文件

I/O库函数建立在系统调用之上,将对数据块的操作屏蔽,以达到更友好的用户接口。

(5) 用户命令

用户可以使用命令进行文件操作,简单便捷。

例如常常使用的:mkdir cd rm cat mv 等

(6) sh脚本

通过脚本语言实现批量的文件操作,以及有条件的文件操作。

文件I/O操作

文件I/O操作分为用户模式和内核模式操作

用户模式

用户模式下的程序执行操作

FILE *p = fopen("file", "r"); or FILE *p = fopen( "file", "w")
可以打开一个读/写文件流。

fopen()在用户(heap)空间中创建一个FILE结构体,包含一个文件描述符fd、一个fbuf[BLKSIZE]和一些控制变量。它会向内核中的kopen()发出一个fd = open("file",flags=READ or WRITE)系统调用,构建一个OpenTable来表示打开文件示例。OpenTable的mptr指向内存中的文件INODE。对于非特殊文件,INODE 的i_block数组指向存储设备上的数据块。成功后,fp会指向FILE结构体,其中fd是open()系统调用返回的文件描述符。
 
fread(ubuf, size,nitem, fp):将nitem个size字节读取到ubuf上,通过:
将数据从FILE结构体的fbuf上复制到ubuf上,若数据足够、则返回。
如果fbuf没有更多数据,则执行(4a)。

发出read(fd, fbuf, BLKSIZE)系统调用,将文件数据块从内核读取到fbuf上,然后将数据复制到ubuf上,直到数据足够或者文件无更多数据可复制。

fwrite(ubuf, size, nitem, fp):将数据从ubuf复制到 fbuf。

若(fbuf有空间):将数据复制到fbuf上,并返回。

若(fbuf已满):发出 write(fd, fbuf, BLKSIZE)系统调用,将数据块写入内核,然后再次写入fbuf。

这样,fread()/fwrite()会向内核发出read(/write)系统调用,但仅在必要时发出,而且它们会以块集大小来传输数据,提高效率。同样,其他库I/O函数,如 fgetc/fputc、fgets/fputs、fscanf/fprintf等也可以在用户空间内的FILE结构体中对fbuf进行操作。

内核模式

内核中的文件系统函数:

假设非特殊文件的read(fd, fbuf[], BLKSIZE)系统调用。

在read()系统调用中,fd是一个打开的文件描述符,它是运行进程的fd数组中的一个索引,指向一个表示打开文件的 OpenTable。

OpenTable包含文件的打开模式、一个指向内存中文件 INODE的指针和读/写文件的当前字节偏移量。从OpenTable的偏移量,

计算逻辑块编号lbk。

通过INODE.i_block[]数组将逻辑块编号转换为物理块编号blk 。

Minode包含文件的内存INODE。EMODE.i_block[]数组包含指向物理磁盘块的指针。文件系统可使用物理块编号从磁盘块直接读取数据或将数据直接写入磁盘块,但将会导致过多的物理磁盘I/O。

为提高磁盘VO效率,操作系统内核通常会使用一组I/O缓冲区作为高速缓存,以减少物理I/O的数量。

对于read(fd, buf, BLKSIZE)系统调用,要确定所需的(dev, blk)编号,然后查询I/O缓冲区高速缓存。

对于write(fd, fbuf, BLKSIZE)系统调用,要确定需要的(dev, blk)编号,然后查询IO缓冲区高速缓存。

低级别文件操作

分区:一个块存储设备,如硬盘、u盘、SD卡等,可以分为几个逻辑单元,称为分区。

各分区均可以格式化为特定的文件系统,也可以安装在不同的操作系统上。

MBR:MBR 共占用了一个扇区,也就是 512 Byte。其中 446 Byte 安装了启动引导程序,其后 64 Byte 描述分区表,最后的 2 Byte 是结束标记。为什么每块硬盘只能划分 4 个主分区,原因就是在 MBR 中描述分区表的空间只有 64 Byte。其中每个分区必须占用 16 Byte,那么 64 Byte 就只能划分 4 个主分区。

MBR 中最主要的功能就是存储启动引导程序。

在linux中创建并挂载分区

虚拟磁盘映像创建

查看现有分区

在刚创建的磁盘映像文件上运行fdisk

进行分区操作并同步

使用losetup将该文件映射到loop设备

sudo losetup /dev/loop0 hello.img

使用 fdisk -l 查看创建好的虚拟磁盘映像

格式化(创建文件系统)

mkfs.ext4 -q /dev/loop0p1

完成

EXT2文件系统

文件系统中存储的最小单元是块(block),一个块的大小是在格式化时确定的。启动块(Boot Block)的大小为1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。

启动块之后才是ext2文件系统的开始,ext2文件系统将整个分区划分成若干个同样大小的块组(Block Group)。

在整体的规划当中,文件系统最前面有一个启动扇区(boot sector),这个启动扇区可以安装启动管理程序, 这是个非常重要的设计,因为如此一来我们就能够将不同的启动管理程序安装到个别的文件系统最前端,而不用覆盖整颗硬盘唯一的 MBR 。

每个块组的组成:

  • 超级块(Super Block)描述整个分区的文件系统信息,如inode/block的大小、总量、使用量、剩余量,以及文件系统的格式与相关信息。超级块在每个块组的开头都有一份拷贝(第一个块组必须有,后面的块组可以没有)。 为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的super block信息在这种情况下也能正常访问。所以一个文件系统的super block会在多个block group中进行备份,这些super block区域的数据保持一致。

    • 超级块记录的信息有:

    • 1、block 与 inode 的总量(分区内所有Block Group的block和inode总量);

    • 2、未使用与已使用的 inode / block 数量;

    • 3、block 与 inode 的大小 (block 为 1, 2, 4K,inode 为 128 bytes);

    • 4、filesystem 的挂载时间、最近一次写入数据的时间、最近一次检验磁盘 (fsck) 的时间等文件系统的相关信息;

    • 5、一个 valid bit 数值,若此文件系统已被挂载,则 valid bit 为 0 ,若未被挂载,则 valid bit 为 1 。

每个区段与 superblock 的信息都可以使用 dumpe2fs 这个命令查询

  • 块组描述符表(GDT,Group Descriptor Table)由很多块组描述符组成,整个分区分成多个块组就对应有多少个块组描述符。

每个块组描述符存储一个块组的描述信息,如在这个块组中从哪里开始是inode Table,从哪里开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有一份拷贝。

  • 块位图(Block Bitmap)用来描述整个块组中哪些块已用哪些块空闲。块位图本身占一个块,其中的每个bit代表本块组的一个block,这个bit为1代表该块已用,为0表示空闲可用。假设格式化时block大小为1KB,这样大小的一个块位图就可以表示1024*8个块的占用情况,因此一个块组最多可以有10248个块。

  • inode位图(inode Bitmap)和块位图类似,本身占一个块,其中每个bit表示一个inode是否空闲可用。 Inode bitmap的作用是记录block group中Inode区域的使用情况,Ext文件系统中一个block group中可以有16384个Inode,代表着这个Ext文件系统中一个block group最多可以描述16384个文件。

  • inode表(inode Table)由一个块组中的所有inode组成。一个文件除了数据需要存储之外,一些描述信息也需要存储,如文件类型,权限,文件大小,创建、修改、访问时间等,这些信息存在inode中而不是数据块中。inode表占多少个块在格式化时就要写入块组描述符中。

在Ext2/Ext3文件系统中,每个文件在磁盘上的位置都由文件系统block group中的一个Inode指针进行索引,Inode将会把具体的位置指向一些真正记录文件数据的block块,需要注意的是这些block可能和Inode同属于一个block group也可能分属于不同的block group。我们把文件系统上这些真实记录文件数据的block称为Data blocks。

  • 数据块(Data Block)是用来放置文件内容数据的地方。

注意到OpenEuler操作系统内核也使用EXT2作为文件系统

其结构为

文件描述符与打开文件之间的关系

在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。

每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也有可能指向不同的文件。具体情况要具体分析,要理解具体其概况如何,需要查看由内核维护的3个数据结构。

    1. 进程级的文件描述符表
    1. 系统级的打开文件描述符表
    1. 文件系统的i-node表

文件描述符、打开的文件句柄以及i-node之间的关系

可以通过以下程序来查看文件描述符

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

int main(){
        int fd1,fd2;
        fd1 = open("test_io.txt", O_RDONLY,0);
        int res = close(fd1);
        printf("fd1 = %d\n" , fd1);
        printf("close1 = %d\n" , res);
        res = close(fd1);
        printf("fd1' = %d\n" , fd1);
        printf("close2 = %d\n" , res);
        fd2 = open("t.txt" , O_RDONLY, 0);
        printf("fd2 = %d\n" , fd2);
        exit(0);
}

需要注意的是,关闭一个已关闭的描述符会出错,如:

文件流缓冲理解

我们可以简单的认为系统I/O调用是unbuffered I/O,而C语言标准库I/O函数(即stdio函数)是buffered I/O。

但是需要注意的是:认为使用unbuffered I/O读写磁盘时没有使用缓冲是一个很大的错误认识,实际上,内核是通过高速缓存(kernel buffer cache)来进行实际的磁盘读写的。也就是说,read()和write()系统调用在操作磁盘文件时通常也不会直接发起磁盘访问,而是仅仅在用户空间缓冲区(程序中申请的内存空间)与内核高速缓存之间复制数据。采用这一设计的目的是使read()和write()调用更为快速,减少实际的磁盘访问,因为磁盘访问是很费时的。

而buffered I/O提供的缓冲区工作在用户空间,其存在的目的无疑也是加快程序的效率,主要体现在两个方面

  1. 减少read()和write()的调用次数 -- 因为频繁地调用系统调用对应用程序来说是很大的效率损失。

  2. 可以块对齐(block-align)地操作磁盘, -- 因为现在的文件系统和磁盘大都是以“块”为操作单位,所以遵循对齐原则肯定能加速程序速度

因此C语言标准库提供了stdio函数,这些函数提供了stdio缓冲区,那么使用这些函数即提高了程序的效率,又帮助我们免了自行处理数据缓冲的麻烦。

第八章 使用系统调用进行文件操作

学习目标

了解系统调用,理解使用常用系统调用,stat系统调用,write系统调用,open-close-lseek系统调用,链接文件。

系统调用

在操作系统中,进程有两种模式:

内核 (Kmode)
用户 (Umode)

系统调用(简称syscall)是一种允许进程进入Kmode以 执行Umode不允许操作的机制

复刻子进程、修改执行映像.甚至是终止等操作都必须在内核中执行。

系统调用(简称syscall)是一种允许进程进入Kmode以 执行Umode不允许操作的机制。

系统调用手册

过Unix I/O函数打开/关闭文件,以及读/写文件

总结open/close/read/write函数的用法:

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

int open(char *filename, int falgs, mode_t mode); 
/*	成功则返回新文件描述符,出错返回-1;
	char *filename:函数将filename转换为一个文件描述符,并返回描述符数字;返回的描述符总是在进程中当前没有打开的最小描述符;
	int flags:指明进程打算如何访问这个文件;
	mode_t mode:指定了新文件的访问权限位。
*/

int close(int fd);
/*	成功则返回0,出错则为-1。 */

ssize_t read(int fd, void *buf, size_t n);
/*	成功则返回读的字节数,若EOF则为0,若出错则为-1。  */

ssize_t write(int fd, const void *buf, size_t n);
/*	成功则返回写的字节数,若出错则为-1。  */

使用系统调用进行文件操作

access:检査对某个文件的权限
int access(char •pathname, int mode);
chdir:更改目录
int chdir(const char *path);
chmod:更改某个文件的权限
int chmod(char *path, mode_t mode);
chown:更改文件所有人
int chown(char *name, int uid, int gid);
chroot:将(逻辑)根目录更改为路径名
int chroot (char *patiiname);
getcwd:获取CWD的绝对路径名
char *getcwd(char *buf, int size);
mkdir:创建目录
int mkdir(char *pathname, mode_t mode);
rmdir:移除目录(必须为空)
int rmdir (char *pathname);
link:将新文件名硬链接到旧文件名
int link(char *oldpath, char *newpath);
unlink:减少文件的链接数;如果链接数达到0,则删除文件
int uniink(char *pathname);
symlink:为文件创建一个符号链接
int symliak(char *oldpath, char *newpath);
rename:更改文件名称
int rename(char *oldpath, char *newpath)/
utime:更改文件的访问和修改时间
int utime(char *pathname, struct utimebuf *time)
以下系统调用需要超级用户权限。
mount:将文件系统添加到挂载点目录上
int mount(char *specialfile, char *mountDir)/
umount:分离挂载的文件系统
int umount(char *dir);
mknod:创建特殊文件
int mknod(char *path, int mode, int device);

常用系统调用

文件操作读写:

函数 作用
fcntl 文件控制
open 打开文件
creat 创建新文件
close 关闭文件描述字
read 读文件
write 写文件
readv 从文件读入数据到缓冲数组中
writev 将缓冲数组里的数据写入文件
pread 对文件随机读
pwrite 对文件随机写
lseek 移动文件指针
_llseek 在64位地址空间里移动文件指针
dup 复制已打开的文件描述字
dup2 按指定条件复制文件描述字
flock 文件加/解锁
poll I/O多路转换
truncate 截断文件
ftruncate 参见truncate
umask 设置文件权限掩码
fsync 把文件在内存中的部分写回磁盘

文件系统操作:

函数 作用
stat 取文件状态信息
lstat 参见stat
fstat 参见stat
statfs 取文件系统信息
fstatfs 参见statfs
readdir 读取目录项
getdents 读取目录项
mkdir 创建目录
mknod 创建索引节点
rmdir 删除目录
rename 文件改名
link 创建链接
symlink 创建符号链接
unlink 删除链接
readlink 读符号链接的值
mount 安装文件系统
umount 卸下文件系统
ustat 取文件系统信息
utime 改变文件的访问修改时间
utimes 参见utime
quotactl 控制磁盘配额

文件类型和权限

文件类型

首位字母 解释
‘-’ 普通文件f
d 目录文件directory
l 符号链接文件link 软链接 快捷方式
b 块文件block 随机读,随机存的设备 (光驱光盘,硬盘) 表示为装置文件里面的可供储存的接口设备(可随机存取装置);
c 字符设备character 顺序存取设备-键盘鼠标 表示为装置文件里面的串行端口设备,例如键盘、鼠标(一次性读取装置)
s 套接字文件socket ip port
p 管道文件pipe

权限类型

代表字符 权限 对文件的含义 对目录的含义
r 读权限 可以读文件的内容 可以列出目录中的文件列表
w 写权限 可以修改该文件 可以在目录中创建删除文件
x 执行权限 可以执行该文件 可以使用cd命令进入该目录

权限数字表示法

每个数字都是是由r=4;w=2;x=1相加的结果得出来的

-rw------- (600) – 只有属主有读写权限。
-rw-r–r-- (644) – 只有属主有读写权限;而属组用户和其他用户只有读权限。
-rwx------ (700) – 只有属主有读、写、执行权限。
-rwxr-xr-x (755) – 属主有读、写、执行权限;而属组用户和其他用户只有读、执行权限。
-rwx–x--x (711) – 属主有读、写、执行权限;而属组用户和其他用户只有执行权限。
-rw-rw-rw- (666) – 所有用户都有文件读、写权限。这种做法不可取。
-rwxrwxrwx (777) – 所有用户都有读、写、执行权限。更不可取的做法。

使用系统调用实现 ls-l

代码以托管至码云:https://gitee.com/DKY2019/xxaqxt/blob/master/myls_l.c

参考资料

Linux内核浅析-文件系统 https://zhuanlan.zhihu.com/p/61123802

缓冲区的理解 https://blog.csdn.net/astrotycoon/article/details/44993197

详解MBR http://c.biancheng.net/view/1015.html

详解EXT2 https://blog.csdn.net/gongjiwei/article/details/82025142

OpenEuler内核源代码 https://gitee.com/openeuler/kernel

linux常用系统调用 https://www.cnblogs.com/shijiaqi1066/p/5749030.html

linux文件类型和权限 https://blog.csdn.net/weixin_45160969/article/details/98333590





20191331lyx
2021/10/10

posted @ 2021-10-10 01:25  20191331liyu  阅读(209)  评论(0编辑  收藏  举报