刚刚遇到一个问题:重新启动 Linux 系统时,已挂载的 VFAT 文件系统中的所有文件的时间戳显示在错误的时区中。设备似乎开始认为其本地时间采用 UTC 格式,因此它会显示带有偏移的所有时间戳。
重现步骤:
创建一些小的 FAT 格式的映像:
dd if=/dev/zero of=small.img bs=1M seek=1 count=0
mkfs.vfat small.img
在本地挂载此镜像:
mount -t vfat -o umask=0022,gid=1001,uid=1001 small.img mnt
将时区设置为某个非 UTC 时区;
在已安装的文件系统中创建一个文件(即
touch mnt/newfile
)观察文件修改/更改时间戳:对于当前设置的时间戳,它们是正确的:
stat mnt/newfile
File: mnt/newfile Size: 0 Blocks: 0 IO Block: 16384 regular empty file Device: 700h/1792d Inode: 40 Links: 1 Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2021-03-22 12:19:56.000000000 +0100 Modify: 2021-03-22 12:19:56.000000000 +0100 Change: 2021-03-22 12:19:56.000000000 +0100 Birth: -
timedatectl
Local time: Mon 2021-03-22 12:19:07 CET Universal time: Mon 2021-03-22 11:19:07 UTC RTC time: Mon 2021-03-22 11:19:07 Time zone: Europe/Vienna (CET, +0100) System clock synchronized: yes NTP service: active RTC in local TZ: no
卸载文件系统,检查重新挂载是否发生了任何更改:
umount mnt; mount -t vfat -o umask=0022,gid=1001,uid=1001 small.img mnt; stat mnt/newfile
File: mnt/newfile Size: 0 Blocks: 0 IO Block: 16384 regular empty file Device: 700h/1792d Inode: 64 Links: 1 Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2021-03-22 00:00:00.000000000 +0100 Modify: 2021-03-22 12:19:56.000000000 +0100 Change: 2021-03-22 12:19:56.000000000 +0100 Birth: -
重新启动系统;
再次挂载镜像,看看创建的文件的时间戳:
File: mnt/newfile Size: 0 Blocks: 0 IO Block: 16384 regular empty file Device: 700h/1792d Inode: 26 Links: 1 Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2021-03-22 01:00:00.000000000 +0100 Modify: 2021-03-22 13:19:56.000000000 +0100 Change: 2021-03-22 13:19:56.000000000 +0100 Birth: -
可以清楚地观察到,时间向前移动了 1 小时(12:10 到 13:19),而时区显示相同 - +0100。看起来mount
现在认为文件时间戳是用 UTC 记录的,因此它尝试使用“正确”的偏移来显示它们。
要检查上一条语句的有效性,让我们tz=UTC
显式地使用该选项重新挂载相同的文件系统:
mount -t vfat -o umask=0022,gid=1001,uid=1001,tz=UTC small.img mnt; stat mnt/newfile
File: mnt/newfile
Size: 0 Blocks: 0 IO Block: 16384 regular empty file
Device: 700h/1792d Inode: 50 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2021-03-22 01:00:00.000000000 +0100
Modify: 2021-03-22 13:19:56.000000000 +0100
Change: 2021-03-22 13:19:56.000000000 +0100
Birth: -
即使系统的时区确实是 CET:
date
Mon Mar 22 12:26:42 CET 2021
聚苯乙烯https://stackoverflow.com/questions/10068855/how-do-i-get-the- Correct-modified-datetime-of-a-fat32-file-regardless-of-timezo不是这个问题的答案,因为我无法从中得到答案,为什么在机器重新启动后而不是重新安装后会立即看到此更改?如果 vfat 以本地时间存储时间戳,为什么重新启动后挂载会假定时间戳为 UTC 而不是本地时间?
答案1
看来问题出在Linux内核本身,内核和用户空间之间的时区可能(通常)不同。Linux 内核源代码树time.c
中的 文件保存(并导出) ,然后在 FAT 时间 <-> UNIX 时间约定中使用。该结构体的字段用于显示当前时区与 UTC 之间的差异,如果选项为kernel/time
struct timezone sys_tz
fs/fat/misc.c
tz_minuteswest
tz=UTC
不是传递给mount.vfat
命令。然而,上述字段默认设置为 0,如所解释的这里,
在 Linux 下,如果在具有非 NULL tz 参数的第一次调用(启动后)时,tv 参数为 NULL 并且 tz_minuteswest 字段非零,则有一些与 settimeofday() 系统调用相关的特殊“扭曲时钟”语义。 (对于这种情况,tz_dsttime 字段应为零。)在这种情况下,假定 CMOS 时钟为本地时间,并且必须将其增加此量才能获得 UTC 系统时间。毫无疑问,使用此功能是一个坏主意。
因此,让内核(及其驱动程序)始终看到正确时区的唯一方法是调用settimeofday()
withtz
参数,其中tz.tz_minuteswest
所需的时间偏移量为“向西”,以小时为单位,相对于 UTC(即 -60 CET 等),并tz_dsttime
在每次系统启动后设置为 0。这可以通过将系统时区设置(以任何方式)来实现当前的时区将其更改为另一个(即UTC)后timedatectl
,因为如果所需的时区等于当前时区,则命令行工具 sa通常不会执行实际的时区更改。创建以下代码来证明这个概念:
#include <sys/time.h>
#include <stdio.h>
int main()
{
struct timeval tv;
struct timezone tz;
int ret = gettimeofday(&tv, &tz);
printf("%d, %dr\n", tz.tz_minuteswest, tz.tz_dsttime);
return ret;
}
该代码执行如下:
$gcc testtz.cpp -o testtz
$./testtz
minuteswest: 0, dsttime: 0r
$timedatectl set-timezone Europe/Vienna
$./testtz
minuteswest: 0, dsttime: 0r
$timedatectl set-timezone UTC
$timedatectl set-timezone Europe/Vienna
$./testtz
minuteswest: -60, dsttime: 0r
timezone
尽管该结构的使用被认为已经过时,但这个问题不会很快消失。所以对于我的问题,我会考虑使用time_offset=分钟选项的mount.vfat
。