glibc exposes C functions such as read(2) and write(2), while Linux exposes
architecture-specific system-call entry conventions. A wrapper bridges those layers:
it accepts the public C ABI, follows the selected architecture’s system-call ABI,
translates kernel errors, and may implement POSIX thread-cancellation behavior.
Examples below describe the current upstream source tree. A released glibc can select different files or macro definitions, so source inspection must begin with the release and target triple being built.
sysdeps/ Selection
#
The sysdeps/ tree
holds implementations selected by operating system, CPU, ABI, word size, and
features. Generic code is a fallback; a more specific directory can replace a file
with the same relative name. For a Linux build, relevant paths include
sysdeps/unix/sysv/linux/; an architecture may further supply Linux-specific entry
code or an ABI-specific variant.
glibc’s configure and build machinery constructs an ordered list of applicable
system-dependency directories. Build rules search that list so that the selected
implementation wins over a generic fallback. The layout is consequently not a
taxonomy in which each file has exactly one dimension: an OS directory can depend on
architecture headers, and an architecture directory can contain OS-independent
code. Inspect config.make, the configured build directory, and the selected source
paths rather than inferring behavior from one directory name.
Generated Thin Wrappers #
Some thin assembly wrappers are generated from
syscalls.list.
Each record names an output, a Linux syscall, an argument signature, a strong symbol,
and optional weak or versioned names. The build invokes
make-syscalls.sh,
which:
- removes entries overridden by higher-priority
sysdepsdirectories; - resolves the
SYS_number through the target’ssysdep.h; - derives arity and special argument handling from the signature; and
- emits a rule using
syscall-template.S, including aliases and compatibility symbols where the list requests them.
syscalls.list does not define every public function. A wrapper with validation,
fallback logic, cancellation handling, time-width conversion, or another policy is
often hand-written. write is one such hand-written C wrapper in current glibc
write.c;
it is therefore a useful contrast with the generated path.
write and Cancellation-Aware Dispatch
#
Conceptually, the Linux write wrapper does the following:
ssize_t
write(int fd, const void *buffer, size_t count)
{
return cancellation_aware_syscall(SYS_write, fd, buffer, count);
}The actual source calls SYSCALL_CANCEL (write, fd, buf, nbytes). That macro selects
an inline system-call path when cancellation checking is unnecessary. Otherwise it
temporarily changes the thread’s cancellation type, issues the call, and restores
the prior type. write is a POSIX cancellation point, so this boundary lets a
pending cancellation be acted on without treating every internal system call as a
cancellation point.
The macro dispatches by argument count before reaching the architecture’s inline system-call primitive. On Linux, that primitive places the syscall number and arguments in registers prescribed by the target ABI, performs the architecture’s system-call instruction, and receives the kernel return value. Neither the C function ABI nor the register sequence is universal: x86-64, i386, AArch64, and other ports use different registers and clobber rules.
The kernel returns an error in its low-level convention. The selected glibc support
code recognizes the error range, stores a positive error number in thread-local
errno, and makes a normal public wrapper return -1. Interfaces that intentionally
expose a raw error value use distinct internal conventions. The
glibc manual’s system-call section
documents the application-level contract; the source macros implement the
architecture-specific boundary.
Public Names, Hidden Definitions, and Aliases #
The write.c definition is named __libc_write, then its source declares internal
visibility and aliases including __write and the public write name. This shape
separates implementation identity from public ABI identity:
libc_hidden_defandlibc_hidden_weakallow calls made inside libc to bind to a hidden definition when appropriate, avoiding an interposable public-symbol lookup.- A strong alias gives another internal name the same implementation.
- A weak public alias permits a different definition to override it when the build or ABI requires that behavior.
- Symbol-version machinery records which ABI version exports a public name and can retain compatibility entry points for old binaries.
hidden affects references within glibc; it does not erase the documented public
symbol from the shared-library ABI. Examine the architecture’s libc.so with
readelf --dyn-syms --version-info and compare it with the source and release
version scripts when diagnosing an unresolved symbol. An alias declaration alone is
not sufficient evidence of the version a distributed library exports.
Comments
Read or comment on GitHub (sign-in required to comment)