|
|
Subscribe / Log in / New account

Kernel time APIs for Rust

By Jonathan Corbet
March 2, 2023
While the 6.3 kernel has gained more support for the Rust language, it still remains true that there is little that can be done in Rust beyond the creation of a "hello world" module. That functionality was already available in C, of course, with a level of safety similar to what Rust can provide. Interest is growing, though, in merging actually useful modules written in Rust; that will require some more capable infrastructure than is currently present. A recent discussion on the handling of time values in Rust demonstrates the challenges — and opportunities — inherent in this effort.

Asahi Lina, who is implementing a graphics driver for Apple hardware in Rust, has posted a number of pieces of Rust infrastructure, including a module for timekeeping functions. The timekeeping module itself is just a beginning, weighing in at all of 25 lines; it looks like this:

    // SPDX-License-Identifier: GPL-2.0
    
    //! Timekeeping functions.
    //!
    //! C header: [`include/linux/ktime.h`](../../../../include/linux/ktime.h)
    //! C header: [`include/linux/timekeeping.h`](../../../../include/linux/timekeeping.h)
    
    use crate::bindings;
    use core::time::Duration;
    
    /// Returns the kernel time elapsed since boot, excluding time spent
    /// sleeping, as a [`Duration`].
    pub fn ktime_get() -> Duration {
        // SAFETY: Function has no side effects and no inputs.
        Duration::from_nanos(unsafe { bindings::ktime_get() }.try_into().unwrap())
    }
    
    /// Returns the kernel time elapsed since boot, including time spent
    /// sleeping, as a [`Duration`].
    pub fn ktime_get_boottime() -> Duration {
        Duration::from_nanos(
            // SAFETY: Function has no side effects and no variable inputs.
            unsafe { bindings::ktime_get_with_offset(bindings::tk_offsets_TK_OFFS_BOOT) }
                .try_into()
                .unwrap(),
        )
    }

This module expresses two kernel functions — ktime_get() and ktime_get_boottime() — as Rust equivalents that return values as the Rust Duration type. In C, these functions both return a ktime_t, which is a signed, 64-bit value reflecting a time in nanoseconds. The origin of that time — what real-world date and time is represented by a ktime_t of zero — varies depending on which clock is being queried. In the case of ktime_get_boottime(), for example, the returned value represents the time that has passed since the system booted.

Kernel times are thus, at their core, a delta value; they are a count of nanoseconds since some beginning-of-the-universe event. The proposed Rust implementation followed that lead in its use of the Duration type. But Thomas Gleixner, who is responsible for much of the kernel's timekeeping code, questioned this approach. Since both of the functions are meant, each in its own way, to represent an absolute point in time, he suggested that the Rust functions should return an absolute-time type; he suggested either Instant or SystemTime. Both represent absolute time values; Instant is monotonic (it will never go backward) while SystemTime is not.

That approach will not work well in the kernel, though, for a couple of reasons. The first, as pointed out by Boqun Feng, is that those two types are defined in the Rust standard library ("std"), which, just like the C standard library, is not available in the kernel. So, at best, those two types would have to be reimplemented for kernel use. But the other problem is that the kernel supports a multitude of clocks; the list can be found in the clock_gettime() man page. Each clock has a reason for existing, and each behaves a little differently. A type like Instant is defined to use exactly one clock, but kernel code will need access to several of them.

Gleixner was not really concerned about the exact types used, but he did call for different types to be used for absolute times (timestamps) and delta times (or intervals). The kernel does not currently have such a distinction in its types for times, but libraries for many languages do make that distinction. Given that Rust is being brought into the kernel in the hope of making it easy to write safer code, it makes sense to use Rust's type system to prevent developers from, for example, trying to add two absolute-time values together.

Indeed, as Lina pointed out, type safety in the Rust interface should even go one step further. Subtracting one absolute time from another will yield a delta time — but that delta time will only make sense if the two absolute times came from the same clock. So the type system should prevent the incorrect mixing of times from different clocks.

What about delta times? Gleixner initially suggested that time deltas could be independent of any clock; a time delta obtained by subtracting one CLOCK_BOOTTIME value from another would be the same type as a delta calculated as a difference of CLOCK_TAI values. Heghedus Razvan agreed with this idea and posted a sample implementation; Gary Guo then polished that idea into a "more Rusty" implementation. Miguel Ojeda, though, suggested that delta times, too, could be tied to a specific clock. Gleixner was not entirely convinced that this distinction was needed, but agreed that there might be value in it, especially when dealing with timers. Kernel timers, too, can be based on specific clocks, so it might make sense to ensure that any time deltas used with those timers are generated with the same clock, he said.

Feng suggested proceeding with an implementation using Duration for all time delta values and something resembling Instant, but with clock-specific typing, for absolute times. Lina agreed, and seems ready to proceed in this direction, starting with the example posted by Razvan. A new patch will, presumably, be forthcoming.

It seems likely that we will see this sort of conversation happening repeatedly as more Rust infrastructure heads toward the mainline. It is certainly possible to reproduce something like the existing kernel APIs in Rust, and doing so would make the resulting Rust code look more familiar to current kernel developers. But that approach also risks losing much of the hoped-for benefit that is driving the Rust experiment in the first place. Doing this experiment properly will require showing how Rust can lead to the creation of better, safer APIs than what the kernel has now. So a lot of APIs are going to have to be redesigned from the beginning; they can benefit from years of experience in the kernel community, but will have to leave many of that community's conventions behind. It seems like a project that will keep people busy for some time.

Index entries for this article
KernelDevelopment tools/Rust
KernelTimekeeping


to post comments

Kernel time APIs for Rust

Posted Mar 2, 2023 16:51 UTC (Thu) by rillian (subscriber, #11344) [Link] (18 responses)

In case anyone else was wondering, the bindings::ktime_get().try_into().unwrap() is because ktime_t is a signed 64-bit integer, but Rust's core::time::Duration wraps an unsigned type, since durations are non-negative intervals. The Rust code will panic if ktime_get() returns a negative clock value. Part of the absolute vs. offset complications!

It seems like the module could call the higher-level ktime_get_ns() and ktime_get_boottime() instead to be more isolated from implementation changes. Maybe explicit is better?

Kernel time APIs for Rust

Posted Mar 2, 2023 20:46 UTC (Thu) by rrolls (subscriber, #151126) [Link] (5 responses)

That a type named "Duration" wraps an unsigned integer comes as a surprise to me!

Perhaps it's just because I'm used to how Python does things (here, with timedelta), but if I have two instants, A and B, then I'd expect to be able to do A - B and get a duration that could be positive, zero or negative - positive if A was in the future of B, zero if A and B are the same instant, and negative if A was in the past of B. Similarly, adding a positive duration to some instant C would result in an instant in C's future, and adding a negative duration to C would result in an instant in C's past.

Does Rust have a different type intended to be used for this purpose? If so, then what is core::time::Duration for? Or if not, would I be expected to carry around a future/past flag when it's needed, like an "absolute value plus sign" representation?

Kernel time APIs for Rust

Posted Mar 2, 2023 21:20 UTC (Thu) by mb (subscriber, #50428) [Link]

Subtracting two Instants saturates to zero. (I think in older Rust versions it panicked, if the result was negative).
It also has a checked_duration_since() function, which returns None, if the result would be negative (by your definition).

Kernel time APIs for Rust

Posted Mar 2, 2023 21:53 UTC (Thu) by mbunkus (subscriber, #87248) [Link]

core::time::Duration & std::time::Duration are indeed unsigned. Their documentation states their purpose as "A Duration type to represent a span of time, typically used for system timeouts."

There are several popular crates out there with signed duration types, allowing for real differences. "chrono" and "time" are popular ones, and both state explicitly that they allow arithmetic and negative durations.

Kernel time APIs for Rust

Posted Mar 2, 2023 21:56 UTC (Thu) by mbunkus (subscriber, #87248) [Link] (2 responses)

BTW, and maybe this is due to me not being a native English speaker: I always think of "duration" being unsigned/non-negative. There's no negative time, no way to go backwards in time. I would expect the type of "timestamp1 - timestamp2" to be named something else, actually, e.g. a "difference", and that I would totally expect to be signed.

Kernel time APIs for Rust

Posted Mar 3, 2023 15:34 UTC (Fri) by magi (subscriber, #4051) [Link] (1 responses)

I guess that's why python calls it "timedelta" rather than "duration".

Kernel time APIs for Rust

Posted Mar 7, 2023 5:20 UTC (Tue) by ssmith32 (subscriber, #72404) [Link]

Indeed. Duration indicates the amount of time, and an amount of time cannot be negative.

https://www.merriam-webster.com/dictionary/duration

For the non-negative speaker: as sometimes happens, you're understanding of the language is more correct than, perhaps, some native English speakers ;)

Kernel time APIs for Rust

Posted Mar 2, 2023 23:42 UTC (Thu) by RogerOdle (subscriber, #60791) [Link] (11 responses)

Stop using absolute types, just stop it. Stop using multiple types for the same thing when they eventually have to be compared. How many bugs do we have to create where signed and unsigned values are used? There should only be one type for time in the kernel and it should be a relative time value so it should be signed. And it should be 64 bits.

Make time a signed 64 bit integer everywhere. Do not use Duration, use Time. Do not use Instant, use Time. Just pick one type please. If you create more than one, may kitten will die,

Kernel time APIs for Rust

Posted Mar 3, 2023 0:48 UTC (Fri) by mathstuf (subscriber, #69389) [Link]

> How many bugs do we have to create where signed and unsigned values are used?

This is a C problem. Rust doesn't allow such comparisons. It has explicit casts, but they look "weird" and usually indicate that some API is missing.

I wouldn't mind seeing `Duration<Monotonic>` and `Duration<Wall>` that are *not* comparable. Everything being the same type makes it too easy to mix them up and make nonsense differences.

Kernel time APIs for Rust

Posted Mar 3, 2023 1:33 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (8 responses)

Signed (or unsigned, for that matter) 64-bit integers do not allow for nanosecond precision to an acceptable range. They go to a reasonably large range, but I would argue that it's not really good enough:

Python 3.10.9 (main, Dec  7 2022, 13:47:07) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime as dt
>>> epoch = dt.datetime(1970, 1, 1, 0, 0, 0, tzinfo=dt.timezone.utc)
>>> print(epoch)
1970-01-01 00:00:00+00:00
>>> print(epoch + dt.timedelta(microseconds=(2**63 - 1) // 1000))
2262-04-11 23:47:16.854775+00:00
>>> print(epoch - dt.timedelta(microseconds=(2**63) // 1000))
1677-09-21 00:12:43.145225+00:00
>>> # For comparison:
>>> print(epoch + dt.timedelta(seconds=(2**31 - 1)))
2038-01-19 03:14:07+00:00
>>> print(epoch + dt.timedelta(seconds=(2**32 - 1)))
2106-02-07 06:28:15+00:00
>>> print(epoch + dt.timedelta(seconds=(2**63 - 1)))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: Python int too large to convert to C int
>>> print(dt.datetime.max)
9999-12-31 23:59:59.999999

Sure, 2262 is a long time in the future. But it's not so absurdly far into the future that you won't eventually have compatibility problems (unlike the Python max datetime, which is thousands of years from now, or the 64-bit *second* rollover, which is multiple times the age of the universe). Realistically, if you want precision finer than 1 second increments, you should probably use some kind of struct. So now you have two time types, like it or not.

Aside from that, the whole point of using multiple types is so that the type checker can catch invalid comparisons. Rust is not C, it does not let you just write nonsense and have it automagically coerce to the Wrong Thing.

Kernel time APIs for Rust

Posted Mar 3, 2023 7:48 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link] (1 responses)

> Signed (or unsigned, for that matter) 64-bit integers do not allow for nanosecond precision to an acceptable range

This is actually a real problem already! My friends are working on software that processes Excel spreadsheets, and they had to abandon the standard Go timekeeping infrastructure, because it can't represent date differences that are used in actual spreadsheets that work with historical dates.

Kernel time APIs for Rust

Posted Mar 5, 2023 16:57 UTC (Sun) by Wol (subscriber, #4433) [Link]

As part of my "redesigning in my head" of the LGV system we use, I came across OpenQM's "Epoch" time type. Pick's "Day 1" is, iirc, 1 Jan 1967. "Epoch" counts in seconds from "00:00GMT 1 Jan 1967" then, and "time of day" is seconds from midnight. Which suddenly makes the maths for at least one of my problems simple.

I've heard of it before, but it makes things SOOO much simpler if the 24hr clock doesn't force you to use times between 00:00 and 23:59. I'll be able to start shifts at the correct time of -5:00! Or we have shifts that are supposed to finish at 24:00, and I'll be able to correctly record drivers coming back to base and clocking off at 25:30, which I did on one occasion ...

Cheers,
Wol

Kernel time APIs for Rust

Posted Mar 3, 2023 16:26 UTC (Fri) by rillian (subscriber, #11344) [Link] (5 responses)

Following this logic, the Rust std library Duration type is actually a 96-bit struct, so it has both nanosecond precision and the range of a 64-bit second counter.

This aligns with the general language philosophy of being correct by default. The downside is it's slower. Of course if that's an issue, one is free to make one's own types with smaller representations.

Kernel time APIs for Rust

Posted Mar 3, 2023 23:00 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (4 responses)

The only problem I have with this struct is that it does not implement div_duration_u64 and Rem<Duration>, both of which ought to exist and satisfy the following invariants:

a == b * (a.div_duration_u64(b)) + (a % b)
a % b < b

Where a and b are arbitrary durations, b is nonzero, and overflow does not occur (there might also need to be a checked variant). div_duration_u64 returns a u64. Implementing those correctly is not trivial, and library consumers ought not have to do it themselves. They are needed for answering questions such as "How many days, hours, minutes, and seconds is this duration?" without having to extract seconds into u64 and do all the math in untyped integers.

Kernel time APIs for Rust

Posted Mar 4, 2023 0:31 UTC (Sat) by rillian (subscriber, #11344) [Link] (3 responses)

Are there other use-cases? I agree converting a Duration to various calendar intervals isn't trivial, but does that support belong in the std library, rather than the library implementing the conversions? Wouldn't such a method further constrain the (currently private) representation?

The time and chrono libraries in fact both provide their own Duration types with methods for computing how many minutes, hours, weeks, etc. a given Duration represents. Amusingly for this discussion they both use signed integers internally.

Kernel time APIs for Rust

Posted Mar 4, 2023 1:24 UTC (Sat) by NYKevin (subscriber, #129325) [Link] (2 responses)

That's why I'm suggesting a generic division implementation, not a "how many days?" method. Want sidereal time? Just divide by a sidereal day. Want Gregorian average years? Divide by 365.2425 days. Julian years? 365.25 days. The whole point is that this is a general function that can be used for a wide variety of purposes, and doesn't force you into using a particular set of units.

Kernel time APIs for Rust

Posted Mar 4, 2023 1:25 UTC (Sat) by NYKevin (subscriber, #129325) [Link]

(Incidentally, a sidereal day is not a whole number of seconds, so you *can't* just use the "do the math in seconds" trick there.)

Kernel time APIs for Rust

Posted Mar 4, 2023 2:23 UTC (Sat) by rillian (subscriber, #11344) [Link]

I see what you mean. Thanks.

I suppose there's pub const fn as_nanos(&self) -> u128...

Kernel time APIs for Rust

Posted Mar 3, 2023 5:46 UTC (Fri) by josh (subscriber, #17465) [Link]

Stop using multiple types for the same thing

Absolute time and relative time are not the same thing. It's a good thing that:

  • Subtracting two absolute times gives a relative time.
  • Adding a relative time to an absolute time, or subtracting a relative time from an absolute time, gives an absolute time.
  • Adding or subtracting relative times gives relative times.
  • Adding an absolute time to an absolute time *does not work*.

Kernel time APIs for Rust

Posted Mar 2, 2023 20:27 UTC (Thu) by iabervon (subscriber, #722) [Link] (4 responses)

I thought get_ktime() was intended to reflect the fact that the kernel mostly doesn't care what time it is really, but wants some sort of increasing reference that really shouldn't be an absolute time. If you used to be wrong about the time when the system booted, and now you've got a better idea, the difference between the value you got from get_ktime() before the correction and the value you get now would still only reflect the passage of time, whereas any absolute times reported would be expected to draft towards the best possible agreement with external references.

I think returning Durations might be a good cue that the caller should use these functions in order to have the difference between two return values best reflect the time that elapsed between those calls, and look elsewhere for the official current time. (But developers looking for time information should probably be reminded of the existence of what they probably really want.)

Kernel time APIs for Rust

Posted Mar 2, 2023 23:42 UTC (Thu) by NYKevin (subscriber, #129325) [Link]

IMHO encoding the clock you used into the type still solves that problem, because if an API wants wall time (or some reasonable approximation thereof), it won't accept Instant<ktime> (or however they've decided to spell it), since ktime is the "wrong clock" for that purpose.

Kernel time APIs for Rust

Posted Mar 3, 2023 0:12 UTC (Fri) by tglx (subscriber, #31301) [Link]

The kernel very much cares about what time it is and all ktime_get*() variants return absolute time and some of them are very well related to external references (REALTIME/TAI). Even if MONOTONIC/BOOTTIME are not externally referenced they are absolute by definition.

So in theory the return value for these functions could be unsigned. But C is patently bad about unsigned vs. signed math unless you are very carefully wrapping things with the required type casts. So we ended up with a signed data type.

But that does not mean that ktime_get*() will ever return a negative value. That would be clearly a bug.

Now we are talking about interfaces for a proper type safe language and we really have to be very careful about these things and not just willy-nilly declare that the kernel does not care and timestamps are considered relative.

There is a clear distinction between reading time, which is by definition absolute and the delta between two timestamps which is by definition relative. That's nothing which is defined by the kernel or by rust, it's a well understood fact.

Making the sloppy abstraction of everything is a Duration is just thwarting the justification for Rust in the kernel.

Kernel time APIs for Rust

Posted Mar 3, 2023 6:07 UTC (Fri) by matthias (subscriber, #94967) [Link] (1 responses)

> I thought get_ktime() was intended to reflect the fact that the kernel mostly doesn't care what time it is really, but wants some sort of increasing reference that really shouldn't be an absolute time.

This somewhat misses the point. Of course this should be absolute times, in the sense that they refer to some point in time and not a duration, i.e. the difference between two points in time. However get_ktime() uses a different calendar, i.e., one that does not start with the birth of Christ as some other calendars. But the distinction between point in time (returned by get_ktime()) and duration (time that passed between two points in time) is still meaningful and important.

Of course, if you want to store some point in time, you always have to store a difference to some arbitrary point in time that you fix as the 0 in your calendar. Be it birth of Christ, Unix epoch, or time of system boot. But there is always the clear distinction between a point in time (stored relative to the 0) and the duration between two points in time. And comparing one to another is most likely a big mistake that should be prevented by the type system.

> I think returning Durations might be a good cue that the caller should use these functions in order to have the difference between two return values best reflect the time that elapsed between those calls, and look elsewhere for the official current time. (But developers looking for time information should probably be reminded of the existence of what they probably really want.)

No, as also discussed in the article, you can use different kinds of Instances instead. So you have Instant<KTIME>, Instant<TAI>, Instant<WALL>, etc. It would be an error to calculate a Duration from two Instances that come from different clocks. Which type of clock you want would be encoded in the concrete type. What is not that clear to me is whether Durations calculated from different clocks are different types. But in kernel space it probably makes sense, because the Duration between the same two points in time can depend on the clock used to record these points in time. Some clocks are monotonic while others are not.

Kernel time APIs for Rust

Posted Mar 3, 2023 9:51 UTC (Fri) by Wol (subscriber, #4433) [Link]

So basically we have two sorts of time, monotonic and wall, Monotonic time may have a wall-clock zero attached to them, or may be a plain timer. Wall clocks may have a calendar and time zone attached to them, or may just be a plain clock. Interval is a special case of monotonic, I'd think.

That - I think - gives you everything you need to convert between them, but the rule would be any attempt to convert/compare either requires the information to do a valid operation or, if the information is not there, you can force the operation on bare values. What you cannot do is the operation where you have the clock definition but no conversion information.

Cheers,
Wol

Kernel time APIs for Rust

Posted Mar 2, 2023 22:37 UTC (Thu) by amarao (guest, #87073) [Link] (1 responses)

I really happy to see those discussions. Rusty programming usually brings clarity (even if it's not welcomed), and just discussion about proper types and their relations may yield unexpected discoveries about unsoundness.

I find myself occasionally 'thinking in Rust' even if I write in soft-typed language.

Kernel time APIs for Rust

Posted Mar 3, 2023 0:14 UTC (Fri) by tglx (subscriber, #31301) [Link]

I can relate. My ADA history taught me a few things about correctness first :)

Kernel time APIs for Rust

Posted Mar 4, 2023 0:26 UTC (Sat) by edeloget (subscriber, #88392) [Link] (1 responses)

Is there a chance that this work will also translate to cleaner C interface within the kernel? Given the fact that C is mostly (well, not always, but) strong typed, it should be able to get some benefits by using clearer and cleaner abstractions as well (for example, structure-based types instead of integers of various length and signedness). If you turn a ktime_t into a structure you won't be able to use it when you need another kind of duration. It would make the core a bit more clunky (no more direct addition or comparison) but that would make the intent clearer.

Or maybe there is something I don't see?

Kernel time APIs for Rust

Posted Mar 5, 2023 16:01 UTC (Sun) by plugwash (subscriber, #29694) [Link]

C lacks any form of generics or overloading, so if you have different types you either have to duplicate all the code with different function names for each type or do a bunch of nasty typecasting.

Kernel time APIs for Rust

Posted Apr 22, 2023 11:13 UTC (Sat) by charmitro (guest, #164741) [Link] (1 responses)

When are we expected to see modules implemented in Rust on the mainline?

AFAIK there are only samples, but it's been a long time since last time I checked.

Kernel time APIs for Rust

Posted Apr 22, 2023 11:29 UTC (Sat) by rahulsundaram (subscriber, #21946) [Link]

> When are we expected to see modules implemented in Rust on the mainline?

I haven't seen a specific timeline but here is what I have read

https://www.heise.de/hintergrund/Three-Questions-and-Answ...

"The first drivers (and the abstractions supporting them) that will start to be upstreamed are likely to be the Asahi Linux's GPU driver, Android's Binder and the NVMe driver. These are all non-trivial and will set the example for future Rust kernel abstractions and drivers."


Copyright © 2023, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds