|
|
Subscribe / Log in / New account

What's in a (type) name?

By Jonathan Corbet
September 2, 2022
The kernel's manual pages are in a bit of an interesting position. They are managed as a separate project, distinct from the kernel's documentation, and have the task of documenting both the kernel's system-call interface and the wrappers for that interface provided by the C library. Sometimes the two objectives come into conflict, as can be seen in a discussion that has been playing out over the course of the last year on whether to use C standard type names to describe kernel-defined structures.

The C <stdint.h> header file defines a number of types for developers who need to specify exactly how they need an integer variable to be represented. For example, int16_t is a 16-bit, signed type, while uint64_t is a 64-bit, unsigned type. This level of control is needed when defining data structures that are implemented by hardware, are exchanged through communications protocols — or are passed between user and kernel space.

The kernel, though, does not use these types to define its system-call interface. Instead, the kernel has its own types defined internally. Rather than use uint64_t, for example, the kernel's API definitions use __u64. That has been the situation for a long time — since before the standard C types existed — and is simply part of how the kernel project does things.

As a general rule, the man pages reflect the kernel's definition of data types. So, for example, the bpf() man page defines one piece of the bpf_attr union as:

    struct {    /* Used by BPF_MAP_*_ELEM and BPF_MAP_GET_NEXT_KEY
                   commands */
	__u32         map_fd;
	__aligned_u64 key;
	union {
	    __aligned_u64 value;
	    __aligned_u64 next_key;
	};
	__u64         flags;
    };

These types are familiar to kernel developers, but they may look a bit strange to user-space developers. Back in April of 2021, man-pages co-maintainer Alejandro Colomar decided to make things look more familiar by rewriting the man pages to use the standard C types instead. Perhaps out of love for a challenge, Colomar started with the bpf() man page; after applying the patch, the above structure was defined as:

    struct {    /* Used by BPF_MAP_*_ELEM and BPF_MAP_GET_NEXT_KEY commands */
        uint32_t                     map_fd;
        uint64_t [[gnu::aligned(8)]] key;
        union {
            uint64_t [[gnu::aligned(8)]] value;
            uint64_t [[gnu::aligned(8)]] next_key;
         };
         uint64_t                     flags;
     };

This patch was immediately vetoed by BPF maintainer Alexei Starovoitov, who said: "The man page should describe the kernel api the way it is in .h file". Colomar answered that the actual types used are the same either way, and that his change was better for users:

If we have a standard syntax for fixed-width integral types (and for anything, actually), the manual pages should probably follow it, whenever possible. Any deviation from the standard (be it C or POSIX) should have a very good reason to be; otherwise, it only creates confusion.

Starovoitov stood firm in his opposition, though, saying that the man pages should describe the types as they will be defined when code includes the associated kernel header file.

Colomar returned in May 2021 with a new version of the patch that was little changed from its predecessor. Also unchanged was the reception it got. This time, Greg Kroah-Hartman also expressed his opposition, saying that the types involved "are not the same, they live in different namespaces, and worlds, and can not always be swapped out for each other on all arches". GNU C Library developer Zack Weinberg disagreed, though:

Manpage documentation of C structs is *not* expected to match the actual declaration in the headers. The documented field type is usually assignment-compatible with the actual type, but not always. There's no guarantee whatsoever that the fields are in the same order as the header, or that the listed set of fields is complete.

This argument failed to convince the kernel community, though, which remained strongly against the change. This discussion then died down for over a year.

Colomar returned with a new patch converting many more files in August 2022; he included the Nacked-by tags he had received from three different developers. Unsurprisingly, those developers had not become more sympathetic toward the idea during the pause. Starovoitov repeated his opposition and asked Colomar to stop sending the patch.

In response, Colomar went ahead and applied the patch to the man-pages repository. A kernel patch that had encountered such opposition would almost certainly never have been applied, but the man pages are not a kernel project. Colomar appears to be the only active man-pages maintainer at the moment; longtime maintainer Michael Kerrisk has seemingly vanished from the scene since the man pages 5.13 release in August 2021. So there is nobody who is in a position to overrule Colomar when it comes to decisions in this area.

Much of the discussion covered the same ground as with the previous versions, but this time Linus Torvalds jumped in as well. He pointed out that the kernel's types simply cannot be the same as the standard C types without creating namespace problems: the kernel cannot include <stdint.h> to define those types, but also cannot define those types itself in files used by user space without creating conflicts there. Torvalds agreed with the others that the documentation should match the actual types used.

Honestly, I don't think it makes a *huge* amount of difference, but documentation that doesn't actually match the source of the documentation will just confuse somebody in the end. Somebody will go "that's not right", and maybe even change the structure definitions to match the documentation.

This message, along with a request from Kroah-Hartman to revert the change, was enough to convince Colomar to back down. His concluding words were:

You convinced me. The man-pages will document the types exactly as they are in kernel. It's just simpler.

As the patch was recently reverted after Greg asked me to do, I'll keep it that way. I guess this closes the man-pages discussion.

The interesting thing, of course, is that the kernel does, indeed, define many of the standard types internally, and there are thousands of variables defined using those types. Using standard C types in the kernel is not, itself, a problem; only using them in the user-space API definitions is. With sufficient will, this might well be a problem that could be overcome, but it would not be a small job. Meanwhile, it seems that the man pages will continue to document the types that are actually used in the kernel's user-space API header files.

Index entries for this article
KernelDocumentation/man pages


to post comments

What's in a (type) name?

Posted Sep 2, 2022 17:09 UTC (Fri) by adobriyan (subscriber, #30858) [Link] (2 responses)

__aligned_u64 should have been named u64_a8 so that there is less confusion.

What's in a (type) name?

Posted Sep 2, 2022 20:09 UTC (Fri) by Bigos (subscriber, #96807) [Link] (1 responses)

If I understand correctly, __aligned_u64 is just a regular __u64 that is guaranteed to be natural-aligned (i.e. 64-bit aligned). This does not make any difference on 64-bit architectures, but on 32-bit ones the regular __u64 might only by 32-bit aligned.

Your proposal of u64_a8 is pretty confusing, as the first number is in bits and the second is in bytes.

What's in a (type) name?

Posted Sep 3, 2022 14:50 UTC (Sat) by adobriyan (subscriber, #30858) [Link]

Alignment is always in bytes: alignof(), __attribute__ ((aligned())), alignas(), _Alignas, [[gnu::aligned()]].

Fixed-width integer types are always in bits: uintN_t, uN, __uN, sN, iN, newly added_DecimalN, _Bitint(N)
Even SIMD stuff is in bits: __m128, __m256.

What's in a (type) name?

Posted Sep 2, 2022 20:34 UTC (Fri) by ddevault (subscriber, #99589) [Link] (1 responses)

I didn't know that Colomar had joined on to man-pages. Maybe now someone will read my patches :)

What's in a (type) name?

Posted Sep 3, 2022 5:31 UTC (Sat) by neilbrown (subscriber, #359) [Link]

It is nice and explicit where to send patches now:

https://git.kernel.org/pub/scm/docs/man-pages/man-pages.g...

What's in a (type) name?

Posted Sep 2, 2022 20:44 UTC (Fri) by nickodell (subscriber, #125165) [Link] (17 responses)

GKH said:

>There's a very old post from Linus where he describes the difference between things like __u32 and uint32_t [...] Dig it up if you are curious

Does anyone have a link to this?

What's in a (type) name?

Posted Sep 2, 2022 21:11 UTC (Fri) by cesarb (subscriber, #6266) [Link] (15 responses)

I haven't seen that old post, but I can guess that it's going to be about "int" vs "long" on 32-bit architectures, and the subtleties of C integer promotion rules. Unlike on 64-bit architectures, both "int" and "long" are 32 bits on 32-bit architectures, and it wouldn't surprise me if for instance "__u32" used to be "unsigned long" while "uint32_t" was "unsigned int", or vice-versa.

What's in a (type) name?

Posted Sep 2, 2022 23:00 UTC (Fri) by koh (subscriber, #101482) [Link] (8 responses)

That's quite unlikely. u32 / uint32_t are quite clearly 32 bit wide unsigned integers. Alignment constraints might be stronger on the kernel level (they can't be weaker than "none" as defined by the standard). Though from this article it really is not clear why there would be any difference at all. Why is there so much opposition to interchangeability between std- and kernel-defined types when clearly there is the syscall interface between them which uses the same kinds of types?

What's in a (type) name?

Posted Sep 3, 2022 1:02 UTC (Sat) by abatters (✭ supporter ✭, #6932) [Link] (7 responses)

I haven't had much problem with 32-bit ints, but 64-bit ints are annoying to work with portably.
32-bit userspace, 32-bit kernel, and 64-bit kernel:
typedef unsigned long long uint64_t;

64-bit userspace:
typedef unsigned long uint64_t;

uint64_t x = 777;

// compiler complains about these depending on arch
printf("%lu", x);
printf("%llu", x);
scanf("%lu", &x);
scanf("%llu", &x);

// these are portable but require more code
printf("%llu", (unsigned long long) x);
unsigned long long tmp;
scanf("%llu", &tmp);
x = tmp;

What's in a (type) name?

Posted Sep 3, 2022 3:07 UTC (Sat) by ABCD (subscriber, #53650) [Link] (6 responses)

The portable (as of C99) way to use printf and scanf (in userspace) with those types is to do things like:

#include <inttypes.h>

uint64_t x = 777;

printf("%" PRIu64, x);
scanf("%" SCNu64, &x);

What's in a (type) name?

Posted Sep 3, 2022 16:37 UTC (Sat) by pm215 (subscriber, #98099) [Link] (5 responses)

Yes, but the compiler often won't warn if you forget and instead use the format specifier for the underlying type by mistake. Also the portable way is more longwinded and annoyingly requires you to close and reopen the string, which breaks the design principle that the right thing should be the easy thing :-(

What's in a (type) name?

Posted Sep 3, 2022 19:55 UTC (Sat) by SAI_Peregrinus (subscriber, #151778) [Link] (2 responses)

> breaks the design principle that the right thing should be the easy thing :-(

When has that ever been a design principle of C?

What's in a (type) name?

Posted Sep 4, 2022 12:01 UTC (Sun) by jezuch (subscriber, #52988) [Link]

That depends on what "the right thing" is. In C, designed for systems programing in the 70s, when things were different, that would be "easily map to hardware". (Differently on each hardware architecture, of course, but the exact details are, well, undefined! So it's not clear it succeeds even at that.)

What's in a (type) name?

Posted Sep 5, 2022 11:06 UTC (Mon) by pm215 (subscriber, #98099) [Link]

I meant it in the sense of a general principle of good API design. (It's one of the ideas of Rusty Russell's set of levels of API design: https://ozlabs.org/~rusty/index.cgi/tech/2008-03-30.html ) C of course has many parts which are not well-designed (for various reasons including 'historical'), and this is one of them.

What's in a (type) name?

Posted Sep 5, 2022 23:51 UTC (Mon) by skissane (subscriber, #38675) [Link] (1 responses)

> Also the portable way is more longwinded and annoyingly requires you to close and reopen the string

Really, the C standards committee should introduce a nicer way of doing that.

%(u64) or something like that. (I don't know which punctuation symbols are in use or reserved and which are not, but surely someone can come up with something which doesn't clash with existing uses.)

What's in a (type) name?

Posted Sep 6, 2022 23:02 UTC (Tue) by dezgeg (subscriber, #92243) [Link]

What's in a (type) name?

Posted Sep 3, 2022 9:41 UTC (Sat) by pbonzini (subscriber, #60935) [Link] (5 responses)

IIRC u64 is always unsigned long long, while uint64_t is unsigned long on 64-bit machines. This way the kernel does not need PRIx64 and friends, u64 is always printed with %llx.

What's in a (type) name?

Posted Sep 3, 2022 15:18 UTC (Sat) by cpacejo (subscriber, #153871) [Link] (1 responses)

Yes, this appears to be the case, at least on my amd64 Linux (i.e., LP64 data model) system.

It's therefore relevant that, because they are of different rank, `long` and `long long` are not "compatible types", even if they happen to be of the same width. Notably, this means that you cannot substitute a pointer to one for the other (likely resulting in a compile time error), and that such mixed pointers are assumed by the compiler not to alias each other (possibly leading to subtle runtime issues). See https://stackoverflow.com/a/66850819 for a good explanation.

What's in a (type) name?

Posted Sep 5, 2022 8:31 UTC (Mon) by geert (subscriber, #98403) [Link]

But it hasn't always been the case like that. Fortunately all odd architectures (e.g. alpha) using "long" for 64-bit types have been converted to "long long" a long time ago, bringing more uniformity into the kernel.

For the interested, you can find some clues in the commit that removed the last remnants:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/...

What's in a (type) name?

Posted Sep 3, 2022 16:01 UTC (Sat) by rwmj (subscriber, #5474) [Link] (2 responses)

That's a reason, but it doesn't seem like a good reason. Why doesn't the kernel adopt the relevant standards? They're fairly sensible and work successfully for userspace code.

What's in a (type) name?

Posted Sep 3, 2022 16:48 UTC (Sat) by pbonzini (subscriber, #60935) [Link] (1 responses)

Because PRIx64 1) is a total pain in the butt, and 2) is in inttypes.h which is *not* part of the freestanding environment. And anyway the kernel is older than C99 stdint.h.

What's in a (type) name?

Posted Sep 9, 2022 14:38 UTC (Fri) by andyp (subscriber, #48701) [Link]

Indeed it seems like a stretch to imagine the whole kernel switching to the standard types, but it would be great if _just the uapi_ headers used the standard types so that we didn't have to work around the incompatibilities in every userspace project that uses them.

#ifdef __KERNEL__
/* Current definitions */
#else
typedef __u64 uint64_t;
typedef __u32 uint32_t;
/* etc. */
#endif

Or something like that.

What's in a (type) name?

Posted Sep 4, 2022 9:00 UTC (Sun) by bmork (subscriber, #88411) [Link]

> Does anyone have a link to this?

This has been discussed so many times that it's hard to know exactly which post that refers to. But I guess it could be the 2004 rant Russel reposted here: https://lkml.iu.edu/hypermail/linux/kernel/1506.0/00160.html

What's in a (type) name?

Posted Sep 2, 2022 23:33 UTC (Fri) by koh (subscriber, #101482) [Link] (9 responses)

I commend Colomar for trying to use std types! What I don't understand is why it's frowned upon to use std-definable types. Std-C has come a long way since the kernel was born (and so has the kernel). It is possible now, even in man-pages, to specify exactly how alignment should be. Where exactly is the problem of using a differentky named but compatible type?

Note: "compatible type" is all C requires for interchangeability.

What's in a (type) name?

Posted Sep 3, 2022 6:07 UTC (Sat) by epa (subscriber, #39769) [Link] (8 responses)

Yes, can someone explain why the kernel cannot include stdint.h, or have its own copy? Obviously it cannot pull in the whole C standard library but it could pick a few typedefs.

What's in a (type) name?

Posted Sep 3, 2022 10:18 UTC (Sat) by jengelh (guest, #33263) [Link] (7 responses)

1. The kernel is a so-called freestanding environment. There is no C library it could use - the presence of e.g. strcpy or uint32_t in the kernel source code for its own use comes from the kernel's own implementation, not from libc. (Build tools excluded from the discussion.)

2. For *any* typedef for uint32_t that /usr/include/linux/types.h would provide, a libc implementation sufficiently unknown to the Linux project could provide a /usr/include/stdint.h with a re-declaration of the typedef in an incompatible form (e.g. by making use of some weird implementation-defined extensions). So that's why even a subset of the C library is not going to be used.

(That didn't prevent people from using uint* in e.g. /usr/include/linux/sctp.h. Because frankly, the number of libcs that are decidedly playing evil is quite close to zero.)

What's in a (type) name?

Posted Sep 3, 2022 10:46 UTC (Sat) by Cyberax (✭ supporter ✭, #52523) [Link] (1 responses)

> 2. For *any* typedef for uint32_t that /usr/include/linux/types.h would provide, a libc implementation sufficiently unknown to the Linux project could provide a /usr/include/stdint.h with a re-declaration of the typedef in an incompatible form

Why is this a problem? The kernel will not be using ANY libc during its build. It comes into play only when the headers are used in userspace software. Using something like:

> #ifdef __KERNEL__
> #include <kernels_homegrown_types.h>
> #else
> #include <stdint.h>
> #endif

What's in a (type) name?

Posted Sep 5, 2022 8:55 UTC (Mon) by metan (subscriber, #74107) [Link]

Tried that, it's not that simple unfortunately. For C++ this change breaks ABI since the mangled names end up different if you change the types and overloaded function may not match either.

See the v3 of the patch I send https://lore.kernel.org/lkml/CAK8P3a2J5k2ub6TNu9qDympdWEd...

What's in a (type) name?

Posted Sep 3, 2022 11:01 UTC (Sat) by pbonzini (subscriber, #60935) [Link]

Some headers, including stdint.h, are available even in freestanding environments. They are provided by the compiler, not the C library, and are effectively part of the platform ABI.

One reason for Linux does not use them is for compatibility between platforms, so that the same driver code compiles independent of which types are used for the definition of uintNN_t (without e.g. warnings about incompatible pointer types between long long and intNN_t).

What's in a (type) name?

Posted Sep 5, 2022 22:03 UTC (Mon) by NYKevin (subscriber, #129325) [Link] (3 responses)

Why does the reader of this man page care? My assumption is that the average person reading a man page about a syscall is probably a userspace developer, who is (most likely) going to call it through a libc wrapper (or perhaps through a Go wrapper, although at that point the API is going to differ anyway).* In that context, the kernel typedefs are not in scope, are probably unfamiliar to the average userspace developer, and manually recreating those typedefs is forbidden by the C standard (which reserves underscore-prefixed type names), so it is not possible for the given declaration to be correct on the userspace side of the fence.

TL;DR: Is this man page intended for kernel developers, and just incidentally useful to userspace developers, or vice-versa? It can't be both.

* Yes, libc probably should provide documentation for their wrappers. But they don't, because the GNU people hate man pages.

What's in a (type) name?

Posted Sep 6, 2022 17:15 UTC (Tue) by Wol (subscriber, #4433) [Link] (2 responses)

> * Yes, libc probably should provide documentation for their wrappers. But they don't, because the GNU people hate man pages.

I'm inclined to agree, but they could always do info pages instead. There's a fairly simple way to convert tbem to man, I believe...

Cheers,
Wol

What's in a (type) name?

Posted Sep 6, 2022 17:57 UTC (Tue) by mpr22 (subscriber, #60784) [Link] (1 responses)

An info file structured like GNU say info files are supposed to be structured makes a very poor man page.

What's in a (type) name?

Posted Sep 6, 2022 19:56 UTC (Tue) by NYKevin (subscriber, #129325) [Link]

On top of that, they already have info pages for glibc: https://www.gnu.org/software/libc/manual/

As you can see from the table of contents (https://www.gnu.org/software/libc/manual/html_node/index....), they really aren't structured like a set of man pages at all. This manual is useful, if you're trying to learn an entire API from the ground up, but less so if you just want to look up the signature of a function, its invariants, errno values, or something like that.

What's in a (type) name?

Posted Sep 3, 2022 0:18 UTC (Sat) by Manifault (guest, #155796) [Link] (3 responses)

For what it's worth, Michael introduced himself as a "comaintainer" of the man-pages project (since 2020) when he gave his talk [0] at KR in May 2022.

Also, it seems like a bit of a broken process if the maintainer of a documented system with a man page cannot override a suggestion to update how that system is documented in its man page. I get that man-pages is a different repo, but this doesn't seem conducive to providing users with the most accurate documentation, nor does it seem scalable. In general, I'll admit that I don't really understand why it wouldn't be better for everyone for the man-pages project to just pull documentation for these large subsystems directly from the kernel tree. What's the point of having two sources of "truth" for documentation? Especially when the maintainer of the actual subsystem only has control over one of them (so only one of them is really the source of truth).

https://kernel-recipes.org/en/2022/once-upon-an-api/

What's in a (type) name?

Posted Sep 3, 2022 16:02 UTC (Sat) by cortana (subscriber, #24596) [Link]

And here's me simply wishing that Glibc provided documentation for every system call that it provides a wrapper for...

What's in a (type) name?

Posted Sep 3, 2022 16:41 UTC (Sat) by pm215 (subscriber, #98099) [Link] (1 responses)

Having the manpages in the kernel tree would also make it easier to ensure that all new syscalls and other userspace interfaces came with documentation...

What's in a (type) name?

Posted Sep 6, 2022 23:51 UTC (Tue) by Manifault (guest, #155796) [Link]

Agreed. In my opinion the kernel should absolutely document the behavior of its syscalls, rather than relying on man or e.g. some libc library to document it for them. I think that's especially true for Linux, where there is 0 tolerance for backwards compatibility changes, even for unintended behavior / bugs that were introduced in earlier implementations of a syscall (see Michael's talk for a particularly "rich" example).

What's in a (type) name?

Posted Sep 3, 2022 19:56 UTC (Sat) by scientes (guest, #83068) [Link] (2 responses)

> You've been told multiple times that the kernel doesn't use the
"standard" names, and *cannot* use them for namespace reasons, and you
ignore all the feedback, and then you claim you are asking for review?
-Torvalds

This isn't true. Look at <stdbool.h> (Linus' criticism of it non-withstanding)---c compilers certainly *could* support <stdint.h> in freestanding mode, they just don't.

It is like his criticism of the C++ memory model, which is just fine, and could totally be used by Linux.

What's in a (type) name?

Posted Sep 3, 2022 20:00 UTC (Sat) by scientes (guest, #83068) [Link]

I have read glibc code that used K&R old-style function declarations, which make C's origin's as a macro processor very clear. We need to support those in Linux! /notfunny (and neither is Linus trying to say that <stdint.h> cannot be used, instead of asking why gcc/clang have not fixed it. (same goes with his criticisms of <stdbool.h> and once asking memcpy() to be redefined as memmove()----go fix the standard! go fix the programs! stop claiming you are so special you can't use the C++ memory model!

What's in a (type) name?

Posted Sep 4, 2022 20:00 UTC (Sun) by fw (subscriber, #26023) [Link]

GCC supports <stdint.h> in freestanding mode. The #include_next directive in the compiler-provided header is skipped, making it self-contained. The kernel just does not use freestanding mode on most architectures.

What's in a (type) name?

Posted Sep 8, 2022 0:32 UTC (Thu) by milesrout (subscriber, #126894) [Link] (1 responses)

I suspect this would have been more acceptable if it didn't use the HIDEOUS new [[gnu::aligned(8)]] syntax. Who thought that incredibly ugly crap looks good? It might not stand out much in C++, but what is this double colon thing doing in C?

What's in a (type) name?

Posted Sep 9, 2022 1:13 UTC (Fri) by foom (subscriber, #14868) [Link]

I dunno why it used the non-standard gnu attribute in syntax that's only valid since C23, instead of the standard (since C11) `alignas(8)`, anyhow...


Copyright © 2022, 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