64-Bit Time_T in Linux Kernel

Is there any way to get 64-bit time_t in 32-bit programs in Linux?

Apparently, no it's not possible. For starters, there is only one time() function in Linux, no time32() or time64().

After searching for a while, I can see that it's not libc's fault, but the culprit is actually the kernel.

In order for libc to fetch the current time, it need to execute a system call for it: (Source)

time_t time (t) time_t *t;
{
// ...
INTERNAL_SYSCALL_DECL (err);
time_t res = INTERNAL_SYSCALL (time, err, 1, NULL);
// ...
return res;
}

The system call is defined as: (Source)

SYSCALL_DEFINE1(time, time_t __user *, tloc)
{
time_t i = get_seconds();
// ...
return i;
}

The function get_seconds() returns an unsigned long, like so: (Source)

unsigned long get_seconds(void)
{
struct timekeeper *tk = &timekeeper;

return tk->xtime_sec;
}

And timekeeper.xtime_sec is actually 64-bit: (Source)

struct timekeeper {
// ...
/* Current CLOCK_REALTIME time in seconds */
u64 xtime_sec;
// ...
}

Now, if you know your C, you know that the size of unsigned long is actually implementation-dependant. On my 64-bit machine here, it's 64-bit; but on my 32-bit machine here, it's 32-bit. It possibly could be 64-bit on some 32-bit implementation, but there's no guarantee.

On the other hand, u64 is always 64-bit, so at the very base, the kernel keeps track of the time in a 64-bit type. Why it then proceeds to return this as an unsigned long, which is not guaranteed to be 64-bit long, is beyond me.

In the end, even if libc's would force time_t to hold a 64-bit value, it wouldn't change a thing.

You could tie your application deeply into the kernel, but I don't think it's even worth it.

How can I do 64 bit division on linux kernel?

The GCC C compiler generates code that calls functions in the libgcc library to implement the / and % operations with 64-bit operands on 32-bit CPUs. However, the Linux kernel is not linked against the libgcc library, so such code will fail to link when building code for a 32-bit Linux kernel. (When building an external kernel module, the problem may not be apparent until you try and dynamically load the module into the running kernel.)

Originally, the Linux kernel only had the do_div(n,base) macro defined by #include <asm/div64.h>. The usage of this macro is unusual because it modifies its first argument in place to become the quotient resulting from the division, and yields (returns) the remainder from the division as its result. This was done for code efficiency reasons but is a bit of a pain to use. Also, it only supports division of a 64-bit unsigned dividend by a 32-bit divisor.

Linux kernel version 2.6.22 introduced the #include <linux/math64.h> header, which defines a set of functions which is more comprehensive than the old do_div(n,base) macro and is easier to use because they behave like normal C functions.

The functions declared by #include <linux/math64.h> for 64-bit division are listed below. Except where indicated, all of these have been available since kernel version 2.6.26.

One of the functions listed below in italics does not exist yet as of kernel version 4.18-rc8. Who knows if it will ever be implemented? (Some other functions declared by the header file related to multiply and shift operations in later kernel versions have been omitted below.)

  • u64 div_u64(u64 dividend, u32 divisor) — unsigned division of 64-bit dividend by 32-bit divisor.
  • s64 div_s64(s64 dividend, s32 divisor) — signed division of 64-bit dividend by 32-bit divisor.
  • u64 div_u64_rem(u64 dividend, u32 divisor, u32 *remainder) — unsigned division of 64-bit dividend by 32-bit divisor with remainder.
  • s64 div_s64_rem(s64 dividend, s32 divisor, s32 *remainder) — signed division of 64-bit dividend by 32-bit divisor with remainder.
  • u64 div64_u64(u64 dividend, u64 divisor) — unsigned division of 64-bit dividend by 64-bit divisor.
  • s64 div64_s64(s64 dividend, s64 divisor)(since 2.6.37) signed division of 64-bit dividend by 64-bit divisor.
  • u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder)(since 3.12.0) unsigned division of 64-bit dividend by 64-bit divisor with remainder.
  • s64 div64_s64_rem(s64 dividend, s64 divisor, s64 *remainder)(does not exist yet as of 4.18-rc8) signed division of 64-bit dividend by 64-bit divisor with remainder.
  • div64_long(x,y)(since 3.4.0) macro to do signed division of a 64-bit dividend by a long int divisor (which is 32-bit or 64 bit, depending on the architecture).
  • div64_ul(x,y)(since 3.10.0) macro to do unsigned division of a 64-bit dividend by an unsigned long int divisor (which is 32-bit or 64-bit, depending on the architecture).
  • u32 iter_div_u64_rem(u64 dividend, u32 divisor, u64 *remainder) — unsigned division of 64-bit division by 32-bit divisor by repeated subtraction of divisor from dividend, with remainder (may be faster than regular division if the dividend is not expected to be much bigger than the divisor).

How much memory does a 64bit Linux Kernel take up?

Each user-space process can use its own 2^47 bytes (128 TiB) of virtual address space. Or more on a system with PML5 support.

The available physical RAM to back those pages is the total size of physical RAM, minus maybe 30 MiB or so that the kernel needs for its own code/data. (Not including the pagecache: Linux will use any spare pages as buffers and disk cache). This is mostly unrelated to virtual address-space limits.


1G is how much virtual address space a kernel used up. Not how much physical RAM.

The address-space question mattered for how much memory a single process could use at the same time, but the kernel can still use all your RAM for caching file data, etc. Unless you're finding the 2^(48-1) or 2^(57-1) bytes of the low half virtual address-space range cramped, there's no equivalent problem.

See the kernel's Documentation/x86/x86-64/mm.txt for the x86-64 virtual memory map. Also Why 4-level paging can only cover 64 TiB of physical address re: x86-64 Linux not doing inconvenient HIGHMEM stuff - the entire high half of virtual address space is reserved for the kernel, and it maps all the RAM because it's a kernel.

Virtual address space usage does indirectly set a 64 TiB limit on how much physical RAM the kernel can use, but if you have less than that there's no effect. Just like how a 32-bit kernel wasn't a problem if your machine had less than 1 or 2 GiB of RAM.


The amount of physical RAM actually reserved by the kernel depends on build options and modules, but might be something like 16 to 32 MiB.

Check dmesg output and look for something like this kernel log message from an x86-64 5.16.3-arch1 kernel I found in an old boot-log message.

Memory: 32538176K/33352340K available (14344K kernel code, 2040K rwdata, 8996K rodata, 1652K init, 4336K bss, 813904K reserved, 0K cma-reserved

Don't count the init (freed in after boot) or reserved parts; I'm pretty sure Linux doesn't actually reserve ~800 MiB in a way that makes it unusable for anything else.

Also look for the later Freeing unused decrypted memory: 2036K / Freeing unused kernel image (initmem) memory: 1652K etc. (That's the same size as the init part listed earlier, which is why you don't have to count it.)

It might also dynamically allocate some memory during startup; that initial "memory" line is just the sum of its .text, .data, and .bss sections, static code+data sizes.

On x64, how does the Linux kernel access the data segment? Does it use -mcmodel=large during compilation?

I think the confusion is between the gcc memory model and the 64-bit CPU's MMU. Using the kernel memory model generates code that uses signed 32-bit offsets, which means all symbols in the kernel must fit in the top 2GB of the address space. This does not change the fact that virtual address pointers in the kernel are 64-bit, of which 48 or so bits are significant, allowing anything in the kernel or current user space to be indirectly accessed via the page tables and MMU.



Related Topics



Leave a reply



Submit