news 2026/9/11 21:20:16

SerenityOS 中的 futimens 与 utimensat:文件时间戳更新机制全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SerenityOS 中的 futimens 与 utimensat:文件时间戳更新机制全解析

SerenityOS 中的 futimens 与 utimensat:文件时间戳更新机制全解析

【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity

导读

本文基于 SerenityOS 官方手册页 utimensat(3),深入讲解futimens()utimensat()两个系统级接口如何精确更新文件的访问时间(atime)与修改时间(mtime)。你将从用户态 API 的语义、特殊标记值UTIME_NOW/UTIME_OMIT的用法、错误码含义,一路追踪到 LibC 封装与内核系统调用的完整调用链,最终掌握用 C++ 在 SerenityOS 上"只改访问时间、不动修改时间"等精细时间戳操作能力,并理解touchlutimes等既有工具/API 如何复用这一机制。

概述:一对用于时间戳更新的接口

futimens()utimensat()用于将文件的访问时间与修改时间设置为times数组指定的值。二者在 SerenityOS 中声明于 Userland/Libraries/LibC/sys/stat.h,原型如下:

#include <sys/stat.h> int futimens(int fd, struct timespec const times[2]); #include <fcntl.h> int utimensat(int dirfd, char const* path, struct timespec const times[2], int flag);

两者的关键区别在于目标文件的指定方式:

  • futimens():通过文件描述符fd指定目标文件,更新该描述符所关联文件的时间戳。
  • utimensat():通过路径指定目标文件,且拥有两种工作模式(见下文)。

times是一个长度为 2 的timespec数组,times[0]对应访问时间(atime),times[1]对应修改时间(mtime),每个元素由秒(tv_sec)与纳秒(tv_nsec)两个字段组成。

utimensat 的两种工作模式

utimensat()依据pathdirfd的组合表现出两种截然不同的行为,这也是该接口设计上最值得注意的部分:

模式一:目录描述符 + 相对路径(标准 POSIX 行为)

给定一个指向目录的有效文件描述符dirfd以及一个非空路径path时,utimensat()更新相对于该目录描述符所定位的文件的时间戳。若dirfd被设为AT_FDCWD,则相对路径基于当前工作目录解析。

模式二:普通文件描述符 + 空路径(SerenityOS 扩展行为)

给定一个指向普通文件的有效文件描述符fd以及空路径path时,utimensat()更新该描述符所关联文件的时间戳。这一行为并非标准 POSIX 语义,其存在价值在于:它允许futimens()完全基于utimensat()实现,从而复用同一套参数校验与系统调用路径,减少重复逻辑。

这一点可以在 SerenityOS 的 LibC 实现中得到印证:Userland/Libraries/LibC/stat.cpp 中futimens()utimensat()最终都汇入同一个内部函数__utimens()

int futimens(int fd, struct timespec const times[2]) { return __utimens(fd, nullptr, times, 0); } int utimensat(int dirfd, char const* path, struct timespec const times[2], int flag) { if (!path) { errno = EFAULT; return -1; } return __utimens(dirfd, path, times, flag); }

futimens()path传为nullptr,而utimensat()先对空路径做EFAULT检查——这正好对应了手册中EFAULT错误码的含义。随后__utimens()依据path是否为空来决定调用内核的SC_utimensat还是SC_futimens系统调用(Userland/Libraries/LibC/stat.cpp)。

特殊标记值:UTIME_NOW 与 UTIME_OMIT

times数组中tv_nsec字段支持两个特殊常量,用于表达"取当前时间"与"保持不变"两种语义:

tv_nsec取值语义tv_sec字段
UTIME_NOW对应时间戳设置为当前时间被忽略
UTIME_OMIT对应时间戳保持不变被忽略

这两个常量的实际取值定义在 Kernel/API/POSIX/sys/stat.h:

#define UTIME_OMIT -1 #define UTIME_NOW -2

使用负值作为特殊标记,可以保证与任何合法的纳秒数(0 ~ 999,999,999)都不会冲突。当times为**空指针(nullptr)**时,两个时间戳都会被设置为当前时间,等价于将数组中两个timespectv_nsec都设为UTIME_NOW

LibC 层在 Userland/Libraries/LibC/stat.cpp 中提前做了三件优化与校验工作:

  1. 双 OMIT 短路:若两个tv_nsec均为UTIME_OMIT,则任何修改都不需要发生,直接返回 0,避免一次无意义的系统调用。
  2. 双 NOW 归一化:若两个tv_nsec均为UTIME_NOW,则将times归一化为nullptr,注释明确指出"避免内核中复制它的需要"。
  3. 纳秒范围校验:对非特殊值逐一检查tv_nsec是否落在[0, 1'000'000'000)区间内,越界即返回EINVAL——这正是手册中EINVAL错误码的底层实现。

flag 参数与符号链接处理

utimensat()flag参数可取值0AT_SYMLINK_NOFOLLOW

  • flag = 0:默认行为,若路径是符号链接则跟随链接,更新链接指向的目标文件。
  • flag = AT_SYMLINK_NOFOLLOW不跟随符号链接,直接更新符号链接自身的时间戳。

futimens()由于操作对象是已打开的文件描述符,总是作用于描述符所关联的文件(即始终"跟随"符号链接语义)。常量定义于 Kernel/API/POSIX/fcntl.h:

#define AT_FDCWD -100 #define AT_SYMLINK_NOFOLLOW 0x100

AT_FDCWD用负值(-100)表示"当前工作目录",用于将相对路径的基准从某个目录描述符切换为进程的当前工作目录。

LibC 层对flag的校验同样严格:__utimens()中仅允许AT_SYMLINK_NOFOLLOW位被置位,其余任何位组合都会返回EINVAL(Userland/Libraries/LibC/stat.cpp):

// POSIX allows AT_SYMLINK_NOFOLLOW flag or no flags. if (flag & ~AT_SYMLINK_NOFOLLOW) { errno = EINVAL; return -1; }

在内核端,sys$utimensat会把AT_SYMLINK_NOFOLLOW映射为O_NOFOLLOW_NOERROR语义再交给 VFS 处理(Kernel/Syscalls/utimensat.cpp),而sys$futimens则直接通过描述符对应的 custody 定位文件(Kernel/Syscalls/utimensat.cpp)。

返回值

两个函数在成功时返回0,失败时返回-1。失败时还会设置errno以指示具体错误,并且保证文件的访问时间与修改时间保持原样、不被部分修改——即要么完整生效,要么完全不生效。

错误码详解

错误码触发条件
EFAULTutimensat()path为空指针
EINVALpath长度过长
EINVALflag不是0AT_SYMLINK_NOFOLLOW
EINVAL文件系统不支持该时间戳
EINVALtimestv_nsec字段小于 0 或大于等于 10 亿(1,000,000,000),且不是UTIME_NOW/UTIME_OMIT
EACCES当前用户对文件没有写权限
EROFS包含该文件的文件系统为只读
ENOTDIRpath不是绝对路径,且dirfd不是与目录关联的文件描述符

其中"path长度过长"在 Userland/Libraries/LibC/stat.cpp 中的判定阈值为INT32_MAX,一旦超过即返回EINVAL

完整示例:仅更新访问时间

手册中给出了一个极具代表性的示例——只把当前目录下README.md访问时间更新为当前时间,同时保持修改时间不变

#include <fcntl.h> #include <sys/stat.h> int main() { timespec times[2]; auto& atime = times[0]; auto& mtime = times[1]; atime.tv_sec = 0; atime.tv_nsec = UTIME_NOW; mtime.tv_sec = 0; mtime.tv_nsec = UTIME_OMIT; // Update only last access time of file "README.md" in current working // directory to current time. Leave last modification time unchanged. if (utimensat(AT_FDCWD, "README.md", times, 0) == -1) { return 1; } return 0; }

示例要点拆解:

  • times[0](atime)设置tv_nsec = UTIME_NOW,其tv_sec = 0会被忽略;
  • times[1](mtime)设置tv_nsec = UTIME_OMIT,其tv_sec = 0同样被忽略,修改时间保持原值;
  • dirfd = AT_FDCWD让相对路径"README.md"基于当前工作目录解析;
  • flag = 0表示遵循默认的符号链接跟随语义。

从用户态到内核的完整调用链

将手册、LibC 与内核代码串起来,一次utimensat()调用的完整旅程如下:

  1. 用户程序调用utimensat(dirfd, path, times, flag)
  2. LibC 层utimensat()(Userland/Libraries/LibC/stat.cpp)检查path非空后进入__utimens()
  3. __utimens()依次完成:路径长度检查、flag 位校验、双 OMIT 短路、双 NOW 归一化、纳秒范围校验,然后组装SC_utimensat_params发起SC_utimensat系统调用(Userland/Libraries/LibC/stat.cpp);
  4. 内核sys$utimensat(Kernel/Syscalls/utimensat.cpp)首先通过require_promise(Pledge::fattr)执行 pledge 权限检查,用kgettimeofday()解析UTIME_NOW,将AT_SYMLINK_NOFOLLOW翻译为O_NOFOLLOW_NOERROR,然后基于dirfd+path构造CustodyBase
  5. VFS 层调用VirtualFileSystem::utimensat()解析出目标 custody 后进入VirtualFileSystem::do_utimens(),由文件系统驱动最终完成 inode 时间戳的写入(Kernel/FileSystem/VirtualFileSystem.cpp)。

sys$futimens的路径类似,但由于目标由文件描述符直接指定,省去了路径解析,直接通过open_file_description()获取 custody 后调用do_utimens()(Kernel/Syscalls/utimensat.cpp)。

从实现细节还可以看到,UTIME_NOW的解析发生内核侧sys$futimens/sys$utimensat在复制用户态times后,用kgettimeofday().to_timespec()统一替换UTIME_NOW,而UTIME_OMIT则交由 VFS 层的do_utimens()判断是否跳过对应字段的写入。

生态联动:touch、lutimes 与 futimes

这两个接口并非孤立存在,SerenityOS 中多处既有设施直接构建于其上:

  • touch命令:手册的 See also 节将 touch(1) 列为关联工具。touch通过-a-m选项分别只改访问/修改时间,通过-t-d-r指定具体时间或参考文件,其时间戳写入语义正是由utimensat()/futimens()提供的。
  • lutimes():在 Userland/Libraries/LibC/time.cpp 中,lutimes()timeval转换为timespec后,以AT_SYMLINK_NOFOLLOW标志调用utimensat(AT_FDCWD, pathname, ts, AT_SYMLINK_NOFOLLOW),从而实现对符号链接自身时间戳的更新。
  • futimes():同样在 Userland/Libraries/LibC/time.cpp 中,futimes()nullptr路径调用utimensat(fd, nullptr, nullptr, 0)或直接委托给futimens(),体现了"以 utimensat 实现 futimens"这一设计思想在相邻 API 上的延伸复用。

这些上游 API 的存在说明:理解utimensat()/futimens()的语义,是掌握 SerenityOS 全套时间戳相关操作(utimeutimeslutimesfutimestouch)的共同基础。

小结

futimens()utimensat()是 SerenityOS 中设置文件访问/修改时间的核心接口:前者面向文件描述符,后者面向路径并支持AT_FDCWD相对路径、AT_SYMLINK_NOFOLLOW不跟随链接两种扩展;UTIME_NOW/UTIME_OMIT提供了"取当前时间/保持不变"的精细控制。从 LibC 的参数校验与归一化,到内核系统调用的 pledge 检查与 VFS 落盘,再到touchlutimes等上层设施,一条完整、可验证的调用链清晰展示了这一 POSIX 接口在 SerenityOS 中的落地方式,也为自行编写时间戳管理工具提供了可直接复用的样板。

【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 21:16:29

智能垃圾分类系统实战:MobileNetV2模型加载与Grad-CAM可视化

简介&#xff1a;这是一份面向计算机、人工智能等专业学生与从业者的毕业设计资源&#xff0c;实现基于深度学习卷积神经网络的智能垃圾分类功能&#xff0c;主体为Python源码与配套说明文档。项目经过完整调试&#xff0c;已在答辩评审中取得98分&#xff0c;可稳定运行&#…

作者头像 李华
网站建设 2026/9/11 21:15:00

时空RBF神经网络实现混沌时间序列预测的Matlab指南

简介&#xff1a;这是一套面向神经网络预测与信号处理教研场景的MATLAB仿真资源&#xff0c;专注解决混沌时间序列的建模与预测问题&#xff0c;采用时空RBF神经网络&#xff08;RBF-NN&#xff09;实现。代码兼容MATLAB 2014/2019a&#xff0c;共10个文件&#xff0c;包含3个可…

作者头像 李华
网站建设 2026/9/11 21:12:49

动漫同人创作技术解析:从命名规则到3D实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 21:12:47

低功耗开发不是调休眠,是全链路工程约束

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华