How does the process title works?
Sept. 1, 2018, 12:43 p.m.Let's start by defining what the title of a process is - in this article, we will understand the name reported by ps(1)/top(1). When we are creating a new process using fork(2) the process inherits the name from its parent. In the scenario when we call exec(2) function we also pass the list of the arguments for a process, which will be treated as a process title. What if we would like to change the title of the process when it's running? It turns out that many operating systems do it in a different way. In this article we will discuss how open source operating systems like FreeBSD, Linux, OpenBSD, NetBSD, and DragonFlyBSD do it.
FreeBSD
Before jumping into the topic of how to change the process title, first let's look at how the programs like top(1) or ps(1) create a list of processes. Instead of reading code (which we should probably do), let's use ktrace(1) do analyze what ps(1) do in FreeBSD.
ps CALL getuid
ps RET getuid 1001/0x3e9
ps CALL __sysctl(0x7fffffffd550,0x4,0,0x7fffffffd538,0,0)
ps SCTL "kern.proc.uid.1001"
ps RET __sysctl 0
ps CALL mmap(0,0x15000,0x3<PROT_READ|PROT_WRITE>,0x1002<MAP_PRIVATE|MAP_ANON>,0xffffffff,0)
ps RET mmap 34367434752/0x800757000
ps CALL __sysctl(0x7fffffffd550,0x4,0x800757980,0x7fffffffd538,0,0)
ps SCTL "kern.proc.uid.1001"
ps RET __sysctl 0
Above we have the first suspect - `kern.proc.uid` sysctl. This sysctl is used to gather all processes of the given user. In this case the user of uid 1001. There a multiple different oid's which allow one to filter the list of processes. In this specific case, we can use `kern.proc.proc` which will return all processes (no threads). As you may notice, we call the same sysctl twice. The first call is to obtain the number of processes, and the second one is two obtain a list of them - this is a common pattern for obtaining data from a sysctl of unknown size.
Let's look into the output of ktrace(1) one more time:
ps CALL __sysctl(0x7fffffffd5f0,0x4,0x8007ae000,0x7fffffffd5e8,0,0)
ps SCTL "kern.proc.args.41402"
ps RET __sysctl 0
This time we use `kern.proc.args.PID` sysctl to finally obtain the process argument list, which also contains the process title.
In FreeBSD, like in all the BSD operating system family, there is a setproctitle(3) function in the libc library. The function first appears in FreeBSD 2.2. The idea of this function was borrowed from the sendmail. Thesetproctitle(3) function does a few things. The first is that it gets the "kern.ps_strings" sysctl. The returned value is a pointer to a ps_strings structure. By modifying the value of this structure, we are changing the process memory - but this is only done for the compatibility purpose in this scenario. Another step is to set `kern.proc.args.PID` sysctl - which really changes the process name.
From FreeBSD 12.0, there exists one additional function - setproctitle_fast(3). In the case of the setproctitle - sysctl is set to the value of the new name of the function. When the setproctitle_fast(3) is called, the sysctl is called but the title is set to an empty string. This is done to notify kernel to not look into internal kernel structures but to read the process memory to get the process title. Every next call to setproctitle_fast(3) only changes the value of the ps_string structure. This saves some precious syscalls in case of changing the process title.
In all BSDs, the string given to the setproctitle(3) will be preceded by the value of the progname which can be changed using setprogname(3) and obtained using getprogname(3). The setprogrname(3) function doesn't send any signal to the kernel; it only changes the memory of the process. Changing the progname will not be automatically reflected in the ps(1)/top(1) output. To notice kernel about change of progname - additional setproctitle(3) call should be done.
Source code of FreeBSD setproctitle(2) and setproctitle_fast(2).
GNU/Linux
The most popular implementation of ps(1) in the GNU/Linux operating system is a part of the procpsand can be found here. Let's look like it works using the strace(1) program.
stat("/proc/1304", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/1304/stat", O_RDONLY) = 6
read(6, "1304 (bash) S 1183 1304 1183 108"..., 2048) = 331
close(6) = 0
openat(AT_FDCWD, "/proc/1304/status", O_RDONLY) = 6
read(6, "Name:\tbash\nUmask:\t0002\nState:\tS "..., 2041999.8) = 1333
close(6) = 0
As we can see, the ps(1) depends straightly onprocfs. The procfs is a virtual file system allowing one to access kernel data from the userland. In this case, stat and status file are read from the /proc/PID/ directory, and both files contain similar data. The difference is that status contains data in a human-readable format.
The implementation of the status file can be found in the fs/proc/array.c. To obtain the name of the process, the proc_task_name function from that file is used. The function reads the internal structure of task which contains the executable name. This value can be controlled by prctl (PR_SET_NAME).
This is how ps(1) works - by default it returns only the binary name. Let's try to get some more information from it using ps(1) with additional flag 'u' - which force it to print a user-oriented output.
stat("/proc/6061", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc/6061/stat", O_RDONLY) = 6
read(6, "6061 (a.out) S 1304 6061 1183 10"..., 2048) = 307
close(6) = 0
openat(AT_FDCWD, "/proc/6061/status", O_RDONLY) = 6
read(6, "Name:\ta.out\nUmask:\t0002\nState:\tS"..., 2048) = 1331
close(6) = 0
openat(AT_FDCWD, "/proc/6061/cmdline", O_RDONLY) = 6
read(6, "a.out\0", 131072) = 8
read(6, "", 131064) = 0
close(6) = 0
Additional file cmdline from the procfswas read. As the name suggests, this file contains a completed command line for the process. Let's look under the hood to see what happened. The cmdline pseudo-file is implemented in the fs/proc/base.c file. The function responsible for getting the cmdline is get_mm_cmdline, which reads process memory - the address of the argv.
After all this analysis, finally we know that to change a process title (in this particular case more command line, which will be presented to the user as the title) we need to manipulate argv from the main function. If you don't like this approach you can use libbsd under GNU/Linux, which implements a setproctitle(3) under it.
OpenBSD
Like all BSDs, OpenBSD also implements a setproctitle(3) function in the libc library, although the implementation looks a little bit different and more compact than the one from FreeBSD. In this version the setproctitle(3) function gets a `vm.vm_psstring` sysctl. The sysctl returns a pointer to the ps_strings structure which contains a reference to the current name of the process. While modifying the process memory, the name is changed too. The address returned by sysctl doesn't change, so OpenBSD cache returned value. All next calls of the setproctitle(3) function don't do any additional syscalls.
Implementation of the OpenBSD setproctitle(2).
NetBSD
The NetBSD project also uses a setproctitle(3) function to change the name of a process. The difference between OpenBSD and NetBSD is that it doesn't do any syscall to obtain a pointer. The address of the ps_string structure is set in the __start function called before the main().
The source code of the NetBSD setproctitle(2).
DragonFlyBSD
At the time of publishing this post, DragonFlyBSD does the same thing as FreeBSD. It's use a `kern.proc.args.PID` sysctl to set a new process title, as well as `kern.ps_strings` to fetch the address in process memory, The DragonFlyBSD doesn't implement the setproctitle_fast(3) function.
You can analyze DragonFlyBSD setproctitle(2) here.
Summary
Even such a simple thing as changing process title can be implemented with a different philosophy. All the major open source operating systems do it in a different way. The motivation for this research was a recent change in FreeBSD. It turns out that it matters if setting the process title requires additional syscalls. Applications like PostgreSQL change the process titles very often. In cases when the additional syscall was required, the performance drop was noticeable. This is why the r335939 FreeBSD introduced an additional fast way of changing the process title.