In this article I am describing the difference between a process, thread and mutex.
Process
When and exe file or other executable file is run, some memory in RAM is allocated and the program’s code and data is read from the hard drive and written into RAM. While it’s being run, this program exists in two states: in volatile and non-volatile memory. This “container” is called a process and can be seen using such tools as Process Explorer or Process Hacker. Each process manages its resources (handles, memory). If you ran the same program twice, and two processes will be created, the will have different physical addresses but the same virtual addresses (more on that in another article).
Thread
Each process must have at least one thread. A thread is what actually gets executed. To go even further, each CPU core has two threads: main and idle. main is the thread where the instructions are processed, and idle thread. The idle thread is executed when nothing else is runnable. Its job is to activate architecture-specific features to lower the energy usage of the CPU. Thus whenever the scheduler is called due to the current thread leaving its CPU, another thread can always be found to run on that CPU, even if it is only the CPU’s idle thread.
Thread context - registry state information that is shared between different threads of the same process. Whenever OS switches between threads, registries (EAX
, ESP
etc) are saved in thread context. Each thread has its own registries and stack but shares code and other resources. A thread puts a lock on the CPU core and other threads cannot change registries in the meantime.
There are also fibers which are like threads but they share one thread context and managed by the process itself, not by OS.
Mutex
Some data can be shared between processes and threads. What if some different processes change the same file? How will OS handle the changes? Merge? Or priritize? Or may the file gets corrupted? This is how race condition issue arises (see below). To avoid confusions and problems, a lock is put on a resource once it was requested by a process. Up until it’s released by that process, the resource will be unavailable for other processes. That might sometimes cause deadlock issue ☠️ 🔒 (below). This locking 🔒 mechanism is performed by a mutex (Mutual Exclusion Object) object. The process has to acquire the lock on mutex object if it wants to acquire the resource. Mutex allow multiple program thread to access a single resource but not simultaneously.
Mutexes are often used by malware to make sure only one instance of it is run on the system. ❓ How does this help? When a malware starts asks the system to create a mutex object. The system creates the mutex object with a unique name (specified by the malware in OpenMutex
function) or ID. These names are sometimes good indicators of compromise and sometimes even help determine the APT in place. Whenever the program thread wants to use the resource it occupies lock on mutex object, utilizes the resource and after use, it releases the lock on mutex object. Then the next process is allowed to acquire the lock on mutex object.
Meanwhile, a process has acquired the lock on mutex object no other thread/process can access that resource. If the mutex object is already locked, the process desiring to acquire the lock on mutex object has to wait and is queued up by the system till the mutex object is unlocked.
Race condition
Say, we have two functions and a variable roses
🌹:
roses = 1
def triple_roses():
if roses == 1:
roses *= 3
return
def add_roses():
if roses == 1:
roses += 1
return
Let’s say that triple_roses()
and add_roses()
are run in separete threads. They both access roses
variable. First, add_roses()
accesses the variable and checks, whether there is a single 🌹. Since this variable is initialized with 1, yes. It’s okay for add_roses()
function and it evaluates the condition (if roses == 1
) to True
. However, before it executes roses += 1
, triple triple_roses()
gains access to the variable from another thread. Since roses
is still equal to 1, if roses == 1
condition of triple_roses()
also evaluates to True
and the next line of code is executed roses *= 3
. Now, there are 3 roses. Now, the add_roses()
executes its roses += 1
and now there are 4 roses. So, to summarize, roses
get triples first and then incremented by one.
❓ But what if add_roses()
executed its roses += 1
first, immediatly after if roses == 1
?
Then, roses
would be incremented by 1 first (it would become equal to 2), then triple_roses()
would evaluate this condition if roses == 1
to False
and the amount of roses would remain the same (2).
❓ But what if add_roses()
executed its roses += 1
after triple_roses()
has evaluated if roses == 1
, but before it executed roses *= 3
?
Then, roses
would be incremented by 1 first and then multiplied by 3 which would give us 6 roses.
❓ But what if triple_roses()
executed its roses *= 3
first, immediatly after if roses == 1
?
Then, roses
would be multiplied by 3 first (it would become equal to 3), then add_roses()
would evaluate this condition if roses == 1
to False
and the amount of roses would remain the same (3).
As you can see, too much depends on which statement was executed first. The program is unpredictible which is usually not desired for programs. They are not women after all 💃. It would be like a black box, you’ll never know, whether there are 4, 6, 3, or 1 roses out there… This would be scary world of course…
That’s why locks 🔒 on resources were introduced and that’s also how deadlocks ☠️ 🔒 were “introduced” as well.
Here is the picture from my old notebook about it:
Deadlock
Let’s say we have two functions pour_beer()
and drink_beer()
. Each of the functions needs an access to a 🍺. But there is only one 🍺 available. Say, drink_beer()
puts a lock on the 🔒 🍺 (using a mutex) and calls pour_beer()
. pour_beer()
needs an access to 🔒 🍺 as well and cannot return until the 🔒 🍺 is released, but it’s locked by drink_beer()
! On the other hand, drink_beer()
cannot return until it’s call to pour_beer()
returns some result and doesn’t release the 🔒 🍺. This is what a deadlock ☠️ 🔒 is. It looks something like below (I could not find the exact picture and I couldn’t then decide between the two, therefore I paste both):
void func1(){
get(c);
a = a+2
get(a);
}
void func2(){
put(a)
a = a+3
get()
}
I hope that clears things up a bit 😊.
References
https://hal.archives-ouvertes.fr/tel-02977242/document
https://techdifferences.com/difference-between-semaphore-and-mutex.html
https://stackoverflow.com/questions/34510/what-is-a-race-condition