August 1, 2019·4 min read·engineeringcglibclinux

glibc 2.30 exposes Linux thread IDs and directory records

New Linux wrappers make kernel interfaces directly available through glibc, with distinct lifetime and record-layout contracts.

A process identifier cannot distinguish the threads inside a process, and a portable directory iterator does not expose the same interface as a kernel directory read. glibc 2.30, released August 1, adds public Linux interfaces for both needs. Its new functions include gettid and getdents64, alongside tgkill. Their value lies in exposing specific Linux operations through declared library interfaces.

I find runtime changes like these important because C defines so much of the practical vocabulary used at operating-system boundaries. A wrapper does not make a Linux operation portable. It gives code using that operation a library declaration and a documented result, which is useful even when the operating-system dependency is intentional.

A kernel thread ID has its own lifetime

The 2.30 process manual distinguishes process IDs from Linux thread IDs. The initial thread has the same ID as the process. Subsequently created threads receive distinct IDs from the same numbering space. The process ID identifies the thread group in glibc's Linux implementation.

The new gettid function returns the calling thread's kernel ID. The manual says the result comes from the kernel and is not cached. That description matters because the identifier is an observation of the current thread, not an application-assigned label with an independent lifetime.

A minimal diagnostic function can print both identifiers. It requests GNU interfaces before including the headers:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

void print_identity(void)
{
    printf("process=%ld thread=%ld\n",
           (long)getpid(), (long)gettid());
}

The casts make the format's expected argument types explicit. The function reports the current execution context. It does not retain the ID or imply that an ID remains associated with the same thread indefinitely.

That last point is central to the manual's discussion. A Linux thread ID becomes eligible for reuse when the thread exits or is canceled, including when the thread is joinable. Keeping a thread joinable does not preserve its kernel number as a permanent identity token. A program correlating diagnostic records needs to account for this lifetime.

An application can therefore use gettid to identify the thread making a current call without treating a saved number as ownership of that thread. The distinction resembles other resource handles whose meaning depends on whether the underlying object still exists. A numeric representation is convenient for output, but its convenience does not extend its validity.

Directory reads return bytes containing records

The new getdents64 interface accepts a file descriptor, a buffer and a byte count. The release's filesystem manual specifies a successful return as the number of bytes written. Zero means the directory stream has reached its end. Failure returns minus one and sets errno.

The data consists of a sequence of dirent64 records. Their d_reclen member supplies the length used to reach the next record. Treating the returned buffer as a fixed-size array of structures would discard that contract. The function's byte count and each record's length belong to the interpretation of the result. A positive return cannot be used directly as a count of filenames, since the number of records depends on the lengths of the entries in that particular batch.

The manual also advises making the buffer large enough for the largest possible directory entry. It recommends at least 1024 bytes and notes that some filesystems support names longer than NAME_MAX bytes. A limit expressed in characters does not necessarily equal a byte count for an encoded filename.

These details explain why a low-level wrapper does not automatically simplify every directory traversal. It gives an application direct responsibility for a batch of records. That can be appropriate for code needing the Linux interface, while also introducing parsing obligations absent from a call that hands back one already-selected entry.

The wrapper adapts an argument boundary

The getdents64 implementation contains a small but useful adaptation. Its public size argument has size_t type, while the kernel call takes an unsigned integer argument and some kernel checks use int. glibc caps the requested count at INT_MAX before invoking the system call.

This is a concrete reason to examine a wrapper even when it appears to pass arguments straight through. The public C type and the kernel's accepted range are not always identical. A declaration alone can hide that difference unless the implementation or documentation explains it.

The same source contains compatibility code for an older directory representation on configurations that need it. It checks whether inode values fit the older type and uses separate structures and memory copying to avoid aliasing problems during layout conversion. That historical work belongs to maintaining an established runtime interface across changes in representation.

I value this part of C's library engineering. An interface can remain compact because the runtime contains the details needed to meet the platform contract. Those details still deserve inspection when application code depends on a particular representation or maximum size.

The release makes other boundaries explicit

glibc 2.30 also changes allocation behavior for total object sizes exceeding PTRDIFF_MAX. The announcement explains the reason in terms of pointer subtraction within a large allocated object. A size representable in an allocation argument can exceed the range representable by the signed difference type. The library now rejects those allocations across the listed allocation functions.

Another change removes glibc's userspace emulation for copy_file_range when the kernel lacks the system call. Such a call now fails with ENOSYS, leaving a fallback to the application. The maintainers cite differences between the emulation and kernel behavior. An apparently transparent fallback had provided a contract that was too difficult to make consistent.

These changes have a related practical consequence. Application code has to distinguish what the library guarantees from what a kernel facility supplies. getdents64 exposes a Linux record stream with byte counts and variable record lengths. gettid exposes a Linux identity whose validity depends on a thread's lifetime. Having named library functions makes those dependencies easier to state, while their exact contracts still determine how callers may retain and interpret the results.