The RTC is the motherboard clock that keeps time even when the machine is powered off. On boot, the OS reads that clock and decides how to interpret it.
The conflict:
Linux usually treats the RTC as UTC
Windows usually treats the RTC as local time
[1] Boot Linux
|
v
Linux writes RTC = UTC
|
| Example:
| Real local time = 15:00
| Timezone = UTC+2
| RTC stored = 13:00 (UTC)
v
[2] Reboot into Windows
|
v
Windows reads same RTC value as LOCAL TIME
|
| Reads RTC 13:00
| Interprets it as local time
v
[3] Windows shows wrong clock
|
| Displayed time = 13:00
| Expected time = 15:00
| Error = -2 hours
v
(Not tested yet, but intuitively expected - the next steps)
[4] Windows may write RTC back as LOCAL TIME
|
| RTC becomes 15:00 (but stored as localtime)
v
[5] Reboot back into Linux
|
v
Linux reads RTC again as UTC
|
| Reads RTC 15:00
| Interprets it as UTC
v
[6] Linux shows wrong clock too
|
| 15:00 UTC = 17:00 local time (UTC+2)
| Linux clock is now wrong
v
Result:
Linux and Windows keep reinterpreting the same hardware clock
in different ways, so the system time differs after rebooting.
The easiest solution is to change how Windows sees RTC: see article.
What if I want to make Linux see RTC as localtime not UTC?
A fast google search (example: linuxmint.com)will find this common solution that is detailed in timedatectl(1):
timedatectl set-local-rtc 1
After applying this fix, some people run into this problem (that I also found on this fedora forum):
Linux running
|
+--> system clock synced by Chrony
|
+--> RTC written in UTC
|
Linux shutdown
|
+--> RTC not written at shutdown (see note)
|
Boot Windows
|
+--> Windows reads RTC as local time
|
+--> Windows system clock is wrong
Note: systemd change
The issue is that Linux still writes the RTC in UTC. At shutdown, the hardware clock is left in UTC again. When Windows boots afterward, it reads that RTC value as local time, so the Windows system clock is shifted and appears incorrect.
Why this happens?
Let's take a look at the setup and configuration:
+-----------------------------------------------------------+
| System setup |
+-----------------------------------------------------------+
+------------------- machine -------------------+
| |
| Dual boot: Linux <-> Windows |
| |
+--------------------+--------------------------+
|
v
+----------------------+
| Hardware clock |
| (RTC) |
+----------------------+
^
|
rtcsync on | (chrony config)
|
|
+------------------------- Linux side -------------------------+
| |
| +-------------------+ syncs time +---------+ |
| | chronyd | -----------------------> | System | |
| | (Chrony) | | Clock | |
| +-------------------+ +---------+ |
| |
| - Chrony manages Linux time |
| - rtcsync takes care of RTC through kernel 11 minutes |
+--------------------------------------------------------------+
+----------------------- Windows side -------------------------+
| |
| Windows also reads the same RTC |
| but may interpret it differently than Linux |
| |
+--------------------------------------------------------------+
Documentation note: chrony.conf(5); adjtimex(2); rtc.4; hwclock(8);
Chrony uses rtcsync
With rtcsync enabled, the RTC is periodically synchronized with the system clock.
How does rtcsync work
According to chrony documentation:
How to check whether rtcsync is enabled
Run:
adjtimex --print
Using this command you do not check directly if rtcsync is on, but whether the hardware clock update is active when the kernel clock is synchronized (you can check kernel’s RTC “11 minute mode” behavior on/off). Bit 6 from status shows this.
status & 0x0040 != 0 -> clock is UNSYNCHRONIZED
status & 0x0040 == 0 -> clock is synchronized
Examples:
status = 0 -> synchronized -> RTC sync can be active
status = 1 -> synchronized -> RTC sync can be active
status = 64 -> unsynchronized -> RTC sync not active
status = 65 -> unsynchronized -> RTC sync not active
What is adjtimex()
adjtimex() is a syscall. This is the struct sent to kernel:
From what I read in the documentation, There is:
- No timezone parameter
- No localtime/UTC flag
- No RTC mode field
- No hardware clock conversion setting
In the struct timex definition, time is documented as “current time (read only)” as shown in the doc below. It’s a value we get back from the kernel, not something we set to change the system clock (from what I understood). The offset that can be adjusted is way too small to make up for a timezone.
So chrony tells kernel to update RTC every 11 minutes. And kernel updates it in UTC, not local time. On kernel.org they say:
When Linux developers talk about a “Real Time Clock”, they usually mean something that tracks wall clock time and is battery backed so that it works even with system power off. Such clocks will normally not track the local time zone or daylight savings time -- unless they dual boot with MS-Windows -- but will instead be set to Coordinated Universal Time (UTC, formerly “Greenwich Mean Time”).
How to tell the kernel RTC is in localtime
There is an option to inform the kernel that the RTC is in localtime: persistent_clock_is_local. This is documented here: hwclock(8):
_
The Linux kernel’s timezone is set by hwclock. But don’t be misled
— almost nobody cares what timezone the kernel thinks it is in.
Instead, programs that care about the timezone (perhaps because
they want to display a local time for you) almost always use a
more traditional method of determining the timezone: They use the
TZ environment variable or the /etc/localtime file, as explained
in the man page for tzset(3). However, some programs and fringe
parts of the Linux kernel such as filesystems use the kernel’s
timezone value. An example is the vfat filesystem. If the kernel
timezone value is wrong, the vfat filesystem will report and set
the wrong timestamps on files. Another example is the kernel’s NTP
'11 minute mode'. If the kernel’s timezone value and/or the
persistent_clock_is_local variable are wrong, then the Hardware
Clock will be set incorrectly by '11 minute mode'. See the
discussion below, under Automatic Hardware Clock Synchronization
by the Kernel. hwclock sets the kernel’s timezone to the value indicated by TZ or
/etc/localtime with the --hctosys or --systz functions.
If your system runs with '11 minute mode' on, it may need to use
either --hctosys or --systz in a startup script, especially if the
Hardware Clock is configured to use the local timescale. Unless
the kernel is informed of what timescale the Hardware Clock is
using, it may clobber it with the wrong one. The kernel uses UTC
by default.
The first userspace command to set the System Clock informs the
kernel what timescale the Hardware Clock is using. This happens
via the persistent_clock_is_local kernel variable. If --hctosys or
--systz is the first, it will set this variable according to the
adjtime file or the appropriate command-line argument. Note that
when using this capability and the Hardware Clock timescale
configuration is changed, then a reboot is required to notify the
kernel.
Where is the time set in UTC
Inside the kernel code, time.c file:
asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz)
{
if (likely(tv != NULL)) {
struct timeval ktv;
do_gettimeofday(&ktv);
if (copy_to_user(tv, &ktv, sizeof(ktv)))
return -EFAULT;
}
if (unlikely(tz != NULL)) {
if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))
return -EFAULT;
}
return 0;
}
/*
* Adjust the time obtained from the CMOS to be UTC time instead of
* local time.
*
* This is ugly, but preferable to the alternatives. Otherwise we
* would either need to write a program to do it in /etc/rc (and risk
* confusion if the program gets run more than once; it would also be
* hard to make the program warp the clock precisely n hours) or
* compile in the timezone information into the kernel. Bad, bad....
*
* - TYT, 1992-01-01
*
* The best thing to do is to keep the CMOS clock in universal time (UTC)
* as real UNIX machines always do it. This avoids all headaches about
* daylight saving times and warping kernel clocks.
*/
static inline void warp_clock(void)
{
write_seqlock_irq(&xtime_lock);
wall_to_monotonic.tv_sec -= sys_tz.tz_minuteswest * 60;
xtime.tv_sec += sys_tz.tz_minuteswest * 60;
time_interpolator_reset();
write_sequnlock_irq(&xtime_lock);
clock_was_set();
}
/*
* In case for some reason the CMOS clock has not already been running
* in UTC, but in some local time: The first time we set the timezone,
* we will warp the clock so that it is ticking UTC time instead of
* local time. Presumably, if someone is setting the timezone then we
* are running in an environment where the programs understand about
* timezones. This should be done at boot time in the /etc/rc script,
* as soon as possible, so that the clock can be set right. Otherwise,
* various programs will get confused when the clock gets warped.
*/
int do_sys_settimeofday(struct timespec *tv, struct timezone *tz)
{
static int firsttime = 1;
int error = 0;
if (tv && !timespec_valid(tv))
return -EINVAL;
error = security_settime(tv, tz);
if (error)
return error;
if (tz) {
/* SMP safe, global irq locking makes it work. */
sys_tz = *tz;
if (firsttime) {
firsttime = 0;
if (!tv)
warp_clock();
}
}
if (tv)
{
/* SMP safe, again the code in arch/foo/time.c should
* globally block out interrupts when it runs.
*/
return do_settimeofday(tv);
}
return 0;
}
What actually happens with dual boot Windows/Linux + chrony (rtcsync):
Seems like the kernel really only wants to treat the hardware RTC as UTC.
Chrony is configured with rtcsync, which helps keep the RTC from drifting by having the kernel periodically copy the current system time into the RTC. Enabling rtcsync causes the kernel to use the classic “11-minute mode.” 11-minute mode means: when the system clock is synchronized, the kernel will automatically write the system time back to the RTC at intervals and also at certain events, so the RTC stays close to the correct time.
Because the kernel assumes the RTC is UTC, if the RTC is configured as localtime, when the kernel reads the RTC expecting UTC but finds a localtime-based value, it applies a correction and “warps” RTC back to UTC.
So with chrony using rtcsync to enable the 11-minute mode for kernel, the kernel will always write RTC as UTC. According to the documentation, chrony itself, does not write into the RTC with this mode on, but the kernel does.
For better understanding I generated this image using Gemeni:
Fixing the Linux/Windows Dual-Boot Clock Issue with Chrony and rtcsyncon Linux side
This is not as easy as making Windows see RTC in UTC not localtime.
The first step is to make the changes described at wiki.archlinux.org. If you are using chrony with rtcsync, you need to modify the chrony configuration to (I assume NTP is configured as well for chrony):
rtcfile /var/lib/chrony/rtc
rtcautotrim 20
#rtcsync disabled
Note: When I try to run hwclock, I get: “device or resource busy, no usable interface found”. This happens (PROBABLY) because chronyd is holding/locking the RTC device. This would probably be a problem if other process tries to use it.
Edge cases:
- When offline or without NTP server set, RTC will not be written by chrony. Systemd does not write the RTC at shutdown anymore (see archieve)so any manual time set on system will happen for system clock and perhaps nothing will write the system clock to RTC.
Final Note: All artistic images were generated using Gemeni.





Top comments (0)