文件和目录
stat()、fstat()、lstat()、fstatat()
#include <sys/stat.h>
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
int fstatat(int dirfd, const char *pathname, struct stat *buf, int flags);
stat:根据文件路径获取文件属性,会跟随符号链接,返回链接指向的目标文件信息。
-
pathname:文件路径
-
跟随符号链接,获取链接指向文件信息
fstat:通过已打开的文件描述符 fd 获取文件属性,无需文件路径。
-
fd:已打开文件描述符
-
无需路径,不受进程目录、链接影响
lstat:和 stat 路径参数一致,不跟随符号链接,如果路径是软链接,返回软链接本身的 inode 信息,遍历目录树时必须使用 lstat。
- 参数同 stat,不跟随软链接,直接获取链接本身属性
fstatat:相对于指定目录 dirfd 进行路径查询的扩展函数
-
dirfd:基准目录 fd(AT_FDCWD = 当前目录,pathname 为绝对路径时 fd 参数被忽略。)
-
pathname:相对 / 绝对路径
-
flags:
-
AT_SYMLINK_NOFOLLOW:不追踪软链接(等价 lstat)
-
无该标志:追踪软链接(等价 stat)
-
struct stat 结构体成员
struct stat {
mode_t st_mode; // 文件类型 + 文件访问权限
ino_t st_ino; // 文件i节点编号,文件唯一标识
dev_t st_dev; // 文件所在的设备编号
dev_t st_rdev; // 特殊设备文件的设备编号
nlink_t st_nlink; // 文件硬链接数量
uid_t st_uid; // 文件所有者用户ID
gid_t st_gid; // 文件所属组ID
off_t st_size; // 普通文件的字节大小
struct timespec st_atime; // 最后访问时间
struct timespec st_mtime; // 最后修改文件内容时间
struct timespec st_ctime; // 文件元属性最后修改时间(权限、所有者、链接数变更)
blksize_t st_blksize; // 文件系统最优IO块大小
blkcnt_t st_blocks; // 文件占用的磁盘块数量
};
struct timespec {
time_t tv_sec; // 秒
long tv_nsec; // 纳秒,实现高精度时间戳
};
设置用户id和设置组id
进程关联的 7 个 ID
| ID 名称 | 作用说明 |
|---|---|
| 实际用户 ID (RUID) | 标识进程真实是谁;登录会话一般保持不变,超级用户可修改 |
| 实际组 ID (RGID) | 进程真实所属用户组 |
| 有效用户 ID (EUID) | 用于文件访问权限校验 |
| 有效组 ID (EGID) | 用于文件访问权限校验 |
| 附属组 ID | 额外附属用户组,权限校验一并参与 |
| 保存的设置用户 ID (SUID) | exec 执行程序时,保存 EUID 副本;权限切换场景使用 |
| 保存的设置组 ID (SGID) | exec 执行程序时,保存 EGID 副本 |
默认状态:EUID=RUID,EGID=RGID。
文件模式字st_mode中存在两个特殊标志位:
-
S_ISUID(设置用户 ID 位,setuid 位) 若文件开启该位,执行此程序时:进程有效用户 ID (EUID) 被设置为【文件所有者 st_uid】,而不是进程原本的实际用户 ID。
-
S_ISGID(设置组 ID 位,setgid 位) 若文件开启该位,执行此程序时:进程有效组 ID (EGID) 被设置为【文件所属组 st_gid】
getuid():返回实际用户 ID(real uid, ruid)
geteuid():返回有效用户 ID(effective uid, euid)
setuid(uid_t u)
特权进程 (root):同时修改 ruid、euid、saved uid 三个 UID,永久切换身份;
普通进程:仅当 u == ruid 或 u == saved uid 时,修改 euid;切换后无法切回更高权限。
seteuid(uid_t u):只修改有效 UID euid。仅能在 ruid ↔ saved uid 之间来回切换权限(setuid 程序常用临时降权 / 提权)
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
void print_uids(const char *tag) {
uid_t ruid, euid, suid;
getresuid(&ruid, &euid, &suid);
printf("[%s] RUID=%d, EUID=%d, saved-suid=%d\n", tag, ruid, euid, suid);
}
int main() {
print_uids("start");
// ① 临时放弃特权:将有效UID改为实际UID(普通用户)
if (seteuid(getuid()) != 0) {
perror("seteuid(getuid())");
exit(1);
}
print_uids("after seteuid(ruid)");
// ② 此时以普通用户权限安全地读取其个人配置文件
FILE *user_file = fopen("/home/user/.myapprc", "r");
if (user_file) {
char line[256];
if (fgets(line, sizeof(line), user_file))
printf("读取到用户配置: %s", line);
fclose(user_file);
} else {
perror("fopen user config");
}
// ③ 需要执行特权操作时,从保存的 set-user-ID 恢复特权
// 因为 saved-suid 还是 root(0),可以直接 seteuid(0) 恢复
if (seteuid(0) != 0) {
perror("seteuid(0)");
exit(1);
}
print_uids("after seteuid(0)");
// ④ 以 root 权限修改系统文件
FILE *sys_file = fopen("/etc/myapp.conf", "a");
if (sys_file) {
fprintf(sys_file, "# 由程序更新\n");
fclose(sys_file);
printf("成功写入系统配置。\n");
} else {
perror("fopen system config");
}
return 0;
}
文件访问权限
没个文件都有9个访问权限位,分为三类 用户(u)、组(g)、其他(o)。
| 宏常量 | 含义 |
|---|---|
| S_IRUSR | 用户读 |
| S_IWUSR | 用户写 |
| S_IXUSR | 用户执行 |
| S_IRGRP | 组读 |
| S_IWGRP | 组写 |
| S_IXGRP | 组执行 |
| S_IROTH | 其他读 |
| S_IWOTH | 其他写 |
| S_IXOTH | 其他执行 |
目录文件的读权限、执行权限和其他文件有所不同
-
读权限 (r):可以列出目录内所有文件名(ls),不能进入目录、不能访问目录内文件。
-
执行权限 (x) → 搜索位: 允许穿越目录、按文件名查找内部文件;想要打开目录下文件,路径上每一级目录必须具备执行权限。
-
目录 写权限 (w):允许在目录内创建、删除、重命名文件。
一个目录仅有 x 无 r:不能 ls 列出所有文件,但是如果你提前知道文件名,可以直接打开该文件。
访问权限判定
-
EUID == 0(超级用户 root) → 直接允许访问,不再后续判断
-
EUID == 文件 st_uid(进程是文件所有者) → 检查【用户权限位】,不再检查组、其他权限
-
EGID / 附属组 ID == 文件 st_gid → 检查【组权限位】,不再检查其他权限
-
否则 → 检查【其他用户权限位】
新文件和目录文件的所有权
文件
-
用户id:新建文件 / 目录的所有者 UID = 进程有效用户 ID (EUID)
-
组id:Linux默认下面方案 1;仅当父目录设置了S_ISGID(置组 ID 位),才启用方案 2 继承目录 GID
方案 1:新文件 GID = 进程有效组 ID (EGID)
方案 2:新文件 GID = 文件所在父目录的组 ID(继承目录组)
目录
- mkdir创建目录时,会自动继承父目录的 SGID 标志,保证子目录也能传递组所有权。
access()和faccessat()
检查调用进程对文件的访问权限,基于进程的实际 uid/gid
#include <unistd.h>
int access(const char *pathname, int mode);
int faccessat(int dirfd, const char *pathname, int mode, int flags);
-
mode:检测权限掩码 F_OK:文件是否存在 R_OK:可读 W_OK:可写 X_OK:可执行
-
返回值:成功 0,失败 - 1 并置 errno。
umask()
为当前进程设置文件创建屏蔽字,仅对本进程内open/creat/mkdir新建文件生效,不影响其他进程、shell 全局 umask(只对当前打开的 shell 窗口有效)。
最终权限 = 创建时传入mode & (~umask)
#include <sys/stat.h>
mode_t umask(mode_t mask);
-
mask:新的文件权限屏蔽字(八进制常用,如 0022)
-
返回值:返回调用前旧的 umask 值。
chmod()、fchmod()、fchmodat()
-
hmod:接收文件路径,默认跟随符号链接,修改链接指向的真实文件权限。
-
fchmod:基于已打开fd操作,无法作用于符号链接(打开链接时已穿透到目标文件)。
-
fchmodat
-
fd=AT_FDCWD、pathname 为绝对路径:等价chmod
-
fd指向目录:pathname 为相对路径,基于该目录解析
-
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int dirfd, const char *path, mode_t mode, int flags);
- chmod
-
参数:文件路径 path,目标权限 mode
-
特性:跟随符号链接,修改链接指向文件权限
-
返回:成功 0,失败 - 1 并置 errno
- fchmod
-
参数:已打开文件描述符 fd,目标权限 mode
-
特性:通过 fd 操作,不受路径、软链接影响
-
返回:成功 0,失败 - 1 并置 errno
- fchmodat
-
dirfd:基准目录 fd,AT_FDCWD 等价 chmod
-
path:相对 / 绝对路径
-
mode:目标权限
-
flags:仅支持 AT_SYMLINK_NOFOLLOW,不追踪软链接
-
返回:成功 0,失败 - 1 并置 errno
粘着位(保存正文位)
早期UNIX 系统无请求分页虚拟内存,可执行程序设置该位后,第一次运行结束,机器指令正文段会永久保存在交换分区,下次启动程序无需重新从磁盘加载,提速启动。现代操作系统全部实现虚拟内存、高速文件系统,普通文件该功能彻底废弃。
现代用途
给目录设置S_ISVTX后,目录内文件的删除 / 重命名权限被强制限制。满足以下三者之一,才能能删除 / 改名文件
-
是该文件的所有者;
-
是该目录的所有者;
-
是超级用户 root。
目录开放全局读写执行权限(所有用户均可创建临时文件);
设置粘着位后,普通用户只能删除自己创建的临时文件,不能删除别人的临时文件,保障多用户临时目录安全。
chown ()、lchown()、fchown()、fchownat()
修改文件的uid和gid。
chown(),跟随符号链接,修改链接指向的文件
lchown(),不跟随符号链接,直接修改符号链接自身所属主属组
fchown(),基于打开文件修改
fchownat,有flags,等价于chown(),flags=AT_SYMLINK_NOFOLLOW:等价于lchown
#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
#include <fcntl.h>
int fchownat(int dirfd, const char *path, uid_t owner, gid_t group, int flags);
-
path:文件路径
-
fd:已打开文件描述符
-
owner:新属主 uid,传-1则不修改用户
-
group:新属组 gid,传-1则不修改组
-
dirfd:基准目录 fd,AT_FDCWD等价当前工作目录
-
flags:仅AT_SYMLINK_NOFOLLOW有效,不跟随软链接
-
返回值:成功返回 0;失败返回 - 1,设置 errno。
文件长度
stat 结构体中成员
-
st_size(文件逻辑长度,字节)仅对普通文件、目录、符号链接有效:
-
普通文件:逻辑字节大小,可取值 0;读超出已写入位置返回 EOF。
-
目录:长度一般是 16/512 字节的整数倍,存放目录项。
-
符号链接:存储目标路径字符串的实际字节数,不含末尾\0;示例 lib -> usr/lib,st_size=7。
-
-
st_blksize /st_blocks(物理存储)
-
st_blksize:文件系统最优 I/O 块大小,标准库读写会按该块批量操作,减少 IO 次数。
-
st_blocks:文件实际占用磁盘块数量,不同系统单位不统一(不可移植): Linux:默认 512 字节 / 块;设POSIXLY_CORRECT环境变量时为 1024 字节 / 块
-
ls -l 读取 st_size(逻辑长度);du 读取 st_blocks(实际磁盘占用)。
文件空洞(普通文件特有)
产生原因:调用lseek将文件偏移量移动到文件尾部之后,再执行 write 写入数据,中间未写入的区域形成空洞。
读空洞:未写入的空洞位置,read读取返回字节0;wc -c统计会把空洞计入总逻辑长度。
磁盘占用:空洞不占用实际磁盘数据块,因此du显示占用远小于ls -l逻辑大小。
复制空洞文件:
cat core > core.copy
cat 逐字节读取,空洞位置读出 0 并写入新文件,新文件空洞被填满,磁盘占用大幅上升。
文件截断
truncate ()和ftruncate()
两个函数功能一致,将文件逻辑长度设置为length,
若原文件长度 > length:截断尾部多余数据;若原文件长度 < length:文件空洞扩展,填充 0;不修改文件读写偏移量
#include <unistd.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
truncate
-
path:文件路径,会跟随软链接
-
length:目标文件字节长度
-
成功返回 0;失败返回 - 1,设置 errno
ftruncate
-
fd:已打开文件描述符(必须可写)
-
length:目标文件字节长度
-
成功返回 0;失败返回 - 1,设置 errno
ftruncate:常用于加锁后清空文件
文件系统
-
磁盘可切分为多个分区,一个分区存放一套独立文件系统(UFS)。
-
单个文件系统内部分层:自举块 → 超级块 → 柱面组(多组)
-
单个柱面组组成: 超级块副本、配置信息、i 节点图、块位图、i 节点数组、数据块
核心概念
节点(inode):定长记录,保存文件全部属性;类型ino_t;stat 绝大多数字段来自 inode。
数据块:存放普通文件内容、目录项、符号链接目标路径。
目录项:仅存「文件名 + inode 编号」,不属于 inode。

硬链接
-
定义 多个目录项指向同一个 inode,称为硬链接;链接计数st_nlink(nlink_t)记录指向该 inode 的目录项总数。
-
关键规则 删除文件本质:unlink删除一条目录项,链接计数 - 1;仅当计数 = 0 时,inode 与数据块才释放。 限制:不能跨文件系统(不同分区 inode 编号独立,无法映射);ln命令不能跨分区创建硬链接。 同分区重命名(mv):仅新增 / 删除目录项,inode 不变,链接计数不变,文件数据不移动。 目录硬链接计数特殊规则: 空叶子目录(无下级子目录)链接数 = 2:自身目录项 + 目录内. 有子目录的目录链接数≥3:父目录项 + . + 每个子目录的..(每多一个子目录,父目录链接数 + 1) 常量LINK_MAX:单个 inode 允许的最大硬链接数量。
**注:**用户手动不能创建指向目录的硬链接;
符号链接(软链接 Symbolic Link)
-
文件类型标识:inode 中文件类型为S_IFLNK。
-
存储:软链接自身数据块存放目标文件路径字符串;st_size 等于路径字符长度,不含\0。
-
无跨分区限制,可指向目录、不存在文件、跨文件系统。
-
与硬链接核心区别:不增加目标文件的st_nlink计数。
inode和目录项的分工
inode 存储:文件类型、权限、长度、数据块指针、时间戳、链接计数。
目录项仅存两项:文件名、inode 编号;文件名不存于 inode。
目录项是文件名与 inode 编号的映射记录,目录本质是目录项列表。每条目录项存储三部分核心信息:文件名(字符串)、对应文件的 inode 编号、文件类型标记(普通文件 / 目录 / 软链接等)
link ()、linkat()、unlink ()、unlinkat()、remove()
link()和linkat()为文件创建硬链接,跟随软链接。
unlink()和unlinkat()删除路径对应的硬链接;链接计数归 0 且无进程打开文件时,释放 inode。不能删除目录。遇软链接,只删链接本身,不操作目标文件。
#include <unistd.h>
// 创建硬链接
int link(const char *oldpath, const char *newpath);
int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags);
// 删除硬链接
int unlink(const char *pathname);
int unlinkat(int dirfd, const char *pathname, int flags);
#include <stdio.h>
// 删除文件/空目录
int remove(const char *pathname);
- link()
-
oldpath 原文件
-
newpath 新硬链接路径。
- linkat()
-
olddirfd:原文件所在目录 fd
-
newdirfd:新链接所在目录 fd
-
flags:AT_SYMLINK_FOLLOW 跟随原路径软链接
- unlinkat()
-
dirfd:基准目录 fd,AT_FDCWD等价 unlink
-
flags:AT_REMOVEDIR → 可删除空目录
- remove()封装接口:
-
普通文件:等价 unlink ()
-
空目录:等价 rmdir ()
返回值全部:成功返回 0,失败返回 - 1 并置 errno。
rename()、renameat()
rename()、renameat()的本质是"改目录项的名字;文件数据在磁盘上没动。
特点:可跨同文件系统移动文件;跨设备无法使用 若 newpath 已存在且是文件,直接覆盖删除 若 newpath 是空目录,覆盖;非空目录报错 对软链接操作:操作链接本身,不追踪指向文件
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
#include <fcntl.h>
int renameat2(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath, unsigned int flags);
rename ()
-
oldpath:原文件 / 目录路径
-
newpath:新路径名
renameat ()
-
olddirfd:原文件所在目录 fd,AT_FDCWD 代表当前目录
-
oldpath:相对 / 绝对原路径
-
newdirfd:新目录 fd
-
newpath:相对 / 绝对新路径
-
flags:AT_RENAME_EXCHANGE(交换两个文件)、AT_RENAME_NOREPLACE(不覆盖已存在文件)(Linux特有)
返回值均:成功 0,失败 - 1,置 errno
符号链接
硬链接限制
-
必须和原文件位于同一文件系统;
-
仅 root 用户可创建目录硬链接,极易造成目录循环,多数系统禁止普通用户创建目录硬链接。
符号链接优势
-
无文件系统限制,可跨分区、跨设备;
-
任意用户均可创建指向目录的软链接;
-
本质是间接指针,单独存储一段目标路径字符串,拥有独立 inode。
特殊特例:open(O_CREAT | O_EXCL)
若路径是软链接,open 直接返回失败,errno=EEXIST;用于安全防护,防止特权进程被欺骗覆盖文件。
符号链接循环
touch foo/a
ln -s ../foo foo/testdir # testdir指向父目录foo,形成循环
遍历工具(如ftw)递归访问时无限循环,内核检测后返回错误 errno=ELOOP。
循环消除难易对比
软链接循环:极易清理,unlink foo/testdir 直接删除软链接本身(unlink 不跟随链接);
目录硬链接循环:极难修复,会破坏文件系统,fsck 无法修复,因此link()默认禁止创建目录硬链接。
创建和读取符号链接
symlink ()、symlinkat()、readlink()、readlinkat()
symlink():基于linkpath的路径创建符号链接,target 无需真实存在
symlinkat():基于文件描述符的相对路径创建符号链接。
#include <unistd.h>
// 创建软链接
int symlink(const char *target, const char *linkpath);
int symlinkat(const char *target, int newdirfd, const char *linkpath);
symlink()
-
target 链接指向的目标字符串;
-
linkpath 软链接文件路径
-
返回值:成功 0,失败 - 1
symlinkat()
-
newdirfd:基准目录 fd,AT_FDCWD等价 symlink
-
linkpath 为相对路径时,以 newdirfd 对应目录为基准创建软链接
-
返回值:成功 0,失败 - 1
readlink():读取软链接存储的目标字符串,不跟随链接;
readlinkat():与readlink()一致
#include <unistd.h>
// 读取软链接内容
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz);
readlink()
-
pathname:软链接路径
-
buf:输出缓冲区
-
bufsiz:缓冲区大小
-
返回:成功返回读到的字节数;-1 失败
注:结果不带字符串结束符\0
readlinkat()
-
dirfd:基准目录 fd,AT_FDCWD等价 readlink
-
pathname 相对路径基于 dirfd 目录解析,只读取软链接自身内容
-
返回值:成功:返回有效字节, 失败:返回-1。
文件的时间
stat里的3个时间字段
struct timespec st_atime; // 最后访问时间
struct timespec st_mtime; // 最后修改文件内容时间
struct timespec st_ctime; // 文件元属性最后修改时间(权限、所有者、链接数变更)
st_mtim vs st_ctim 核心区分
st_mtim(修改时间):仅当文件磁盘数据内容被改写时更新;权限、属主、链接数变更不影响该字段。
st_ctim(状态更改时间):只要inode 元数据发生变化就更新:权限、UID/GID、硬链接计数、文件大小、时间戳全部会触发 ctim 更新;哪怕不修改文件内容。
目录与普通文件的区别
读写普通文件 仅修改该文件自身的 st_atim /st_mtim/st_ctim;父目录时间不受影响。
增删改目录内文件(touch/rm/rename) 改动的是目录项列表,会更新父目录的 st_mtim、st_ctim;同时新建 / 被修改文件自身时间戳同步更更新;修改时间戳也会触发 ctime 更新
access()和stat()不会更新时间戳,系统不会因为查询修改st_atime。
futimen()、utimensat()、utimes()
修改文件的 atime、mtime 时间戳;基于进程当前工作目录解析路径。
#include <sys/time.h>
// utimes
int utimes(const char *path, const struct timeval times[2]);
#include <fcntl.h>
#include <sys/stat.h>
// futimens
int futimens(int fd, const struct timespec times[2]);
// utimensat
int utimensat(int dirfd, const char *path, const struct timespec times[2], int flags);
utimes()
- struct timeval:秒 + 微秒(精度:微秒级)
futimens()
- struct timespec:秒 + 纳秒(精度:纳秒级)
utimensat():相对于dirfd的路径
-
dirfd:AT_FDCWD 等价 futimens 的路径版本
-
flags:AT_SYMLINK_NOFOLLOW 不跟随软链接,直接修改链接本身时间
-
time:同futimens()
返回值均: 成功:0 失败:-1,设置 errno
mkdir()、mkdirat()、rmdir()
mkdir()mkdirat():创建目录
rmdiir():只能删除空目录,目录存在文件 / 子目录则报错若 path 是软链接,无法删除链接,作用于链接指向目录,不可删除普通文件
#include <sys/stat.h>
// 创建目录
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int dirfd, const char *pathname, mode_t mode);
#include <unistd.h>
// 删除空目录
int rmdir(const char *pathname);
- mkdir()
-
pathname:待创建目录路径
-
mode:目录权限,受进程 umask 屏蔽
- mkdirat()
-
dirfd:基准目录 fd,AT_FDCWD 等价 mkdir
-
pathname 为相对路径时,以 dirfd 对应目录为基准创建
-
mode 规则与 mkdir 完全一致
全部:成功返回 0,失败返回 - 1 并设置 errno
读目录
对目录有访问权限的任何用户都可以读目录,只有内核可以写目录。
| 函数 | 功能 | 返回值 |
|---|---|---|
DIR *opendir(const char *pathname); |
通过路径打开目录,返回 DIR 句柄 | 成功返回 DIR*,失败 NULL |
DIR *fdopendir(int fd); |
将已打开的目录 fd 转为 DIR 句柄(SUSv4 XSI 扩展) | 成功返回 DIR*,失败 NULL |
struct dirent *readdir(DIR *dp); |
读取目录下一条目录项 | 读到条目返回 dirent*;目录尾 / 出错返回 NULL |
void rewinddir(DIR *dp); |
将目录读取指针重置到目录开头 | 无返回值 |
int closedir(DIR *dp); |
关闭 DIR 句柄,释放资源 | 成功 0,失败 - 1 |
long telldir(DIR *dp); |
获取当前目录读取偏移位置(XSI 扩展,非 POSIX 基础) | 当前位置偏移量 |
void seekdir(DIR *dp, long loc); |
设置目录读取偏移位置(XSI 扩展) | 无返回值 |
-
DIR 是内部不透明结构体,类似标准 IO 的FILE,保存目录读取状态;
-
fdopendir 依赖文件描述符的当前偏移,读取起点由 fd 偏移决定;opendir 固定从目录第一条开始;
-
目录项遍历顺序不保证字母序,由文件系统实现决定。
struct dirent
{
char d_name[]; // 以'\0'结尾的文件名,容量至少NAME_MAX字节
ino_t d_ino; // 文件inode编号,POSIX基础标准未定义
}
-
不能通过数组长度判断文件名长度,必须用strlen(d_name);
-
目录默认包含 .(当前目录)、..(父目录),遍历时常需要过滤。
chdir()、fchdir()、getcwd()
内核仅保存目录 inode 指针,不存储完整绝对路径,因此需要getcwd逐层回溯拼接完整路径。每个进程独有当前工作目录(CWD),是解析相对路径的起点。改变进程属性,仅作用于自身,不影响父 / 子进程、shell。
-
chdir():文件系统路径字符串(相对 / 绝对路径均可)
-
fchdir() :切换当前工作目录为 fd 对应的目录
-
getcwd() :把当前工作目录绝对路径存入 buf
#include <unistd.h>
// 修改进程当前工作目录
int chdir(const char *path);
int fchdir(int fd);
// 获取当前工作目录
char *getcwd(char *buf, size_t size);
chadir():返回:成功 0,失败 -1 置error
fchdir():返回:成功 0,失败 -1
getcwd():
-
buf:用户提供缓冲区;传 NULL 时部分系统自动 malloc 缓冲区(需手动 free)
-
size:缓冲区字节大小,必须 ≥ 路径长度 + 1(存终止符\0)
-
返回值:成功:返回 buf 指针 ;失败:NULL,置 errno(常见 ERANGE:缓冲区太小)
为什么cd 不做成外部程序,而是 shell 内置命令(builtin)
shell 执行外部程序时,会创建子进程运行程序; chdir 修改子进程的 CWD,子进程退出后销毁,shell 父进程目录不变;
注:chdir 会跟随符号链接; getcwd 回溯时展示真实物理路径,不会显示软链接路径。
设备特殊文件
dev_t、主设备号、次设备号
dev_t:存储设备编号的系统类型,由主设备号 + 次设备号拼接而成。
-
主设备号(major):标识硬件驱动程序,同一种硬件驱动共用同一个主号。
-
次设备号(minor):区分同一驱动下的不同子设备(分区、终端等)。
提取宏:头文件:Linux:<sys/sysmacros.h> Solaris:<sys/mkdev.h> BSD:<sys/types.h>
major(x):传入一个 dev_t 变量 x,从这个整数里拆分、提取出主设备号,作为整数返回。 minor(x):传入一个 dev_t 变量 x,拆分提取次设备号,作为整数返回。
struct stat st; stat("/dev/tty0", &st); dev_t x = st.st_dev; // 从stat获取合法设备号 cout << major(x) << minor(x);
st_dev
定义:文件所在文件系统的设备号。即该文件 inode 存放于哪一块存储设备。
所有文件均拥有st_dev
st_rdev(仅块 / 字符特殊文件有效)
定义:硬件设备本身的设备号,只有两类文件存在有效值:块设备文件和字符设备文件。
S_ISCHR(buf.st_mode) // 是否字符特殊文件 S_ISBLK(buf.st_mode) // 是否块特殊文件
struct stat st;
stat("/dev/sda", &st);
if (S_ISBLK(st.st_mode)) cout << "块设备";
if (S_ISCHR(st.st_mode)) cout << "字符设备";
文件访问权限
| 权限段 | 读 | 写 | 执行 |
|---|---|---|---|
| 用户 (属主) | S_IRUSR | S_IWUSR | S_IXUSR |
| 用户组 | S_IRGRP | S_IWGRP | S_IXGRP |
| 其他用户 | S_IROTH | S_IWOTH | S_IXOTH |
| 常量 | 名称 | 普通文件作用 | 目录作用 |
|---|---|---|---|
| S_ISUID | 设置用户 ID | 程序执行时,进程有效用户 ID 改为文件所有者 ID | 目录无效果 |
| S_ISGID | 设置组 ID | 可执行文件:执行时有效组 ID 改为文件组 ID;部分系统支持文件锁 | 目录内新建文件自动继承该目录的组 ID |
| S_ISVTX | 粘滞位 | 早期:程序正文缓存到交换区(现代系统废弃) | 目录限制:仅文件所有者 /root 才能删除、重命名目录内文件(典型:/tmp) |