Loading content...
Operating systems maintain vast amounts of state—the current time, system configuration, resource usage statistics, process attributes, and kernel parameters. Programs need to query this state for timestamps, to configure behavior based on system capabilities, to implement resource monitoring, and to diagnose problems. They also occasionally need to modify this state: setting the system clock, adjusting process attributes, or tuning kernel parameters.
Information maintenance system calls form the fourth major category in our taxonomy. Unlike process control, file management, or device management, these calls don't create or move data in the traditional sense. Instead, they transfer metadata—information about the system and its components. This category is diverse, encompassing everything from nanosecond-precision time queries to reading entire kernel data structures.
While individually simple, these system calls collectively form the foundation for system administration, monitoring, debugging, and adaptive behavior. Understanding them reveals how programs become aware of their environment and how administrators control system behavior.
By the end of this page, you will understand how to query and manipulate time with high precision, how to retrieve and modify process attributes, how to access system-wide information through uname and sysconf, and how the /proc and /sys virtual filesystems expose rich kernel state. You'll grasp how programs adapt to their environment and how system state becomes transparent to user space.
Time is fundamental to computing: logging, scheduling, performance measurement, synchronization protocols, cache expiration, and cryptographic certificate validation all depend on accurate time. The operating system provides multiple time-related system calls with varying precision, semantics, and use cases.
Clock Types and Concepts
Before examining system calls, we must understand the different "clocks" available:
CLOCK_REALTIME reflects what time it is; CLOCK_MONOTONIC reflects how much time has passed.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
#include <stdio.h>#include <time.h>#include <sys/time.h>#include <sys/times.h>#include <unistd.h> void demonstrate_time_functions() { // ======================================== // time() - Seconds since epoch (low precision) // ======================================== time_t now = time(NULL); printf("time(): %ld (seconds since 1970-01-01 00:00:00 UTC)\n", now); // Convert to human-readable struct tm *local = localtime(&now); char buffer[80]; strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S %Z", local); printf("Local time: %s\n", buffer); // ======================================== // gettimeofday() - Microsecond precision (legacy) // ======================================== struct timeval tv; gettimeofday(&tv, NULL); printf("gettimeofday(): %ld.%06ld seconds\n", tv.tv_sec, tv.tv_usec); // ======================================== // clock_gettime() - Nanosecond precision (modern) // ======================================== struct timespec ts; // Real time (wall clock) clock_gettime(CLOCK_REALTIME, &ts); printf("CLOCK_REALTIME: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); // Monotonic time (for measuring durations) clock_gettime(CLOCK_MONOTONIC, &ts); printf("CLOCK_MONOTONIC: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); // Monotonic with no NTP adjustments (fastest, coarsest) clock_gettime(CLOCK_MONOTONIC_RAW, &ts); printf("CLOCK_MONOTONIC_RAW: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); // Process CPU time clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts); printf("CLOCK_PROCESS_CPUTIME: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); // Thread CPU time clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts); printf("CLOCK_THREAD_CPUTIME: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec); // ======================================== // Clock resolution // ======================================== clock_getres(CLOCK_REALTIME, &ts); printf("CLOCK_REALTIME resolution: %ld ns\n", ts.tv_nsec); clock_getres(CLOCK_MONOTONIC, &ts); printf("CLOCK_MONOTONIC resolution: %ld ns\n", ts.tv_nsec);} // Proper benchmarking with monotonic clockdouble measure_operation() { struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); // Operation to measure volatile long sum = 0; for (long i = 0; i < 10000000; i++) { sum += i; } clock_gettime(CLOCK_MONOTONIC, &end); // Calculate elapsed time double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9; printf("Operation took %.6f seconds\n", elapsed); return elapsed;} // Getting process/children timesvoid show_cpu_times() { struct tms t; clock_t total = times(&t); long ticks_per_sec = sysconf(_SC_CLK_TCK); printf("CPU times (ticks, divide by %ld for seconds):\n", ticks_per_sec); printf(" User time: %ld\n", t.tms_utime); printf(" System time: %ld\n", t.tms_stime); printf(" Children user time: %ld\n", t.tms_cutime); printf(" Children system time: %ld\n", t.tms_cstime);}| Function | Precision | Clock Type | Recommended Use |
|---|---|---|---|
| time() | 1 second | Real time | Logging, coarse timestamps |
| gettimeofday() | Microsecond | Real time | Legacy code (use clock_gettime) |
| clock_gettime(REALTIME) | Nanosecond | Real time | High-precision timestamps |
| clock_gettime(MONOTONIC) | Nanosecond | Monotonic | Duration measurement, benchmarks |
| clock_gettime(CPUTIME) | Nanosecond | CPU time | Profiling CPU usage |
| times() | Clock ticks | All types | Process/children CPU accounting |
Never use CLOCK_REALTIME to measure durations! Wall clock time can jump forward or backward due to NTP adjustments, daylight saving changes, or manual clock sets. A benchmark measured with real time might report negative duration. Always use CLOCK_MONOTONIC for elapsed time measurements.
Setting System Time
Modifying system time requires elevated privileges:
int clock_settime(clockid_t clk_id, const struct timespec *tp);
int settimeofday(const struct timeval *tv, const struct timezone *tz);
These calls are typically invoked only by system administration utilities like ntpd or chrony. Setting time incorrectly can break certificates, authentication tokens, log ordering, and distributed systems. Modern systems prefer gradual slewing over abrupt time changes.
Every process carries numerous attributes: its identity, permissions, resource limits, scheduling parameters, and more. Information maintenance system calls enable programs to query and, where permitted, modify these attributes.
Identity Attributes
We've seen getpid(), getppid(), getuid(), getgid() in process control. Related calls provide deeper identity information:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
#include <stdio.h>#include <unistd.h>#include <sys/resource.h>#include <sys/types.h>#include <pwd.h>#include <grp.h>#include <sched.h> void show_identity_attributes() { printf("=== Identity Attributes ===\n"); // Basic IDs printf("PID: %d\n", getpid()); printf("Parent PID: %d\n", getppid()); printf("Process Group ID: %d\n", getpgrp()); printf("Session ID: %d\n", getsid(0)); // Real and effective user/group IDs uid_t ruid, euid, suid; gid_t rgid, egid, sgid; getresuid(&ruid, &euid, &suid); getresgid(&rgid, &egid, &sgid); printf("User IDs - real: %d, effective: %d, saved: %d\n", ruid, euid, suid); printf("Group IDs - real: %d, effective: %d, saved: %d\n", rgid, egid, sgid); // Supplementary groups gid_t groups[32]; int ngroups = getgroups(32, groups); printf("Supplementary groups (%d): ", ngroups); for (int i = 0; i < ngroups; i++) { printf("%d ", groups[i]); } printf("\n"); // Username from UID struct passwd *pw = getpwuid(ruid); if (pw) { printf("Username: %s\n", pw->pw_name); printf("Home directory: %s\n", pw->pw_dir); printf("Login shell: %s\n", pw->pw_shell); }} void show_resource_limits() { printf("\n=== Resource Limits ===\n"); struct rlimit rl; // File size limit getrlimit(RLIMIT_FSIZE, &rl); printf("Max file size: soft=%llu, hard=%llu bytes\n", (unsigned long long)rl.rlim_cur, (unsigned long long)rl.rlim_max); // Open files limit getrlimit(RLIMIT_NOFILE, &rl); printf("Max open files: soft=%llu, hard=%llu\n", (unsigned long long)rl.rlim_cur, (unsigned long long)rl.rlim_max); // Stack size getrlimit(RLIMIT_STACK, &rl); printf("Stack size: soft=%llu, hard=%llu bytes\n", (unsigned long long)rl.rlim_cur, (unsigned long long)rl.rlim_max); // Address space getrlimit(RLIMIT_AS, &rl); if (rl.rlim_cur == RLIM_INFINITY) { printf("Address space: unlimited\n"); } else { printf("Address space: %llu bytes\n", (unsigned long long)rl.rlim_cur); } // Core dump size getrlimit(RLIMIT_CORE, &rl); printf("Core dump: %s\n", (rl.rlim_cur == 0) ? "disabled" : "enabled"); // CPU time limit getrlimit(RLIMIT_CPU, &rl); if (rl.rlim_cur == RLIM_INFINITY) { printf("CPU time: unlimited\n"); } else { printf("CPU time limit: %llu seconds\n", (unsigned long long)rl.rlim_cur); }} void show_resource_usage() { printf("\n=== Resource Usage ===\n"); struct rusage usage; getrusage(RUSAGE_SELF, &usage); printf("User CPU time: %ld.%06ld s\n", usage.ru_utime.tv_sec, usage.ru_utime.tv_usec); printf("System CPU time: %ld.%06ld s\n", usage.ru_stime.tv_sec, usage.ru_stime.tv_usec); printf("Max resident set size: %ld KB\n", usage.ru_maxrss); printf("Page faults (minor/major): %ld / %ld\n", usage.ru_minflt, usage.ru_majflt); printf("Block input/output: %ld / %ld\n", usage.ru_inblock, usage.ru_oublock); printf("Voluntary/involuntary context switches: %ld / %ld\n", usage.ru_nvcsw, usage.ru_nivcsw);} void modify_resource_limits() { printf("\n=== Modifying Resource Limits ===\n"); struct rlimit rl; // Increase open file limit (within hard limit) getrlimit(RLIMIT_NOFILE, &rl); printf("Current open file limit: %llu\n", (unsigned long long)rl.rlim_cur); rl.rlim_cur = rl.rlim_max; // Raise to hard limit if (setrlimit(RLIMIT_NOFILE, &rl) == 0) { printf("Raised to: %llu\n", (unsigned long long)rl.rlim_cur); } else { perror("setrlimit"); } // Enable core dumps (if disabled) getrlimit(RLIMIT_CORE, &rl); if (rl.rlim_cur == 0) { rl.rlim_cur = RLIM_INFINITY; if (setrlimit(RLIMIT_CORE, &rl) == 0) { printf("Enabled unlimited core dumps\n"); } }}| Limit | Affects | Default Behavior |
|---|---|---|
| RLIMIT_NOFILE | Max open file descriptors | Often 1024; servers raise this |
| RLIMIT_NPROC | Max processes for user | fork() fails with EAGAIN |
| RLIMIT_FSIZE | Max file size creatable | SIGXFSZ if exceeded |
| RLIMIT_CORE | Max core dump size | 0 disables core dumps |
| RLIMIT_STACK | Max stack size | SIGSEGV on overflow |
| RLIMIT_AS | Max address space | malloc() returns NULL |
| RLIMIT_CPU | Max CPU seconds | SIGXCPU then SIGKILL |
Each limit has two values: soft (enforced, can be raised) and hard (ceiling for soft, only root can raise). A process can raise its soft limit up to the hard limit without privileges. This enables running as root to set high hard limits, then dropping privileges while retaining the ability to use those limits.
Programs often need to know about the system they're running on: OS version for compatibility decisions, CPU count for parallel algorithms, memory size for cache sizing. Several interfaces provide this information.
uname() — System Identification
int uname(struct utsname *buf);
Returns the kernel name, network hostname, kernel release, kernel version, and hardware architecture—similar to the uname command output.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
#include <stdio.h>#include <sys/utsname.h>#include <unistd.h>#include <sys/sysinfo.h> void show_uname_info() { printf("=== System Identification (uname) ===\n"); struct utsname uts; if (uname(&uts) == 0) { printf("System name: %s\n", uts.sysname); // e.g., "Linux" printf("Node name: %s\n", uts.nodename); // hostname printf("Release: %s\n", uts.release); // e.g., "5.15.0-generic" printf("Version: %s\n", uts.version); // build info printf("Machine: %s\n", uts.machine); // e.g., "x86_64" #ifdef _GNU_SOURCE printf("Domain name: %s\n", uts.domainname); // NIS domain #endif }} void show_sysconf_info() { printf("\n=== System Configuration (sysconf) ===\n"); // CPU and processing printf("CPUs configured: %ld\n", sysconf(_SC_NPROCESSORS_CONF)); printf("CPUs online: %ld\n", sysconf(_SC_NPROCESSORS_ONLN)); printf("Clock ticks/sec: %ld\n", sysconf(_SC_CLK_TCK)); // Memory long page_size = sysconf(_SC_PAGESIZE); long phys_pages = sysconf(_SC_PHYS_PAGES); long avail_pages = sysconf(_SC_AVPHYS_PAGES); printf("Page size: %ld bytes\n", page_size); printf("Physical pages: %ld\n", phys_pages); printf("Physical memory: %.2f GB\n", (double)page_size * phys_pages / (1024*1024*1024)); printf("Available memory: %.2f GB\n", (double)page_size * avail_pages / (1024*1024*1024)); // Limits printf("Max open files: %ld\n", sysconf(_SC_OPEN_MAX)); printf("Max arg length: %ld bytes\n", sysconf(_SC_ARG_MAX)); printf("Max hostname length: %ld\n", sysconf(_SC_HOST_NAME_MAX)); printf("Max login name: %ld\n", sysconf(_SC_LOGIN_NAME_MAX)); printf("Max symlink depth: %ld\n", sysconf(_SC_SYMLOOP_MAX)); // POSIX version printf("POSIX version: %ld\n", sysconf(_SC_VERSION)); // Features printf("Memory protection: %s\n", sysconf(_SC_MEMORY_PROTECTION) > 0 ? "yes" : "no"); printf("Job control: %s\n", sysconf(_SC_JOB_CONTROL) > 0 ? "yes" : "no");} void show_sysinfo_info() { printf("\n=== System Info (Linux-specific) ===\n"); struct sysinfo si; if (sysinfo(&si) == 0) { printf("Uptime: %ld seconds (%.2f days)\n", si.uptime, (double)si.uptime / 86400); printf("Load averages: %.2f, %.2f, %.2f\n", si.loads[0] / 65536.0, si.loads[1] / 65536.0, si.loads[2] / 65536.0); printf("Total RAM: %lu MB\n", si.totalram * si.mem_unit / (1024*1024)); printf("Free RAM: %lu MB\n", si.freeram * si.mem_unit / (1024*1024)); printf("Shared RAM: %lu MB\n", si.sharedram * si.mem_unit / (1024*1024)); printf("Buffer RAM: %lu MB\n", si.bufferram * si.mem_unit / (1024*1024)); printf("Total swap: %lu MB\n", si.totalswap * si.mem_unit / (1024*1024)); printf("Free swap: %lu MB\n", si.freeswap * si.mem_unit / (1024*1024)); printf("Number of processes: %d\n", si.procs); }} // Determining CPU count for parallelismint get_usable_cpu_count() { // Option 1: sysconf (most portable) long ncpu = sysconf(_SC_NPROCESSORS_ONLN); // Option 2: sched_getaffinity (respects cgroup limits) cpu_set_t cpuset; if (sched_getaffinity(0, sizeof(cpuset), &cpuset) == 0) { int count = 0; for (int i = 0; i < CPU_SETSIZE; i++) { if (CPU_ISSET(i, &cpuset)) count++; } // In containers, this might be less than sysconf result return count; } return ncpu > 0 ? ncpu : 1;}sysconf() — Runtime Configurable Limits
Unlike compile-time constants, sysconf() queries can vary:
Always query at runtime rather than hardcoding assumptions.
pathconf() and fpathconf() — Path-Specific Limits
For filesystem-dependent limits:
long pathconf(const char *path, int name);
long fpathconf(int fd, int name);
These return limits that vary by filesystem: max filename length, max path length, whether symlinks are supported, etc.
In containerized environments, sysconf(_SC_NPROCESSORS_ONLN) may return the host's CPU count, not the container's cgroup limit. For accurate CPU counts in containers, read /sys/fs/cgroup/cpu.max or use sched_getaffinity(). Memory limits similarly require reading cgroup files. Many libraries now handle this automatically.
Linux exposes extensive kernel information through /proc, a virtual filesystem where files don't exist on disk but are generated on-demand from kernel data structures. Reading a file in /proc invokes kernel code that formats current state into human-readable text.
/proc Structure
/proc/
├── 1/ # Process 1 (init/systemd)
│ ├── cmdline # Command-line arguments
│ ├── environ # Environment variables
│ ├── fd/ # Open file descriptors
│ ├── maps # Memory mappings
│ ├── stat # Process statistics
│ ├── status # Human-readable status
│ └── ... # Many more
├── cpuinfo # CPU details
├── meminfo # Memory statistics
├── loadavg # Load averages
├── uptime # System uptime
├── version # Kernel version
└── sys/ # Tunable parameters
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <fcntl.h>#include <dirent.h> // Read entire file contentchar* read_proc_file(const char *path) { int fd = open(path, O_RDONLY); if (fd == -1) return NULL; // /proc files have size 0 in stat, so read until EOF char *buffer = malloc(8192); ssize_t len = read(fd, buffer, 8191); close(fd); if (len <= 0) { free(buffer); return NULL; } buffer[len] = '\0'; return buffer;} void show_process_info(pid_t pid) { char path[256]; // Command line snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); char *cmdline = read_proc_file(path); if (cmdline) { // Arguments separated by NUL bytes printf("Command: "); for (char *p = cmdline; *p || *(p+1); p++) { putchar(*p ? *p : ' '); } printf("\n"); free(cmdline); } // Current working directory (symlink) snprintf(path, sizeof(path), "/proc/%d/cwd", pid); char cwd[1024]; ssize_t len = readlink(path, cwd, sizeof(cwd)-1); if (len > 0) { cwd[len] = '\0'; printf("CWD: %s\n", cwd); } // Executable path (symlink) snprintf(path, sizeof(path), "/proc/%d/exe", pid); char exe[1024]; len = readlink(path, exe, sizeof(exe)-1); if (len > 0) { exe[len] = '\0'; printf("Executable: %s\n", exe); } // Status file (parsed data) snprintf(path, sizeof(path), "/proc/%d/status", pid); char *status = read_proc_file(path); if (status) { // Parse specific fields char *line = strtok(status, "\n"); while (line) { if (strncmp(line, "Name:", 5) == 0 || strncmp(line, "State:", 6) == 0 || strncmp(line, "Threads:", 8) == 0 || strncmp(line, "VmRSS:", 6) == 0 || strncmp(line, "VmSize:", 7) == 0) { printf("%s\n", line); } line = strtok(NULL, "\n"); } free(status); }} void show_memory_maps(pid_t pid) { char path[256]; snprintf(path, sizeof(path), "/proc/%d/maps", pid); FILE *fp = fopen(path, "r"); if (!fp) return; printf("\nMemory mappings (first 10):\n"); char line[512]; int count = 0; while (fgets(line, sizeof(line), fp) && count < 10) { printf("%s", line); count++; } fclose(fp);} void show_system_proc() { printf("\n=== System /proc Files ===\n"); // CPU info char *cpuinfo = read_proc_file("/proc/cpuinfo"); if (cpuinfo) { // Extract model name char *model = strstr(cpuinfo, "model name"); if (model) { char *end = strchr(model, '\n'); if (end) *end = '\0'; printf("%s\n", model); } free(cpuinfo); } // Memory info char *meminfo = read_proc_file("/proc/meminfo"); if (meminfo) { char *line = strtok(meminfo, "\n"); while (line) { if (strncmp(line, "MemTotal:", 9) == 0 || strncmp(line, "MemFree:", 8) == 0 || strncmp(line, "MemAvailable:", 13) == 0) { printf("%s\n", line); } line = strtok(NULL, "\n"); } free(meminfo); } // Load average char *loadavg = read_proc_file("/proc/loadavg"); if (loadavg) { printf("Load average: %s", loadavg); free(loadavg); }} // List all processesvoid list_all_processes() { DIR *dir = opendir("/proc"); struct dirent *entry; printf("\nProcess list:\n"); while ((entry = readdir(dir)) != NULL) { // Numeric directories are process IDs char *end; long pid = strtol(entry->d_name, &end, 10); if (*end == '\0') { char path[256]; snprintf(path, sizeof(path), "/proc/%ld/comm", pid); char *comm = read_proc_file(path); if (comm) { printf(" %5ld: %s", pid, comm); // comm has newline free(comm); } } } closedir(dir);}| /proc File | Contents | Common Use |
|---|---|---|
| /proc/cpuinfo | CPU details, features, frequency | Capability detection |
| /proc/meminfo | Memory statistics | Memory monitoring |
| /proc/loadavg | 1/5/15 minute load averages | System health |
| /proc/uptime | Seconds since boot, idle time | Uptime monitoring |
| /proc/version | Kernel version string | Compatibility checks |
| /proc/[pid]/maps | Memory region mappings | Debugging, security |
| /proc/[pid]/fd/ | Open file descriptors | Leak detection |
| /proc/[pid]/status | Detailed process info | Process monitoring |
/proc files are synthetic—they generate content when read. Reading in multiple chunks may yield inconsistent data if state changes mid-read. For critical data, read the entire file in one read() call (buffer adequately) or use specific system calls when available. Some /proc files (like /proc/[pid]/stat) are designed for atomic single-read consumption.
While /proc exposes process information and kernel internals somewhat haphazardly, /sys (sysfs) provides a structured view of the device model. Each subdirectory represents a kernel object—device, driver, bus, or class—with attributes as files.
/sys Structure
/sys/
├── block/ # Block devices (disks)
│ └── sda/
│ ├── size # Size in sectors
│ ├── queue/ # I/O scheduler settings
│ └── device/ # Link to device
├── bus/ # Bus types
│ ├── pci/
│ └── usb/
├── class/ # Device classes
│ ├── net/ # Network interfaces
│ ├── tty/ # Terminal devices
│ └── block/ # Block devices
├── devices/ # Device hierarchy
├── fs/ # Filesystems
└── kernel/ # Kernel tunables
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <dirent.h> char* read_sysfs_attr(const char *path) { int fd = open(path, O_RDONLY); if (fd == -1) return NULL; char *buffer = malloc(256); ssize_t len = read(fd, buffer, 255); close(fd); if (len <= 0) { free(buffer); return NULL; } buffer[len] = '\0'; // Remove trailing newline char *nl = strchr(buffer, '\n'); if (nl) *nl = '\0'; return buffer;} int write_sysfs_attr(const char *path, const char *value) { int fd = open(path, O_WRONLY); if (fd == -1) return -1; ssize_t written = write(fd, value, strlen(value)); close(fd); return written == (ssize_t)strlen(value) ? 0 : -1;} void show_network_interfaces() { printf("=== Network Interfaces ===\n"); DIR *dir = opendir("/sys/class/net"); if (!dir) return; struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] == '.') continue; char path[256]; // Get MAC address snprintf(path, sizeof(path), "/sys/class/net/%s/address", entry->d_name); char *mac = read_sysfs_attr(path); // Get operational state snprintf(path, sizeof(path), "/sys/class/net/%s/operstate", entry->d_name); char *state = read_sysfs_attr(path); // Get speed (if available) snprintf(path, sizeof(path), "/sys/class/net/%s/speed", entry->d_name); char *speed = read_sysfs_attr(path); printf("%s: MAC=%s, state=%s, speed=%s Mbps\n", entry->d_name, mac ? mac : "n/a", state ? state : "n/a", speed ? speed : "n/a"); free(mac); free(state); free(speed); } closedir(dir);} void show_block_devices() { printf("\n=== Block Devices ===\n"); DIR *dir = opendir("/sys/block"); if (!dir) return; struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] == '.') continue; char path[256]; // Get size (in 512-byte sectors) snprintf(path, sizeof(path), "/sys/block/%s/size", entry->d_name); char *size_str = read_sysfs_attr(path); // Get rotational (1 = HDD, 0 = SSD) snprintf(path, sizeof(path), "/sys/block/%s/queue/rotational", entry->d_name); char *rot = read_sysfs_attr(path); // Get model (if available) snprintf(path, sizeof(path), "/sys/block/%s/device/model", entry->d_name); char *model = read_sysfs_attr(path); if (size_str) { unsigned long long sectors = strtoull(size_str, NULL, 10); double gb = (double)(sectors * 512) / (1024 * 1024 * 1024); printf("%s: %.1f GB, %s, model=%s\n", entry->d_name, gb, (rot && *rot == '0') ? "SSD" : "HDD", model ? model : "unknown"); } free(size_str); free(rot); free(model); } closedir(dir);} void show_cpu_frequencies() { printf("\n=== CPU Frequencies ===\n"); char path[256]; for (int cpu = 0; cpu < 64; cpu++) { snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", cpu); char *freq = read_sysfs_attr(path); if (!freq) break; double ghz = atol(freq) / 1000000.0; printf("CPU%d: %.2f GHz\n", cpu, ghz); free(freq); }} // Example: Tuning via sysfs (requires root)void tune_system_examples() { printf("\n=== System Tuning Examples ===\n"); // Set I/O scheduler (requires root) // write_sysfs_attr("/sys/block/sda/queue/scheduler", "deadline"); // Adjust network settings // write_sysfs_attr("/sys/class/net/eth0/mtu", "9000"); // CPU governor // write_sysfs_attr("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", // "performance"); printf("(Tuning requires root privileges)\n");}sysfs vs /proc
| Aspect | /proc | /sys |
|---|---|---|
| Focus | Processes, kernel internals | Device model, hardware |
| Structure | Historical accumulation | Clean hierarchy |
| Naming | Ad-hoc | Follows kernel object model |
| Write access | Some tunables in /proc/sys | Extensive device configuration |
| Stability | Many interfaces stable | Considered more structured |
For device information and configuration, prefer /sys. For process information, use /proc.
The /proc/sys/ directory contains tunable kernel parameters. While technically under /proc, these are distinct from process information. The sysctl command reads/writes these files. Examples: /proc/sys/vm/swappiness controls swap behavior; /proc/sys/net/ipv4/ip_forward enables routing. These persist in /etc/sysctl.conf.
Processes inherit configuration through environment variables and access system-wide settings through various mechanisms.
Environment Variables
Every process has an environment—a set of name=value pairs inherited from its parent (and modifiable via exec):
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
#include <stdio.h>#include <stdlib.h>#include <string.h> // The global 'environ' variable contains all environment varsextern char **environ; void show_all_environment() { printf("=== All Environment Variables ===\n"); for (char **env = environ; *env != NULL; env++) { printf("%s\n", *env); }} void demonstrate_environment_access() { printf("\n=== Environment Access ===\n"); // Reading environment variables char *home = getenv("HOME"); char *path = getenv("PATH"); char *shell = getenv("SHELL"); char *user = getenv("USER"); char *lang = getenv("LANG"); printf("HOME: %s\n", home ? home : "(not set)"); printf("PATH: %s\n", path ? path : "(not set)"); printf("SHELL: %s\n", shell ? shell : "(not set)"); printf("USER: %s\n", user ? user : "(not set)"); printf("LANG: %s\n", lang ? lang : "(not set)"); // Setting environment variables // setenv() adds or modifies, unsetenv() removes // Add new variable if (setenv("MY_APP_CONFIG", "/etc/myapp.conf", 0) == 0) { printf("Set MY_APP_CONFIG (overwrite=0)\n"); } // Overwrite existing (third arg = 1) setenv("MY_APP_CONFIG", "/opt/myapp.conf", 1); printf("MY_APP_CONFIG now: %s\n", getenv("MY_APP_CONFIG")); // Alternative: putenv() uses the actual string (dangeous with stack vars!) static char custom[] = "CUSTOM_VAR=value"; // Must be static! putenv(custom); printf("CUSTOM_VAR: %s\n", getenv("CUSTOM_VAR")); // Remove variable unsetenv("MY_APP_CONFIG"); printf("After unsetenv, MY_APP_CONFIG: %s\n", getenv("MY_APP_CONFIG") ? getenv("MY_APP_CONFIG") : "(unset)");} // Clear entire environment (secure for setuid programs)void secure_environment() { // For setuid programs, inherited environment can be a security risk // Clear environment except for specific allowed variables char *allowed[] = {"PATH", "TERM", "LANG", NULL}; char *saved[10]; int n = 0; // Save allowed variables for (int i = 0; allowed[i] && n < 10; i++) { char *val = getenv(allowed[i]); if (val) { saved[n] = malloc(strlen(allowed[i]) + strlen(val) + 2); sprintf(saved[n], "%s=%s", allowed[i], val); n++; } } // Clear all // Note: Some systems have clearenv(), but it's not portable #ifdef _GNU_SOURCE clearenv(); #else while (environ[0]) { unsetenv(environ[0]); // This is inefficient but portable } #endif // Restore allowed for (int i = 0; i < n; i++) { putenv(saved[i]); // saved[i] must remain valid } printf("Environment cleared except allowed vars\n");} // Configuration file path resolutionchar* find_config_file(const char *app_name) { static char path[512]; // Priority order: // 1. Environment override char env_name[64]; snprintf(env_name, sizeof(env_name), "%s_CONFIG", app_name); char *env_path = getenv(env_name); if (env_path) return env_path; // 2. XDG config dir char *xdg = getenv("XDG_CONFIG_HOME"); if (xdg) { snprintf(path, sizeof(path), "%s/%s/config", xdg, app_name); if (access(path, R_OK) == 0) return path; } // 3. Home directory char *home = getenv("HOME"); if (home) { snprintf(path, sizeof(path), "%s/.%s.conf", home, app_name); if (access(path, R_OK) == 0) return path; snprintf(path, sizeof(path), "%s/.config/%s/config", home, app_name); if (access(path, R_OK) == 0) return path; } // 4. System-wide snprintf(path, sizeof(path), "/etc/%s.conf", app_name); if (access(path, R_OK) == 0) return path; return NULL;}Setuid programs that trust environment variables (like PATH, LD_PRELOAD, LD_LIBRARY_PATH) are vulnerable to privilege escalation. Attackers can inject malicious libraries or executables. Secure setuid programs should sanitize or clear the environment, use absolute paths, and avoid trusting user-supplied configuration.
Other Configuration Mechanisms
These complete the picture of how programs discover their execution context.
Information maintenance system calls enable programs to understand their environment and adapt accordingly. We've explored the diverse mechanisms for querying and modifying system state:
Looking Ahead
With information maintenance covered, we turn to the final category of system calls: communication. These system calls enable processes to exchange data and coordinate—through pipes, sockets, shared memory, and message passing. Interprocess communication completes our taxonomy of the interfaces through which programs access operating system services.
You now understand how programs query time, inspect their own attributes, discover system capabilities, and access rich kernel state through /proc and /sys. This knowledge is essential for writing adaptable, well-behaved, and diagnosable software. Next, we'll explore communication system calls that enable processes to work together.