2024-03-18
Sorting out PIDs, Tgids, and tasks on Linux
In the beginning, Unix only had processes and processes had process IDs (PIDs), and life was simple. Then people added (kernel-supported) threads, so processes could be multi-threaded. When you add threads, you need to give them some user-visible identifier. There are many options for what this identifier is and how it works (and how threads themselves work inside the kernel). The choice Linux made was that threads were just processes (that shared more than usual with other processes), and so their identifier was a process ID, allocated from the same global space of process IDs as regular independent processes. This has created some ambiguity in what programs and other tools mean by 'process ID' (including for me).
The true name for what used to be a 'process ID', which is to say
the PID of the overall entity that is 'a process with all its
threads', is a TGID (Thread or Task Group ID). The TGID of a
process is the PID of the main thread; a single-threaded program
will have a TGID that is the same as its PID. You can see this in
the 'Tgid:' and 'Pid:' fields of /proc/<PID>/status. Although some
places will talk about 'pids' as separate from 'tids' (eg some parts
of proc(5)
),
the two types are both allocated from the same range of numbers
because they're both 'PIDs'. If I just give you a 'PID' with no
further detail, there's no way to know if it's a process's PID or
a task's PID.
In every /proc/<PID> directory, there is a 'tasks' subdirectory;
this contains the PIDs of all tasks (threads) that are part of
the thread group (ie, have the same TGID). All PIDs have a /proc/<PID>
directory, but for convenience things like 'ls /proc' only lists
the PIDs of processes (which you can think of as TGIDs). The
/proc/<PID> directories for other tasks aren't returned by the
kernel when you ask for the directory contents of /proc, although
you can use them if you access them directly (and you can also
access or discover them through /proc/<PID>/tasks). I'm not sure
what information in the /proc/<PID> directories for tasks are
specific to the task itself or are in total across all tasks in the
TGID. The proc(5)
manual page sometimes talks about processes and sometimes about
tasks, but I'm not sure that's comprehensive.
(Much of the time when you're looking at what is actually a TGID, you want the total information across all threads in the TGID. If /proc/<PID> always gave you only task information even for the 'process' PID/TGID, multi-threaded programs could report confusingly low numbers for things like CPU usage unless you went out of your way to sum /proc/<PID>/tasks/* information yourself.)
Various tools will normally return the PID (TGID) of the overall process, not the PID of a random task in a multi-threaded process. For example 'pidof <thing>' behaves this way. Depending on how the specific process works, this may or may not be the 'main thread' of the program (some multi-threaded programs more or less park their initial thread and do their main work on another one created later), and the program may not even have such a thing (I believe Go programs mostly don't, as they multiplex goroutines on to actual threads as needed).
If a tool or system offers you the choice to work on or with a 'PID' or a 'TGID', you are being given the choice to work with a single thread (task) or the overall process. Which one you want depends on what you're doing, but if you're doing things like asking for task delay information, using the TGID may better correspond to what you expect (since it will be the overall information for the entire process, not information for a specific thread). If a program only talks about PIDs, it's probably going to operate on or give you information about the entire process by default, although if you give it the PID of a task within the process (instead of the PID that is the TGID), you may get things specific to that task.
In a kernel context such as eBPF programs, I think you'll almost always want to track things by PID, not TGID. It is PIDs that do things like experience run queue scheduling latency, make system calls, and incur block IO delays, not TGIDs. However, if you're selecting what to report on, monitor, and so on, you'll most likely want to match on the TGID, not the PID, so that you report on all of the tasks in a multi-threaded program, not just one of them (unless you're specifically looking at tasks/threads, not 'a process').
(I'm writing this down partly to get it clear in my head, since I had some confusion recently when working with eBPF programs.)