Threads and processes don't exist
On Linux, threads and processes don’t actually exist. Underneath all the abstractions, the kernel knows only of “tasks” - independent flows of execution, created using the clone(2)
family of syscalls. The differences between what we call a “thread” and what we call a “process” lie in the flags
argument of the clone syscall, which determines many kinds of properties of the eventually spawned task.
The most notable of those is the sharing of virtual memory space, which is controlled by the CLONE_VM
flag of the argument.
This is also evident by examining the relevant manpages. For example fork(2)
states:
Since glibc 2.3.3, rather than invoking the kernel’s fork() system call, the glibc fork() wrapper that is provided as part of the NPTL threading implementation invokes clone(2) with flags that provide the same effect as the traditional system call. (A call to fork() is equivalent to a call to clone(2) specifying flags as just SIGCHLD.) The glibc wrapper invokes any fork handlers that have been established using pthread_atfork(3).
It seems that a fork
linux syscall did exist at some point in time, was eventually superseded by clone
, and now “forking” has taken on the meaning of calling the libc fork
wrapper which then
invokes the clone(2)
syscall.
But it can also be very easily proven by tracing a program which calls fork()
:
1#include <unistd.h>
2
3int main(void) {
4 fork();
5}
The strace
output of this quite useless program includes, at the very bottom, the following line:
1clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7509741d5a10) = 10463
Note the lack of the CLONE_VM
flag in the flags
argument. Forking creates what is referred to as a “process”, which again does not share memory space with the parent.
pthreads
On Linux, the pthreads
specification is implemented by NPTL (“Native POSIX Thread Library”), which has been part of glibc for some time.
Much like with fork
, its functionality is implemented by relying on clone(2)
. We can confirm this by browsing through Glibc’s NPTL source-code,
and observing that a call to pthread_create(3)
eventually culminates in a call to clone()
, or rather to some sort of internal wrapper which then eventually
calls clone(2)
.
Or we can of course prove it by running and observing this small program:
1#include <unistd.h>
2#include <pthread.h>
3
4void* routine(void *arg);
5
6int main(void) {
7 pthread_t threadId;
8
9 pthread_create(&threadId, NULL, &routine, NULL);
10}
11
12void* routine(void *arg) {
13 return NULL;
14}
… whose strace
output includes the following:
1clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, child_tid=0x7162afa8e990, parent_tid=0x7162afa8e990, exit_signal=0, stack=0x7162af28e000, stack_size=0x7fff80, tls=0x7162afa8e6c0} => {parent_tid=[14807]}, 88) = 14807
Note the presence of the CLONE_VM
flag in the flags
argument, as different “threads” of the same processes conventionally share the same memory space.
There’s lots more to talk about here, such as the existence of “thread groups” and such, but I just wanted to highlight this very interesting fact.