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 interface is read-only for this task: the commands below do not arm, disarm or delete another process's timers.
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.
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.
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.
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.
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.
sleep, timerfd or an arbitrary service will not populate it; the file lists POSIX timers only, and each program picks its own timing mechanism.rm -f /tmp/show-posix-timer.c /tmp/show-posix-timer /tmp/proc-12345-timers.txt; ordinary user cleanup, no elevated privileges needed./proc/<pid>/timers.