Rust and Linux Kernel ABI Stability: A Technical Deep Dive

Does Rust in the Linux kernel provide userspace interfaces? What’s the kernel’s ABI stability policy? This analysis examines how Rust drivers interact with userspace, the critical distinction between internal and external ABI stability, and concrete examples from production code like Android Binder and DRM drivers.

TL;DR: Quick Answers

Q1: Does Rust currently provide userspace interfaces?Yes. Rust drivers already expose userspace APIs through ioctl, /dev nodes, sysfs, and other standard mechanisms.

Q2: Does the kernel pursue internal ABI stability?No. Internal kernel APIs (between modules and kernel) are explicitly unstable. Only userspace ABI is sacred.

Q3: Will Rust be used for userspace-facing features that require ABI stability?Already happening. Android Binder (Rust) provides critical userspace ABI to billions of devices.

Deep Dive: System Call ABI - The Immutable Contract

Before examining Rust’s userspace interfaces, let’s understand what makes userspace ABI so critical by looking at the system call layer - the most fundamental userspace interface.

The Sacred System Call ABI

Linux supports three different system call mechanisms simultaneously to maintain ABI compatibility:

Mechanism Introduced Instruction Syscall # Parameters Status
INT 0x80 Linux 1.0 (1994) int $0x80 %eax %ebx, %ecx, %edx, %esi, %edi, %ebp ✅ Still supported (32-bit compat)
SYSENTER Intel P6 (1995) sysenter %eax %ebx, %ecx, %edx, %esi, %edi, %ebp ✅ Still supported (Intel 32-bit)
SYSCALL AMD K6 (1997) syscall %rax %rdi, %rsi, %rdx, %r10, %r8, %r9 ✅ Primary 64-bit method

All three are maintained in parallel to ensure no userspace application ever breaks.

Actual Kernel Implementation

From arch/x86/kernel/cpu/common.c (Linux kernel source):

// syscall_init() - called during kernel initialization
void syscall_init(void)
{
    /* Set up segment selectors for user/kernel mode */
    wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS);

    if (!cpu_feature_enabled(X86_FEATURE_FRED))
        idt_syscall_init();
}

static inline void idt_syscall_init(void)
{
    // 64-bit native syscall entry
    wrmsrq(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

    // 32-bit compatibility mode - MUST maintain old ABI
    if (ia32_enabled()) {
        wrmsrq_cstar((unsigned long)entry_SYSCALL_compat);

        /* SYSENTER support for 32-bit applications */
        wrmsrq_safe(MSR_IA32_SYSENTER_CS, (u64)__KERNEL_CS);
        wrmsrq_safe(MSR_IA32_SYSENTER_ESP,
                    (unsigned long)(cpu_entry_stack(smp_processor_id()) + 1));
        wrmsrq_safe(MSR_IA32_SYSENTER_EIP, (u64)entry_SYSENTER_compat);
    }
}

What this means: A 32-bit application compiled in 1994 using int $0x80 still works on a 2026 Linux kernel running on modern hardware.

Two System Call Tables

// 64-bit native system calls
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
    [0 ... __NR_syscall_max] = &__x64_sys_ni_syscall,
    #include <asm/syscalls_64.h>
};

// 32-bit compatibility system calls
const sys_call_ptr_t ia32_sys_call_table[__NR_ia32_syscall_max+1] = {
    [0 ... __NR_ia32_syscall_max] = &__ia32_sys_ni_syscall,
    #include <asm/syscalls_32.h>
};

Key insight: Linux maintains completely separate system call tables for 32-bit and 64-bit to ensure ABI stability. The 32-bit table has never removed a syscall - only added new ones.

Boot Protocol ABI - Even Bootloaders Have Contracts

From the Linux kernel compressed boot loader (arch/x86/boot/compressed/head_64.S):

/*
 * 32bit entry is 0 and it is ABI so immutable!
 * This is the compressed kernel entry point.
 */
    .code32
SYM_FUNC_START(startup_32)

The comment “ABI so immutable!” is critical:

Boot protocol specifications (Documentation/x86/boot.rst):

This is internal boot ABI - distinct from userspace ABI but equally immutable because external tools (bootloaders) depend on it.

The Lesson for Rust

When Rust drivers provide userspace interfaces, they inherit these same ironclad rules:

C example (traditional):

// Userspace never knows this changed from C to Rust
int fd = open("/dev/binder", O_RDWR);
ioctl(fd, BINDER_WRITE_READ, &bwr);  // ABI unchanged

Rust implementation (modern):

// Must provide IDENTICAL ABI
const BINDER_WRITE_READ: u32 = kernel::ioctl::_IOWR::<BinderWriteRead>(
    BINDER_TYPE as u32,
    1  // ioctl number - NEVER changes
);

The ioctl number, structure layout, and semantics are frozen in time - whether implemented in C or Rust.


Rust’s ABI Guarantees: System V Compatibility

Before examining specific userspace interfaces, it’s crucial to understand how Rust guarantees compatibility with the System V ABI that Linux uses on x86-64.

Does Rust Comply with System V ABI?

Yes - rustc explicitly guarantees System V ABI compliance through language features.

The Linux kernel on x86-64 uses the System V AMD64 ABI for:

Rust provides multiple mechanisms to ensure ABI compatibility:

ABI Type Rust Syntax x86-64 Linux Behavior Guarantee Level
Rust ABI extern "Rust" (default) Unspecified, may change ❌ Unstable
C ABI extern "C" System V AMD64 ABI Language spec guarantee
System V extern "sysv64" System V AMD64 ABI Explicit guarantee
Data layout #[repr(C)] Matches C struct layout Compiler guarantee

Compiler-Enforced ABI Correctness

Unlike C where ABI compliance is implicit and unchecked, Rust makes ABI contracts explicit and verified at compile time:

// Explicit C ABI - compiler verifies calling convention
#[no_mangle]
pub extern "C" fn kernel_function(arg: u64) -> i32 {
    // Function uses System V calling convention:
    // - arg passed in %rdi register
    // - return value in %rax register
    // - Guaranteed across Rust compiler versions
    0
}

// Explicit memory layout - compiler verifies size/alignment
#[repr(C)]
pub struct KernelStruct {
    field1: u64,  // offset 0, 8 bytes
    field2: u32,  // offset 8, 4 bytes
    field3: u32,  // offset 12, 4 bytes
}

// Compile-time verification - FAILS if layout changes
const _: () = assert!(core::mem::size_of::<KernelStruct>() == 16);
const _: () = assert!(core::mem::align_of::<KernelStruct>() == 8);

Real Kernel Example: Binder ABI Compliance

From the Android Binder driver (actual kernel code):

// drivers/android/binder/defs.rs
#[repr(C)]
#[derive(Copy, Clone)]
pub(crate) struct BinderTransactionData(
    MaybeUninit<uapi::binder_transaction_data>
);

// SAFETY: Explicit FromBytes/AsBytes ensures binary compatibility
unsafe impl FromBytes for BinderTransactionData {}
unsafe impl AsBytes for BinderTransactionData {}

Why MaybeUninit? It preserves padding bytes to ensure bit-for-bit identical layout with C, including uninitialized padding. This is critical for userspace compatibility.

rustc’s ABI Stability Promise

From the Rust language specification:

#[repr(C)] Guarantee: Types marked with #[repr(C)] have the same layout as the corresponding C type, following the C ABI for the target platform. This guarantee is stable across Rust compiler versions.

Contrast with C:

Aspect C Rust
ABI specification Implicit, platform-dependent Explicit with extern "C"
Layout verification Runtime bugs if wrong Compile-time assert!
Padding control Implicit, error-prone MaybeUninit explicit
Cross-version stability Trust the developer Language specification

System Call Register Usage

The System V ABI specifies register usage for function calls. For system calls, Linux uses a modified System V convention:

System V function call (used by extern "C"):

Linux syscall (special case):

Rust respects both conventions:

// Regular C function - uses standard System V ABI
extern "C" fn regular_function(a: u64, b: u64) {
    // a in %rdi, b in %rsi
}

// System call wrapper - uses syscall convention
#[inline(always)]
unsafe fn syscall1(n: u64, arg1: u64) -> u64 {
    let ret: u64;
    core::arch::asm!(
        "syscall",
        in("rax") n,     // syscall number
        in("rdi") arg1,  // first argument
        lateout("rax") ret,
    );
    ret
}

Answer: Can Rust Compile to System V ABI?

Yes, rustc guarantees System V ABI compliance through:

  1. extern "C" - Explicitly uses platform C ABI (System V on x86-64 Linux)
  2. #[repr(C)] - Guarantees C-compatible data layout
  3. Compile-time verification - Size/alignment assertions catch ABI breaks
  4. Language specification - Stability across compiler versions

This is not a “best effort” - it’s a language-level guarantee backed by the Rust specification.


Question 1: Rust’s Userspace Interface Infrastructure

The uapi Crate: Userspace API Bindings

Rust provides a dedicated crate for userspace APIs. From the actual kernel source:

// rust/uapi/lib.rs (actual kernel code)
//! UAPI Bindings.
//!
//! Contains the bindings generated by `bindgen` for UAPI interfaces.
//!
//! This crate may be used directly by drivers that need to interact with
//! userspace APIs.

#![no_std]

// Auto-generated UAPI bindings
include!(concat!(env!("OBJTREE"), "/rust/uapi/uapi_generated.rs"));

Key insight: The kernel has a separate uapi crate specifically for userspace interfaces, distinct from internal kernel APIs.

ioctl Support in Rust

The kernel provides full ioctl support for Rust drivers:

// rust/kernel/ioctl.rs (actual kernel code)
//! `ioctl()` number definitions.
//!
//! C header: [`include/asm-generic/ioctl.h`](srctree/include/asm-generic/ioctl.h)

/// Build an ioctl number for a read-only ioctl.
#[inline(always)]
pub const fn _IOR<T>(ty: u32, nr: u32) -> u32 {
    _IOC(uapi::_IOC_READ, ty, nr, core::mem::size_of::<T>())
}

/// Build an ioctl number for a write-only ioctl.
#[inline(always)]
pub const fn _IOW<T>(ty: u32, nr: u32) -> u32 {
    _IOC(uapi::_IOC_WRITE, ty, nr, core::mem::size_of::<T>())
}

/// Build an ioctl number for a read-write ioctl.
#[inline(always)]
pub const fn _IOWR<T>(ty: u32, nr: u32) -> u32 {
    _IOC(
        uapi::_IOC_READ | uapi::_IOC_WRITE,
        ty,
        nr,
        core::mem::size_of::<T>(),
    )
}

This is identical to C’s ioctl macros, but with type safety.

Real Example: DRM Driver ioctl Interface

From the actual DRM subsystem Rust abstractions:

// rust/kernel/drm/ioctl.rs (actual kernel code)
//! DRM IOCTL definitions.

const BASE: u32 = uapi::DRM_IOCTL_BASE as u32;

/// Construct a DRM ioctl number with a read-write argument.
#[allow(non_snake_case)]
#[inline(always)]
pub const fn IOWR<T>(nr: u32) -> u32 {
    ioctl::_IOWR::<T>(BASE, nr)
}

/// Descriptor type for DRM ioctls.
pub type DrmIoctlDescriptor = bindings::drm_ioctl_desc;

// ioctl flags
pub const AUTH: u32 = bindings::drm_ioctl_flags_DRM_AUTH;
pub const MASTER: u32 = bindings::drm_ioctl_flags_DRM_MASTER;
pub const RENDER_ALLOW: u32 = bindings::drm_ioctl_flags_DRM_RENDER_ALLOW;

Usage in drivers:

// Declaring DRM ioctls in a Rust driver
kernel::declare_drm_ioctls! {
    (NOVA_GETPARAM, drm_nova_getparam, ioctl::RENDER_ALLOW, my_get_param_handler),
    (NOVA_GEM_CREATE, drm_nova_gem_create, ioctl::AUTH | ioctl::RENDER_ALLOW, gem_create),
    (NOVA_VM_BIND, drm_nova_vm_bind, ioctl::AUTH | ioctl::RENDER_ALLOW, vm_bind),
}

These ioctls are directly exposed to userspace - the same ABI as C drivers.

Real Example: Android Binder Userspace Protocol

The Android Binder driver (rewritten in Rust) exposes extensive userspace APIs:

// drivers/android/binder/defs.rs (actual kernel code)
use kernel::{
    transmute::{AsBytes, FromBytes},
    uapi::{self, *},
};

// Userspace protocol constants - MUST remain stable
pub_no_prefix!(
    binder_driver_return_protocol_,
    BR_TRANSACTION,
    BR_REPLY,
    BR_DEAD_REPLY,
    BR_FAILED_REPLY,
    BR_OK,
    BR_ERROR,
    BR_INCREFS,
    BR_ACQUIRE,
    BR_RELEASE,
    BR_DECREFS,
    BR_DEAD_BINDER,
    // ... 21 total protocol constants
);

pub_no_prefix!(
    binder_driver_command_protocol_,
    BC_TRANSACTION,
    BC_REPLY,
    BC_FREE_BUFFER,
    BC_INCREFS,
    BC_ACQUIRE,
    BC_RELEASE,
    BC_DECREFS,
    // ... 24 total command constants
);

// Userspace data structures - wrapped to preserve ABI
decl_wrapper!(BinderTransactionData, uapi::binder_transaction_data);
decl_wrapper!(BinderWriteRead, uapi::binder_write_read);
decl_wrapper!(BinderVersion, uapi::binder_version);
decl_wrapper!(FlatBinderObject, uapi::flat_binder_object);

Critical detail: These use MaybeUninit to preserve padding bytes, ensuring binary-identical ABI with C:

// Wrapper that preserves exact memory layout, including padding
#[derive(Copy, Clone)]
#[repr(transparent)]
pub(crate) struct BinderTransactionData(MaybeUninit<uapi::binder_transaction_data>);

// SAFETY: Explicit FromBytes/AsBytes implementation
unsafe impl FromBytes for BinderTransactionData {}
unsafe impl AsBytes for BinderTransactionData {}

Why this matters: Userspace code compiled against C headers sends exact same binary data to Rust driver.

Userspace Interface Summary

Interface Type Rust Support Example
ioctl ✅ Full support DRM drivers, Binder
/dev device nodes ✅ Via miscdevice/cdev Character devices
/sys (sysfs) ✅ Via kobject bindings Device attributes
/proc ✅ Via seq_file Process info
System calls ⚠️ Not yet (all syscalls are C) -
Netlink ✅ Via net subsystem Network configuration

Answer: Yes, Rust fully supports userspace interfaces through standard kernel mechanisms.

Question 2: Kernel Internal ABI Stability Policy

The Critical Distinction

Linux kernel has two completely different ABI policies:

┌─────────────────────────────────────────────────────┐
│                  USERSPACE                          │
│  (applications, libraries, tools)                   │
└─────────────────┬───────────────────────────────────┘
                  │
                  │  ← USERSPACE ABI (STABLE, SACRED)
                  │     System calls, ioctl, /proc, /sys
                  │     "WE DO NOT BREAK USERSPACE" - Linus
                  │
┌─────────────────┴───────────────────────────────────┐
│            LINUX KERNEL                             │
│  ┌─────────────────────────────────────────┐       │
│  │  Kernel Subsystems (VFS, MM, Net, etc)  │       │
│  └─────────────────┬───────────────────────┘       │
│                    │                                │
│                    │  ← INTERNAL API (UNSTABLE!)    │
│                    │     Can change anytime         │
│                    │     No backward compat         │
│  ┌─────────────────┴───────────────────────┐       │
│  │  Loadable Kernel Modules (.ko files)    │       │
│  │  (drivers, filesystems, etc)             │       │
│  └─────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────┘

Official Kernel Policy: Internal ABI is Unstable

From the Linux kernel documentation1:

The kernel does NOT have a stable internal API/ABI.

The kernel internal API can and does change at any time, for any reason.

In practice: If you compile a kernel module for Linux 6.5, it will not load on Linux 6.6 without recompilation.

Why Internal ABI is Unstable

Greg Kroah-Hartman explained this in his famous document:

Reasons for no internal ABI stability:

  1. Rapid evolution: Subsystems need freedom to refactor
  2. No binary modules: All modules must be GPL and recompilable
  3. Quality control: Forces out-of-tree drivers to stay updated
  4. Security: Allows fixing fundamental design flaws

The philosophy: “If your code is good enough, it should be in-tree. If it’s in-tree, recompilation is free.”

Userspace ABI: Absolute Stability

Linus Torvalds’ famous rule (paraphrased from countless LKML posts):

“WE DO NOT BREAK USERSPACE. EVER.”

If a kernel change breaks a working userspace application, that change will be reverted, no matter how “correct” it was.

From the official documentation2:

Stable interfaces:

  • System calls: Must never change semantics
  • /proc and /sys ABI: Guaranteed stable for at least 2 years
  • ioctl numbers: Never reused once defined
  • Binary formats (ELF, etc): Backward compatible

Real Example: ABI Stability Levels

From /Documentation/ABI/README3:

stable/     - Interfaces with guaranteed backward compatibility
              Examples: syscalls, core /proc entries

testing/    - Interfaces believed stable but not yet guaranteed
              May still change with warning

obsolete/   - Deprecated but still present interfaces
              Marked for removal but with migration period

removed/    - Historical record only

Answer: The kernel does not pursue internal ABI stability. Only userspace ABI is stable.

Question 3: Rust and Userspace ABI Stability

Current State: Rust Already Provides Stable Userspace ABI

Android Binder: Running on billions of devices with identical userspace ABI as C version.

// Same BINDER_WRITE_READ ioctl as C version
const BINDER_WRITE_READ: u32 = kernel::ioctl::_IOWR::<BinderWriteRead>(
    BINDER_TYPE as u32,
    1
);

// Userspace code using C headers sends exact same binary data

Verification: Android’s libbinder (C++ userspace library) works without modification with Rust kernel driver.

Why Rust is Actually Better for ABI Stability

Problem in C: Accidental ABI breakage

// C - easy to accidentally change ABI
struct binder_transaction_data {
    uint64_t cookie;
    uint32_t code;
    // Oops, developer adds field here - ABI BROKEN!
    uint32_t new_field;
    uint32_t flags;
};

Rust solution: Explicit versioning and #[repr(C)]

// Rust - ABI layout is explicit and checked
#[repr(C)]
pub struct binder_transaction_data {
    pub cookie: u64,
    pub code: u32,
    // Cannot add field here without explicit version bump
    pub flags: u32,
}

// Compile-time size check
const _: () = assert!(
    core::mem::size_of::<binder_transaction_data>() == 48
);

Rust’s #[repr(C)] Guarantees

From the Rust language specification:

#[repr(C)]
struct UserspaceFacingStruct {
    field1: u64,
    field2: u32,
}

Guarantees:

This is a language-level guarantee, not just convention.

Real Example: DRM Driver Backward Compatibility

From the Nova GPU driver (Rust):

// Must maintain compatibility with userspace mesa drivers
pub const DRM_NOVA_GEM_CREATE: u32 = drm::ioctl::IOWR::<drm_nova_gem_create>(0x00);
pub const DRM_NOVA_GEM_INFO: u32 = drm::ioctl::IOWR::<drm_nova_gem_info>(0x01);

// Once these ioctl numbers are released, they NEVER change
// Rust's type system helps prevent accidental changes:

#[repr(C)]
pub struct drm_nova_gem_create {
    pub size: u64,
    pub handle: u32,
    pub flags: u32,
}

// If someone tries to change this, compilation breaks due to size assertions

ABI Stability: Rust vs C Comparison

Aspect C Rust
Layout control Implicit, compiler-dependent #[repr(C)] explicit
Padding preservation Manual, error-prone MaybeUninit automatic
Size verification Manual BUILD_BUG_ON const _: assert!(size == X)
Breaking changes Silent, runtime failure Compile error
Versioning Manual, by convention Can be enforced by type system
Binary compatibility Trust the developer Compiler-verified

Will Rust Provide Critical Userspace ABI?

Already happening:

  1. Android Binder (IPC): Billions of devices
  2. GPU drivers (Nova): DRM userspace ABI
  3. Network PHY drivers: ethtool/netlink ABI
  4. Block devices: ioctl ABI
  5. Android ashmem (Memory Management): Android 16’s memory allocator

Case Study: ashmem - Rust in Memory Management Subsystem

Breaking news: Rust has already entered the memory management (mm) subsystem - earlier than most predictions suggested.

What is ashmem?

The Rust Rewrite (announced December 2025):

Why this matters:

  1. First mm subsystem component in Rust: Proves Rust can handle kernel memory management
  2. Production scale: Running on millions of devices in the wild
  3. Earlier than expected: Most predictions put mm subsystem adoption at 2028-2030
  4. Memory safety in memory management: The irony of using a memory-safe language to implement memory allocation is not lost - but highly beneficial

Technical details:

// Simplified example of ashmem's safety benefits
pub struct AshmemArea {
    size: usize,
    prot: u32,
    // Rust's ownership system prevents:
    // - Double-free of memory regions
    // - Use-after-free when area is unmapped
    // - Race conditions via type system
}

impl Drop for AshmemArea {
    fn drop(&mut self) {
        // Automatic cleanup - cannot forget to free
        // Guaranteed to run, unlike manual cleanup in C
    }
}

Security impact:

Memory management code is particularly prone to memory safety bugs:

Rust’s type system eliminates these at compile time for ashmem’s implementation.

References:

Coming soon (based on current development and LSF/MM/BPF 2026 discussions):

  1. File systems: VFS operations, mount options
  2. Network protocols: Socket options, packet formats
  3. More mm components: Additional memory allocators, page management experiments
  4. LSF/MM/BPF Summit (May 2026): Technical discussions on memory management subsystem improvements - likely including more Rust adoption plans

The Key Policy: Language-Agnostic ABI

Critical insight: The kernel’s ABI stability policy is language-agnostic.

From Linus Torvalds (summarized from various LKML posts):

“I don’t care if you write it in C, Rust, or assembly. If you break userspace, you broke the kernel.”

In practice:

Answer: Yes, Rust will be and already is used for userspace-facing features requiring ABI stability.

Practical Implications

For Rust Kernel Developers

Do:

Don’t:

For Userspace Developers

Good news: Nothing changes!

// Userspace C code (unchanged)
int fd = open("/dev/binder", O_RDWR);
struct binder_write_read bwr = { ... };
ioctl(fd, BINDER_WRITE_READ, &bwr);

Whether the kernel driver is C or Rust, this code works identically.

For Distribution Maintainers

Internal modules (out-of-tree):

Userspace applications:

Common Misconceptions

Myth 1: “Rust’s ABI is unstable, so it can’t be used for kernel interfaces”

Reality:

Myth 2: “Rust adds a new ABI to maintain”

Reality:

Myth 3: “Rust internal instability affects userspace”

Reality:

Myth 4: “Modules must be recompiled because of Rust”

Reality:

Conclusion

Summary of findings:

  1. Rust provides userspace interfaces through uapi crate, ioctl support, device nodes, sysfs, etc.

  2. Kernel internal ABI is NOT stable - modules must recompile for each kernel version (same as C)

  3. Userspace ABI IS stable - never breaks (same rule for C and Rust)

  4. Rust already provides critical userspace ABI - Android Binder on billions of devices, GPU drivers, network drivers

Key insight: The kernel’s ABI stability policy is orthogonal to the implementation language. Rust drivers must follow the same rules as C drivers:

Rust’s advantage: Better compile-time verification of ABI compatibility through #[repr(C)], size assertions, and type safety, reducing accidental ABI breaks.

References


中文版 / Chinese Version

Rust与Linux内核ABI稳定性:技术深度分析

摘要: Rust在Linux内核中提供用户空间接口吗?内核的ABI稳定性策略是什么?本文分析Rust驱动如何与用户空间交互,内部和外部ABI稳定性的关键区别,以及Android Binder和DRM驱动等生产代码的具体示例。

快速回答

问题1: Rust目前是否提供用户空间接口?是的。 Rust驱动已经通过ioctl、/dev节点、sysfs和其他标准机制暴露用户空间API。

问题2: 内核内部追求ABI稳定性吗?不。 内核内部API(模块和内核之间)明确不稳定。只有用户空间ABI是神圣的。

问题3: Rust是否会被用于提供需要ABI稳定性的用户空间功能?已经在发生。 Android Binder (Rust) 为数十亿设备提供关键的用户空间ABI。

深入探讨:系统调用ABI - 不可变的契约

在研究Rust的用户空间接口之前,让我们先了解用户空间ABI为何如此关键,通过查看系统调用层 - 最基础的用户空间接口。

神圣的系统调用ABI

Linux同时支持三种不同的系统调用机制以维持ABI兼容性:

机制 引入时间 指令 系统调用号 参数 状态
INT 0x80 Linux 1.0 (1994) int $0x80 %eax %ebx, %ecx, %edx, %esi, %edi, %ebp ✅ 仍支持(32位兼容)
SYSENTER Intel P6 (1995) sysenter %eax %ebx, %ecx, %edx, %esi, %edi, %ebp ✅ 仍支持(Intel 32位)
SYSCALL AMD K6 (1997) syscall %rax %rdi, %rsi, %rdx, %r10, %r8, %r9 ✅ 主要64位方法

所有三种都并行维护,以确保任何用户空间应用程序永不破坏。

实际内核实现

来自arch/x86/kernel/cpu/common.c(Linux内核源代码):

// syscall_init() - 在内核初始化期间调用
void syscall_init(void)
{
    /* 为用户/内核模式设置段选择子 */
    wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS);

    if (!cpu_feature_enabled(X86_FEATURE_FRED))
        idt_syscall_init();
}

static inline void idt_syscall_init(void)
{
    // 64位原生syscall入口
    wrmsrq(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

    // 32位兼容模式 - 必须维护旧ABI
    if (ia32_enabled()) {
        wrmsrq_cstar((unsigned long)entry_SYSCALL_compat);

        /* 为32位应用程序提供SYSENTER支持 */
        wrmsrq_safe(MSR_IA32_SYSENTER_CS, (u64)__KERNEL_CS);
        wrmsrq_safe(MSR_IA32_SYSENTER_ESP,
                    (unsigned long)(cpu_entry_stack(smp_processor_id()) + 1));
        wrmsrq_safe(MSR_IA32_SYSENTER_EIP, (u64)entry_SYSENTER_compat);
    }
}

这意味着什么: 1994年使用int $0x80编译的32位应用程序在运行在现代硬件上的2026 Linux内核上仍然可以工作

两个系统调用表

// 64位原生系统调用
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
    [0 ... __NR_syscall_max] = &__x64_sys_ni_syscall,
    #include <asm/syscalls_64.h>
};

// 32位兼容系统调用
const sys_call_ptr_t ia32_sys_call_table[__NR_ia32_syscall_max+1] = {
    [0 ... __NR_ia32_syscall_max] = &__ia32_sys_ni_syscall,
    #include <asm/syscalls_32.h>
};

关键洞察: Linux为32位和64位维护完全独立的系统调用表以确保ABI稳定性。32位表从未删除系统调用 - 只添加新的。

启动协议ABI - 连引导加载程序都有契约

来自Linux内核压缩引导加载程序(arch/x86/boot/compressed/head_64.S):

/*
 * 32位入口在0且是ABI所以不可变!
 * 这是压缩内核入口点。
 */
    .code32
SYM_FUNC_START(startup_32)

注释”ABI so immutable!”至关重要

启动协议规范Documentation/x86/boot.rst):

这是内部启动ABI - 与用户空间ABI不同,但同样不可变,因为外部工具(引导加载程序)依赖于它。

给Rust的教训

当Rust驱动提供用户空间接口时,它们继承这些相同的铁律:

C示例(传统):

// 用户空间永远不知道这从C变成了Rust
int fd = open("/dev/binder", O_RDWR);
ioctl(fd, BINDER_WRITE_READ, &bwr);  // ABI未改变

Rust实现(现代):

// 必须提供相同的ABI
const BINDER_WRITE_READ: u32 = kernel::ioctl::_IOWR::<BinderWriteRead>(
    BINDER_TYPE as u32,
    1  // ioctl编号 - 永不改变
);

ioctl编号、结构布局和语义都冻结在时间中 - 无论是用C还是Rust实现。


Rust的ABI保证:System V兼容性

在研究具体的用户空间接口之前,理解Rust如何保证与Linux在x86-64上使用的System V ABI兼容至关重要。

Rust符合System V ABI吗?

是的 - rustc通过语言特性明确保证System V ABI兼容性。

x86-64上的Linux内核使用System V AMD64 ABI来定义:

Rust提供多种机制来确保ABI兼容性:

ABI类型 Rust语法 x86-64 Linux行为 保证级别
Rust ABI extern "Rust" (默认) 未指定,可能改变 ❌ 不稳定
C ABI extern "C" System V AMD64 ABI 语言规范保证
System V extern "sysv64" System V AMD64 ABI 显式保证
数据布局 #[repr(C)] 匹配C结构体布局 编译器保证

编译器强制的ABI正确性

与C中ABI兼容性是隐式且未检查的不同,Rust使ABI契约显式并在编译时验证

// 显式C ABI - 编译器验证调用约定
#[no_mangle]
pub extern "C" fn kernel_function(arg: u64) -> i32 {
    // 函数使用System V调用约定:
    // - arg在%rdi寄存器中传递
    // - 返回值在%rax寄存器中
    // - 跨Rust编译器版本保证
    0
}

// 显式内存布局 - 编译器验证大小/对齐
#[repr(C)]
pub struct KernelStruct {
    field1: u64,  // 偏移0,8字节
    field2: u32,  // 偏移8,4字节
    field3: u32,  // 偏移12,4字节
}

// 编译时验证 - 如果布局改变则失败
const _: () = assert!(core::mem::size_of::<KernelStruct>() == 16);
const _: () = assert!(core::mem::align_of::<KernelStruct>() == 8);

实际内核示例:Binder ABI兼容性

来自Android Binder驱动(实际内核代码):

// drivers/android/binder/defs.rs
#[repr(C)]
#[derive(Copy, Clone)]
pub(crate) struct BinderTransactionData(
    MaybeUninit<uapi::binder_transaction_data>
);

// SAFETY: 显式FromBytes/AsBytes确保二进制兼容性
unsafe impl FromBytes for BinderTransactionData {}
unsafe impl AsBytes for BinderTransactionData {}

为什么使用MaybeUninit? 它保留填充字节以确保与C的逐位相同布局,包括未初始化的填充。这对用户空间兼容性至关重要。

rustc的ABI稳定性承诺

来自Rust语言规范:

#[repr(C)]保证: 用#[repr(C)]标记的类型与相应的C类型具有相同的布局,遵循目标平台的C ABI。这个保证在Rust编译器版本之间是稳定的

与C对比:

方面 C Rust
ABI规范 隐式,平台相关 显式使用extern "C"
布局验证 运行时bug 编译时assert!
填充控制 隐式,易出错 MaybeUninit显式
跨版本稳定性 信任开发者 语言规范

系统调用寄存器使用

System V ABI指定函数调用的寄存器使用。对于系统调用,Linux使用修改过的System V约定:

System V函数调用extern "C"使用):

Linux syscall(特殊情况):

Rust尊重两种约定:

// 常规C函数 - 使用标准System V ABI
extern "C" fn regular_function(a: u64, b: u64) {
    // a在%rdi, b在%rsi
}

// 系统调用包装器 - 使用syscall约定
#[inline(always)]
unsafe fn syscall1(n: u64, arg1: u64) -> u64 {
    let ret: u64;
    core::arch::asm!(
        "syscall",
        in("rax") n,     // 系统调用号
        in("rdi") arg1,  // 第一个参数
        lateout("rax") ret,
    );
    ret
}

答案:Rust能编译成符合System V ABI的代码吗?

是的,rustc通过以下方式保证System V ABI兼容性:

  1. extern "C" - 显式使用平台C ABI(x86-64 Linux上是System V)
  2. #[repr(C)] - 保证C兼容的数据布局
  3. 编译时验证 - 大小/对齐断言捕获ABI破坏
  4. 语言规范 - 跨编译器版本的稳定性

这不是”尽力而为” - 这是由Rust规范支持的语言级保证


问题1:Rust的用户空间接口基础设施

uapi Crate: 用户空间API绑定

Rust为用户空间API提供了专门的crate。来自实际内核源代码:

// rust/uapi/lib.rs (实际内核代码)
//! UAPI绑定。
//!
//! 包含bindgen为UAPI接口生成的绑定。
//!
//! 这个crate可以被需要与用户空间API交互的驱动直接使用。

#![no_std]

// 自动生成的UAPI绑定
include!(concat!(env!("OBJTREE"), "/rust/uapi/uapi_generated.rs"));

关键洞察: 内核有单独的uapi crate专门用于用户空间接口,与内部内核API分离。

Rust中的ioctl支持

内核为Rust驱动提供完整的ioctl支持:

// rust/kernel/ioctl.rs (实际内核代码)
//! `ioctl()`编号定义。

/// 为只读ioctl构建ioctl编号
#[inline(always)]
pub const fn _IOR<T>(ty: u32, nr: u32) -> u32 {
    _IOC(uapi::_IOC_READ, ty, nr, core::mem::size_of::<T>())
}

/// 为只写ioctl构建ioctl编号
#[inline(always)]
pub const fn _IOW<T>(ty: u32, nr: u32) -> u32 {
    _IOC(uapi::_IOC_WRITE, ty, nr, core::mem::size_of::<T>())
}

/// 为读写ioctl构建ioctl编号
#[inline(always)]
pub const fn _IOWR<T>(ty: u32, nr: u32) -> u32 {
    _IOC(
        uapi::_IOC_READ | uapi::_IOC_WRITE,
        ty,
        nr,
        core::mem::size_of::<T>(),
    )
}

这与C的ioctl宏完全相同,但具有类型安全。

实际例子:Android Binder用户空间协议

Android Binder驱动(用Rust重写)暴露了广泛的用户空间API:

// drivers/android/binder/defs.rs (实际内核代码)
use kernel::uapi::{self, *};

// 用户空间协议常量 - 必须保持稳定
pub_no_prefix!(
    binder_driver_return_protocol_,
    BR_TRANSACTION,
    BR_REPLY,
    BR_DEAD_REPLY,
    BR_OK,
    BR_ERROR,
    // ... 21个总协议常量
);

// 用户空间数据结构 - 包装以保持ABI
decl_wrapper!(BinderTransactionData, uapi::binder_transaction_data);
decl_wrapper!(BinderWriteRead, uapi::binder_write_read);
decl_wrapper!(BinderVersion, uapi::binder_version);

关键细节: 这些使用MaybeUninit保留填充字节,确保与C的二进制相同ABI:

// 保留确切内存布局的包装器,包括填充
#[derive(Copy, Clone)]
#[repr(transparent)]
pub(crate) struct BinderTransactionData(MaybeUninit<uapi::binder_transaction_data>);

// SAFETY: 显式FromBytes/AsBytes实现
unsafe impl FromBytes for BinderTransactionData {}
unsafe impl AsBytes for BinderTransactionData {}

为什么重要: 针对C头文件编译的用户空间代码向Rust驱动发送完全相同的二进制数据

用户空间接口总结

接口类型 Rust支持 示例
ioctl ✅ 完全支持 DRM驱动, Binder
/dev设备节点 ✅ 通过miscdevice/cdev 字符设备
/sys (sysfs) ✅ 通过kobject绑定 设备属性
/proc ✅ 通过seq_file 进程信息
系统调用 ⚠️ 尚未(所有syscall都是C) -
Netlink ✅ 通过net子系统 网络配置

答案: 是的,Rust通过标准内核机制完全支持用户空间接口。

问题2:内核内部ABI稳定性策略

关键区别

Linux内核有两种完全不同的ABI策略

┌─────────────────────────────────────────────────────┐
│                  用户空间                            │
│  (应用程序、库、工具)                                │
└─────────────────┬───────────────────────────────────┘
                  │
                  │  ← 用户空间ABI (稳定、神圣)
                  │     系统调用、ioctl、/proc、/sys
                  │     "我们不破坏用户空间" - Linus
                  │
┌─────────────────┴───────────────────────────────────┐
│            LINUX内核                                 │
│  ┌─────────────────────────────────────────┐       │
│  │  内核子系统 (VFS, MM, Net等)            │       │
│  └─────────────────┬───────────────────────┘       │
│                    │                                │
│                    │  ← 内部API (不稳定!)           │
│                    │     随时可以改变                │
│                    │     无向后兼容                  │
│  ┌─────────────────┴───────────────────────┐       │
│  │  可加载内核模块 (.ko文件)                │       │
│  │  (驱动、文件系统等)                      │       │
│  └─────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────┘

官方内核策略:内部ABI不稳定

来自Linux内核文档1

内核没有稳定的内部API/ABI。

内核内部API可以而且确实随时改变,出于任何原因。

实践中: 如果你为Linux 6.5编译内核模块,它在Linux 6.6上将无法加载,除非重新编译。

为什么内部ABI不稳定

Greg Kroah-Hartman在他著名的文档中解释了这一点:

没有内部ABI稳定性的原因:

  1. 快速演进: 子系统需要重构的自由
  2. 无二进制模块: 所有模块必须是GPL且可重新编译
  3. 质量控制: 强制树外驱动保持更新
  4. 安全性: 允许修复根本性设计缺陷

哲学: “如果你的代码足够好,它应该在树内。如果在树内,重新编译是免费的。”

用户空间ABI:绝对稳定

Linus Torvalds的著名规则(从无数LKML帖子中概括):

“我们不破坏用户空间。永远。”

如果内核更改破坏了正常工作的用户空间应用程序,该更改将被回退,无论它多么”正确”。

来自官方文档2

稳定接口:

  • 系统调用: 绝不能改变语义
  • /proc和/sys ABI: 保证至少2年稳定
  • ioctl编号: 一旦定义就永不重用
  • 二进制格式 (ELF等): 向后兼容

答案: 内核不追求内部ABI稳定性。只有用户空间ABI是稳定的。

问题3:Rust与用户空间ABI稳定性

当前状态:Rust已经提供稳定的用户空间ABI

Android Binder: 运行在数十亿设备上,与C版本具有相同的用户空间ABI

// 与C版本相同的BINDER_WRITE_READ ioctl
const BINDER_WRITE_READ: u32 = kernel::ioctl::_IOWR::<BinderWriteRead>(
    BINDER_TYPE as u32,
    1
);

// 使用C头文件的用户空间代码发送完全相同的二进制数据

验证: Android的libbinder(C++用户空间库)与Rust内核驱动无需修改即可工作。

为什么Rust实际上更适合ABI稳定性

C中的问题: 意外的ABI破坏

// C - 容易意外改变ABI
struct binder_transaction_data {
    uint64_t cookie;
    uint32_t code;
    // 糟糕,开发者在这里添加字段 - ABI破坏了!
    uint32_t new_field;
    uint32_t flags;
};

Rust解决方案: 显式版本控制和#[repr(C)]

// Rust - ABI布局是显式的并经过检查
#[repr(C)]
pub struct binder_transaction_data {
    pub cookie: u64,
    pub code: u32,
    // 不能在这里添加字段,除非显式版本升级
    pub flags: u32,
}

// 编译时大小检查
const _: () = assert!(
    core::mem::size_of::<binder_transaction_data>() == 48
);

Rust的#[repr(C)]保证

从Rust语言规范:

#[repr(C)]
struct UserspaceFacingStruct {
    field1: u64,
    field2: u32,
}

保证:

这是语言级别的保证,不仅仅是约定。

ABI稳定性:Rust vs C对比

方面 C Rust
布局控制 隐式,编译器依赖 #[repr(C)]显式
填充保留 手动,易出错 MaybeUninit自动
大小验证 手动BUILD_BUG_ON const _: assert!(size == X)
破坏性更改 静默,运行时失败 编译错误
版本控制 手动,按约定 可由类型系统强制
二进制兼容性 信任开发者 编译器验证

Rust会提供关键的用户空间ABI吗?

已经在发生:

  1. Android Binder (IPC): 数十亿设备
  2. GPU驱动 (Nova): DRM用户空间ABI
  3. 网络PHY驱动: ethtool/netlink ABI
  4. 块设备: ioctl ABI
  5. Android ashmem (内存管理): Android 16的内存分配器

案例研究:ashmem - Rust进入内存管理子系统

重大新闻: Rust已经进入内存管理(mm)子系统 - 比大多数预测都要早。

什么是ashmem?

Rust重写 (2025年12月宣布):

为什么重要:

  1. mm子系统中的第一个Rust组件: 证明Rust可以处理内核内存管理
  2. 生产规模: 在数百万设备上运行
  3. 比预期更早: 大多数预测将mm子系统采用时间定在2028-2030年
  4. 内存管理中的内存安全: 使用内存安全语言实现内存分配的讽刺意味不言而喻 - 但非常有益

技术细节:

// ashmem安全优势的简化示例
pub struct AshmemArea {
    size: usize,
    prot: u32,
    // Rust的所有权系统防止:
    // - 内存区域的double-free
    // - 区域取消映射时的use-after-free
    // - 通过类型系统防止竞态条件
}

impl Drop for AshmemArea {
    fn drop(&mut self) {
        // 自动清理 - 不会忘记释放
        // 保证运行,不像C中的手动清理
    }
}

安全影响:

内存管理代码特别容易出现内存安全bug:

Rust的类型系统在编译时消除了ashmem实现中的这些问题。

参考资料:

即将推出 (基于当前开发和LSF/MM/BPF 2026讨论):

  1. 文件系统: VFS操作,挂载选项
  2. 网络协议: Socket选项,数据包格式
  3. 更多mm组件: 额外的内存分配器,页面管理实验
  4. LSF/MM/BPF峰会 (2026年5月): 关于内存管理子系统改进的技术讨论 - 可能包括更多Rust采用计划

关键策略:与语言无关的ABI

关键洞察: 内核的ABI稳定性策略是与语言无关的

来自Linus Torvalds(从各种LKML帖子总结):

“我不在乎你用C、Rust还是汇编编写。如果你破坏了用户空间,你就破坏了内核。”

实践中:

答案: 是的,Rust将会并且已经被用于需要ABI稳定性的用户空间功能。

实际影响

对Rust内核开发者

要做:

不要做:

对用户空间开发者

好消息: 什么都不变!

// 用户空间C代码(不变)
int fd = open("/dev/binder", O_RDWR);
struct binder_write_read bwr = { ... };
ioctl(fd, BINDER_WRITE_READ, &bwr);

无论内核驱动是C还是Rust,这段代码工作完全相同

常见误解

误解1:”Rust的ABI不稳定,所以不能用于内核接口”

现实:

误解2:”Rust添加了需要维护的新ABI”

现实:

误解3:”Rust内部不稳定性影响用户空间”

现实:

误解4:”因为Rust模块必须重新编译”

现实:

结论

发现总结:

  1. Rust通过uapi crate、ioctl支持、设备节点、sysfs等提供用户空间接口

  2. 内核内部ABI不稳定 - 模块必须为每个内核版本重新编译(与C相同)

  3. 用户空间ABI是稳定的 - 永不破坏(C和Rust规则相同)

  4. Rust已经提供关键的用户空间ABI - 数十亿设备上的Android Binder,GPU驱动,网络驱动

关键洞察: 内核的ABI稳定性策略与实现语言正交。Rust驱动必须遵循与C驱动相同的规则:

Rust的优势: 通过#[repr(C)]、大小断言和类型安全更好地编译时验证ABI兼容性,减少意外的ABI破坏。

  1. Linux Kernel Stable API Nonsense - Greg Kroah-Hartman’s explanation of why internal kernel API is unstable  2

  2. Linux ABI description - Official kernel documentation on ABI stability levels  2

  3. ABI README - Documentation of ABI stability categories 

My Github Page: https://github.com/liweinan

Powered by Jekyll and Theme by solid

If you have any question want to ask or find bugs regarding with my blog posts, please report it here:
https://github.com/liweinan/liweinan.github.io/issues