七、Linux文件系统编程
目录:
- 七、Linux文件系统编程
- int stat(const char *pathname, struct stat *buf);
- \ int lstat(const char *pathname, struct stat *buf);
- int link(const char *oldpath, const char *newpath);
- \ int unlink(const char *pathname);
- int symlink(const char *target, const char *linkpath);
- ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
- int rename(const char *oldpath, const char *newpath);
- int chmod(const char *pathname, mode_t mode);
- int truncate(const char *path, off_t length);
- int access(const char *pathname, int mode);
- char *getcwd(char *buf, size_t size);
- \ int chdir(const char *path);
- DIR *opendir(const char *name);
- \ int closedir(DIR *dirp);
- struct dirent *readdir(DIR *dirp);
一、传入参数、传出参数和传入传出参数
传入参数:
1.指针作为函数参数;
2.通常有 const
关键字修饰;
3.指针指向有向区域,在函数内部做读操作;
比如:char *strcpy(char *dest, const char *src);
中的 const char *src
传出参数:
1.指针作为函数参数;
2.在函数调用之前,指针指向的空间可以无意义,但必须有效;
3.在函数内部做写操作;
4.函数调用结束后,充当函数返回值;
比如:char *strcpy(char *dest, const char *src);
中的 char *dest
传入传出参数:
1.指针作为函数参数;
2.在函数调用之前,指针指向的空间有实际意义;
3.在函数内部,先做读操作,后做写操作;
4.函数调用结束后,充当函数返回值;
二、dentry和inode
dentry 和 inode 是描述文件的最主要的两个属性
1. 首先 dentry 存放的是文件名和相应的 inode 编号,而 inode 是一个结构体,存放着文件的属性,如:权限、大小、盘符、用户组等信息
2. 文件的内容最终保存的位置是电脑的磁盘。通过文件名可以访问到文件的 dentry,在 dentry 上可以得到文件对应的 inode 编号,通过 inode 可以访问到文件的盘符信息,进而可以在电脑磁盘上访问到文件
3. 实际上,硬链接与原文件不同的地方在于 dentry,即不同的文件名,而 inode 编号是相同的,因此,通过硬链接也可以访问到原文件的 inode 信息,硬链接因此与原文件紧密关联
如图所示:
为什么目录项 dentry 要游离于 inode 之外,画蛇添足般的将文件名单独存储呢?
其目的是为了实现文件共享,Linux允许多个目录项共享一个 inode,即共享盘块(data),不同文件名,在人类眼中将它理解成两个文件,但是在内核眼里是同一个文件
三、stat和lstat函数
1.函数原型
包含头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *buf);
获取文件属性结构体,从 inode 结构体中获取
const char *pathname
文件路径
struct stat *buf
inode 结构体指针(传出参数)
返回值
成功时返回 0,失败返回 -1、errno
int lstat(const char *pathname, struct stat *buf);和
stat()
函数用法一样,它们的区别在于,stat()
可以穿透软链接,而lstat()
不会穿透软连接
注意:穿透软连接的意思是将软链接视为原文件
struct stat *buf
结构体成员:
struct stat {
dev_t st_dev; /* 包含文件的设备的id */
ino_t st_ino; /* inode 编号 */
mode_t st_mode; /* 低9位为权限位,高4位为文件类型位 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 用户 ID */
gid_t st_gid; /* 组 ID */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* 文件大小 */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* 扇区数量(一个扇区 512 bit) */
/* 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; /* 最后访问时间 */
struct timespec st_mtim; /* 属性最后修改时间 */
struct timespec st_ctim; /* 内容最后修改时间 */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
例如:写一个指令,使其能通过 指令 <文件名>
,来显示文件的大小
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
struct stat sbuf;
int ret = stat(argv[1], &sbuf);
if(ret == -1)
{
perror("stat error");
exit(1);
}
printf("file_size:%ld\n", sbuf.st_size);
return 0;
}
2.宏函数
在通过 stat()
函数取得文件属性结构体后,还可以通过宏函数来对文件的一些属性进行判断:参数为 st_mode
(取高4位)
是就返回 1,否则返回 0
例如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
struct stat sbuf;
int ret = stat(argv[1], &sbuf);
if(ret == -1)
{
perror("stat error");
exit(1);
}
if(S_ISREG(sbuf.st_mode))
{
printf("It's a regular file\n");
}
else if(S_ISDIR(sbuf.st_mode))
{
printf("It's a directory\n");
}else if(S_ISCHR(sbuf.st_mode))
{
printf("It's a character device\n");
}else if(S_ISBLK(sbuf.st_mode))
{
/* ... */
}
/* ... */
return 0;
}
注意:由于 stat()
可以穿透软链接,其获取的 sbuf.st_mode
在使用 S_ISLNK()
询问是否为软链接时,都不会是软链接,而是原文件类型,而 lstat()
获取的 sbuf.st_mode
就会回答是软链接
使用 stat()
的结果:
使用 lstat()
的结果:
四、link、unlink和symlink函数
首先明确:
1.函数原型
包含头文件:
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
可以为已经存在的文件创建目录项(硬链接)
const char *oldpath
原文件路径
const char *newpath
硬链接路径
返回值
成功时返回 0,失败返回 -1、errno
int unlink(const char *pathname);删除一个文件的目录项(硬链接),可以删除原文件路径
const char *pathname
文件路径
返回值
成功时返回 0,失败返回 -1、errno
删除文件实际上就是删除文件的最后一个硬链接,即没有 dentry 与之对应,这样就可以使系统无法访问文件在磁盘中的位置,但文件在磁盘上的内容依旧存在;要等到所有打开该文件的进程关闭,该文件才会被操作系统择机释放,即等待其他内容覆盖之
终端命令 mv 实际上只是改变了文件的目录项
例如:模拟终端指令 mv
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int ret = link(argv[1], argv[2]);
if(ret == -1)
{
perror("link error");
exit(1);
}
ret = unlink(argv[1]);
if(ret == -1)
{
perror("unlink error");
exit(1);
}
return 0;
}
2.隐式回收
当进程结束运行时,所有该进程打开的文件会被关闭,申请的内存空间会被释放,Linux系统的这一特性称之为隐式回收系统资源
因此,即使文件的最后一个硬链接被 unlink 掉,只要还有打开了该文件的进程正在运行,它的系统资源就仍然存在,仍然可以对该文件进行操作
3.symlink函数
包含头文件:
#include <unistd.h>
int symlink(const char *target, const char *linkpath);
创建一个符号链接(软连接)
const char *target
原文件路径
const char *linkpath
软连接路径
返回值
成功时返回 0,失败返回 -1、errno
4.与链接相关的其他函数
包含头文件:
#include <unistd.h>
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
读取符号链接(软链接)所链接的文件的内容
const char *pathname
软链接路径
char *buf
存数据的缓冲区,其大小决定每次读取数据的字节大小
size_t bufsiz
缓冲区的字节大小,相当于每次读取数据的字节大小
返回值
成功则返回读到的字节数,当读到文件末尾时,读到的字节数为 0,会返回 0,失败时返回 -1、errno
包含头文件:
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
重命名一个文件
const char *oldpath
原文件路径
const char *newpath
新文件路径
返回值
成功时返回 0,失败返回 -1、errno
五、其他函数
1.chmod函数
包含头文件:
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
修改文件的访问权限
const char *pathname
文件路径
mode_t mode
修改后的权限,特殊权限位+三位八进制,受 umask 影响
返回值
成功返回 0,失败返回 -1、errno
2.truncate函数
包含头文件:
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
截断文件长度成指定长度,常用来拓展文件大小
const char *path
文件路径
off_t length
截断后的大小
返回值
成功返回 0,失败返回 -1、errno
3.access函数
包含头文件:
#include <unistd.h>
int access(const char *pathname, int mode);
测试指定文件是否存在/拥有某种权限
const char *pathname
文件路径
int mode
需要验证的权限
返回值
成功/具备该权限时返回 0,失败/不具备该权限返回 -1、errno
int mode | 作用 |
---|---|
R_OK | 是否具有读权限 |
W_OK | 是否具有写权限 |
X_OK | 是否具有可执行权限 |
F_OK | 文件是否存在 |
六、目录及其相关函数
1.文件和目录的权限差异
权限 | 文件 | 目录 |
---|---|---|
r | 可读(cat、more、less、vi) | 目录可被浏览(less、tree) |
w | 可写/修改 | 创建、删除、修改文件(mv、touch、mkdir) |
x | 可以运行产生一个进程 | 可以被打开、进入(cd) |
目录文件也是文件,其文件内容是该目录下所有子文件的目录项 dentry
2.getcwd和chdir函数
包含头文件:
#include <unistd.h>
char *getcwd(char *buf, size_t size);
获取进程当前工作目录,man 手册第3卷——标库函数
char *buf
传出参数,存放工作目录(字符串)
size_t size
传出参数buf
的大小
返回值
成功时返回工作目录(字符串),失败返回 NULL、errno
int chdir(const char *path);改变当前进程的工作目录
const char *path
改变后的工作目录
返回值
成功返回 0,失败返回 -1、errno
3.opendir/closedir函数
包含头文件:
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
根据传入的目录名打开一个目录/库函数
const char *name
目录/库函数路径
返回值
DIR * 类似于 FILE *,成功返回该目录结构体指针,失败返回 NULL、errno
int closedir(DIR *dirp);关闭打开的目录/库函数
DIR *dirp
要关闭的目录的 DIR
返回值
成功返回 0,失败返回 -1、errno
const char *name
可以是绝对路径,可以是相对路径,可以通过 char *path = getcwd()
获取当前工作目录
可以通过 man 手册查询,在第 3 卷
man 3 opendir
man 3 closedir
4.readdir函数
包含头文件:
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
读取目录/库函数,根据目录偏移指针位置来读
DIR *dirp
要读取的目录的 DIR
返回值
成功时返回目录项结构体指针,根据目录偏移指针位置来返回,读到结尾(读完最后一个文件)时返回 NULL,失败返回 NULL、errno
目录项结构体 struct dirent
成员图:
例如:模拟终端指令 ls
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
int main(int argc, char *argv[])
{
struct dirent *sdp;
DIR *dp = opendir(argv[1]);
if(dp == NULL)
{
perror("opendir error");
exit(1);
}
while((sdp = readdir(dp)) != NULL)
{
printf("%s\t", sdp->d_name);
}
printf("\n");
int ret = closedir(dp);
if(ret == -1)
{
perror("closedir error");
exit(1);
}
return 0;
}
5.目录偏移指针
目录偏移指针的位置也可以被指定,相关函数:rewinddir()、telldir()、seekdir()