Linux note2

文件和目录

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中存在两个特殊标志位:
  1. S_ISUID(设置用户 ID 位,setuid 位) 若文件开启该位,执行此程序时:进程有效用户 ID (EUID) 被设置为【文件所有者 st_uid】,而不是进程原本的实际用户 ID。

  2. S_ISGID(设置组 ID 位,setgid 位) 若文件开启该位,执行此程序时:进程有效组 ID (EGID) 被设置为【文件所属组 st_gid】

  1. getuid():返回实际用户 ID(real uid, ruid)

  2. geteuid():返回有效用户 ID(effective uid, euid)

  3. setuid(uid_t u)

    • 特权进程 (root):同时修改 ruid、euid、saved uid 三个 UID,永久切换身份;

    • 普通进程:仅当 u == ruid 或 u == saved uid 时,修改 euid;切换后无法切回更高权限。

  4. 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 列出所有文件,但是如果你提前知道文件名,可以直接打开该文件。

访问权限判定
  1. EUID == 0(超级用户 root) → 直接允许访问,不再后续判断

  2. EUID == 文件 st_uid(进程是文件所有者) → 检查【用户权限位】,不再检查组、其他权限

  3. EGID / 附属组 ID == 文件 st_gid → 检查【组权限位】,不再检查其他权限

  4. 否则 → 检查【其他用户权限位】

新文件和目录文件的所有权
文件
  • 用户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()
  1. hmod:接收文件路径,默认跟随符号链接,修改链接指向的真实文件权限。

  2. fchmod:基于已打开fd操作,无法作用于符号链接(打开链接时已穿透到目标文件)。

  3. 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);
  1. chmod
  • 参数:文件路径 path,目标权限 mode

  • 特性:跟随符号链接,修改链接指向文件权限

  • 返回:成功 0,失败 - 1 并置 errno

  1. fchmod
  • 参数:已打开文件描述符 fd,目标权限 mode

  • 特性:通过 fd 操作,不受路径、软链接影响

  • 返回:成功 0,失败 - 1 并置 errno

  1. 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:常用于加锁后清空文件

文件系统
  1. 磁盘可切分为多个分区,一个分区存放一套独立文件系统(UFS)。

  2. 单个文件系统内部分层:自举块 → 超级块 → 柱面组(多组)

  3. 单个柱面组组成: 超级块副本、配置信息、i 节点图、块位图、i 节点数组、数据块

核心概念

节点(inode):定长记录,保存文件全部属性;类型ino_t;stat 绝大多数字段来自 inode。

数据块:存放普通文件内容、目录项、符号链接目标路径。

目录项:仅存「文件名 + inode 编号」,不属于 inode。

硬链接
  1. 定义 多个目录项指向同一个 inode,称为硬链接;链接计数st_nlink(nlink_t)记录指向该 inode 的目录项总数。

  2. 关键规则 删除文件本质:unlink删除一条目录项,链接计数 - 1;仅当计数 = 0 时,inode 与数据块才释放。 限制:不能跨文件系统(不同分区 inode 编号独立,无法映射);ln命令不能跨分区创建硬链接。 同分区重命名(mv):仅新增 / 删除目录项,inode 不变,链接计数不变,文件数据不移动。 目录硬链接计数特殊规则: 空叶子目录(无下级子目录)链接数 = 2:自身目录项 + 目录内. 有子目录的目录链接数≥3:父目录项 + . + 每个子目录的..(每多一个子目录,父目录链接数 + 1) 常量LINK_MAX:单个 inode 允许的最大硬链接数量。

**注:**用户手动不能创建指向目录的硬链接;

符号链接(软链接 Symbolic Link)
  1. 文件类型标识:inode 中文件类型为S_IFLNK。

  2. 存储:软链接自身数据块存放目标文件路径字符串;st_size 等于路径字符长度,不含\0。

  3. 无跨分区限制,可指向目录、不存在文件、跨文件系统。

  4. 与硬链接核心区别:不增加目标文件的st_nlink计数。

inode和目录项的分工

inode 存储:文件类型、权限、长度、数据块指针、时间戳、链接计数。

目录项仅存两项:文件名、inode 编号;文件名不存于 inode。

目录项是文件名与 inode 编号的映射记录,目录本质是目录项列表。每条目录项存储三部分核心信息:文件名(字符串)、对应文件的 inode 编号、文件类型标记(普通文件 / 目录 / 软链接等)

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);
  1. link()
  • oldpath 原文件

  • newpath 新硬链接路径。

  1. linkat()
  • olddirfd:原文件所在目录 fd

  • newdirfd:新链接所在目录 fd

  • flags:AT_SYMLINK_FOLLOW 跟随原路径软链接

  1. unlinkat()
  • dirfd:基准目录 fd,AT_FDCWD等价 unlink

  • flags:AT_REMOVEDIR → 可删除空目录

  1. 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

符号链接

硬链接限制

  1. 必须和原文件位于同一文件系统;

  2. 仅 root 用户可创建目录硬链接,极易造成目录循环,多数系统禁止普通用户创建目录硬链接。

符号链接优势

  1. 无文件系统限制,可跨分区、跨设备;

  2. 任意用户均可创建指向目录的软链接;

  3. 本质是间接指针,单独存储一段目标路径字符串,拥有独立 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():基于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);
  1. mkdir()
  • pathname:待创建目录路径

  • mode:目录权限,受进程 umask 屏蔽

  1. 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。

  1. chdir():文件系统路径字符串(相对 / 绝对路径均可)

  2. fchdir() :切换当前工作目录为 fd 对应的目录

  3. 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)
相关推荐
雾里0不看花1 小时前
【App Service Linux】在Linux App Service中安装 tcpdump 并抓取网络包
linux·网络·tcpdump
xqqxqxxq2 小时前
MySQL 数据类型笔记
数据库·笔记·mysql
Dawn-bit2 小时前
Linux日志处理三剑客之基础篇:(基础正则+扩展正则)
linux·运维·服务器·正则表达式·云计算·运维开发
☆凡尘清心☆3 小时前
Linux运维故障排查速查命令清单
linux·运维·服务器
小泊客3 小时前
友善R5C刷OpenWrt后RTL8822CE无线网卡显示“禁用”或“未激活”的完整解决方案
linux·github·运维开发
mounter6253 小时前
跨内核实时更新:如何实现 hugetlbfs 巨页的无缝保留与恢复?
linux·linux kernel·kernel·kexec·liveupdate·kho
qeen873 小时前
【Linux】make/Makefile 自动化工具的介绍
linux·运维·服务器·自动化
funnycoffee1233 小时前
Cisco ip access-list序号重排方法
linux·网络·tcp/ip
艾莉丝努力练剑3 小时前
【Linux:动静态库】Linux 动静态库与可执行文件
linux·运维·服务器·学习·面试·文件系统·动静态库