February 3, 2022·6 min read·engineeringcglibclinux

glibc 2.35 makes rseq registration a runtime responsibility

Automatic restartable-sequence registration and C.UTF-8 support move shared responsibilities into glibc. The release also changes dynamic linking and brings several draft C2x facilities into the library.

A library that wants fast access to per-CPU data needs more than a compiler optimization. It needs an agreement with the kernel about each thread, and an agreement with other libraries about where that thread's state lives. The glibc 2.35 announcement, published on February 3, makes automatic registration for Linux restartable sequences part of the C runtime. That places a process-wide coordination problem with the component already responsible for creating ordinary POSIX threads.

The same release adds C.UTF-8, a locale that downstream distributions have already been supplying in different environments. These additions address different mechanisms, but both reduce the amount of platform integration an individual library or application has to arrange. Neither addition means that every installation immediately gains identical behaviour. Kernel availability matters for rseq, and the new locale still needs installation.

I tend to think about C through its role in interoperation. Calling conventions and library contracts determine much of what other languages can use on a system. This release is interesting on those terms because its changes extend beyond programs whose application code happens to be written in C.

A shared area for restartable sequences

The announcement dates Linux's rseq system call to kernel 4.18. Its purpose is to support efficient user-space operations on per-CPU data without requiring heavyweight atomic operations for every update. glibc 2.35 automatically registers threads so that participating libraries can use the documented ABI immediately.

Registration is distinct from writing a correct restartable sequence. The 2.35 thread manual describes a per-thread struct rseq area. A library obtains its location relative to the thread pointer through __rseq_offset. That offset has the same value for all threads in a process, although each thread has its own area. A negative offset is valid when the area precedes the thread pointer.

The cpu_id member provides one concrete use. Reading it can supply the current CPU number in an inline operation analogous to sched_getcpu. The manual says glibc sets that field to RSEQ_CPU_ID_REGISTRATION_FAILED if registration fails or has been explicitly disabled. Code that consumes the area therefore needs to distinguish availability from the mere presence of the exported interface.

A more involved use stores the address of a critical-section descriptor in the area's rseq_cs member. The kernel then has information about the sequence the thread is entering. This is where ownership becomes important. The descriptor and the code it describes must remain valid while the thread's area refers to them. Automatic registration cannot extend the lifetime of memory owned by a dynamically loaded library.

The manual gives two possible approaches. A library may arrange for those memory regions never to be freed or reused. Otherwise, it must clear references before that memory goes away. In particular, the documentation recommends that libraries which might release the relevant memory clear rseq_cs before returning from their rseq-using functions. The application generally cannot know which loaded libraries have left references behind.

That is a useful boundary in the design. glibc handles registration and publishes the location of the state. Libraries remain responsible for the lifetime of the descriptors and code they publish through that state. Treating the runtime change as an automatic acceleration of arbitrary C code would miss that division of responsibility.

The other exported state makes availability explicit. The manual defines __rseq_size as zero when registration failed or was disabled. Successful registration has a size of at least 32 bytes, but the manual warns that the registered size can differ from the size of the structure as the kernel evolves. __rseq_flags reports the registration flags, currently zero. Libraries therefore have a documented way to inspect the registration supplied by glibc without assuming that their own structure definition describes every future kernel extension.

C.UTF-8 is an installed locale

The new locale combines UTF-8 support with code-point sorting for valid Unicode code points. According to the release announcement, its complete size is about 400 KiB, of which 346 KiB is character classification information for Unicode. Those numbers explain why a locale that sounds minimal still has a substantial data component.

The announcement explicitly says C.UTF-8 is not built into glibc and must be installed. That detail affects small runtime images and restricted environments. A system can contain the new library while lacking the locale data an application expects to select. Version detection and locale availability are separate questions.

There is also a documented restriction in pattern matching. Limitations in the framework used by fnmatch, regexec, and regcomp mean that only ASCII-based range expressions are supported for now. Code-point sorting does not imply unrestricted support for non-ASCII ranges in every locale-sensitive interface. The release notes connect that compromise to space usage and bug 28255.

My preference is for a clearly specified common locale over distribution-specific assumptions about the same name. The benefit here is a shared definition that packaging can adopt. The installation requirement and range-expression restriction remain part of that definition.

Runtime support and compiler support meet at formatted output

glibc 2.35 also implements the draft C2x %b integer output conversion and the recommended %B variant. This gives binary formatting a library implementation. It does not, by itself, establish that the compiler's format checker understands the same conversion. The formatter and the diagnostic machinery belong to different components of the toolchain.

The mathematical additions make the same distinction between a language revision and a library implementation. Functions such as fsqrt, fsqrtl, and dsqrtl round results to narrower types. Related fused multiply-add functions include ffma, ffmal, and dfmal. The announcement attributes these additions to the floating-point technical specifications and draft C2x, with corresponding type-generic macros.

Another set of functions implements floating-point maximum and minimum operations corresponding to IEEE 754-2019. The release names separate forms such as fmaximum, fmaximum_num, and fmaximum_mag. Their distinct names are a reason to consult the operation's definition rather than assume they are interchangeable spellings of an existing minimum or maximum function. The release announcement establishes their availability without making every application portable to libraries that lack them.

Dynamic linking has its own compatibility work

An application can observe a glibc upgrade before calling any of these new interfaces. The dynamic linker now defaults to a depth-first-search topological sorting algorithm for shared-object dependencies. The announcement says it addresses performance problems involving particular circular dependency cases. That is a specific claim about dependency ordering, not a general claim that every program starts faster.

The release retains a selection mechanism for the older algorithm. The tunable glibc.rtld.dynamic_sort defaults to 2 for the new implementation, while 1 selects the algorithm used in glibc 2.33 and earlier. A process can select that documented compatibility setting through the environment.

GLIBC_TUNABLES=glibc.rtld.dynamic_sort=1 ./application

That command changes the loader's sorting choice for this invocation. It does not roll back the other library changes. The distinction matters because 2.35 also updates the debugger interface to version 2 and adds r_next for multiple namespaces. The announcement identifies older GDB binaries without the required fixes as incompatible when audit modules or dlmopen are used.

Audit modules have another explicit boundary on AArch64. The extended interface includes the indirect result location register and NEON Q registers, which changes structure sizes. Old modules are rejected, so these modules require rebuilding against the new interface. This is a different compatibility issue from dependency sorting and cannot be addressed by selecting the old sorting algorithm.

Hardening still depends on the toolchain

Source fortification level 3 becomes available for applications using glibc with GCC 12 and later. The release attributes the additional coverage to __builtin_dynamic_object_size, which permits checks involving nonconstant bounds. Those checks can add runtime cost. The compiler requirement is part of the feature, so installing glibc 2.35 alone is insufficient to infer that a particular application uses it.

The release also changes glibc's own build defaults to position-independent executables where the architecture and toolchain support them. Static programs receive the same treatment when static PIE is supported. The new --disable-default-pie switch controls this behaviour, and the previous --enable-static-pie switch no longer changes the build configuration.

These details fit the release's broader practical effect. Per-thread kernel integration, locale installation, and dynamic-loader interfaces each involve components outside an application's source files. An environment adopting 2.35 needs its packaging to supply the locale data and its debugging tools to understand the new loader interface. Libraries using rseq gain runtime-managed registration while retaining responsibility for the descriptor references they create.