该系列文章总纲链接:专题分纲目录 LinuxC 系统编程
本章节思维导图如下所示(思维导图会持续迭代):
第一层:
第二层:
文件管理:在linux下,每个文件都保存在内核中的stat结构中,该结构保存一个文件的状态,这个结构中几乎包含了所有需要的信息。
1 文件状态 stat
在内核中,linux下文件的状态信息和文件内容是分开存储的。在linux中用stat函数来取得一个文件的状态,stat的函数原型:
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
参数path :文件所在的路径。对未进行open的文件进行操作。
参数fd :已经打开的文件描述符。对已经open的文件进行操作。
参数buf :返回的struct stat结构体的指针,属于值结果参数。
函数执行成功返回0,失败返回-1。
lstat函数类似于stat,但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用文件的信息。
stat结构体以及说明如下:
struct stat {
mode_t st_mode; //文件访问权限
// st_mode包含文件的类型与访问权限、设置用户ID位和设置组ID位,所有信息以位的形式存储,往往涉及大量位运算操作。
ino_t st_ino; //inode索引节点号
dev_t st_dev; //文件系统的设备号
dev_t st_rdev; //设备文件的设备号
//这3个变量涉及文件系统的实现。
nlink_t st_nlink; //文件的硬连接数
//这个变量涉及文件系统的实现。
uid_t st_uid; //所有者用户ID
gid_t st_gid; //所有者组ID
//empty
time_t st_atime; //最后一次访问该文件的时间
time_t st_mtime; //最后一次修改该文件的时间
time_t st_ctime; //最后一次改变该文件状态的时间
//几乎所有的文件I/O函数都会改变这3个和时间有关的变量,只有access和stat函数例外。
off_t st_size; //文件大小,以字节为单位;只对普通文件有效
blksize_t st_blksize; //最合适的缓冲区大小
blkcnt_t st_blocks; //该文件在外存上占用的盘块数
//empty
};
2 文件类型
linux下共有7中文件类型:
- 普通文件 - :文本文件和二进制文件
- 目录文件 d :目录文件主要存放目录下的文件名和指向它们的指针
- 块设备文件 b:带有缓冲区的设备文件
- 字符设备文件 c :无缓冲区的设备文件
- 命名管道 f:用于进程间通信的文件,只存在于内核中
- 符号链接 l:指向另一个文件
- 套接字 s:网络进程之间的通信,一种特殊的管道,也可以用于同一主机内的进程间非网络通信。
在stat结构体中文件类型存储在st_mode变量中,表示文件类型的是3个连续的二进制数,共有8个状态,包括7中文件类型与未知类型。linux提供一些宏来取得相应的二进制位用于判断文件的类型,宏定义在文件sys/stat.h中。
文件类型和对应的测试宏:
S_ISREG() : 普通文件
S_ISDIR() : 目录文件
S_ISCHR() : 块设备文件
S_ISBLK() : 字符设备文件
S_ISFIFO() : 管道文件
S_ISLNK() : 符号链接
S_ISSOCK(): 套接字
使用方法:宏(stat结构体变量.st_mode),若非宏所属的这种类型,返回0,是的话返回非0值。
3 文件权限
3.1 访问权限
每个文件的访问权限分3类,每一类分别针对3种用户。
- 文件的用户:文件所有者(u),文件所有者的组(g),其他用户(o)。
- 文件的访问权限:可读(r)、可写(w)、可执行(x)。
在stat结构体中st_mode变量一共有9个标志位给访问权限,其顺序由高到低分别是:
- 文件所有者(u) :可读(r)、可写(w)、可执行(x);
- 文件所有者的组(g) :可读(r)、可写(w)、可执行(x);
- 其他用户(o) :可读(r)、可写(w)、可执行(x);
如果判断该位是否具有权限则利用st_mode与相应的测试宏进行与操作即可,类似于判断文件类型。
文件权限测试宏:
S_IRUSR :0400:用户-读
S_IWUSR :0200:用户-写
S_IXUSR :0100:用户-执行
S_IRGRP :0040:用户组-读
S_IWGRP :0020:用户组-写
S_IXGRP :0010:用户组-执行
S_IROTH :0004:其他用户-读
S_IWOTH :0002:其他用户-写
S_IXOTH :0001:其他用户-执行
使用方法:stat结构体变量.st_mode &宏,若非宏所属的这种类型,返回0;是的话返回非0值。
3.2 设置用户ID位与设置用户组ID位
文件权限位中有两位分别用于查看并设置是否有设置用户ID位与设置用户组ID位的权限。与文件权限有关的进程ID分为4种情况:
- ruid:实际用户ID:表示进程属于哪个用户,一般是谁创建了进程,进程就属于哪个用户。
- euid:有效用户ID :表示进程目前实际具有的用户权限。一个进程得到某个用户的允许,暂时成为该用户的进程,从而可以行使与该用户其他进程同样的权限。
- rgid:实际用户组ID:表示进程属于哪个用户组,一般是谁创建了进程,进程就属于哪个用户的用户组。
- egid:有效用户组ID:表示进程目前实际具有的用户组权限。一个进程得到某个用户组的用户的允许,暂时称为该用户的用户组的进程,从而可以行使与该用户所在用户组其他进程同样的权限。
文件的设置用户ID位和设置用户组ID位可以使进程的有效用户ID和用户组ID等同于文件所有者的用户ID和用户组ID,即可以使进程拥有者的权限暂时等同于文件所有者的权限。用户ID和组ID的值是在调用exec函数的时候改变的,因此设置用户ID位和设置组ID位只对可执行文件(二进制文件)起作用。
如果判断设置用户ID位和设置用户组ID位的值,需要用st_mode与相应的宏进行与操作:
设置用户ID位与设置用户组ID位测试宏:
S_ISUID :04000 :设置用户ID位
S_ISGID :02000 :设置用户组ID位
使用方法:stat结构体变量.st_mode &宏,若非宏所属的属性,返回0;是的话返回非0值。
设置用户ID位的作用:用户程序本身不可以操作内核区的文件,但是通过向内核请求得到的批准,暂时创建一个新的进程后,利用这个进程就可以用内核的权限操作文件。进程的有效用户ID有以下几种情况:
- 0(该进程属于根用户/得到根用户的授权),此时,可以访问,即进程拥有文件所有者的权限。
- 和文件的所有者用户ID一致(该进程属于文件的所有者/得到所有者的授权),且文件所有者适当的文件权限位已经被设置,则可以访问,否则拒绝访问。
- 和文件的组ID一致(该进程属于文件所在的组/得到该组的授权),且文件的用户组权限已经设置,则可以访问。
不满足上面3条,则拒绝访问。
4 文件权限操作
4.1 测试文件访问权限 access
测试当前的用户是否有权限对某些文件进行操作,linux下使用access函数进行访问权限测试。access函数的原型:
#include <unistd.h>
int access(const char *pathname, int mode);
参数pathname :测试文件的路径
参数mode :测试文件的模式。
R_OK :测试读权限
W_OK :测试写权限
X_OK :测试可执行权限
F_OK :测试文件是否存在
函数执行成功返回0,失败返回-1。
access函数使用进程的实际用户ID和用户组ID与文件所有者ID和组ID进行比较:
- 如果进程的实际用户ID与文件所有者ID相等,则使用参数mode指定的权限与文件所有者的权限进行比较。
- 如果上面不成立,且进程的实际组ID与文件的组ID一致,则使用mode指定的权限与文件的组权限进行比较。
- 如果上述条件都不成立,则使用mode指定的权限与其他用户权限进行比较。
4.2 使用文件权限屏蔽字
在实际应用中,系统有时并不希望用户设置文件所有的权限,因此设置了umask权限屏蔽码,可以屏蔽一些不希望用户干预的权限位。这样即便用户设置了相关的位,但是由于掩码的原因设置无效。
此时文件权限 = 进程创建文件的权限字 & umask掩码屏蔽。
linux用umask来创建一个权限屏蔽字。umask函数的原型:
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
参数mask:一个新的屏蔽字,是一个位向量,表示9个权限标志位,哪一位的屏蔽位为1,说明用户在创建文件时所指定的权限标志无效。如果mask的值是0,则新文件的所有权限完全由用户来指定,完全有效。
umask函数的返回值是以前的屏蔽字,可以将它保存后用于操作结束后对屏蔽字的恢复。
在shell中可以使用命令umask来查看掩码的值,同样也可以加上权限值来直接设置掩码的值。
$umask 查看掩码; $umask 007 设置掩码为007;
注意:当使用umask函数时,仅对当前进程有效,退出程序后无效。
4.3 改变文件访问权限
Linux下使用chmod函数来改变文件的权限。chmod函数的原型:
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
参数path :需要更改权限的文件的路径
参数mode :新的权限。
参数fd :已经打开的文件的文件描述符
S_ISUID :04000:设置用户ID位
S_ISGID :02000:设置用户组ID位
S_ISVTX :01000:粘性位
S_IRWXU :0700:用户-读写执行
S_IRUSR :0400:用户-读
S_IWUSR :0200:用户-写
S_IXUSR :0100:用户-执行
S_IRWXG :0070:用户组-读写执行
S_IRGRP :0040:用户组-读
S_IWGRP :0020:用户组-写
S_IXGRP :0010:用户组-执行
S_IRWXO :0007:其他用户-读写执行
S_IROTH :0004:其他用户-读
S_IWOTH :0002:其他用户-写
S_IXOTH :0001:其他用户-执行
函数执行成功返回0,失败返回-1。
在linux下,可以通过chmod命令直接改变权限。在程序中利用chmod函数是一样的效果。
fchmod函数仅仅是对已经打开的文件进行操作,其他地方与chmod是一样的。
5 文件系统结构简介
文件系统中存储的最小单位是块(block),一块到底有多大是由磁盘进行格式化时指定的 。linux下使用mke2fs命令将一个指定的磁盘分区格式化,并且在该分区上建立一个文件系统,命令参数-b用于指定块的大小。所有的柱面组之前有两个主要的块:引导块与超级块
- 引导块:用来存储磁盘分区信息和启动信息,任何系统都不能改写引导块
- 超级块:描述整个分区的文件系统信息,如块的大小、文件系统版本号、上次挂载文件系统的时间等。
文件系统将整个分区划成若干个同样大小的柱面组,每个柱面组有6个部分组成:
- 超级块副本 :和柱面之前的超级块一样,只是复制了一份而已
- 柱面组描述表 :存储柱面组的信息,如inode节点的起始位置,数据块的起始位置等。这些信息一损坏就会丢失整个分区
- inode节点位图 :inode位图信息本身占一块,每个位代表柱面组的一块,1表示本块已用,0表示未用。
- 数据块位图 :类似于inode位图,每个位代表柱面组的一块,1表示本块已用,0表示未用。
- inode存储区 :存储本柱面组的inode节点
- 数据块存储区 :存储本柱面组的数据块。(根据文件类型数据块分为:存储文件内容的数据块、存储目录项的目录块)
6 文件的硬链接
硬链接:把文件名和计算机文件系统使用的节点号链接起来。因此我们可以用多个文件名与同一个文件进行链接,这些文件名可以在同一目录或不同目录。一个文件有几个文件名(用ln命令实现多个文件名),我们就说该文件的链接数为几。由定义可知,此链接数可以是1, 这表明该文件只有一个文件名。创建一个硬链接:(linux下使用link函数创建一个硬链接)。link函数的原型:
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
参数oldpath :创建硬链接的源本,也就是现有的文件
参数newpath :创建的新链接
函数成功执行返回0,失败返回-1。
删除一个硬链接:(linux下使用unlink函数创建一个硬链接)
unlink函数的原型:
#include <unistd.h>
int unlink(const char *pathname);
参数pathname:将pathname的最末端的目录项删除。同时目录项所对应的磁盘文件的链接数减1。如果链接数为0,则文件将被删除。
函数成功执行返回0,失败返回-1。
注意:当一个文件的链接数为0且文件不再处于打开的状态时,系统将自动删除该文件。如果文件的链接数为0,但是文件处于打开状态则文件不被删除,只是对文件的读和写操作实际上是对内存缓冲区进行操作,当文件关闭时,内存中的缓冲区清空,文件也就消失了。
7 改变文件所有者
linux下使用chown函数改变一个文件的所有者。chown函数的原型:
#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
参数path :文件的路径
参数owner :新的所有者用户的ID。值为-1时表示文件所有者用户ID不发生变化。
参数group :新的所有者用户组的ID。值为-1时表示文件所有者用户组ID不发生变化。
参数fd :已经打开的文件的文件描述符
函数成功执行返回0,失败返回-1。
如果要修改一个文件的所有者ID和组ID,该用户必须是根用户,或者需要同时满足以下3点才可以成功更改文件的所有者:
- 修改文件进程的有效用户ID等于该文件的ID,即修改经过文件所有者授权。
- 参数owner为-1或者文件用户的ID,即文件用户的可以不更该/改成原所有者的文件
- 参数group等于进程的有效组ID,即用户只能将文件改到用户组当中
注意:对于文件所有者的修改,即使指定的用户并不存在,chown函数部不检查其存在性,同样不会报错,依然会将所有者修改为用户指定的,只有在操作该文件的时候会报错。
fchown与chown函数相比,只是对已经打开的文件进行操作。
lchown与chown函数相比,只是改变符号链接的所有者,不改变所指向的文件的所有者。
8 操作文件的时间简介
文件结构中有3个与文件操作有关的时间:
- st_atime :最近访问文件的时间
- st_mtime :文件内容修改时间
- st_ctime :文件状态修改时间,即stat结构体中变量有所变化。
使用stat命令可以查看时间属性。
注意:read函数只对文件内容进行读操作,因此不会改变ctime,只会改变atime。