一 文件存储相关概念,这和后面函数学习息息相关。
inode
前面我们学习过 使用 stat a.txt 去查看一个文件的属性,可以看到每个文件都一个Inode,从内容来看,像是一个 int,实际其本质为结构体
hunandede@hunandede-virtual-machine:~/projects/linuxcpp$ stat main.cpp
File: 'main.cpp'
Size: 6428 Blocks: 16 IO Block: 4096 regular file
Device: 801h/2049d Inode: 665076 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/hunandede) Gid: ( 1000/hunandede)
Access: 2024-02-24 12:44:27.250263847 +0800
Modify: 2024-02-24 12:44:26.662256231 +0800
Change: 2024-02-24 12:44:26.662256231 +0800
Birth: -
其本质为结构体,存储文件的属性信息。如:权限、类型、大小、时间、用户、盘块位置……也叫作文件属性管理结构,大多数的inode都存储在磁盘上。
少量常用、近期使用的inode会被缓存到内存中。
dentry
目录项,其本质依然是结构体,重要成员变量有两个 {文件名,inode,...},而文件内容(data)保存在磁盘盘块中。
当我们增加 或者减少一个 "硬链接" 的时候,实际上就是 增加一个 dentry 或者减少一个 dentry
注意 :当我们的dentry 被删除完毕后,磁盘上的区域并不会擦除。
即使是单纯的初始化也没有用。
那应该怎么擦除这些信息呢?把磁盘的这块区域重新写一遍就OK了,怎么知道是这块区域呢?
当然肯定有确定的办法, 但是还有笨办法,将你的磁盘重新写满,拷贝300集的 圣斗士星矢。
这就是 艳照门 事件 的原理,冠希哥看来读计算机的时候内有认真听讲。
二 文件操作 stat函数
函数原型:
获取文件属性,(从inode结构体中获取)
int stat(const char *path, struct stat *buf); 成返回0;失败返回-1 设置errno为恰当值。
参数:
参数1:文件名,文件路径
参数2:inode结构体指针 (传出参数),函数调用完毕后,会将file stat信息存在这个struct中
文件属性将通过传出参数返回给调用者。
返回值:
成功,返回0
失败,返回-1,errno被设置
stat 结构体的样子
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
/* 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
};
st_mode表示文件的类型和存取的权限,为16位整数
//0-2bit--其他人权限
S_IROTH 00004 读权限
S_IWOTH 00002 写权限
S_IXOTH 00001 执行权限
S_IRWXO 00007 掩码,过滤st_mode中除其他人权限以外的信息
//3-5bit--所属组权限
S_IRGRP 00040 读权限
S_IWGRP 00020 写权限
S_IXGRP 00010 执行权限
S_IRWXG 00070 掩码,过滤st_mode中除所属组权限以外的信息
获取权限 //6-8bit--文件所有者权限
S_IRUSR 00400 读权限
S_IWUSR 00200 写权限
S_IXUSR 00100 执行权限
S_IRWXU 00700 掩码,过滤st_mode中除文件所有者权限以外的信息
if(st_mode & S_IRUSR)//-----为真,表示可读
if(st_mode & S_IWUSR)//-----为真,表示可写
if(st_mode & S_IXUSR)//-----为真,表示可执行
获取类型(普通文件,文件夹,软链接,socket套接字,fifo管道,block块设备文件,character 字符设备文件)
//12-15bit--文件类型
S_IFSOCK 0140000 socket(套接字)
S_IFLNK 0120000 symbolic link(软连接)
S_IFREG 0100000 regular file(普通文件)
S_IFBLK 0060000 block device(块设备文件)
S_IFDIR 0040000 directory(目录)
S_IFCHR 0020000 character device(字符设备文件)
S_IFIFO 0010000 FIFO(管道)
S_IFMT 0170000 掩码,过滤st_mode中除文件类型以外的信息
if(( st_mode & S_IFMT) == S_IFREG)//-----为真,表示普通文件
if(S_ISREG (st_mode)) //-----为真,表示普通文件
if(S_ISDIR (st_mode)) //-----为真,表示目录文件
代码例子:练习:使用stat函数查看文件属性
int main() {
const char * filepath = "/home/hunandede/projects/linuxcpp/main.cpp";
struct stat buf ;
int ret = stat(filepath, &buf);
if (ret == -1) {
perror("stat /home/hunandede/projects/linuxcpp/main.cpp has been error ->");
exit(ret);
}
//重要参数st_size 或者文件的大小
cout << "buf.size = " << buf.st_size << endl;;//long int 7696 ,然后我们使用ls -l查看,文件大小是一样的
cout << "buf.st_mode = " << buf.st_mode << endl;;//long int 33204 这个33204包含了文件类型,文件所有者权限,文件所有组权限,其他人权限
//那么这个33204我们怎么知道代表的是啥呢? 在接下来的知识点会说明
cout<<buf.st_atim.tv_sec<<endl; //(long int)
cout << buf.st_atim.tv_nsec << endl;;//long int
cout << buf.st_blksize << endl;;//long int
cout << buf.st_blocks << endl;;//long int
cout << buf.st_ctim.tv_nsec << endl;;//long int
cout << buf.st_ctim.tv_sec << endl;;//long int
cout << buf.st_dev << endl;;//unsigned long int
cout << buf.st_gid << endl;;//unsigned int
cout << buf.st_ino << endl;;//unsigned long int
cout << buf.st_mode << endl;;//unsigned int
cout << buf.st_mtim.tv_nsec << endl;;//long int
cout << buf.st_mtim.tv_sec << endl;;//long int
cout << buf.st_nlink << endl;;//unsigned long int
cout << buf.st_rdev << endl;;//unsigned long int
cout << buf.st_size << endl;;//long int
cout << buf.st_uid << endl;;//unsigned int
return 0;
}
上述遗留的问题,mode 是 文件类型,文件所有者权限,文件所有组权限,其他人权限,上述例子得到的是33204,那么怎么从这个mode中获得我们想要的结果呢?
man 2 statu 中已经给出了方案,参考:
方案一
if ((buf.st_mode & S_IFMT) == S_IFREG) {
//意思就是一般文件
cout << "此文件类型为一般文件" << endl;
}
if ((buf.st_mode & S_IFMT) == S_IFDIR) {
//意思就是一般文件
cout << "此文件类型为文件夹" << endl;
}
方案二
if (S_ISREG(m)) {
cout << " is it a regular file" << endl;//普通文件
}
The following mask values are defined for the file type of the st_mode field:
S_IFMT 0170000 bit mask for the file type bit field
S_IFSOCK 0140000 socket
S_IFLNK 0120000 symbolic link
S_IFREG 0100000 regular file
S_IFBLK 0060000 block device
S_IFDIR 0040000 directory
S_IFCHR 0020000 character device
S_IFIFO 0010000 FIFO
Thus, to test for a regular file (for example), one could write:
stat(pathname, &sb);
if ((sb.st_mode & S_IFMT) == S_IFREG) {
/* Handle regular file */
}
Because tests of the above form are common, additional macros are defined by POSIX to allow the test of the file type in st_mode to be written more concisely:
S_ISREG(m) is it a regular file?
S_ISDIR(m) directory?
S_ISCHR(m) character device?
S_ISBLK(m) block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
The preceding code snippet could thus be rewritten as:
stat(pathname, &sb);
if (S_ISREG(sb.st_mode)) {
/* Handle regular file */
}
S_ISLNK(st_mode):是否是一个连接.
S_ISREG(st_mode):是否是一个常规文件.
S_ISDIR(st_mode):是否是一个目录
S_ISCHR(st_mode):是否是一个字符设备.
S_ISBLK(st_mode):是否是一个块设备
S_ISFIFO(st_mode):是否 是一个FIFO文件.
S_ISSOCK(st_mode):是否是一个SOCKET文件
int main() {
//const char * filepath = "/home/hunandede/projects/linuxcpp/main.cpp";
const char * filepath = "/home/hunandede/projects/linuxcpp/aaa";
struct stat buf ;
int ret = stat(filepath, &buf);
if (ret == -1) {
perror("stat /home/hunandede/projects/linuxcpp/main.cpp has been error ->");
exit(ret);
}
//重要参数st_size 或者文件的大小
cout << "buf.size = " << buf.st_size << endl;;//long int 7696 ,然后我们使用ls -l查看,文件大小是一样的
cout << "buf.st_mode = " << buf.st_mode << endl;;//long int 33204 这个33204包含了文件类型,文件所有者权限,文件所有组权限,其他人权限
//那么这个33204我们怎么知道代表的是啥呢? 在接下来的知识点会说明
if ((buf.st_mode & S_IFMT) == S_IFREG) {
//意思就是一般文件
cout << "此文件类型为一般文件" << endl;
}
if ((buf.st_mode & S_IFMT) == S_IFDIR) {
//意思就是一般文件
cout << "此文件类型为文件夹" << endl;
}
__mode_t m = buf.st_mode;
if (S_ISREG(m)) {
cout << " is it a regular file" << endl;//普通文件
}
if (S_ISDIR(m)) {
cout << " is it a directory file" << endl;//文件夹
}
if (S_ISCHR(m)) {
cout << " is it a character device file" << endl;//字符设备
}
if (S_ISBLK(m)) {
cout << " is it a S_ISBLK device file" << endl;//块设备
}
if (S_ISFIFO(m)) {
cout << " is it a FIFO(named pipe) file" << endl;//FIFO 文件
}
if (S_ISLNK(m)) {
cout << " is it a symbolic link file" << endl; //软链接
}
if (S_ISSOCK(m)) {
cout << " is it a symbolic link file" << endl; //是否是一个socket文件
}
cout<<buf.st_atim.tv_sec<<endl; //(long int)
cout << buf.st_atim.tv_nsec << endl;;//long int
cout << buf.st_blksize << endl;;//long int
cout << buf.st_blocks << endl;;//long int
cout << buf.st_ctim.tv_nsec << endl;;//long int
cout << buf.st_ctim.tv_sec << endl;;//long int
cout << buf.st_dev << endl;;//unsigned long int
cout << buf.st_gid << endl;;//unsigned int
cout << buf.st_ino << endl;;//unsigned long int
cout << buf.st_mode << endl;;//unsigned int
cout << buf.st_mtim.tv_nsec << endl;;//long int
cout << buf.st_mtim.tv_sec << endl;;//long int
cout << buf.st_nlink << endl;;//unsigned long int
cout << buf.st_rdev << endl;;//unsigned long int
cout << buf.st_size << endl;;//long int
cout << buf.st_uid << endl;;//unsigned int
return 0;
}
存在的问题:
我们发现如果是一个软链接,那么不会打印出来 is it a symbolic link file
if (S_ISLNK(m)) {
cout << " is it a symbolic link file" << endl; //软链接
}
这是因为stat函数有软链接穿透的功能,会指向软链接真正指向的那个文件类型
fix 方案 使用lstat 函数
该函数和stat 函数一样,唯一的不同是:对于软连接的穿透效果会
三、access函数
测试指定文件是否存在/拥有某种权限。
int access(const char *pathname, int mode); 成功/具备该权限:0;失败/不具备 -1 设置errno为相应值。
参数2:R_OK、W_OK、X_OK
通常使用access函数来测试某个文件是否存在。F_OK
access() checks whether the calling process can access the file pathname. If pathname is a symbolic link, it is dereferenced.
The mode specifies the accessibility check(s) to be performed, and is either the value F_OK, or a mask consisting of the bitwise OR of one or more of R_OK, W_OK, and
X_OK. F_OK tests for the existence of the file. R_OK, W_OK, and X_OK test whether the file exists and grants read, write, and execute permissions, respectively.
四 chmod函数
修改文件的访问权限
int chmod(const char *path, mode_t mode); 成功:0;失败:-1设置errno为相应值
int fchmod(int fd, mode_t mode);
The chmod() and fchmod() system calls change the permissions of a file. They differ only in how the file is specified:
* chmod() changes the permissions of the file specified whose pathname is given in pathname, which is dereferenced if it is a symbolic link.
* fchmod() changes the permissions of the file referred to by the open file descriptor fd.
The new file permissions are specified in mode, which is a bit mask created by ORing together zero or more of the following:
S_ISUID (04000) set-user-ID (set process effective user ID on execve(2))
S_ISGID (02000) set-group-ID (set process effective group ID on execve(2); mandatory locking, as described in fcntl(2); take a new file's group from parent direc‐
tory, as described in chown(2) and mkdir(2))
S_ISVTX (01000) sticky bit (restricted deletion flag, as described in unlink(2))
S_IRUSR (00400) read by owner
S_IWUSR (00200) write by owner
S_IXUSR (00100) execute/search by owner ("search" applies for directories, and means that entries within the directory can be accessed)
S_IRGRP (00040) read by group
S_IWGRP (00020) write by group
S_IXGRP (00010) execute/search by group
S_IROTH (00004) read by others
S_IWOTH (00002) write by others
S_IXOTH (00001) execute/search by others
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
五 truncate 函数
改变文件的大小
如果length比文件大,则文件扩展为length大小
如果length比文件下,则文件会被截断为length大小
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
六 link函数
思考,为什么目录项要游离于inode之外,画蛇添足般的将文件名单独存储呢??这样的存储方式有什么样的好处呢?
其目的是为了实现文件共享。Linux允许多个目录项共享一个inode,即共享盘块(data)。不同文件名,在人类眼中将它理解成两个文件,但是在内核眼里是同一个文件。
link函数,可以为已经存在的文件创建目录项(硬链接)。
int link(const char *oldpath, const char *newpath); 成功:0;失败:-1设置errno为相应值
注意:由于两个参数可以使用“相对/绝对路径+文件名”的方式来指定,所以易出错。
如:link("../abc/a.c", "../ioc/b.c")若a.c,b.c都对, 但abc,ioc目录不存在也会失败。
mv命令既是修改了目录项,而并不修改文件本身。
七 unlink函数
删除一个文件的目录项;
int unlink(const char *pathname); 成功:0;失败:-1设置errno为相应值
练习:编程实现mv命令的改名操作 【imp_mv.c】
注意Linux下删除文件的机制:不断将st_nlink -1,直至减到0为止。无目录项对应的文件,将会被操作系统择机释放。(具体时间由系统内部调度算法决定)
因此,我们删除文件,从某种意义上说,只是让文件具备了被释放的条件。
unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放。要等到所有打开该文件的进程关闭该文件,系统才会挑时间将该文件释放掉。 【unlink_exe.c】
上面关于link 和 unlink 的一个应用是:
假设我们要做mv a.txt b.txt 的工作
那么理论上核心代码是这样了
link("a.txt","b.txt");
unlink("a.txt");
关于unlink函数的特征的原文是:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放。要等到所有打开该文件的进程关闭该文件,系统才会挑时间将该文件释放掉。
代码解释如下:
八 隐式回收
当进程结束运行时,所有该进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。