Programs Asking Linux for Help
A process cannot do everything by itself
Section titled “A process cannot do everything by itself”Virtual memory lets Linux control which memory each process can access. We also need rules for shared resources such as files and network devices. Two programs may need the same resource at once, and each request has to respect permissions and the work already in progress.
If programs could issue unrestricted disk operations independently, they could overwrite each other’s data or bypass file permissions. We need one part of the system to coordinate those operations and check whether a request is allowed.
Ordinary application code runs in userspace, with the processor enforcing restrictions on privileged instructions and memory access. Kernel code runs with privileges that let it manage those resources. When a program needs to open a file, create a mapping, write output, or exit, it can make a system call: a request that transfers execution into the kernel.
The kernel checks the request and any applicable permissions, performs the operation if it can, and returns a result. The Linux system-call reference describes this interface and the library wrappers programs commonly use to reach it.
A C library function isn’t automatically a system call (this trips up a lot of people). printf does its formatting entirely in userspace, and only eventually leans on a system call like write to push bytes at an output stream. malloc manages allocations itself, and asks Linux for more memory only when its existing pool isn’t enough.
printf("hello")write(1, "hello", 5)system call · return value crosses backInstall and use strace
Section titled “Install and use strace”Check for the tracing tool:
strace --versionIf it is missing:
sudo apt updatesudo apt install straceThe supported course environment reports strace 6.8.
Trace a simple command, then open the trace to see its system calls:
strace -o echo.trace /usr/bin/echo helloless echo.traceThat command prints one friendly line. The trace, meanwhile, contains loader activity, memory mappings, locale files, the output itself, and process termination. Very simple source behavior can require a genuinely startling amount of runtime preparation before a single byte reaches your terminal.
Search for execve, openat, mmap, write, and exit_group. A typical trace line shows the system call name, its arguments, and a return value after the =. A negative return with an error name like ENOENT is evidence of a failed attempt, and programs will try several possible files during perfectly normal startup.
File descriptors, returns, and errors
Section titled “File descriptors, returns, and errors”A file descriptor is a small non-negative integer a process uses to refer to an open kernel-managed resource. By convention 0 is standard input, 1 is standard output, and 2 is standard error. openat returns a new descriptor on success, and the read, write, and close calls that follow all use it.
openat(..., "notes.txt", O_RDONLY) = 3read(3, "hello\n", 4096) = 6close(3) = 0That trace supports three separate claims: descriptor 3 referred to the opened resource for this interval, six bytes reached userspace, and the close succeeded.
At the C library boundary, a failing wrapper usually returns -1 and sets errno, and perror or strerror(errno) is what formats that for a human. strace shows the same failure as = -1 ENOENT (...). Capture errno immediately, because a later library call may well overwrite it before you look.
A successful write may be partial
Section titled “A successful write may be partial”write(fd, buffer, count) returns the number of bytes accepted, or -1. A positive result smaller than count is a partial write, and robust code loops until all the intended bytes are written or some policy stops it:
size_t written = 0;while (written < length) { ssize_t result = write(fd, buffer + written, length - written); if (result < 0) { if (errno == EINTR) continue; return -1; } if (result == 0) { errno = EIO; return -1; } written += (size_t)result;}That example treats a zero-byte result as an I/O failure, because otherwise the loop can make no progress at all. Real code needs an appropriate error report and a policy for nonblocking descriptors on top of all this. The immediate lesson is simply that the requested count and the returned count are two different pieces of evidence.
Buffering separates printf from write
Section titled “Buffering separates printf from write”The C library may combine several printf calls into a single write, or flush on a newline, an explicit fflush, a buffer limit, or a normal exit. Redirection changes the buffering too (which is why the same program behaves differently through a pipe). One print never guarantees one system call, and one write never identifies the source function that produced it.
Reduce noise carefully
Section titled “Reduce noise carefully”Filter the trace to selected calls:
strace -e trace=execve,openat,mmap,write,exit_group /usr/bin/echo helloFiltering helps you answer a focused question, and it also hides absolutely everything outside the filter. So write “the filtered trace showed no additional writes”, and not “the program made no other system calls”. Being precise about the limits of your own observation is part of the job.
strace also can’t show you every userspace instruction, displayed strings may be truncated, concurrent processes can interleave, and attaching requires permission. Check the active filters and string limit before interpreting missing or truncated output.
Capture strings written by a program:
strace -e trace=write -s 200 /usr/bin/echo hello-s 200 raises the displayed string limit. That changes how much of a string is displayed; binary data still needs to be interpreted according to its format.
The strace manual describes trace-selection and output options. Keep the selected filter beside your evidence so an omitted event isn’t mistaken for an event that never happened.
Trace the lifetime example
Section titled “Trace the lifetime example”strace -o lifetime.trace ./lifetimegrep -E 'brk|mmap|munmap|write|exit_group' lifetime.traceYou probably won’t see one kernel call for each malloc and free, because the allocator reuses and manages much larger regions on its own. That’s useful evidence that the C allocation interface and the kernel memory interface are related layers, rather than identical operations.
Common failures and recovery
Section titled “Common failures and recovery”strace: command not found: install the pinnedstracepackage, and checkstrace --versionbefore you continue.Operation not permittedwhile attaching: trace a new process you start directly withstrace. Don’t go changing system-wide security settings for this lesson.- The trace output seems incomplete: check the active
-efilter and the-sstring limit, and then widen one filter at a time. - Old rows are mixed in with new ones: remove the named trace file before you rerun, or just use a fresh filename.
- The program output and the trace are tangled together: use
-o trace-fileso the trace evidence gets its own destination.
What this lets us investigate
Section titled “What this lets us investigate”You can now watch where a program asks Linux to act, and tell that boundary apart from the source-level function that led it there. The Program Investigation Capstone pulls source, ELF metadata, process mappings, named-function instructions, memory lifetime, and system calls together into one reproducible investigation.
Check your understanding
Section titled “Check your understanding”Knowledge check
Question 1 of 2
Practice complete
You've checked the key ideas. Revisit any explanation you need, then continue when it makes sense.