笔记
第七章 文件操作
文件操作级别
文件操作分为五个级别,按照从低到高的顺序排列如下。
(1)硬件级别:
- fdisk:将硬盘、U盘或SDC盘分区。
- mkfs:格式化磁盘分区,为系统做好准备。
- fsck:检查和维修系统。
- 碎片整理:压缩文件系统中的文件。
是创建和维护系统不可缺少的工具
(2)操作系统内核中的文件系统函数:
每个操作系统内核均可为基本文件操作提供支持。
(3)系统调用:
用户模式程序使用系统调用来访问内核函数
open可进入kopen(),read可进入kread()函数
read(fd,buf,1)系统调用只读取一个字节的数据(不明智),最好的方法是匹配内核的功能
(4)1/O库函数:
C语言库提供了一系列标准的1/O函数,提高了运行效率。 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()
除了读/写内存位置的sscanf0/sprintf0函数之外,所有其他I/O库函数都建立在系统调用之上,也就是说,他们最终会通过系统内核发出实际数据传输的系统调用。
(5)用户命令:
用户可以使用Unix/Linux命令来执行文件操作,而不是编写程序。
(6)sh脚本:
虽然比系统调用方便得多,但是必须要手动输入命令。如果使用的是GUI,必须要拖放文件图标和点击指向设备来输入,操作繁琐且耗时。sh脚本是用sh编程语言编写的程序,可通过命令解释程序sh来执行。
文件I/O操作
文件I/O操作示意图:
用户模式下的操作:
(1)用户模式下的程序执行操作:
- FILE *p = fopen("file", "r"); or FILE *p = 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上,通过:
- 将数据从FILE结构体的fbuf上复制到ubuf上,若数据足够、则返回。
- 如果fbuf没有更多数据,则执行(a)。
(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进行操作。
内核模式下的操作:
(5)内核中的文件系统函数:
假设非特殊文件的read(fd, fbuf[], BLKSIZE)系统调用。
(6)在read()系统调用中,fd是一个打开的文件描述符,它是运行进程的fd数组中的一个索引,指向一个表示打开文件的 OpenTable。
(7)OpenTable包含文件的打开模式、一个指向内存中文件 INODE的指针和读/写文件的当前字节偏移量。从OpenTable的偏移量,
- 计算逻辑块编号lbk。
- 通过 INODE.i_block[]数组将逻辑块编号转换为物理块编号blk 。
(8)Minode包含文件的内存INODE。EMODE.i_block[]数组包含指向物理磁盘块的指针。文件系统可使用物理块编号从磁盘块直接读取数据或将数据直接写入磁盘块,但将会导致过多的物理磁盘I/O。
(9)为提高磁盘VO效率,操作系统内核通常会使用一组I/O缓冲区作为高速缓存,以减少物理I/O的数量。
(9a)对于read(fd,buf,BLKSIZE)系统调用,要确定所需的(dev,blk)编号,然后查询I/O缓冲区高速缓存
(9b)对于write(fd,buf,BLKSIZE)系统调用,要确定所需的(dev,blk)编号,然后查询I/O缓冲区高速缓存
(10)设备I/O:I/O缓冲区上的物理I/O最终会仔细检查设备驱动程序,设备驱动程序由上半部分的start_io()和下半部分的磁盘中断处理程序组成。
低级别文件操作
分区
一个块存储设备,如硬盘、U盘、SD卡等,可以分为几个逻辑单元,称为分区。
主引导记录(MBR),本地MBR
格式化分区
为了存储文件,必须先为特定的文件系统准备好分区。该操作习惯上称为格式化磁盘或磁盘分区。
fdisk 将一个存储设备划分为多个分区,但是刚刚划分出来的分区还需要经过格式化才可以存储文件,可以使用mkfs命令在特定的分区上建立 linux 文件系统,mkfs的一般格式如下:
mkfs [-V] [-t fstype] [fs-options] filesys [blocks]
各个参数的含义:
device : 预备检查的硬盘分区,例如:/dev/sda1
-V : 详细显示模式
-t : 给定档案系统的型式,Linux 的预设值为 ext2
-c : 在制做档案系统前,检查该partition 是否有坏轨
-l bad_blocks_file : 将有坏轨的block资料加到 bad_blocks_file 里面
block : 给定 block 的大小,默认为1kb
挂载分区
如果某个虚拟磁盘包含多个分区,那么必须先将这些分区与循环设备关联起来。
losetup命令语法如下:
losetup [-d][-e <加密方式>][-o <平移数目>][循环设备代号][文件]
各个参数含义:
-d 卸除设备。
-e<加密方式> 启动加密编码。
-o<平移数目> 设置数据平移的数目。
EXT2文件系统简介
Block#0:
引导块,文件系统不会使用它。它用于容纳从磁盘引导操作系统的引导程序。
Block#1:
超级块(在硬盘分区中字节偏移量为1024)。用于容纳关于整个文件系统的信息。
Block#2:
块组描述符(硬盘上的s_first_data_blocks-1)EXT2将磁盘块分成几个组,每个组有8192个块(硬盘上的大小为32K)
Block #8:块位图(Bmap)
用来表示某种项的位序列。0表示对应项处于FREE状态,1表示处于IN_USE状态。1个软盘有1440个块,但Block#0未被文件系统使用,所以对应位图只有1439个有效位,无效位视作IN_USE处理,设置为1.
Block #9:索引节点位图(Imap)
一个索引节点就是用来代表一个文件的数据结构。EXT2文件系统是使用有限数量的索引节点创建的。各索引节点的状态用B9 中 Imap中的一个位表示。在EXT2 FS 中,前10个索引节点是预留的。所以,空EXT2FS的Imap 以10个1开头,然后是0。无效位再次设置为1。
Block #10:索引(开始)节点块(bg_inode_table)
每个文件都用一个128字节(EXT4中的是256字节)的独特索引节点结构体表示。
第八章 使用系统调用进行文件操作
系统调用
系统调用(简称syscall)是一种允许进程进入Kmode以执行Umode不允许操作的机制。
系统调用手册页
可用sh命令man 2 NAME显示系统调用名称的手册页,例如:
>+ man 2 stat: 查看stat() ,fstat() ,lstat()函数的说明
>+ man 2 open: 查看open()函数的说明
>+ man 2 read: 查看read()函数的说明
使用系统调用进行文件操作
int syscall(int a, int b, int c. int d);
其中,第一个参数a是系统调用编号,b、C、d是对应内核函数的参数。在基于Intel x86的Linux中,系统调用是由INT 0x80汇编指令实现的,可将CPU从用户模式切换到内核模式。内核的系统调用处理程序根据系统调用编号将调用路由到一个相应的内核函数。当进程结束执行内核函数时,会返回到用户模式,并得到所需的结果。返回值≥0表示成功,-1表示失败。如果失败,errmo 变量(在errno.h中)会记录错误编号,它们会被映射到描述错误原因的字符串。下面的示例演示了如何使用一些简单的系统调用。
简单的系统调用:
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 *pathname):
getewd:获取CWD的绝对路径名
char *getcwd(char *buf, int aize):
mkdir:创建目录
int mkdir(char *pathname,mode_t mode);
rmdir:移除目录(必须为空)
int rmdir(char *pathname);
link:将新文件名硬链接到旧文件名
int 1ink(char *o1dpath,char *newpath);
umlink:减少文件的链接数;如果链接数达到0,则删除文件
int unlink(char *pathname);
symlink:为文件创建一个符号链接
int symlink(char o1dpath, charnewpath);
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 *dix);
mknod:创建特殊文件
int mknod(char *path,int mode, int device);
常用的系统调用
stat 获取文件状态信息
int stat(char *filename ,struct stat *buf)
int fstat(char filedes ,struct stat *buf)
int lstat(char *filename ,struct stat *buf)
open:打开一个文件进行读、写、追加
int open(char *file, int flags,int mode)
close:关闭打开的文件描述符
int close(int fd)
read:读取打开的文件描述符
int read(int fd, char buf[ 1, int count)
write:写入打开的文件描述符
int write(int fd, char buf[ ], int count)
lseek:重新定位文件描述符的读/写偏移量
int 1seek(int fd, int offset, int whence)
dup:将文件描述符复制到可用的最小描述符编号中
int dup(int oldfd);
dup2:将oldfd复制到newfd中,如果newfd已打开,先将其关闭
int dup2(int oldfd, int newfd)
link:将新文件硬链接到旧文件
int link(char *oldPath, char *newPath)
unlink:取消某个文件的链接;如果文件链接数为0,则删除文件
int unlink(char *pathname);
symlink:创建一个符号链接
int symlink(char *target, char *newpath)
readlink:读取符号链接文件的内容
int readlink(char *path, char *buf, int bufsize)
umask:设置文件创建掩码;文件权限为(mask&~umask)
int umask(int umask)
链接文件
在Unix/Linux中,每个文件都有一个路径名。但是,Unix/Linux允许使用不同的路径名来表示同一个文件。这些文件叫做LINK(链接)文件。有两种类型的链接,即便链接和软链接或符号链接。
硬链接文件
硬链接:命令
ln oldpath newpath
创建从newpath到oldpath的硬链接。对应的系统调用为:
link(char *oldpath, char *newpath)
符号链接文件
软链接:命令
ln -s oldpath newpath #ln command with the -s flag
创建从newpath到oldpath的软链接或符号链接。对应的系统调用是:
symlink(char *oldpath, char *newpath)
stat系统调用
stat文件状态
stat按文件名统计指向文件,并在缓冲区中填写stat信息。
- lstat与stat相同,除非是符号链接,统计链接本身,而不是链接所引用文件。所以,stat和lstat的区别是:stat遵循链接,但lstat不是。
- fstat与stat相同,,也只在文件名处说明filedes(由open(2)返回)所指向的打开文件。
stat结构体
- st_size是用字节表示的文件大小。
- st_blocks值是用512字节块表示的文件大小。
stat与文件索引节点
stat与文件索引节点:首先,我们来阐明stat如何工作。每个文件都有一个独有索引节点数据结构,包含文件的所有信息。
文件类型和权限
opendir-readdir函数
目录也是一个文件。我们应该能像其他任何普通文件一样,打开一个READ目录,然后读取和显示它的内容。然而,根据文件系统的不同,目录文件的内容可能会有所不同。因此,用户可能无法正确读取和解释目录的内容。鉴于此,POSIX为目录文件指定了以下接口函数。
readlink函数
Linux的open()系统调用遵循符号链接。因此,无法打开符号链接文件并读取其内容。要想读取符号链接文件的内容,我们必须使用readlink系统调用,即:
int readlink(char *pathname, char buf[], int bufsize);
ls程序
open-close-lseek系统调用
open: 打开一个文件进行读、写、追加
int open(char *file, int flags, int mode);
close: 关闭打开的文件描述符
int close(int fd);
read: 读取打开的文件描述符
int read(int fd, char buf[], int count);
write: 写入打开的文件描述符
int write(int fd, char buf[], int count);
lseek: 将文件描述符的字节偏移量重新定位为偏移量
int lseek(int fd, int offset, int whence);
umask: 设置文件创建掩码;文件权限(mask &~ umask)
问题和解决思路
问题:创建虚拟磁盘时显示权限不够
解决办法:在代码前加一个sudo改为root权限权限
变为:sudo dd if=/dev/zero of=20201224 bs=1024 count=1440即可
实践内容与截图,代码链接
sh命令man 2 NAME的试用: