20201206韩进学习笔记4

文件操作

文件操作级别

  • 硬件级别
    fdisk、mkfs、fsck、碎片整理。

  • 操作系统内核中的文件系统函数
    每个操作系统内核均可为基本文件操作提供支持。

  • 系统调用
    用户模式程序使用系统调用来访问内核函数;
    open()、read()、lseek()和close()函数都是C语言库函数,每个库函数都会发出一个系统调用,使进程进入内核模式来执行相应的内核函数;进程函数结束执行内核函数时,会返回到用户模式,得到所需结果。

  • I/O库函数
    系统调用可让用户读/写多个数据块,这些数据块只是一系列字节。

除了读/写内存位置的sscanf()/sprintf()函数之外,所有其他I/O库函数都建立在系统调用之上,也就是说,他们最终会通过系统内核发出的实际数据传输的系统调用。

  • 用户命令
    用户可以使用Unix/Linux命令来执行文件操作,而不是编写程序。

mkdir,rmdir,cd,pwd,ls,link,unlink,rm,cat,cp,mv,chmod,etc.

  • sh脚本
    虽然比系统调用方便得多,但是必须要手动输入命令,如果使用的是GUI,必须要拖放文件图标和点击指向设备来输入,操作烦琐而且耗时。

文件I/O操作

  • 内核空间
  1. 程序执行操作
    FILE *fp = fopen("file", "r");
    FILE *fp = fopen("file", "w");
    可以打开一个读/写文件流。

  2. 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()系统调用返回的文件描述符。

  3. fread(ubuf,size,nitem,fp):将nitem个size字节读取到ubuf上,通过:
    (1) 将数据从FILE结构体的fbuf上复制到ubuf上,若数据足够,则返回。
    (2) 如果fbuf没有更多数据,则执行(4a)。

    • (4a)发出read(fd, fbuf, BLKSIZE)系统调用,将文件数据块从内核读取到fbuf上,然后将数据复制到ubuf上,直到数据足够或者文件无更多数据可复制。
    • (4b)fwrite(ubuf,size,nitem,fp):将数据从ubuf复制到fbuf。
      • 若(fbuf有空间):将数据复制到fbuf上,并返回。
      • 若(fbuf已满):发出 write(fd,fbuf,BLKSIZE)系统调用,将数据块写入内核,然后再次写入fbuf。
  • 用户空间
  1. 内核中的文件系统函数:
    假设非特殊文件的read(fd, fbuf[ ], BLKSIZE)系统调用。
  2. 在read()系统调用中,fd是一个打开的文件描述符,它是运行进程的fd数组中的一个索引,指向一个表示打开文件的OpenTable。
  3. OpenTable包含文件的打开模式、一个指向内存中文件 INODE的指针和读/写文件的当前字节偏移量。从 OpenTable的偏移量,
    • 计算逻辑块编号1bk。
    • 通过INODE.i_block[ ]数组将逻辑块编号转换为物理块编号blk。
  4. Minode包含文件的内存 INODE。EMODE.i_block[ ]数组包含指向物理磁盘块的指针。文件系统可使用物理块编号从磁盘块直接读取数据或将数据直接写入磁盘块,但将会导致过多的物理磁盘I/O。
  5. 为提高磁盘I/O效率,操作系统内核通常会使用一组I/O缓冲区作为高速缓存,以减少物理I/O的数量。
  • (5a)对于read(fd, buf, BLKSIZE)系统调用,要确定所需的( dev, blk)编号,然后查询I/O缓冲区高速缓存,以执行以下操作:
.get a butter = (dev, blk);
.if(butter's data are invalid){
start_io on butter;
wait for I/O completion;
}
.copy data from butter to fbuf;
.release butter to butter cache;
  • (5b)对于write(fd,fbuf,BLKSIZE)系统调用,要确定需要的(dev,blk)编号,然后查询I/O缓冲区高速缓存,以执行以下操作:
.get a butter = (dev, blk);
.write data to the I/O butter;
.mark buffer as dataValid and DIRTY (for delay-write to disk);
.release the butter to butter cache;
  1. 设备IO:I/O缓冲区上的物理I/O最终会仔细检查设备驱动程序,设备驱动程序由上半部分的start_io()和下半部分的磁盘中断处理程序组成。

低级别文件操作

  • 分区
    一个块存储设备,如硬盘、U盘、SD卡等,可以分为几个逻辑单元,称为分区;
    分区表位于第一个扇区的字节偏移446(0x1BE)处,该扇区称为设备的主引导记录。

  • 格式化分区
    fdisk只是将一个存储设备划分为多个分区。每个分区都有特定的文件系统类型,但是分区还不能使用。为了存储文件,必须先为特定的文件系统准备好分区。该操作习惯上称为格式化磁盘或磁盘分区。

  • 挂载分区
    man 8 losetup : 显示用于系统管理的losetup实用工具命令。

EXT2文件系统简介

  • EXT2文件系统数据结构
    在Linux下创建一个包含简单EXT2文件系统的虚拟磁盘
(1) . dd if=/dev/zero of=mydisk bs=1024 count=1440
(2) . mke2fs -b 1024 mydisk 1440

得到的EXT2文件系统有1440个块,每个块大小为1KB。

  • 超级块
    s_first_data_block:用于确定块组描述符的起始块。
    s_log_block_size:确定文件块大小。
    s_mnt_count :已挂载文件系统的次数。
    s_magic:表示文件系统类型的幻数。

  • 块组描述符
    EXT2将磁盘块分成几个组,每个组都有8192个块,每组用一个块组描述符结构体描述。

  • 位图

  1. 块位图
    表示某种项的位序列(磁盘块或索引节点)。
  2. 索引节点位图
    代表一个文件的数据结构。
  • 索引节点
    索引节点块:
    每个文件都用一个128字节的独特索引节点结构体表示。

  • 目录条目
    包含dir_entry结构

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

系统调用

  • 内核模式(Kmode)
    复刻子进程、修改执行映像、终止等操作都在内核执行。
  • 用户模式(Umode)
    权限非常有限。

通过系统调用来执行Umode不允许操作的机制。

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

  • 简单的系统调用
  1. access:检查对某个文件的权限
    int access(char *pathname,int mode) ;

  2. chdir:更改目录
    int chdir(const char *path);

  3. chmod:更改某个文件的权限
    int chmod(char *path,mode_t mode) ;

  4. chown:更改文件所有人
    int chown (char *name, int uid, int gid);

  5. chroot:将(逻辑)根目录更改为路径名
    int chroot(char *pathname ) ;

  6. getcwd:获取cWD的绝对路径名
    char *getcwd(char *buf, int size);

  7. mkdir :创建目录
    int mkdir(char *pathname,mode_t mode) ;

  8. rmdir:移除目录(必须为空)
    int rmdir(char *pathname );

  9. link:将新文件名硬链接到旧文件名
    int link(char *oldpath, char *newpath) ;

  10. unlink:减少文件的链接数;如果链接数达到0,则删除文件
    int unlink (char *pathname);

  11. symlink:为文件创建一个符号链接
    int symlink(char *oldpath,char *newpath);

  12. rename:更改文件名称
    int rename (char *oldpath, char *newpath);

  13. utime:更改文件的访问和修改时间
    int utime(char *pathname, struct utimebuf *time)

以下系统调用需要超级用户权限

  1. mount:将文件系统添加到挂载点目录上
    int mount(char *specialfile,char *mountDir);

  2. umount:分离挂载的文件系统
    int umount (chaz *dir);

  3. mknod:创建特殊文件
    int mknod(char *path, int mode,int device);

常用的系统调用

  1. stat:获取文件状态信息
    int stat (char*filename, struct stat *buf)
    int fstat(int tiledes,struct stat buf)
    int lstat (char
    至ilename, struct stat *buf)

  2. open:打开一个文件进行读、写、追加
    int open(char *file, int flags, int mode)

  3. close:关闭打开的文件描述符
    int close(int fd)

  4. read:读取打开的文件描述符
    int read(int fd,char buf[ ], int count)

  5. write:写入打开的文件描述符
    int write(int fa,char buf[ 1, int count)

  6. lseck:重新定位文件描述符的读/写偏移量
    int lseek(int fa, int offset,int whence)

  7. dup:将文件描述符复制到可用的最小描述符编号中
    int dup(int oldrd);

  8. dup2:将oldfd复制到newfd中,如果newfd已打开,先将其关闭
    intdup2(int oldfd, int newfd)

  9. link:将新文件硬链接到旧文件
    int link ( char *o1dPath, char *newPath)

  10. unlink:取消某个文件的链接;如果文件链接数为0,则删除文件
    int unlink(char *pathname) ;

  11. symlink:创建一个符号链接
    int symlink(char *target, char *newpath)

  12. readlink:读取符号链接文件的内容
    int readlink(char *path,char *buf,int bufsize)

  13. umask:设置文件创建掩码;文件权限为(mask & ~umask)
    int umask (int umask);

链接文件

Unix/Linux允许使用不同的路径名来表示同一个文件。

  • 硬链接文件
ln oldpath newpath

创建从newpath到oldpath的硬链接。对应的系统调用为:

link(char *oldpath,char *newpath)
  • 符号链接文件
ln -s oldpath newpath

创建从newpath到oldpath的软链接或符号链接。对应的系统调用是:

symlink(char *oldpath,chat *newpath)

stat系统调用

  • stat结构体
    所有stat系统调用都以stat结构体形式返回信息。

  • stat与文件索引节点
    每个文件都有一个独有的索引节点数据结构,包含文件的所有信息。

  • 文件类型和权限
    st_mode:u16

  • opendir-readdir函数
    目录也是一个文件。我们应该能像其他任何普通文件一样,打开一个一个READ目录,然后读取他的内容。然而根据文件系统的不同,目录文件的内容可能会有不同。因此,用户可能无法正确读取和解释目录的内容。

  • readlink函数
    Linux的open()系统调用遵循符号链接。因此,无法打开符号链接文件并读取其内容。要想读取符号链接文件的内容,我们必须使用readlink系统调用,即

int readlink(char *pathname,char buf[],int bufsize);

他将符号链接文件的内容复制到bufsize的buf[]中,并将实际复制的字节数返回。

open-clode-lseek系统调用

  1. open:打开一个文件进行读、写、追加
    int open(char *file, int flags, int mode);

  2. close:关闭打开的文件描述符
    int close(int fd);

  3. read:读取打开的文件描述符
    int read(int fd, char buf[], int count);

  4. write:写入打开的文件描述符
    int write(int fd, char buf[], int count);

  5. lseek:将文件描述符的字节偏移量重新定位为偏移量
    int lseek(int fd, int offset, int whence);

  6. umask:设置文件创建掩码;文件权限为(mask&~umask)

问题及解决思路

硬链接与软链接的具体区别

  1. 定义
    软链接又叫符号链接,这个文件包含了另一个文件的路径名。可以是任意文件或目录,可以链接不同文件系统的文件。

硬链接就是一个文件的一个或多个文件名。把文件名和计算机文件系统使用的节点号链接起来。因此我们可以用多个文件名与同一个文件进行链接,这些文件名可以在同一目录或不同目录。

  1. 限制
    硬链接只能对已存在的文件进行创建,不能交叉文件系统进行硬链接的创建。

软链接可对不存在的文件或目录创建软链接;可交叉文件系统。

  1. 创建方式
    硬链接不能对目录进行创建,只可对文件创建。

软链接可对文件或目录创建。

  1. 影响
    删除一个硬链接文件并不影响其他有相同 inode 号的文件。

删除软链接并不影响被指向的文件,但若被指向的原文件被删除,则相关软连接被称为死链接(即 dangling link,若被指向路径文件被重新创建,死链接可恢复为正常的软链接)。

实践内容与截图


posted @ 2022-10-02 18:28  20201206韩进  阅读(20)  评论(0编辑  收藏  举报