20191323王予涵第七、八章学习笔记
一、知识点归纳
第七章 文件操作
文件操作级别:
- 硬件级别:
- -fdisk:将硬盘、u盘或SDC盘分区;
- -mkfs:格式化磁盘分区,为系统做好准备;
- -fsck:检查和维修系统;
- -碎片整理:压缩文件系统中的文件;
- 操作系统内核中的文件系统函数:
- kmount () ,kumount ( ) (mount/umount file systems)
- kmkdir(), krmdir() (make/ remove directory)
- kchdir(), 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() (lseak/close file descriptors)
- ksymlink(), kreadlink () (create/read symbolic link files)
- kstat(), kfstat(),klstat() (get file status/information)
- kopendir(), kreaddiz() (open/read airectories)
-
系统调用:用户模式程序使用系统调用来访问内核函数。在用户模式和内核模式之间切换需要大量的操作,对于R/W文件最好的办法是匹配内核功能。
-
I/O库函数:
- FILE mode I/O: fopen(),fread();fwrite(),fseek(),fclose(),fflush()
- char mode I/O: getc(), getchar(); ugetc(); putc(),putchar()
- line mode I/O: gets() , fgets();puts( ) , fputs()
- formatted I/O: scanf(),fscanf().sscanf(); printf(),fprintf() , sprintf()
-
用户命令:用户可以使用Unix/Linux命令来执行文件操作,而不是编写程序。每个用户命令实际上是一个可执行程序,通常会调用I/O库函数。
处理顺序为:
Command => Library I/O function => system call => Kernel Function
Command ====================> system call => Kernel Function
-
sh脚本:虽然比系统调用方便的多,但是必须要手动呼入命令,如果使用的是GUI,必须要拖放文件图标和点击指向设备来输入,操作繁琐而且耗时。通过命令解释程序sh执行。
文件I/O操作:
- 文件操作在Linux中分为用户模式和内核模式,内核模式操作用户无法直接进入,需要通过系统调用路由到相应的内核函数
用户模式(用户空间):
- 用户模式下的程序执行操作:
FILE *p = fopen("file", "r"); or FILE *p = fopen( "file", "w");可以打开一个读/写文件流,此函数为I/O库函数。 - fopen()在用户(heap)空间中创建一个FILE结构体:
typedef struct _iobuf {
int cnt; // 剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少个字符未被读取
char *ptr; // 下一个要被读取的字符的地址
char *base; // 缓冲区基地址
int flag; // 读写状态标志位
int fd; // 文件描述符
// 其他成员
} FILE;
然后向内核中的kopen()发出一个系统调用以获取文件描述符fd,内核空间构建一个OpenTable来表示打开文件示例。OpenTable的mptr指向内存中的文件INODE。对于非特殊文件,INODE 的i_block数组指向存储设备上的数据块。成功后,fp会指向FILE结构体。
- fread(ubuf, size,nitem, fp):将nitem个size字节读取到ubuf上,通过:
- 将数据从FILE结构体的fbuf上复制到ubuf上若数据足够、则返回。
- 如果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。
这样,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的数量。
(9a)对于read(fd, buf, BLKSIZE)系统调用,要确定所需的(dev, blk)编号,然后查询I/O缓冲区高速缓存,以执行以下操作:
.get a buffer = (dev, blk);
.if (buffer's data are invalid){
start_io on buffer;
wait for I/O completion;
}
.copy data from buffer to fbuf;
.release buffer to buffer cache;
(9b)对于write(fd, fbuf, BLKSIZE)系统调用,要确定需要的(dev, blk)编号,然后查询IO缓冲区高速缓存,以执行以下操作:
.get a buffer = (dev, blk):
.write data to the I/O buffer;
.mark buffer as dataValid and DIRTY (for delay-write to disk);
.release the buffer to buffer cache;
设备I/O:I/O缓冲区上的物理I/O最终会仔细检查设备驱动程序,设备驱动程序
由上半部分的start_io()和下半部分的磁盘中断处理程序组成。
低级别文件操作:
分区:
- 一个区块存储设备分为几个逻辑单元,成为分区。分区表位于第一个扇区的字节偏移446(0x1BE)处,该扇区称为设备的主引导记录(MBR),若MBR损坏则无法引导系统启动。
- 如果某分区是扩展类型(类型编号=5),那么它可以划分为更多分区(逻辑分区)。
stuct partition {
u8 drive; // 0x80 - active
u8 head; // starting head
u8 sector; // starting sector
u8 cylinder; // starting cylinder
u8 sys_type; // partition type
u8 end_head; // end head
u8 end_sector; // end sector
u8 end_cylinder; // end cylinder
u32 start_sector; // starting sector counting from 0
u32 nr_sectors; // number of sectors in partition
};
u[num]-无符号整型 num为字节数
- 每个扩展分区的第一个扇区是一个本地MBR。每个本地MBR在字节偏移量0xIBE处也有一个分区表,只包含两个条目。第一个条目定义了扩展分区的起始扇区和大小。第二个条目指向下一个本地MBR。所有本地MBR的扇区编号都与P4的起始扇区有关。照例,链表以最后一个本地MBR中的0结尾。在分区表中,CHS值仅对小于8GB的磁盘有效。对大于8GB但小于4G扇区的磁盘,只有最后两个条目start _sector 和nr sector有意义。
格式化分区:
- fdisk只是将一个存储设备划分为多个分区。每个分区都有特定的文件系统类型,但是分区还不能使用。为了存储文件,必须先为特定的文件系统准备好分区。该操作习惯上称为格式化磁盘或磁盘分区。在Linux中,它被称为mkfs,表示make文件系统。
mkfs -t TYPE [-b bsize] device nblocks
在一个nblocks设备上创建一个TYPE文件系统,每个块都是bsize字节。如果bsize未绑定,则默认块大小为1KB。
在Linux中对新文件系统不能直接访问,必须挂载到根文件系统中/mnt,虚拟文件系统不是真正的设备,必须作为循环设备挂载、
sudo mount -o loop vdisk /mnt //挂载循环设备
sudo umount /mnt //卸载
EXT2文件系统
文件系统组成:
0 | 1 | 2 | 3-7 | 8 | 9 | 10-32 | 33-... |
---|---|---|---|---|---|---|---|
引导块 | 超级块 | 块组描述符 | 保留块 | 块位图 | 索引节点位图 | 索引节点 | 数据块 |
引导块:容纳从磁盘引导操作系统的引导程序;
超级块:容纳整个文件系统的信息;
struct et2_super block {
u32 s_inodes_count; /* Inodes count */
u32 s_blocks_count; /* Blocks count */
u32 s_r_blocks_count; /* Reserved blocks count */
u32 s_free blocks_count; /* Free blocks count */
u32 s_free_inodes_count; /* Free inodes count */
u32 s_first_data_block; /* First Data Block */
u32 s_log block_size; /* Block size */
u32 s_log_cluster_size; /* Al1ocation cluster size */
u32 s_blocks per_group; /* # Blocks per group * /
u32 s_clusters per_group; /* # Fragments per group */
u32 s_inodes_per_group; /* # Inodes per group * /
u32 s_mtime; /* Mount time * /
u32 s_wtime; /* write time */
u16 s_mnt_count; /* Mount coune* /
s16 s_max_ntcount; /* Maximal mount count */
u16 B_magic; /* Magic signature */
//more non-essential fields
u16 s_inode_size; /* size of inode structure*/
}
索引节点: 每个文件都用一个128字节(EXT4中的是256字节)的独特索引节点结构体表示。
struct ext2_inode {
u16 i_mode; // 16 bits =|ttttlugs|rwx|rwx|rwxl
u16 i_uid; // owmer uid
u32 i_size; // file size in bytes
u32 i_atime; //time fields in seconds
u32 i_ctime; // since 00:00:00,1-1-1970
u32 i_mtime;
u32 _dtime;
u16 i_gid; // group ID
u16 i_links_count;// hard-link count
u32 i_blocks; // number of 512-byte sectors
u32 i_flags; //IGNORE
u32 i_reservedl;// IGNORE
u32 i_block[15];// See details below
u32 _pad[7]; // for inode size = 128 bytes
}
第八章 使用系统调用进行文件操作
使用系统调用进行文件操作:
int syscall(int a, int b, int c, int d);
- a:系统调用编号
- b、c、d:对应内核函数参数
- 返回值:正数表示成功,-1表示失败
内核系统调用处理程序根据系统调用编号将调用路由到一个相应的内核函数,并不能直接调用内核函数
链接文件:
软连接:保存了其代表的文件的绝对路径,是另外一种文件,在硬盘上有独立的区块,访问时替换自身路径。
命令:
ln -s oldpath newpath
系统调用:
#include <unistd.h>
symlink(char *oldpath, char *newpath);
硬链接:与普通文件没什么不同,inode 都指向同一个文件在硬盘中的区块,链接数变为0,文件就会被完全删除。
命令:
ln oldpath newpath
系统调用:
#include <unistd.h>
link(char *oldpath, char *newpath);
二、问题与解决思路
文件描述符具体的工作原理是什么?
用户或库函数通过调用open()系统调用,使其使用内核函数kopen(),并在fb[]指针数组中添加一个指向OpenTable的指针,并将其索引作为fd返回。同时内核为每个进程都维持着一个单独的proc。