After spending some time thinking about the difference between a program and a process, I wanted to see whether Linux would actually let me look at one.
It turns out that /proc is a rather good place to start.
At first glance it looks like an ordinary directory:
ls /proc
There are files, directories and a suspicious number of directories whose names are just numbers:
1
742
1932
4187
...
But /proc is not an ordinary filesystem full of files stored on disk.
Linux describes proc as a pseudo-filesystem that provides an interface to kernel data structures. It is normally mounted at /proc, and while most entries are read-only, some expose writable kernel settings as well.
That makes /proc an interesting little window into what the kernel knows.
Those numbers are process IDs
The numerical directories were my first useful discovery.
Linux exposes a /proc/<pid>/ directory for each running process, where <pid> is that process’s ID.
So if I start something deliberately boring:
sleep 120 &
pid=$!
printf 'PID: %s\n' "$pid"
I might get:
PID: 4187
And now:
ls "/proc/$pid"
reveals an entire directory associated with that particular sleep process.
Suddenly the PID from my earlier process experiments has somewhere physical-looking to go:
process
│
│ PID 4187
▼
/proc/4187/
It is not really a directory on disk describing the process. It is an interface through which Linux exposes information about it.
Exactly what one process is allowed to inspect about another can depend on permissions and how /proc is mounted, so not every entry is necessarily readable in every situation.
That distinction seems worth keeping.
status: a readable process summary
The first file I looked at was:
cat "/proc/$pid/status"
It contains fields such as:
Name: sleep
State: S (sleeping)
Pid: 4187
PPid: 3902
...
/proc/<pid>/status exposes process status and memory information in a format intended to be relatively easy for humans to read.
A few fields immediately connect back to things I’ve already encountered:
Pid
PPid
State
Pid identifies this process.
PPid identifies its parent process.
And State tells me something about what the process is currently doing.
My sleep process being reported as sleeping is reassuringly literal.
I can make the output less noisy:
grep -E '^(Name|State|Pid|PPid):' "/proc/$pid/status"
Now /proc is starting to feel less mysterious.
It is exposing the process model I’ve been reading about.
What program is this process executing?
There is also:
ls -l "/proc/$pid/exe"
For my example, that may resolve to something like:
/proc/4187/exe -> /usr/bin/sleep
Linux documents /proc/<pid>/exe as a symbolic-link-like entry referring to the pathname of the program being executed by that process.
That is a lovely visual demonstration of the distinction from my first note:
/usr/bin/sleep
│
│ executable program
│
▼
process 4187
│
└── /proc/4187/exe
└── /usr/bin/sleep
The process has its own identity.
exe tells me which program that process is executing.
Where is the process standing?
Another entry caught my attention:
ls -l "/proc/$pid/cwd"
cwd represents the process’s current working directory. Linux exposes it through /proc/<pid>/cwd.
That means the current directory isn’t merely a shell convenience.
It is part of a process’s execution context.
I can see it:
readlink "/proc/$pid/cwd"
This also helps explain why two processes executing the same program can behave differently.
They can have different:
working directories
environment
arguments
open files
Same program.
Different process context.
What command line started it?
Next:
cat "/proc/$pid/cmdline"
produces output that can initially look slightly strange.
The arguments in /proc/<pid>/cmdline are separated by null bytes rather than newlines or spaces.
So this is easier to read:
tr '\0' ' ' < "/proc/$pid/cmdline"
printf '\n'
For the example:
sleep 120
For an ordinary process that has not rewritten its argument strings, there are the arguments supplied when the program was executed.
That connects nicely with the argv discussion from the previous essay.
What Bash eventually supplied to the new program has left an observable trace.
And its environment?
There is also:
/proc/<pid>/environ
Again, the entries are null-byte separated, so:
tr '\0' '\n' < "/proc/$pid/environ"
makes them much easier to inspect.
Linux documents this file as containing the initial environment supplied when the currently executing program was started via execve().
The word initial matters.
This isn’t necessarily a magical live view of every environment change a program subsequently makes internally.
Still, it makes another invisible part of program execution visible:
shell environment
│
│ exec
▼
process environment
│
▼
/proc/<pid>/environ
I am beginning to appreciate just how much context surrounds a running program.
Open files are visible too
Then there is:
ls -l "/proc/$pid/fd"
The fd directory contains one entry for each file descriptor the process has open. The entries are named by file-descriptor number and refer to the corresponding open files or other objects.
For many processes the familiar trio appears immediately:
0
1
2
which correspond conventionally to:
0 standard input
1 standard output
2 standard error
This is particularly satisfying after looking at shell redirection.
When I write:
some-command > output.txt
“standard output” is no longer an abstract pipe floating somewhere between Bash and the terminal.
It belongs to a process as an open file descriptor, and /proc/<pid>/fd/ lets me inspect it.
Memory mappings appear as files too
One final stop:
head "/proc/$pid/maps"
/proc/<pid>/maps describes the regions currently mapped into a process’s virtual address space, including their permissions and, where applicable, associated pathnames.
The output is much less immediately friendly:
address-range perms offset ... pathname
...
and I am not going to pretend I understand all of it yet.
But I can recognise some interesting things:
the executable
shared libraries
anonymous mappings
the stack
This connects directly to the memory mappings I touched on when following what happens during program execution.
There is clearly another rabbit hole here.
I am leaving that one alone for today.
Mostly.
/proc/self is a neat trick
There is one more useful shortcut.
Instead of knowing my own PID, a process accessing:
/proc/self
gets a reference to its own /proc/<pid> directory.
For example:
ls -l /proc/self/exe
The interesting part is that “self” depends on which process accesses it.
There is a small trap hidden in that example. When I run ls -l /proc/self/exe, self refers to the ls process doing the lookup, not necessarily to my shell. I may therefore see something such as /usr/bin/ls.
If I want to compare that with Bash itself, I can ask Bash for its own PID and inspect that explicitly:
readlink "/proc/$$/exe"
/proc/self is therefore not a fixed alias for one particular PID.
It means, effectively:
the
/procentry belonging to the process looking at me.
That feels very Linux.
The model is getting richer
A few weeks ago my mental model of a process was basically:
program
↓
running program
After wandering through /proc, it looks more like:
PROCESS 4187
│
├── identity
│ ├── PID
│ └── parent PID
│
├── execution
│ ├── program
│ └── command-line arguments
│
├── environment
│
├── current working directory
│
├── open file descriptors
│
├── memory mappings
│
└── current state
And /proc/4187/ exposes views into many of those things.
I think that’s the part I find most useful.
The process isn’t just “the code currently running”.
It is an operating-system-managed execution context with identity, state and resources attached to it.
/proc makes that context surprisingly tangible.
A tiny exploration recipe
This is probably the little sequence I’ll keep around while I’m learning:
sleep 120 &
pid=$!
printf 'PID: %s\n\n' "$pid"
grep -E '^(Name|State|Pid|PPid):' "/proc/$pid/status"
printf '\nExecutable:\n'
readlink "/proc/$pid/exe"
printf '\nWorking directory:\n'
readlink "/proc/$pid/cwd"
printf '\nCommand line:\n'
tr '\0' ' ' < "/proc/$pid/cmdline"
printf '\n'
printf '\nOpen file descriptors:\n'
ls -l "/proc/$pid/fd"
printf '\nFirst few memory mappings:\n'
head "/proc/$pid/maps"
kill "$pid"
One disposable sleep process, and quite a lot of Linux becomes visible.
Not bad for a directory I had previously ignored.
References and further reading
Linux man-pages: proc(5)
Overview of the /proc pseudo-filesystem and the system and process information exposed through it.
Linux man-pages: proc_pid(5)
Overview of the per-process /proc/<pid> directories.
Linux man-pages: proc_pid_status(5)
Documents the human-readable process status information exposed through /proc/<pid>/status.
Linux man-pages: proc_pid_cmdline(5)
Documents the command-line information exposed for a process.
Linux man-pages: proc_pid_environ(5)
Documents the initial execution environment exposed through /proc/<pid>/environ.
Linux man-pages: proc_pid_fd(5)
Documents the process’s open file descriptors.
Linux man-pages: proc_pid_maps(5)
Documents the virtual-memory mappings visible through /proc/<pid>/maps.
Linux man-pages: proc_pid_exe(5) and proc_pid_cwd(5)
Document the executable and current-working-directory entries used above.