The uname() System Call

 

 

 

 

The uname() System Call
Ask the kernel: who are you, where are you running, and what version are you? — All from a single C function call
Part 2
of 2
uname()
Syscall
C
Code Examples

Why Would You Ever Need uname()?

Picture this: you are writing a system utility — maybe a diagnostic tool, a kernel module loader, or an installation script. Before doing anything, you want to check: is this a Linux kernel or something else? What version? Is this an ARM machine or x86? What is the hostname of this node?

You could parse /proc/version or read /etc/os-release, but those are files — they need open/read/parse. The uname() system call gives you all of that in one shot, directly from the kernel, into a struct. No file I/O, no parsing, no guessing.

You have actually already used this information without realising it. Every time you run uname -a in the terminal, that command is calling this exact syscall internally.

Topics Covered in This Post
uname() syscall utsname struct sysname field nodename / hostname release and version machine architecture domainname NIS /proc/sys/kernel/osrelease sethostname() C code example

Section 1 — What Is uname() and How Do You Call It?

The uname() function is one of the simplest system calls you will encounter. Its signature is:

#include <sys/utsname.h>

int uname(struct utsname *utsbuf);
/* Returns 0 on success, -1 on error (sets errno) */

You allocate a struct utsname, pass its address to uname(), and when it returns, the struct is filled with information the kernel knows about itself. That is it. No complex setup, no permissions needed — any process can call this.

The utsname struct itself looks like this:

#define _UTSNAME_LENGTH 65

struct utsname {
    char sysname[65];    /* Name of the OS implementation      */
    char nodename[65];   /* Hostname of this machine            */
    char release[65];    /* Kernel release level (e.g. 6.5.0)  */
    char version[65];    /* Kernel version string (build info)  */
    char machine[65];    /* Hardware type (e.g. x86_64, aarch64)*/

    /* Linux-specific — only available when _GNU_SOURCE is defined */
    char domainname[65]; /* NIS domain name of this host        */
};

Each field is 65 bytes — 64 characters plus a null terminator. On other UNIX systems the sizes vary (some as short as 9 bytes, some as long as 257), so if you are writing portable code, do not hardcode 65 — use sizeof(uts.sysname).

utsname Struct — Fields and What They Contain (Typical Linux Example)
Field Example Value Who Sets It What It Means
sysname Linux Kernel (fixed at compile time) The OS name. Always “Linux” on Linux systems
nodename ravi-laptop Set by sethostname() syscall The machine’s hostname — what you see in your shell prompt
release 6.5.0-45-generic Kernel (set at compile time) The kernel release version. Same as uname -r
version #45-Ubuntu SMP Thu… Kernel (set at compile time) Extra build info — build number, SMP flag, date, compiler
machine x86_64 Kernel (set at compile time) CPU architecture. Could be x86_64, aarch64, armv7l, etc.
domainname (none) Set by setdomainname() syscall NIS domain name — usually empty on standalone machines

Section 2 — The Difference Between “release” and “version” (They’re Not the Same)

This trips up a lot of people. The names sound similar but they mean different things.

release vs version: What Each One Actually Shows
Field Example Output Equivalent Command
release 6.5.0-45-generic uname -r
version #45-Ubuntu SMP Thu Sep 21 15:00:00 UTC 2023 uname -v

Think of it this way: release is the actual kernel version number — 6.5.0. The -45-generic suffix is the Ubuntu packaging revision. version is the build identifier — it tells you when this kernel binary was compiled and which patches were applied. The #45 at the start is the kernel build count.

In practice, if you are writing code to check “is this kernel at least version 5.4?”, you parse release. If you want to log detailed kernel build info in a bug report, you log both.

Section 3 — nodename and domainname: Hostname and NIS Domain

The nodename field is the machine’s hostname — the name your system uses to identify itself on the network. This is what you see in your shell prompt (the part before the $ or #). It is set by the sethostname() system call and can also be viewed and changed using the hostname command:

hostname             # view current hostname
sudo hostname newname  # change hostname temporarily (lost on reboot)
cat /proc/sys/kernel/hostname  # same as running hostname command

The domainname field is the NIS (Network Information Service) domain name — not the DNS domain name. NIS is an old network directory service from Sun Microsystems (think LDAP’s predecessor). Most standalone workstations and personal Linux machines have this set to (none) or empty. You would see it populated in old enterprise environments that use NIS for centralised user account management.

⚠️ NIS domainname ≠ DNS domain name

If your machine is ravi-laptop.example.com in DNS, the domainname field returned by uname() will NOT be example.com. That is a DNS domain. The domainname in utsname is specifically the NIS domain. These are completely separate things. Do not confuse them.

Both hostname and NIS domain name are normally set during system boot by startup scripts. You rarely need to call sethostname() or setdomainname() directly in application code. These calls require root privilege.

Equivalent /proc Files for utsname Fields

The kernel also exposes most of this data through /proc, if you prefer reading files:

utsname Fields ↔ Equivalent /proc Paths
utsname field Equivalent /proc path Notes
sysname /proc/sys/kernel/ostype Always “Linux”
release /proc/sys/kernel/osrelease Same as uname -r
version /proc/sys/kernel/version Build number + date
nodename /proc/sys/kernel/hostname Writable (root only)
domainname /proc/sys/kernel/domainname NIS domain, usually “(none)”
(all combined) /proc/version sysname + release + version + gcc compiler info in one line
cat /proc/version
# Linux version 6.5.0-45-generic (buildd@lcy02-amd64-059)
# (gcc version 13.2.0, GNU ld version 2.41) #45-Ubuntu SMP Thu Oct  3 12:12:12 UTC 2023

cat /proc/sys/kernel/osrelease
# 6.5.0-45-generic

cat /proc/sys/kernel/hostname
# ravi-laptop

Section 4 — Complete C Program Using uname()

Here is a full, working C program that calls uname() and prints all fields. I have also added a kernel version comparison feature — something you would actually use in real-world code (e.g., checking if the running kernel supports a feature you need):

/*
 * system_info.c
 * Uses uname() to print system identification info
 * Also parses the kernel version from "release" for comparison
 *
 * Compile: gcc -o system_info system_info.c
 * Run:     ./system_info
 */

#define _GNU_SOURCE        /* Needed to expose domainname in utsname */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>

/* Parse "major.minor.patch" from a release string like "6.5.0-45-generic" */
static void parse_kernel_version(const char *release,
                                  int *major, int *minor, int *patch)
{
    *major = *minor = *patch = 0;
    sscanf(release, "%d.%d.%d", major, minor, patch);
}

int main(void)
{
    struct utsname uts;

    if (uname(&uts) == -1) {
        perror("uname");
        exit(EXIT_FAILURE);
    }

    printf("===== System Identification (via uname) =====\n\n");
    printf("OS Name        : %s\n",  uts.sysname);
    printf("Hostname       : %s\n",  uts.nodename);
    printf("Kernel Release : %s\n",  uts.release);
    printf("Kernel Version : %s\n",  uts.version);
    printf("Architecture   : %s\n",  uts.machine);

#ifdef _GNU_SOURCE
    printf("NIS Domain     : %s\n",  uts.domainname);
#endif

    /* Example: Check if kernel is at least 5.4.0 */
    int major, minor, patch;
    parse_kernel_version(uts.release, &major, &minor, &patch);

    printf("\n===== Kernel Version Check =====\n\n");
    printf("Parsed version : %d.%d.%d\n", major, minor, patch);

    int required_major = 5, required_minor = 4;
    if (major > required_major ||
        (major == required_major && minor >= required_minor)) {
        printf("Status : Kernel >= %d.%d — Required features available\n",
               required_major, required_minor);
    } else {
        printf("Status : Kernel < %d.%d — Some features may be missing\n",
               required_major, required_minor);
    }

    /* Architecture check — useful for cross-compiled deployments */
    printf("\n===== Architecture Check =====\n\n");
    if (strcmp(uts.machine, "x86_64") == 0)
        printf("Platform : 64-bit Intel/AMD\n");
    else if (strncmp(uts.machine, "arm", 3) == 0)
        printf("Platform : ARM (32-bit)\n");
    else if (strcmp(uts.machine, "aarch64") == 0)
        printf("Platform : ARM64 (64-bit)\n");
    else
        printf("Platform : %s (other)\n", uts.machine);

    return 0;
}

Sample output on an Ubuntu 22.04 x86_64 machine:

===== System Identification (via uname) =====

OS Name        : Linux
Hostname       : ravi-laptop
Kernel Release : 6.5.0-45-generic
Kernel Version : #45-Ubuntu SMP Thu Oct  3 12:12:12 UTC 2023
Architecture   : x86_64
NIS Domain     : (none)

===== Kernel Version Check =====

Parsed version : 6.5.0
Status : Kernel >= 5.4 — Required features available

===== Architecture Check =====

Platform : 64-bit Intel/AMD

The version parsing trick with sscanf(release, "%d.%d.%d", ...) is something you will see in real embedded and system software. For example, BlueZ (the Linux Bluetooth stack) does version checks like this internally. If you are writing a driver or module that requires a specific kernel feature, you check the release string at startup and bail out with a clear error if the kernel is too old.

Section 5 — Real-World Use Cases for uname()

You might be thinking — when would I actually use this in a real project? Here are some concrete scenarios:

Practical Scenarios Where uname() Is Useful
Scenario What You Check Why
Kernel module / driver uts.release Refuse to run on kernels that lack your feature
Cross-compiled binary installer uts.machine Install the right .so library for x86_64 vs aarch64
Diagnostics / crash reporter All fields Include kernel version in bug reports automatically
Embedded system health check uts.nodename Tag log entries with device hostname (useful in IoT fleets)
Containerised service uts.sysname Confirm this is actually Linux before using Linux-specific APIs

Here is a very small but practical snippet — imagine you are writing a BLE daemon in C and you want to log a startup banner with system info:

/*
 * Startup banner function — call this at daemon init
 * Logs kernel version and hostname for context
 */
void log_startup_banner(void)
{
    struct utsname uts;

    if (uname(&uts) == 0) {
        printf("[INFO] BLE Daemon starting on %s (%s %s %s)\n",
               uts.nodename,
               uts.sysname,
               uts.release,
               uts.machine);
    } else {
        printf("[INFO] BLE Daemon starting (kernel info unavailable)\n");
    }
}

/* Output example:
   [INFO] BLE Daemon starting on gateway-node-01 (Linux 6.1.0 aarch64)
*/

Section 6 — uname Command vs uname() Syscall vs /proc Files

There are three ways to get the same information. It helps to understand which maps to which:

Three Ways to Get System Info — How They Relate
Shell Command C Equivalent /proc Equivalent
uname -s uts.sysname /proc/sys/kernel/ostype
uname -n uts.nodename /proc/sys/kernel/hostname
uname -r uts.release /proc/sys/kernel/osrelease
uname -v uts.version /proc/sys/kernel/version
uname -m uts.machine (no direct /proc equivalent)
uname -a all fields combined /proc/version (partial)

The uname command you run in the shell is literally a wrapper that calls the uname() syscall and formats the output. So if you call uname() in your C program, you get the exact same data you see when you type uname -a — but without having to fork a process or parse text output.

Quick Recap — What You Learned in Part 2

  • uname() fills a struct utsname with kernel identity information in one call
  • You need #include <sys/utsname.h> to use it
  • sysname = OS name (always “Linux”), release = version number (e.g. 6.5.0), version = build info
  • nodename = hostname, set by sethostname(), also readable from /proc/sys/kernel/hostname
  • machine = CPU architecture (x86_64, aarch64, armv7l etc.) — very useful in cross-compile scenarios
  • domainname is the NIS domain, NOT the DNS domain — usually empty on most machines
  • To get domainname, define _GNU_SOURCE before including headers
  • Parse release to do kernel version checks: sscanf(release, "%d.%d.%d", ...)
  • All fields except machine have equivalent /proc/sys/kernel/ files
  • The uname -a command internally calls this same syscall

Practice Exercises — Try These Yourself
  1. Write a C program that takes a PID as a command-line argument, opens /proc/PID/status, and prints the process Name, State, PPid, and VmRSS. Make sure you handle the case where the process exits before you can open the file.
  2. Scan all directories under /proc/ that have numeric names (those are process directories). For each one, read the Name field from its status file. Print a table of all running process names and their PIDs. This is essentially building a mini version of the ps command.
  3. Write a program that walks all /proc/PID/fd/ directories and finds which process has a specific file open (pass the filename as a command-line argument). Use readlink() to read each symlink in the fd directory. Hint: you will need opendir(), readdir(), and readlink().
  4. Use uname() to detect the CPU architecture and print a message saying whether your machine is 32-bit ARM, 64-bit ARM, 64-bit x86, or something else. Then extend it to also print whether the kernel is a debug build by checking if the version string contains “debug”.
  5. Write a small shell script (not C this time) that reads /proc/meminfo, extracts MemTotal and MemAvailable, calculates the percentage of memory currently in use, and prints a warning if it exceeds 80%.

That Wraps Up System and Process Information
You now know how to read /proc for process and system data, and how to use uname() to query kernel identity — two tools every Linux programmer should have in their toolkit.

Leave a Reply

Your email address will not be published. Required fields are marked *