第7,8章自学笔记
第七章 文件操作
前言:
本章讨论了多种文件系统;解释了操作系统中的各种操作级别,包括为文件存储准备存储设备、内核中的文件系统支持函数、系统调用、文件流上的I/0库函数、用户命令和各种操作的sh脚本;系统性概述了各种操作,包括用户空间的文件流读/写到内核空间的系统调用,直到底层的设备I/O驱动程序级别;
描述了低级别的文件操作,包括磁盘分区、显示分区表的示例程序、文件系统的格式化分区以及挂载磁盘分区;介绍了 Linux系统的EXT2文件系统,包括EXT2文件系统的系统数据结构、显示超级块、组描述符、块和索引节点位图以及目录内容的示例程序。
编程项目将本章中讨论的EXT2/3文件系统和编程技术集中到一个程序中,将路径名转换为索引节点并打印它们的信息。
学到了什么:
1. 硬件级别: 硬件级别的文件操作包括:
-
fdisk:将硬盘、U盘或DC盘分区。
-
mkfs:格式化磁盘分区,为系统做好准备。
-
fsck:检查和维修系统。
-
碎片整理:压缩文件系统中的文件。
其中大多数是针对系统的实用程序。普通用户可能永远都不需要它们,但是它们是创建和维护系统不可缺少的工具。
2. 操作系统内核中的文件系统函数:每个操作系统内核均可为基本文件操作提供支
持。
3. 系统调用:
用户模式程序使用系统调用来访问内核函数。open)、ead)、sek和 I close()函数都是C语言库函数。每个库函数都会发出一个系统调用,使进程进入内核模式来执行相应的内核函数,例如pen可进入 kopen(),read可进入kead函数,等等。
4. 进程结束执行内核函数时会返回到用户模式,并得到所需的结果。在用户模式和内核模式之间切换需要大量的操作(和时)。因此,内核和用户空间之间的数据传输成本昂贵。
5. 虽然可以发出readfd,buf,1)系统调用来只读取一个字节的数据,但是这种做法是不明智的,因为一个字节也会带来可怕的高成本。我们在每次必须进入内核时都要尽可能不虚此行。对于读/写文件,最好的方法是匹配内核的功能。内核会按数据块大小(从1KB到8KB)来读取/写入文件。
6. I/0库函数:
系统调用可让用户读/写多个数据块,这些数据块只是一系列字节。用户通常需要读/写单独的字符、行或数据结构记录等。如果只有系统调用,用户模式程序则必须自己从缓冲区执行这些操作。大多数用户会认为这非常不方便。为此,C语言库提供了一系列标准的I0函数,同时也提高了运行效率。
8. I/O库函数包括:
mode I/0: fopen(), fread(); fwrite(),fseek(), fclose(),fflush()fputs() printf(),fprintf(),sprintf()
除了读/写内存位置的 sscanf /sprintf(函数之外,所有其他I/0库函数都建立在系统调用之上,也就是说,它们最终会通过系统内核发出实际数据传输的系统调用。
8. 用户命令:
用户可以使用Unix/ /Linux命来执行文件操作,而不是编写程序。
每个用户命令实际上是一个可执行程序(cd除外),通常会调用库I/O函数,而库I/0函数再发出系统调用来调用相应的内核函数。
9. 一个块存储设备,如硬盘、U盘、SD卡等,可以分为几个逻辑单元,称为分区。
各分区均可以格式化为特定的文件系统,也可以安装在不同的操作系统上。大多数引导程序,如GRUB、LILO等,都可以配置为从不同的分区引导不同的操作系统。分区表位于第一个扇区的字节偏移446(0xBE)处,该扇区称为设备的主引导记录(MBR)。表有4个条目,每个条目由一个16字节的分区结构体定义
11. 每个扩展分区的第一个扇区是一个本地MBR。每个本地MBR在字节偏移量 OxIBE处也有一个分区表,只包含两个条目。第一个条目定义了扩展分区的起始扇区和大小。第二个条目指向下一个本地MBR。所有本地MBR的扇区编号都与P4的起始扇区有关。
12. fisk是一个交互程序。它有一个显示所有命令的帮助菜单。它收集用户的输入,在内存中创建一个分区表,该分区表仅在用户输入w命令时才被写入磁盘映像的MBR。但是在内存中,它允许用户创建、检查和修改分区。将分区写人磁盘后,通过q命令退出 i fdisk。
13. 位图
Block#8:块位图(map)( by block_ bitmap位图是用来表示某种项的位序列,例如磁盘块或索引节点。位图用于分配和回收项。在位图中,0位表示对应项处于FREE状态1位表示对应项处于 IN USE状态。一个软盘有1440个块,但是 Block#0未被文件系统使用。所以,位图只有1439个有效位。无效位视作 IN USE处理,设置为1。
14.
-
直接块: i block[o]至i-bock[],指向直接磁盘块。
-
间接块:iok12]指向一个包含256个块编号(对于1 KB BLKSIZE)的磁盘块,每个块编号指向一个磁盘块。
-
双重间接块: i block[13]指向一个指向256个块的块,每个块指向256个磁盘块。
-
三重间接块: i block[14]是三重间接块。对于“小型”EXT2文件系统,我们可以忽略它。
15. 节点块都包含整数个索引节点。在简单的EXT2文件系统中,索引节点的数量是184个(inux默认值)。
索引节点块数等于184/=23个。因此,索引节点块为B10至B32。每个索引节点都有一个独特的索引节点编号,即索引节点索引节点块上的位置+1。
16. 注意,索引节点位置从0开始计数,而索引节点编号从1开始计数。0索引节点编号表示没有索引节点。根目录的索引节点编号为2。同样,磁盘块编号也从1开始计数,因为文件系统从未使用块0。块编号0表示没有磁盘块。
第八章:使用系统调用进行文件操作
前言:
本章论述了如何使用系统调用进行文件操作;解释系统调用的作用和 Linux的在线手册页;展示了如何使用系统调用进行文件操作;列举并解释了文件操作中最常用的系统调用;阐明了硬链接和符号链接文件;具体解释了stat系统调用基于stat信息,开发了一个类似于s的程序来显示目录内容和文件信息;
接着,讲解了pen- -close-lseek系统调用和文件描述符;然后,展示了如何使用读写系统调用来读写文件内容;在此基础上,说明了如何使用系统调用来显示和复制文件;还演示了如何开发选择文件复制程序,其行为类似于一个化的 Linux dd实用程序。
编程项目使用 Linux系统调用来实现C程序,该程序将目录递归复制到目标中。该项目的目的是让读者练习程序的分层结构设计,并利用 statO、openrad()、 writef()系统调用进行文件操作。
学到了什么?
1. 在操作系统中,进程以两种不同的模式运行,即内核模式和用户模式,简称 Mode 和Mode l。在 Mode I中,进程的权限非常有限。它不能行任何需要特殊权限的操作。特殊权限的操作必须在 Mode下执行。系统调用(简称 t syscall))是一种允许进程进入 Mode以执行 Mode不允许操作的机制。复刻子进程、修改执行映像,甚至是终止等操作都必须在内核中执行。
2. 在Unix以及大多数版本的Liux中,在线手册页保存在usrman/目录中。而在 Ubuntu Linux I中则保存在 / usr/share/man目录中
3. 许多系统调用需要特别包含头文件,手册页的 SYNOPSS(概要)部分列出了这些文件。如果没有合适的头文件,C编译器可能会因为 syscall p函数名称类型不匹配而发出许多警告一些系统调用可能还需要特定的数据结构作为参数,必须在手册页中描述这些参数。
4. 使用系统调用进行文件操作系统调用必须由程序发出。它们的用法就像普通函数调用一样。每个系统调用都是一个库函数,它汇集系统调用参数,并最终向操作系统内核发出一个系统调用。
5. Ant syscall(int a, int b, int c, int a)i
其中,第一个参数a是系统调用编号,b、c、d是对应内核函数的参数。在基于 Intel x86的Liux中,系统调用是由INT0x80汇编指令实现的,可将CPU从用户模式切换到内核模式。内核的系统调用处理程序根据系统调用编号将调用路由到一个相应的内核函数。当进程结束执行内核函数时,会返回到用户模式,并得到所需的结果。返回值≥0表示成功,-1表示失败。如果失败,ermo变量(在ermo中)会记录错误编号,它们会被映射到描述错误原因的字符串。
6. 简单的系统调用:
access::检查对某个文件的权限
int access(char *pathname, int mode)
chdir:更改目录
int chdir(const char *path);
chmod:更改某个文件的权限
int chmod(char *path, mode_ t mode) i
chown:更改文件所有人
int chown(char *name, int uid, int gid)i
hoot:将(逻辑)根目录更改为路径名
int chroot(char * pathname)i
getcwd:获取Cd的绝对路径名
char *getcwd(char *buf, int size)i
mkdir:创建目录
int mkdir(char * pathname, mode_t mode) i
mdir:移除目录(必须为空)
int rmdir(char *pathname) i
link:将新文件名硬链接到旧文件名
int link(char oldpath, char *newpath)
unlink:减少文件的链接数;如果链接数达到0,则删除文件
int unlink(char *pathname) i
symlink:为文件创建一个符号链接
int symlink(char * oldpath, char * newpath)i
rename:更改文件名称
int rename(char *oldpath, char *newpath)i
utime:更改文件的访问和修改时间
int utime(char *pathname, struct utimebuf *time)
以下系统调用需要超级用户权限。
-
mount:将文件系统添加到挂载点目录上
mount(char *specialfile, char *mountDir
-
umount:分离挂载的文件系统
int umount(char *dir);
-
mknod:创建特殊文件
int mknod(char *path, int mode, int device)i
7. 常用的系统调用
一些最常见的文件操作的系统调用。其中包括
-
stat:获取文件状态信息
int stat(char *filename, struct stat * but) int fstat(int filedes, struct stat +but) int lstat(char *filename, struct stat *bus
-
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 bufint count)
-
lseek:重新定位文件描述符的读/写偏移量
int lseek(int fd, int offset, int whence
-
dup:将文件描述符复制到可用的最小描述符编号中
int dup(int oldfd);
-
dup2:将 oldfd复制到 news中,如果 news已打开,先将其关闭
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)
8. read()将n个字节从打开的文件描述符读入用户空间中的buf。返回值是实际读取的字节数,如果read()失败,会返回-1,例如当fd无效时。注意,buf区必须有足够的空间来接收n个字节,并且返回值可能小于n个字节。还要注意,返回值是一个整数,而不是文件结束(EOF)符,因为文件中没有文件结束符。文件结束符是/库函数在文件流无更多数据时返回的一个特殊整数值。