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.
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).
| 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 |
This trips up a lot of people. The names sound similar but they mean different things.
| 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.
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.
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 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
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.
You might be thinking — when would I actually use this in a real project? Here are some concrete scenarios:
| 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)
*/
There are three ways to get the same information. It helps to understand which maps to which:
| 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 utsnamewith 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_SOURCEbefore including headers - Parse
releaseto do kernel version checks:sscanf(release, "%d.%d.%d", ...) - All fields except
machinehave equivalent/proc/sys/kernel/files - The
uname -acommand internally calls this same syscall
- 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. - 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 thepscommand. - 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). Usereadlink()to read each symlink in the fd directory. Hint: you will needopendir(),readdir(), andreadlink(). - 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”. - Write a small shell script (not C this time) that reads
/proc/meminfo, extractsMemTotalandMemAvailable, calculates the percentage of memory currently in use, and prints a warning if it exceeds 80%.
