第七章 文件操作
文件操作级别
- 硬件级别文件操作包括:
fdisk:将硬盘、U盘或SDC盘分区
mkfs:格式化磁盘分区,为系统做好准备
fsck: 检查和维修系统
碎片整理:压缩文件系统中的文件
- 操作系统内核中的文件系统函数:
kmount(),kumount ( ) (mount/umount file systems)
kmkdir(), krmdir() (make/ remove directory )
kchdir() , kgetewa () (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/c1ose file descriptors)
ksymlink(), kreadlink() (create/read symbolic link files)
kstat() ,kfstat(), k1stat() (get file status/ information)
kopendir(), kreaddir() (open/read directories)
- 系统调用
- I/O库函数
FILE mode I/O: fopen(),fread(); fwrite(), fseek(), fclose(),fflush()char mode I/o: getc(), getchar() ugetc(); putc(),putchaz()
line mode I/O: gets(), fgets();puts(), fputs()
formatted I/o: scanf(), fscanf(),sscanf(); printf(), fprintf(), sprintf()
- 用户命令
- sh脚本
文件I/O操作
- (1)用户模式下的程序执行操作
FILE *fp = fopen("file", "r") or FILE *fp = fopen("file", "w");
可打开一个读/写文件 - (2) fopen()在用户( heap)空间中创建一个FILE结构体,包含一个文件描述符fd、一个fbuf [BLKSIZE]和一些控制变量。
- (3) 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、fgetsllputs、fscanf/fprintf等也可以在用户空间内的FILE结构体中对fbuf进行操作。
- (5)内核中的文件操作:假设非特殊文件的 read(fd, fbuf[ ], BLKSIZE)系统调用。
- (6)在read()系统调用中,fd是一个打开的文件描述符,它是运行进程的f数组中的一个索引,指向一个表示打开文件的OpenTable。
- (7)OpenTable包含文件的打开模式、一个指向内存中文件INODE的指针和读/写文件的当前字节偏移量。从 OpenTable的偏移量,计算逻辑块编号1bk。通过INODE.i_block[ ]数组将逻辑块编号转换为物理块编号blk。
- (8) Minode包含文件的内存 INODE。EMODE.i_block[ ]数组包含指向物理磁盘块的指针。文件系统可使用物理块编号从磁盘块直接读取数据或将数据直接写入磁盘块,但将会导致过多的物理磁盘I/O。
- (9)为提高磁盘IO效率,操作系统内核通常会使用一组IO缓冲区作为高速缓存,以减少物理I/O的数量。磁盘I/O缓冲区管理将在第12章中讨论。
- (9a)对于read(fd, buf, BLKSIZE)系统调用,要确定所需的(dev,blk)编号,然后查询I/O缓冲区高速缓存。
点击查看代码
get abuffer = (dev,b1k) ;. if(buffer's data are
invalid)
{
start_ io on buffer;
wait for I/O completion;
}
. copy datafrom buffer to fbuf ;
.release buffer to buffer cache ;
- (9b)对于write(fd, fbuf, BLKSIZE)系统调用,要确定需要的(dev,blk)编号,然后查询I/O缓冲区高速缓存。
点击查看代码
.get a buffer =(dev, b1k) ;
write data to the I/O buffer ;
. mark buffer as dataValid and DIRTY(for delay-write to disk);
. release the buffer to buffer cache ;
- (10)设备I/O:Io缓冲区上的物理IO最终会仔细检查设备驱动程序,设备驱动程序由上半部分的start_io()和下半部分的磁盘中断处理程序组成。
低级别文件操作
- 分区:一个块存储设备,如硬盘、U盘、SD卡等,可以分为几个逻辑单元,称为分区。各分区均可以格式化为特定的文件系统,也可以安装在不同的操作系统上。大多数引导程序,如GRUB、LILO等,都可以配置为从不同的分区引导不同的操作系统。分区表位于第一个扇区的字节偏移446(0x1BE)处,该扇区称为设备的主引导记录(MBR)。表有4个条目,每个条目由一个16字节的分区结构体定义,即:
struct 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
};
- 格式化分区:fdisk只将一个存储设备划分为多个分区。每个分区都有特定的文件系统类型,但是分区还不能使用。为了存储文件,必须先为特定的文件系统准备好分区。该操作习惯上称为格式化磁盘或磁盘分区。在Linux中,它被称为mkfs,表示Make文件系统。Linux支持多种不同类型的文件系统。每个文件系统都期望存储设备上有特定的格式。在Linux中,命令:
mkfs -t TYPE [-b bsize] device nblocks
在一个nblocks设备上创建一个TYPE文件系统,每个块都是bsize字节。如果bsize未指定,则默认块大小为1KB。具体来说,假设是EXT2/3文件系统,它是Linux的默认文件系统。因此,mkfs -t ext2 vdisk 1440
ormke2fs vdisk 1440
- 挂载分区:man 8 losetup:显示用于系统管理的losetup 实用工具命令:
(1)用dd命令创建-一个虚拟磁盘映像:
dd if=/dev/zero of=vdisk bs=1024 count=32768 #32K (1KB) blocks
(2)在vdisk. 上运行fdisk来创建一一个分区P1:
fdisk vdisk
输人n(new)命令,使用默认的起始和最后扇区编号来创建一个分区Pl。然后,输人w命令将分区表写人vdisk并退出fdisko vdisk 应包含-个分区P1 [start=2048, end=65535]。该分区的大小是63488个扇区。
(3)使用以下扇区数在vdisk的分区1上创建一个循环设备 :
losetup -o $(expr 2048 * 512) --sizelimit $(expr 65535 * 512) /dev/1oop1vdisk
losetup需要分区的开始字节(start_ sector512) 和结束字节(end_ sector512)。 读者可手动计算这些数值,并在losetup命令中使用它们。可用类似方法设置其他分区的循环设备。循环设备创建完成后,读进程可以使用命令
losetup - a 将所有循环设备显示为/dev/loopN。
(4)格式化/dev/loop1,它是一个EXT2文件系统:
mke2fs -b 4096 /dev/loop1 7936 # mke2fs with 7936 4KB blocks
该分区的大小是63488个扇区。4KB块的扇区大小是63488 /8=7936
(5)挂载循环设备:
mount /dev/ 1oop1 /mnt # mount as loop device
(6)访问作为文件系统一部分的挂载设备:
(cd /mnt; mkdir bin boot dev etc user) # populate with DIRs
(7)设备使用完毕后,将其卸载。
umount /mnt
(8)循环设备使用完毕后,通过以下命令将其断开:
losetup -a /dev/loop1 # detach a loop device.
超级块
- Block#1:超级块(在硬盘分区中字节偏移量为1024)B1是超级块,用于容纳关于整个文件系统的信息。下文说明了超级块结构中的一些重要字段。
struct ext2_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; // Allocation 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
u32 s_mnt_count; // Mount count
u16 s_max_mnt_count; // Maximal mount count
u16 s_magic; // Magic signature
// more non-essential fields
u16 s_inode_size; // size of inode structure
};
块组描述符
- Block#2:块组描述符块(硬盘上的s_first_data_blocks-1)EXT2将磁盘块分成几个组。每个组有8192个块(硬盘上的大小为32K)。每组用一个块组描述符结构体描述。
struct ext2_ group_ desc (
u32
bg_ block_ bi tmap; // Bmap block number
u32 bg inode_ bi tmap; //Imap b1ock number
u32 bg inode_ table; // Indes begin block number
u16 bg_ free_ blocks_ count ; // THESE are OBVIOUS
u16 bg_ free_ inodes_ count ;
u16 bg_ used_ dirs_ count;
u16 bg_ pad; //ignore these
u32 bg_ reserved[3] ;
};
位图
- Block#8:块位图(Bmap)(bg_block_bitmap)位图用来表示某种项的位序列,例如,磁盘块或索引节点。位图用于分配和回收项。在位图中,0位表示对应项处于FREE状态,1位表示对应项处于IN_USE状态。一个软盘有1440块。
- Block#9:索引节点位图(Imap)(bg_inode_bitmap)一个索引节点就是用来代表一个文件的数据结构。EXT2文件系统是使用有限数量的索引节点创建的。各索引节点的状态用B9中Imap中的一个位表示。在EXT2 FS中,前10个索引节点是预留的。
索引节点
- Block#10:索引(开始)节点块(bg_inode_table)每个文件都用一个128字节(EXT4中的是256字节)的独特索引节点结构体表示。
struct ext2_ inode {
u16 i_ mode;// 16 bits - ttttlugsIrwxJrwxIrwxl
u16 i_ uid;//owner 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 i_ dtime;
u16 i_ gid;// group ID
u16 i_ 1 inks_ count;// hard-link count
u32 i_ blocks;// number of 512-byte sectors
u32 i_ flags;//IGNORE
u32 i_ reserved1 ;//IGNORE
u32 i_ b1ock[15] ;//See details below
u32 i_ pad[7] ;//for inode size = 128 bytes
}
- 直接块:i_block[0]至i_block[11]指向直接磁块盘
- 间接块:i_block[12]指向一个包含256个块编号的磁盘块,每个块编号指向一个磁盘块
- 双重间接块:i_block[13]指向一个指向256个块的块,每个块指向256个磁盘块
- 三重间接块:i_block[14]对于小型EXT2文件可忽略
目录条目
- EXT2目录条目;目录包含dir_entry_2结构,即:
struct ext2_dir_entry_2 {
u32 inode;
u16 rec_len;
u8 name_len;
u8 file_type;
char name[EXT2_NAME_LEN];
};
第八章:使用系统调用进行文件操作
系统调用
- 在操作系统中,进程以两种不同模式运行,即内核模式和用户模式