示意图:Block(块):
物理磁盘是由一个个的区(所谓的扇区)连接成一片,每个区是 512 字节。为了能有组织,有计划的使用磁盘,设计者把连续若干个(比如2个,4个,8个)区连成一组,抽象出 block 的概念。如果使用 2 个区为一组,那一个 block 的大小就是 1KB。如今,磁盘也不在是什么昂贵资源,动辄就是几百G大小,通常在设计 block 的时候都使用 8 区连成一组,即一个 block 是 4KB。
Boot Block(引导区):
无论如何,Boot Block 都占用 1KB,它并不以你划分的 block 大小来改变自己。即使你划分的 block 大小是 4KB,Boot Block 也只占用 1KB 大小。这个块,是专门为启动CPU的时候保留的(我没说是启动操作系统)。
在你摁下开机键的那一刻,CPU 一上电,硬件会主动把 Boot Block 复制到内存的0x7c00这个位置,然后CPU 就从这个位置开始执行,后面的事情,就是一步一步的把操作系统引导起来。
inode table(inode表):
inode表中存放了inode节点,inode节点记录了文件存放位置、文件权限、最近打开时间等信息。但是并没有存储文件名,文件名则是被放在了称为目录项的结构体中。
inode bitmap(inode位图)和block bitmap(块位图):
位图中的每一位用来表示一个位置,为1则表示被占用,为0表示没有被占用。inode位图用来记录inode表的占用情况,block位图记录block的占用情况。
Group Descriptor Table(组描述符表):
一个组描述符表,它不仅存储当前组的信息,也会保存其它组的信息。这是一种备份思想。组描述符表中存储的组描述符。
组描述符中保存了 inode 位图和 block 位图在哪个 block 上。除此还有 inode 表的位置等等。
Super Block(超级块):
记录一些全局信息,如:inode节点个数、block个数、第一个数据块的位置等。
linux 中,可以使用 stat 函数来获取文件相关的信息,就比如说文件的大小,文件的类型等等。stat 函数将获取到的结果保存到一个名为 struct stat 的结构体中。它的样子如下:
struct stat {
dev_t st_dev; /* 包含这个文件的设备 ID */
ino_t st_ino; /* inode 编号 */
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; /* 文件系统块大小 */
blkcnt_t st_blocks; /* 文件占用了几个512字节 */
time_t st_atime; /* 最后访问时间 */
time_t st_mtime; /* 最后更改时间 */
time_t st_ctime; /* 最后状态更改时间 */
};
15-12 位保存文件类型
11-9 位保存执行文件时设置的信息
8-0 位保存文件访问权限
黏着位:
黏着位对目录文件时有用的,当此目录黏着为1时,虽然多用户对此目录有读写权限,可以在此目下创建文件,但是此目录下文件只能被所有者删除或重命名。
chmod 函数做的是与 stat 获取 i_mode 值相反的操作,它是把值存储到 i_mode 字段里。不过有一点需要注意的是,它只能改变 i_mode 的低 12 位,因为高 4 位保存的是文件类型,就算你改了也不会起作用。
chomd也可以命令形式对文件进行修改,将参数放到chomd后即可。
/ 输入:文件路径和 mode。这里的 mode_t 就是一个整型。
// 返回:0 表示成功,-1 失败
int chmod(const char *path, mode_t mode);
chown 命令只能工作在 root 权限下,主要用来修改文件的所有者和所属组。
$ sudo chown root:jack test.txt // 运行该命令必须具备 root 权限
上面命令的作用是将test.txt文件所有者修改为root,所属组修改为jack。
也可以按照函数体在程序中以函数的形式使用chown()。
/ pathname: 要更改的文件名
// owner: 拥有者 uid
// gropu: 所属组 gid
// 返回 0 成功,-1 失败
int chown(const char *pathname, uid_t owner, gid_t group);
作用:修改文件的最近访问时间(atime)和最近修改时间(mtime).
*
struct utimbuf {
time_t actime; // access time
time_t modtime; // modification time
};
*/
int utime(const char *filename, const struct utimbuf *times);
所谓的文件截断 ,就是把文件尾巴砍掉,留下固定长度的文件。truncate函数就是用来实现这个功能的:
int truncate(const char *path, off_t length);
参数length可以比当前文件长度小,那么多出的部分就会被砍掉,形成length长度的文件。
length也可以比当前文件长度大,这种情况下,就会在文件后面补0。使用stat命令获取到的文件长度为length,但是占用的block*512(或者1024,在Linux系统上,这取决于是否设置了POSIXLY_ CORRECT环境变量)却小于length,这就是所谓的文件空洞。这是由于read操作对于没有写过的字节读取到的字节长度为0,当使用实用程序(如cat)复制这个文件时,这些所有的空洞都会被0填满。
首先,硬链接可以看作是文件的别名,而软链接也叫符号链接,是文件的一种。
link系列函数创建、删除、读取软链接和硬链接
函数 | 功能 |
---|---|
link | 创建硬链接 |
symlink | 创建软链接(符号链接) |
unlink | 删除硬链接或者软链接(不能删除目录,如果想删除目录,可以使用 rmdir 函数或者 ANSI C 中的 remove 函数。) |
readlink | 读取软链接文件自身内容(目标文件路径)(不是所指向的文件内容) |
ln命令创建硬链接
每个文件都有一个索引,也就是inode,里面存储了文件的权限、访问时间、文件位置等等,但是一个inode可能对应多个link,也就是inode中的i_links_count可以大于1.这是种现象就是硬链接。创建一个文件的多个inode对应,这些对应都指向同一个inode(在目录项种的多个dir_entry的inode节点编号相同),相当于给同一个文件起了一些别名,当不小心删除了一个文件名(实际是删除了一个链接),还可以通过别名找到这个文件。只有当所有的硬链接都被删除时,即i_links_count==0时,才会释放文件所占有的空间。
ln -s创建软链接
软链接也叫符号链接,是文件的一种。既然是一个单独的文件,那么它也有自己的inode节点,与指向的文件不同。它内容是目标文件的路径,所以可以通过符号链接的内容找到目标文件,这类似于Windows中常用的快捷方式。
1、硬链接只能在本文件系统中起作用,不能跨文件系统,软链接可以。
2、普通用户不能创建目录的硬链接(因为这有可能会造成死循环,可以参照下面依据我自己的理解画的“丑图”,如有错误,欢迎指正),可以创建任意文件的软链接(软链接不会造成循环,因为系统在遍历目录的过程中可以在inode中看到文件属性,遇到的符号链接超过8个就会自动停止)。
3、解除一个文件的链接时,可以通过其他的硬链接找到文件,但是通过这个路径创建的软链接会失效。
4、文件属性和大小不同,硬链接与原文件一样。软链接的文件属性即符号链接,根据内容有自己的大小。
5、硬链接与原文件对应同一个inode,所以它们是同一个文件,而软链接与原文件是不同的文件。
当前工作目录:
当前工作目录是针对进程而言的,它是进程的属性。
chdir函数
与命令cd类似,进行目录切换。
getcwd函数
与pwd(print working dir)命令类似,获取当前工作目录。
如果在程序中写了这两个函数,例如使用chdir进入子目录,然后利用getcwd函数获取当前工作目录并打印。在shell父目录中执行这个程序,会输出chdir进入的子目录。但是使用pwd命令,会输出父目录。这是为什么呢?因为当前工作目录是针对进程而言的。getcwd获取的是你写的程序的进程的当前目录,pwd打印的是shell进程的工作目录,当然就不一样了!
mkdir:命令和函数均用来创建空目录,但是mkdir函数需要指定目录的权限位。
int mkdir(const char *pathname, mode_t mode);
目录的权限位,至少要设置一个执行许可位。即 rwx 中的那个 x 位。如果不设置,就没办法使用 cd 命令进入目录,也无法读取目录下的文件内容。所以,这个位是必须的。
目录的写权限位。如果未设置写权限位(w 位),将导致无法向目录文件中写入目录项(dir_entry 结构)。比如不能使用 touch 命令创建文件。
目录的读权限位。如果未设置读权限位,将导致无法读取目录文件中的目录项。比如你不能使用 ls 命令查看目录项的具体内容。
此函数不能创建多级目录。
rmdir:
int rmdir(const char *pathname);
此函数只能删除空目录
opendir
DIR *opendir(const char *name);
打开目录,返回目录文件。
readir
struct dirent *readdir(DIR *dirp);
readdir 函数将从当前的目录项偏移的位置开始,读取一条目录项,同时将偏移量增加到下一目录项的位置。
dirent类似与dir_entry数据结构
struct dirent {
ino_t d_ino; // inode number
off_t d_off; // not an offset; see NOTES
unsigned short d_reclen; // length of this record
unsigned char d_type; // type of file; not supported by all filesystem types */
char d_name[256]; // filename
};