Before you start#
Read Start here for the baseline, study routes and safety boundaries. You need no kernel build for this chapter. A terminal and a browser that can reach the source links are useful; the exercise also works from a downloaded source tree.
Learning goals#
Explain the difference between a kernel and a complete operating system. Identify the userspace/kernel boundary and interpret a small real system call without assuming that a source listing is a trace. Describe why an identifier is meaningful in a namespace and why a kernel version alone cannot determine runtime behavior.
How to use this chapter#
New to kernel source? Start with the first two sections and treat the code excerpt as a map: identify the function, its input, and its returned value before following helper calls. If you already know C and process basics, compare the implementation with the entry documentation and write down what the excerpt does not show. Finish with the practice note; each later route builds on these distinctions between source, configuration, and observation. The commands are optional: the source-only explanation is complete on its own. Code blocks are examples to read or run as indicated, not commands to paste into a privileged shell.
The system around the kernel#
Linux is the kernel: the software core that mediates execution, memory, files, networking and devices. A usable system also needs userspace programs, libraries, a way to start them, configuration and usually a filesystem. Distributions combine these pieces in different ways. A Linux kernel can support a tiny embedded system or a large server; the same name does not imply identical enabled features, tools or hardware.
- Firmware and bootloader
- Prepare the machine and hand control to the kernel under an architecture-specific contract.
- Kernel
- Manages processes, memory, files, networking and devices, exposed through system calls.
- C library
- Often wraps a system call and translates a negative kernel result into errno.
- Shell and utilities
- Ordinary userspace programs started as processes; they are not the kernel.
- Init, services, applications
- Userspace programs that use the same kernel interfaces as any other process.
A process is a running program together with its execution state and resources. Its code normally runs in userspace, a less privileged execution environment. The kernel uses architecture-specific mechanisms to enter privileged code when the program makes a system call, encounters an exception, or when hardware delivers an interrupt. These are different causes and should not be treated as identical. A system call is a controlled request, not permission for a program to call an arbitrary kernel function. The details of the transition and calling convention depend on architecture; see the x86 entry documentation for one example.
The kernel is not a single function called once per program. It contains cooperating subsystems, each with its own interfaces and invariants. The scheduler chooses runnable work; memory management creates address-space abstractions; the VFS provides common file operations; drivers interact with particular devices. A request can cross several boundaries. The exact implementation depends on source version, architecture, configuration and runtime state. Reading a function is the beginning of an investigation, not a complete execution trace.
Source walkthrough#
- Userspace
A program asks a library or syscall interface for its process ID.
- System-call entry
Architecture-specific entry and dispatch select the kernel service.
- getpid implementation
kernel/sys.c obtains the thread-group ID visible in the current PID namespace.
- Return
The result crosses back to userspace; it is not a permanent global identity.
Consider a process asking for its process ID. In upstream v6.12 kernel/sys.c, the implementation includes this short excerpt from SYSCALL_DEFINE0(getpid) (GPL-2.0-only; surrounding code omitted):
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}
SYSCALL_DEFINE0 declares a system call taking zero arguments. current refers to the task executing in the kernel. The function returns task_tgid_vnr(current): the thread-group identifier as visible in the relevant virtual PID namespace, rather than a pointer or a permanent machine-wide identity. The helper and its surrounding declarations are in include/linux/pid.h. A process can contain multiple threads; this call returns the thread-group identity conventionally exposed as a process ID, not necessarily the individual thread ID. Namespace visibility matters, and IDs can be reused after a process exits. The PID namespace documentation gives broader context.
The short implementation does not itself show the whole journey. On x86-64, architecture-specific entry and dispatch code handles the transition before the implementation; userspace may invoke a library wrapper. Do not infer that all architectures use the same entry instructions or register conventions. Likewise, a diagram of this path is a source map, not a recording that proves a particular program executed these instructions. The system call interface is documented separately in the kernel syscall documentation.
The dispatch step is visible in a second v6.12 helper. In arch/x86/entry/common.c, x86-64 code accepts only numbers below NR_syscalls and then calls x64_sys_call with the number (SPDX GPL-2.0-only; the whole helper is shown, and the #ifdef CONFIG_X86_64 guard around it is omitted):
static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr)
{
/*
* Convert negative numbers to very high and thus out of range
* numbers for comparisons.
*/
unsigned int unr = nr;
if (likely(unr < NR_syscalls)) {
unr = array_index_nospec(unr, NR_syscalls);
regs->ax = x64_sys_call(regs, unr);
return true;
}
return false;
}
The helper returns false for an out-of-range number. Its caller, do_syscall_64, also tries an x32 path, and only when neither accepts the number does it store the result of the not-implemented system call (lines omitted). The helper does not show the assembly that saves registers and reaches do_syscall_64, the return to userspace, or the table that maps numbers to implementations. In arch/x86/entry/syscalls/syscall_64.tbl, number 39 is named sys_getpid; a table entry is a naming fact, not a trace that a particular call happened. The following teaching model places the getpid steps in order:
- ProgramC library
getpid()
- C libraryKernel entry (entry_SYSCALL_64)
syscall instruction
- Kernel entry (entry_SYSCALL_64)x64_sys_call
do_syscall_64 dispatches
- x64_sys_callgetpid implementation
number 39 maps to sys_getpid
- getpid implementationx64_sys_call
Return. task_tgid_vnr(current)
- x64_sys_callKernel entry (entry_SYSCALL_64)
Return. return value
- Kernel entry (entry_SYSCALL_64)C library
Return. return to C library
- C libraryProgram
Return. pid_t result
Time runs downward. Dashed arrows show returns.
Read the text version
- Program → C library: getpid()
- C library → Kernel entry (entry_SYSCALL_64): syscall instruction
- Kernel entry (entry_SYSCALL_64) → x64_sys_call: do_syscall_64 dispatches
- x64_sys_call → getpid implementation: number 39 maps to sys_getpid
- getpid implementation → x64_sys_call (return): task_tgid_vnr(current)
- x64_sys_call → Kernel entry (entry_SYSCALL_64) (return): return value
- Kernel entry (entry_SYSCALL_64) → C library (return): return to C library
- C library → Program (return): pid_t result
The result is a scalar returned to the caller. This example does not transfer ownership of a kernel object to userspace. current is meaningful in the current execution context; retaining a pointer to a task beyond a valid lifetime would require a separate lifetime protocol. Although this trivial implementation has no explicit local error branch, that observation must not be generalized to other system calls: many validate arguments, copy data across the boundary, check authority and return errors. Later chapters examine these paths and their concurrency rules.
Source, configuration, and evidence#
The tag fixes the source revision, but it does not fix your .config, compiler, machine or boot environment. Two v6.12 kernels may support different filesystems, drivers or debugging facilities. Distribution patches can also change behavior. The release reported by a running host is useful context, but it is not proof that its code exactly matches the upstream tag. A lab report should include the revision and configuration alongside any observed result.
A source-reading claim should say what the code shows. A runtime claim should identify the executable, environment, input and observation. An interpretation connects the two while naming uncertainty. This distinction prevents an elegant diagram from being mistaken for a trace and makes it possible for someone else to challenge or reproduce your conclusion.
Practice#
On a Linux workstation, open an ordinary unprivileged shell. These commands inspect your current environment; they do not change kernel settings:
uname -srmo
printf 'shell PID: %s\n' "$$"
ps -p "$$" -o pid,ppid,comm
Record the output and the date in a local note. uname reports information about the running kernel and architecture; it does not establish that the kernel is upstream v6.12. $$ identifies the shell from which the command was expanded, and ps displays that process if it is visible. Exact values and command names vary. A container or PID namespace can show IDs different from those visible outside it.
As a counterexample, compare the PID printed by $$ with the process ID of a separately launched command: they identify different processes, so assuming every command shares the shell's PID is incorrect. Use sh -c 'printf "child shell PID: %s\n" "$$"' to observe one child shell. Do not assume these IDs remain unique after exit. Deliver a short note with the outputs, the two source links above, a four-step source map and one explicit limit of your observation. No guest or administrative privileges are required.
Knowledge check#
- What parts of a usable Linux system are outside the kernel?
- What does the
getpidimplementation return, and why is that value not a durable global identity? - Why does reading
kernel/sys.calone not prove the entry path used by a particular program? - What information would help another reader reproduce your observation?
Answers#
- Userspace programs, libraries, initialization, configuration and storage are among the components provided around the kernel; their exact composition varies.
- It returns the current task's thread-group ID in the relevant virtual PID namespace. Visibility differs between namespaces and identifiers can be reused after exit.
- The source shows an implementation, not a runtime trace. Entry depends on the target architecture and environment; the program and configuration must also be established.
- Record command, output, date, architecture, kernel revision or release, configuration when available, and whether the observation was on a host or in a guest or namespace.
Checkpoint#
Your artifact is the practice note. Pass when it identifies userspace and kernel boundaries, correctly explains the shown return value, cites the pinned implementation and records one uncertainty in the observation. If it does not, reread the source walkthrough, run the unprivileged commands again, and revise the note before moving on. This checkpoint is a learning aid, not a certification.
Sources#
- Linux v6.12 kernel/sys.c —
getpidimplementation. - Linux v6.12 include/linux/pid.h — task identity helpers.
- x86-64 entry documentation — architecture-specific entry context.
- Kernel syscall development documentation — system call interface considerations.