rubyguides

Kernel#syscall

syscall(number, *args) -> integer or nil

The syscall method provides direct access to the operating system kernel through system call numbers. It’s extremely low-level and rarely needed in normal Ruby development.

How it works

Each operating system has numbered system calls (like read, write, fork, etc.). The syscall method invokes these directly by number, bypassing Ruby’s normal IO abstractions.

That directness is the main reason syscall exists at all. It gives you a path into the kernel when you need to speak in the operating system’s native terms, but it also means the caller has to take responsibility for the details. In normal Ruby code, that tradeoff is usually too steep unless the program really is doing systems work.

Common system calls (Linux x86_64)

# These numbers vary by OS and architecture!
# Linux x86_64 examples:

# sys_read (0)
syscall(0, 0, "buffer", 100)  # Read from fd 0 (stdin)

# sys_write (1)  
syscall(1, 1, "hello\n", 6)   # Write to fd 1 (stdout)

# sys_exit (60)
syscall(60, 0)                # Exit with code 0

# sys_getpid (20)
syscall(20)                   # Get process ID

These examples are intentionally low-level so you can see how the numeric interface works. The numbers are not portable, but the pattern is the same everywhere: pass the call number first, then the raw arguments the kernel expects. That is why the method is so compact and so easy to misuse at the same time.

Practical (but rare) examples

Direct kernel access

# Get current time via gettimeofday (requires C struct packing)
# This is extremely platform-specific

# Get current process ID
pid = syscall(20)  # __NR_getpid on Linux x86_64

Direct access like this is usually reserved for experiments, wrappers, or code that mirrors a specific operating system feature very closely. The example is small on purpose because the real complexity lives in the platform details around it. If you need this kind of access, keeping the wrapper tiny will make future debugging much easier.

Low-level file operations

# Direct read system call
buffer = " " * 100
bytes_read = syscall(0, 0, buffer, 100)  # read(0, buffer, 100)
puts buffer[0, bytes_read] if bytes_read > 0

Reading directly into a buffer is a classic low-level pattern, but it is also a reminder that the caller must manage the data carefully. You have to know the file descriptor, the buffer size, and what the return value means. That extra responsibility is why higher-level IO methods are usually a better default.

Important caveats

Platform Dependence

# System call numbers DIFFER between:
# - Linux vs BSD vs macOS
# - x86 vs ARM
# - 32-bit vs 64-bit

# NEVER hardcode syscall numbers in portable code!

Portability is the biggest warning sign here. A syscall number that works on one kernel or architecture can be wrong on another, and the failure mode can be confusing if you are not expecting it. For that reason, syscall should stay close to code that is already platform-specific.

Better alternatives

# Instead of syscall, use:
File.read("file.txt")           # Instead of sys_read
$stdout.write("hi")            # Instead of sys_write
Process.pid                     # Instead of sys_getpid
Dir.entries(".")               # Instead of sys_getdents

The alternatives are usually easier to test, easier to read, and far more likely to behave the same way across machines. In ordinary application code, those qualities matter more than the tiny amount of extra control that syscall provides.

Security concerns

# syscalls operate at kernel level
# Wrong arguments can cause:
# - Segmentation faults
# - Kernel panics (rare)
# - Undefined behavior

# Never use syscall with untrusted input!

The security warning is serious because a bad call can do far more than raise a friendly Ruby exception. Once you are at the kernel boundary, mistakes can turn into crashes or undefined behavior, so this is a place where input must be trusted and tightly controlled.

When you might use it

  • Implementing system call wrappers
  • Writing low-level systems software
  • Performance-critical code (marginal benefit)
  • Interfacing with custom kernel modules

These are narrow use cases by design. They describe projects that already need close contact with the operating system, so the extra control from syscall is worth the extra care. For most app code, the safer Ruby APIs are still the better default.

Example: exit

# Exit system call
syscall(60, 0)  # Same as exit(0)

# Exit with error
syscall(60, 1)  # Same as exit(1)

This exit example is useful because it shows the method doing a complete action without any wrapper logic around it. That makes the low-level nature of the call obvious, and it also makes the portability warning above feel more concrete.

Warning: This method is advanced and rarely needed. Prefer Ruby’s built-in methods for any practical use case.

Practical Guidance

syscall is best treated as a low-level escape hatch. It can be useful when writing wrappers, exploring operating system behavior, or working inside code that already depends on platform details. For normal application work, the higher-level Ruby APIs are easier to test and much easier to move between systems.

If you do use it, keep the call sites small and well documented. The syscall number, argument order, and return value can vary by platform, so a tiny wrapper method is usually safer than scattering raw numbers through the codebase.

See Also