1. Introduction

Have you ever found yourself confused over the difference between a process and thread in the operating system? In this article, we’ll discuss the details of the process and thread in the context of Linux.

2. Process

A process is a computer program under execution. Linux is running many processes at any given time. We can monitor them on the terminal using the ps command or on the System Monitor UI. For instance, let’s see an example of using the ps command to view all the processes running on the machine:

[user@fedora ~]$ ps -ef 
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 Jun28 ?        00:00:16 /usr/lib/systemd/systemd --switched-root --system --deserialize 31
root           2       0  0 Jun28 ?        00:00:00 [kthreadd]
root           3       2  0 Jun28 ?        00:00:00 [rcu_gp]
root           4       2  0 Jun28 ?        00:00:00 [rcu_par_gp]
root           6       2  0 Jun28 ?        00:00:04 [kworker/0:0H-kblockd]
root           8       2  0 Jun28 ?        00:00:00 [mm_percpu_wq]
root           9       2  0 Jun28 ?        00:00:00 [rcu_tasks_kthre]
root          10       2  0 Jun28 ?        00:00:00 [rcu_tasks_rude_]
root          11       2  0 Jun28 ?        00:00:00 [rcu_tasks_trace]
root          12       2  0 Jun28 ?        00:00:11 [ksoftirqd/0]
root          13       2  0 Jun28 ?        00:01:24 [rcu_sched]
root          14       2  0 Jun28 ?        00:00:00 [migration/0]
root          16       2  0 Jun28 ?        00:00:00 [cpuhp/0]
root          17       2  0 Jun28 ?        00:00:00 [cpuhp/1]

As we run new commands/applications or the old commands complete, we can see the number of processes grow and shrink dynamically. Linux processes are isolated and do not interrupt each other’s execution.

With a PID, we can identify any process in Linux. Internally, the kernel uniquely allocates this number and releases it for reuse after the process exits. We can see PID as the second column in the output of the above ps command.

Since many processes are running at any given time in Linux, they have to share the CPU. The process of switching between two executing processes on the CPU is called process context switching. Process context switching is expensive because the kernel has to save old registers and load current registers, memory maps, and other resources.

3. Thread

A thread is a lightweight process. A process can do more than one unit of work concurrently by creating one or more threads. These threads, being lightweight, can be spawned quickly.

Let’s see an example and identify the process and its thread in Linux using the ps -eLf command. We’re interested in PID, LWP, and NLWP attributes:

  • PID: Unique process identifier
  • LWP: Unique thread identifier inside a process
  • NLWP: Number of threads for a given process
[user@fedora ~]$ ps -eLf 
UID          PID    PPID     LWP  C NLWP STIME TTY          TIME CMD
root           1       0       1  0    1 Jun28 ?        00:00:16 /usr/lib/systemd/systemd --switched-root --system --deserialize 31
root           2       0       2  0    1 Jun28 ?        00:00:00 [kthreadd]
root           3       2       3  0    1 Jun28 ?        00:00:00 [rcu_gp]
root           4       2       4  0    1 Jun28 ?        00:00:00 [rcu_par_gp]
root           6       2       6  0    1 Jun28 ?        00:00:05 [kworker/0:0H-acpi_thermal_pm]
root           8       2       8  0    1 Jun28 ?        00:00:00 [mm_percpu_wq]
root          12       2      12  0    1 Jun28 ?        00:00:11 [ksoftirqd/0]
root          13       2      13  0    1 Jun28 ?        00:01:30 [rcu_sched]
root          14       2      14  0    1 Jun28 ?        00:00:00 [migration/0]
root         690       1     690  0    2 Jun28 ?        00:00:00 /sbin/auditd
root         690       1     691  0    2 Jun28 ?        00:00:00 /sbin/auditd
root         709       1     709  0    4 Jun28 ?        00:00:00 /usr/sbin/ModemManager
root         709       1     728  0    4 Jun28 ?        00:00:00 /usr/sbin/ModemManager
root         709       1     729  0    4 Jun28 ?        00:00:00 /usr/sbin/ModemManager
root         709       1     742  0    4 Jun28 ?        00:00:00 /usr/sbin/ModemManager

We can easily identify single-threaded and multi-threaded processes by their NLWP values. PIDs 690 and 709 have an NLWP of 2 and 4, respectively. Hence, they are multi-threaded, with 2 and 4 threads. All other processes have an NLWP of 1 and are single-threaded.

On careful observation, we can see that single-threaded processes have the same PID and LWP values as if they are the same thing. However, in a multi-threaded process, only one LWP matches its PID, and the others have different values of LWP. Also, note that the value, once assigned to an LWP, is never given to another process.

3.1. Single-Threaded Process

Any thread created within the process shares the same memory and resources of the process. In a single-threaded process, the process and thread are the same, as there’s only one thing happening. We can also validate ps -eLf output from our previous discussion that PID and LWP are the same for the single-threaded process.

3.2. Multi-Threaded Process

In a multi-threaded process, the process has more than one thread. Such a process accomplishes multiple tasks simultaneously or almost at the same time.

As we know, the thread shares the same address space of the process. Therefore, spawning a new thread within a process becomes cheap (in terms of the system resources) compared to starting a new process. Threads also can switch faster (since they have shared address space with the process) compared to the processes in the CPU. Internally, the thread has only a stack in the memory, and they share the heap (process memory) with the parent process.

Due to this nature of thread, we also call it a Light-Weight Process (LWP).

There are both benefits and drawbacks of sharing the same memory with other threads.

The most important benefit is that we can create threads faster than processes since we don’t have to allocate memory and resources. The other benefit is the low cost of inter-thread communication.

Similar to process context switch, there is a concept of a thread context switch. A thread context switch is faster as the thread records only its stack values before the switch happens.

There is one major disadvantage: Since the threads share the same memory, they can become slow if the process does many concurrent tasks.

4. Process Internals

In this section, we’ll look into the internals of the Linux process and its implementation.

4.1. The System Calls

Linux has system calls defined for basic OS functions like file management, network management, process management, and others. Any valid Linux program uses these system calls. Hence, for the ease of application development, the GNU C library exposes them as an API.

We use fork (or clone) and execve system calls for creating a process in Linux. Here, the fork system call creates a child process equivalent to the parent process. The execve system call replaces the executable of the child process. In modern implementations, the fork system call internally uses the clone system call. Hence, we will focus more on the clone system call.

4.2. The Internal Structure

As Linux system users, we never have to create the internal data structure for the process. However, it’s essential to understand the Linux process’s internal data structure. Linux creates every process using a data structure in C called task_struct. The Linux kernel holds them in a dynamic list to represent all the running processes called tasklist. In this tasklist, each element is of task_struct type, which depicts a Linux process.

We categorize various fields in the task structure as scheduling parameters, memory image, signals, machine registers, system calls state, file descriptors, kernel stack, and so on. Hence, when we create a new process, the Linux kernel creates a new task_struct in kernel memory, pointing to the newly created process.

4.3. The Creation Flow

Let’s trace the ls command process to visualize the process creation flow. We’ll use the strace command to trace the calls:

[pathak_home@toolbox ~]$ strace -f -etrace=execve,clone bash -c '{ ls; }'
execve("/usr/bin/bash", ["bash", "-c", "{ ls; }"], 0x7fff153d0ed0 /* 36 vars */) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLDstrace: Process 115538 attached
, child_tidptr=0x7fe8b4f10a10) = 115538
[pid 115538] execve("/usr/bin/ls", ["ls"], 0x55eed8e42be0 /* 36 vars */) = 0
Desktop  Documents  Downloads  Dropbox	IdeaProjects  Music  Pictures  Public  Templates  Videos  revision  soft
[pid 115538] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=115538, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

We can observe from the output above that it takes two steps to create a process:

  • The clone system call creates the process as a clone of the bash process
  • The execve system call then replaces the executable in the process with the ls command binary

Another important point is that even though we say the child process is a copy/clone of the parent with the same address space, implementation-wise, Linux does not copy the child’s memory until the child writes. This clever implementation of the process in Linux saves RAM space and avoids unnecessary memory allocation. This implementation is also called Copy on Write (COW).

4.4. The Process Tree

In the strace result above, we notice that a process internally calls clone after the execve executes bash -c ls. This process flow indicates that the bash process is the parent of the ls command.

Similarly, the parent process creates every process in Linux except the PID 1 (INIT process). The pstree command helps us to visualize the process hierarchy:

[user@fedora ~]$ pstree
systemd─┬─ModemManager───3*[{ModemManager}]
        ├─NetworkManager───2*[{NetworkManager}]
        ├─accounts-daemon───3*[{accounts-daemon}]
        ├─2*[agetty]
        ├─alsactl
        ├─auditd───{auditd}
        ├─avahi-daemon───avahi-daemon
        ├─bluetoothd
        ├─chronyd
        ├─colord───3*[{colord}]
        ├─cupsd
        ├─dbus-broker-lau───dbus-broker
        ├─firewalld───{firewalld}
        ├─gdm─┬─gdm-session-wor─┬─gdm-wayland-ses─┬─gnome-session-b───3*[{gnome-session-b}]
        │     │                 │                 └─2*[{gdm-wayland-ses}]
        │     │                 └─2*[{gdm-session-wor}]
        │     └─2*[{gdm}]
        ├─gnome-keyring-d───3*[{gnome-keyring-d}]
        ├─gssproxy───5*[{gssproxy}]
        ├─low-memory-moni───2*[{low-memory-moni}]

5. Thread Internals

Like with processes, we use the clone system call to create a thread. The clone system call is very versatile. We can even create things that are neither process nor thread by definition. Let’s look at the signature of the clone system call:

pid = clone(function, stack ptr, sharing flags, arg);

The clone system call uses the CLONE flag we provide to determine process or thread creation:

CLONEFlags

We’ll use the above table as a reference to create a process or thread using a clone system call.

For instance, if we want to create a thread, we set the CLONE_VM flag. Similarly, to create a process, we unset the CLONE_VM flag.

By providing the CLONE_VM flag, we instruct the clone command to share the parent process memory with the child. There are other flags that, when set, create new threads or their variants with the shared file system information (CLONE_FS), the opened files (CLONE_FILES), and so on.

Since we can use clone in various ways because of its different flags, we have implementation standards for creating portable threads across all Unix and Linux variants.

The POSIX (Portable Operating System Interface) is one such standard that defines API, shell commands, and utility interfaces for compatibilities in Unix, Linux, and their variants. We recommend using the pthread_create API call from POSIX for portability reasons.

6. Differences Between Process and Thread

Let’s review the differences between processes and threads in the Linux context:

Process Thread
A process is heavyweight. A thread is a lightweight process also called an LWP.
A process has its own memory. A thread shares the memory with the parent process and other threads within the process.
Inter-process communication is slower due to isolated memory. Inter-thread communication is faster due to shared memory.
Context switching between processes is expensive due to saving old and loading new process memory and stack info. Context switching between threads is less expensive due to shared memory.
An application with several processes for its components can provide better memory utilization when memory is scarce. We can assign low priority to inactive processes in the application. This idle process is then eligible to be swapped to disk. This keeps the active components of the application responsive. When memory is scarce, the multi-threaded application does not provide any provision to manage memory.

7. Conclusion

In this article, we began with understanding the basics of the process and thread in Linux.

After that, we learned how to view all the running processes and identify the single-threaded and multi-threaded processes. We then explored the internal structure of Linux processes and how Linux creates a process by tracing the example process flow with the help of the strace command. And then, we discussed a variety of clone system calls we can use to create processes and threads.

Finally, we summarized the differences between a process and a thread.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.