文件系统
注: 李慧芹老师的视频课程请点这里, 本篇为文件系统一章的笔记(上, 剩余内容和李慧芹老师课上提到的myls
的实现预计会一起在下中放出), 课上提到过的内容基本都会包含, 上一章为系统调用IO
本章内容
-
目录和文件
-
获取文件属性(实现类
ls
) -
文件访问权限
-
umask
-
更改/管理文件权限(
chmod
,fchmod
) -
粘住位
-
文件系统: FAT vs UFS
-
硬链接与符号链接
-
时间相关(如:
utime
) -
目录的创建和销毁
-
更改当前工作路径
-
分析目录/读取目录内容
-
-
系统数据文件和信息
-
进程环境
目录和文件
获取文件属性
作业:
编写C程序
myls
, 实现与ls
命令尽量相似的功能(至少要支持
-l
,-a
,-i
,-n
选项)其实,
-l
和-n
选项非常相似:-l
会获取文件的属主名和属组名,-n
则是获取这两者的ID
面试题:
使用
touch
命令, 创建一个名为"-a"的文件# -- 表示选项的结束, 后面的内容都是非选项的传参 touch -- -a # 当然, 也可以 touch ./-a
要获取文件属性, 可以使用stat
一族:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
// f代表使用文件描述符
int fstat(int fd, struct stat *buf);
// l代表在处理链接文件时与stat有不同
int lstat(const char *path, struct stat *buf);
stat()
会获取一个文件的属性信息, 然后将信息填入到struct stat
类型指针* buf
中:
struct stat {
dev_t st_dev; /* 包含当前文件的设备ID号 */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* 权限信息 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 属主ID */
gid_t st_gid; /* 属组ID */
dev_t st_rdev; /* 设备ID(如果当前文件真的是一个设备) */
off_t st_size; /* 文件总大小 */
blksize_t st_blksize; /* 一个块(扇区)的大小(一般为512个字节) */
blkcnt_t st_blocks; /* 当前文件占用的512字节大小块的数量 */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
重写 fsize
fsize.c:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
static off_t fsize(const char *fname);
int main(int argc, char **argv)
{
if (argc < 2)
{
fprintf(stderr, "Usage: %s <file name>\n", argv[0]);
exit(1);
}
printf("%ld\n", fsize(argv[1]));
exit(0);
}
static off_t fsize(const char *fname)
{
struct stat statres;
if (stat(fname, &statres) < 0)
{
perror("stat()");
return -1;
}
return statres.st_size;
}
Makefile:
CFLAGS+=-D_FILE_OFFSET_BITS=64 -Wall
文件大小
struct stat
中, st_blksize
与st_blocks
两个属性真正决定了文件占据的磁盘空间大小(而不是st_size
)
bigfile.c:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
if (argc < 2)
{
fprintf(stderr, "Usage: %s <file_name>\n", argv[0]);
exit(1);
}
fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC);
if (fd < 0)
{
perror("open()");
exit(1);
}
if (lseek(fd, 5LL*1024*1024*1024-1, SEEK_SET) < 0)
{
perror("lseek()");
close(fd);
exit(1);
}
write(fd, "", 1);
close(fd);
exit(0);
}
执行以下命令:
make bigfile
./bigfile ./big
ls -li ./big
结果:
524376 ---x-w---- 1 ecegjoker ecegjoker 5368709120 Sep 30 12:25 big
而如果使用stat ./big
查看文件大小:
File: ./big
Size: 5368709120 Blocks: 8 IO Block: 4096 regular file
Device: fd00h/64768d Inode: 524376 Links: 1
Access: (0120/---x-w----) Uid: ( 1000/ecegjoker) Gid: ( 1000/ecegjoker)
Access: 2023-09-30 12:25:34.869734022 +0000
Modify: 2023-09-30 12:25:34.869734022 +0000
Change: 2023-09-30 12:25:34.869734022 +0000
Birth: 2023-09-30 12:25:34.869734022 +0000
就会发现5G大小的文件所占空间为4k!
空洞文件
上述由bigfile
创建的文件就是空洞文件, cp
命令支持对空洞文件的拷贝:
cp big big.bck
stat big
stat big.bck
执行上述命令, 会发现: big
与big.bck
两个文件的文件大小(size)/数据/亚数据完全一致, 但是big.bck
所占空间为0!
当然, 结果可能依环境不同而不同
st_mode
在执行ls -l
时, 文件信息最前面的一串(文件类型与权限):
-rw-r--r--
就存放在st_mode
字段中, 该字段的类型mode_t
代表16位的位图 (文件类型3位, 文件权限9位, u+s位1位, g+s位1位, 粘著位(t位)1位, 共15位, 需要用16位整数存储)
- Linux文件类型
符号 | 类型 | 用于判断文件类型的宏 |
---|---|---|
d | 目录/directory | S_ISDIR(m) |
c | 字符设备/character device | S_ISCHR(m) |
b | 块设备文件/block device | S_ISBLK(m) |
- | 常规文件/regular | S_ISREG(m) |
l | 符号链接文件/link | S_ISLNK(m) |
s | 网络套接字文件/socket | S_ISSOCK(m) |
p | 命名管道/named pipe | S_ISFIFO(m) |
m处传入mode_t
类型变量
文件类型
ftype.c:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/unistd.h>
static int ftype(const char *fname)
{
struct stat statres;
if (stat(fname, &statres) < 0)
{
perror("stat()");
exit(1);
}
if (S_ISREG(statres.st_mode))
return '-';
else if (S_ISDIR(statres.st_mode))
return 'd';
else if (S_ISSOCK(statres.st_mode))
return 's';
else if (S_ISCHR(statres.st_mode))
return 'c';
else if (S_ISBLK(statres.st_mode))
return 'b';
else if (S_ISLNK(statres.st_mode))
return 'l';
else if (S_ISFIFO(statres.st_mode))
return 'p';
else
return '?';
}
int main(int argc, char **argv)
{
if (argc < 2)
{
fprintf(stderr, "Usage: %s <file name>\n", argv[0]);
exit(1);
}
printf("%c\n", ftype(argv[1]));
exit(0);
}
文件权限
umask
命令是由umask()
封装而来的, 如果想在进程中查看文件信息, 可以使用该函数:
// man 2 umask:
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
可以利用chmod()
改变某一文件的权限信息:
#include <sys/stat.h>
int chmode(const char *path, mode_t mode);
int fchmode(int fd, mode_t mode);
粘住位
粘住位(t位)原本的设计是对某一命令的使用痕迹进行保留(保留在内存中), 但是目前cache机制更加常用, 所以这一作用已被逐渐缩小
现在更加常用的是给目录增加t位(/tmp目录)
文件系统 FAT vs UFS
FAT与UFS是同一时期的文件系统, FAT不开源, UFS开源
- FAT16/32本质: 静态(数组)存储的单链表
链表节点:
struct node {
int next; // 下一个节点在数组中的位置
add_t addr; // 存储文件数据的数据块在磁盘上的位置
}
由于:
-
数组长度是固定的
-
每个节点只对应磁盘上的一个块
因此FAT文件系统最大能够支持的磁盘大小也有限
另, 根据microsoft官网上的一段描述:
FAT文件系统的特点是文件分配表(FAT), 它实际上是一个位于卷最"顶部"的表; 为了保护卷, 当一个副本损坏时, 将保留 FAT 的两个副本; 此外, FAT 表和根目录必须存储在固定位置, 以便系统启动文件能够正确定位
上述数组是一式两份的存储在磁盘中的, 那么如果两份数据因意外不一致(其中某一份坏了), 那么怎样能够辨认出是哪一份坏了呢?
另外, 单链表本身也带有"前驱节点难找"的问题
注:
FAT是支持随机存取的, 这里只是说单链表操作效率低
总结来看, FAT存在支持的磁盘大小有限/无容错/效率低的问题
- UFS 文件系统
UFS文件系统如下图所示("unix的inode"是来自"操作系统概念(第七版)"(高等教育出版社)一书P367的插图):
其中, 一个inode中有12个直接块, 因此一个文件最大为(假设一个数据块或一个索引块大小为4k, 指针大小为4B): 4TB + 4GB + 4MB + 48kB