Inspect POSIX Timers Through /proc
A process claims to have scheduled work and you want proof: /proc/pid/timers lists the POSIX timers it actually owns. You will learn to identify the notification target and clock, and avoid treating the displayed ID as the value returned by timer_create().
The route
Jump straight to the step you need, or tick off Done means at the end.
The interface is read-only for this task: the commands below do not arm, disarm or delete another process's timers.
Before you start
This guide uses the proc_pid_timers(5) page from the installed Debian manpages package, version 6.7-2. The file has existed since Linux 3.10, but it is present only when the kernel has CONFIG_CHECKPOINT_RESTORE enabled. You need a shell, a C compiler for the optional reproducible example, and about 10 minutes. Reading a process that belongs to another user may be restricted by the system's procfs and ptrace access policy, so begin with a process you own.
1. Check that the interface is available
Start with your own process. /proc/self means the process accessing the path, so this avoids a stale PID while you are learning the interface.
test -r /proc/self/timers && echo "timer view is available" || echo "timer view is unavailable"
cat /proc/self/timers
An empty result is valid. It means the shell process currently has no POSIX timers, not that the command failed. If the test says the view is unavailable, check the kernel configuration before debugging an application:
grep '^CONFIG_CHECKPOINT_RESTORE=' /boot/config-$(uname -r) 2>/dev/null || zgrep '^CONFIG_CHECKPOINT_RESTORE=' /proc/config.gz 2>/dev/null || true
A missing configuration file is inconclusive. Do not create or change kernel configuration just to make this diagnostic file appear.
Checkpoint
You have either read an empty file successfully or recorded that this kernel does not expose the interface.
2. Inspect a known process by PID
For a long-running program, replace 12345 with its numeric PID. Read the file directly first, then preserve a snapshot if the program may create or delete timers while you inspect it.
target_pid=12345
test -d "/proc/$target_pid" || { echo "PID does not exist"; exit 1; }
cat "/proc/$target_pid/timers" | tee "/tmp/proc-$target_pid-timers.txt"
Each timer occupies four lines and starts with ID:. A process with two timers therefore normally has eight lines. The snapshot can become stale immediately: timer creation and deletion are properties of the target program, and a PID can later be reused. Use it as a diagnostic record, not as a live API.
For a quick count of the records in the snapshot:
awk '$1 == "ID:" { count++ } END { print count + 0, "POSIX timer(s)" }' "/tmp/proc-$target_pid-timers.txt"
This count is only meaningful if the read completed and the file was not changing underneath you. If cat reports No such file or directory, the process probably exited. If it reports Permission denied, retry as the same user that owns the target process or choose a process you can inspect; do not weaken procfs permissions for a one-off check.
3. Read the four fields
Consider this representative record:
ID: 1
signal: 60/00007fff86e452a8
notify: signal/pid.2634
ClockID: 0
ID: 1 is the kernel-internal timer identifier exposed through the si_timerid member of siginfo_t. It is deliberately not the timer ID returned by timer_create(2). Do not pass this number back to an application's timer API without checking that program's own bookkeeping.
The signal line contains the signal number, followed by the sigev_value supplied when the signal-notification timer was created. This line is meaningful for signal notification. Its numeric value is application data, not a duration or a process ID.
The notify line describes delivery. The first part is signal, thread or none. After the slash, pid identifies ordinary process-directed delivery, while tid identifies SIGEV_THREAD_ID delivery. The number after the dot is the receiving process ID or kernel thread ID when a signal is delivered.
ClockID: 0 is the clock used to measure the timer. For most clocks it matches a user-space CLOCK_* constant from <time.h>. CPU-time timers are special: process CPU time is displayed as -6, and thread CPU time as -2.
4. Create a short-lived timer to test the whole path
If you need a non-empty file, run this small helper. It creates one process CPU-time POSIX timer, prints its PID, and waits until you interrupt it. Compile it in /tmp, where the example can be removed without touching a project.
cat > /tmp/show-posix-timer.c <<'EOF'
#define _GNU_SOURCE
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
int main(void) {
timer_t timer;
struct sigevent event = { .sigev_notify = SIGEV_NONE };
if (timer_create(CLOCK_PROCESS_CPUTIME_ID, &event, &timer) == -1) {
perror("timer_create");
return 1;
}
printf("PID %ld\n", (long)getpid());
fflush(stdout);
pause();
timer_delete(timer);
return 0;
}
EOF
cc -Wall -Wextra -O2 -o /tmp/show-posix-timer /tmp/show-posix-timer.c
/tmp/show-posix-timer
Leave that process running in one terminal. In a second terminal, substitute the printed PID:
cat /proc/12345/timers
You should see an ID: record with notify: none/... and ClockID: -6. The exact internal ID and other values are not fixed. The program has changed only its own process state. Press Ctrl+C in the first terminal to end it; the kernel then removes the process and its timer. If compilation fails, skip this step and inspect a real application instead.
Common traps and safe cleanup
- An empty file proves nothing.
sleep,timerfdor an arbitrary service will not populate it; the file lists POSIX timers only, and each program picks its own timing mechanism. - ClockID is not a timer ID. It selects the time source, and negative values have documented CPU-time meanings.
- Treat the snapshot as perishable. Handle process exit and concurrent timer changes; a disappearing PID or a failed read is a retryable observation, not a bug.
- Clean up after yourself. Remove the test files with
rm -f /tmp/show-posix-timer.c /tmp/show-posix-timer /tmp/proc-12345-timers.txt; ordinary user cleanup, no elevated privileges needed.
Done means
- Confirmed the interface exists. You checked whether your kernel exposes
/proc/<pid>/timers. - Read the four-line record. You did it without confusing kernel IDs, signal payloads and clocks.
- Checked delivery and clocks. You looked at notification delivery and the special CPU-time clock values.
- Left no mess. You kept the inspection read-only and removed any temporary test files.