What is a thread?
The simplest way of thinking about a thread is just a function
running some code. When that function is done,
the thread is done as well, and it terminates.
However,
remember that we can have multiple threads running exactly the same function.
In fact, this is fairly common.
So it might be working on different data by doing the same
kind of operations by multiple threads.
That's fine.
So a function running code independently is something
we can associate with a thread.
What does a thread contain as part of its management aspects?
Here are some of those things.
One of them is the context.
The context includes the registers or the processor used by the thread
at this point in time. When we have something called a context switch
when a thread is removed from a processor because another thread has
to go in its place for reasons we'll discuss later in the course, then
the context has to be saved somewhere, and this is done on the thread
stack.
So the name next time that same thread gets some processor time, which
could be on a different processor, that state could be restored, and the
thread continues from exactly where it left off.
It doesn't really know or feel that anything has changed.
I don't know if threads feel anything, but they shouldn't feel that.
Then we have stacks.
The idea of a stack is the one that we're probably familiar
with where local variables are stored and return addresses
from functions are stored.
Now, a usable thread actually has two stacks.
One of them is in kernel space and the other one is in user space,
and we'll discuss that later on in this module.
Another thing that the thread might have,
but is not mandatory, is message queue and windows.
And the idea here is that a thread is created as a worker thread. It's
assumed it's going to be doing some computation staff,
maybe some I/O, and that is it.
However,
if the thread creates any kind of user interface or windows, those
rectangular areas or rounded rectangular areas on Windows 11,
then it gets a message queue. And the purpose of that message queue is
to receive all the messages related to UI activity.
In this case, the thread has the obligation of pumping messages,
listening to that message queue, and handling messages as they come in.
If it fails to do so,
then we might get to this very unpleasant situation where
we get something called not responding. The window or
windows in question become blurry,
not responding is added to the title bar, and so on.
Nobody likes not responding.
So the thread is responsible for pumping these messages.
I mentioned earlier that a thread normally uses the access token coming
from its parent process when it tries to access external resources such as
trying to access files or other kernel objects.
However, a thread can impersonate and assume a different access token for
a while in order to run on behalf of a different user.
This is something we'll discuss perhaps in a future course.
Now in terms of scheduling, we have threads that want to execute code; however,
there's usually a limited number of processors.
So if you have more threads that want to run and then available
processors, then something, of course, has to be done, and some
scheduling decisions have to be made.
Every thread has a bunch of properties, which are related to scheduling itself.
We'll discuss scheduling in the future model in this course,
but for now let's just briefly discuss the state
that is associated with scheduling.
The first thing is priority, which is probably the most obvious.
Every thread has a priority, which is a number between 0 and 31
where 31 is the highest. And as you might guess, a higher priority
thread has a better chance of executing than a lower priority
thread. We'll discuss scheduling,
as I mentioned, later in this course. Every thread has a state.
These are the three most common states for a thread.
Running is the case where a thread is currently executing
code on one of the processors.
So if I have X processors on the system, only X threads
at most can be in the running state.
The second state is ready.
Ready means that the thread wants to execute code, but currently all the
processors are unavailable for some reason. Maybe if I have X processors
and X + Y threads want to run, then obviously Y threads will have to be in
the ready state. They're ready to go.
They want to run; however, currently it's not their turn, and hopefully
later on, they'll get some processor time. And, of course, we'll discuss
some of these scheduling decisions later on.
And finally, the most common case or the most common state is waiting.
The thread doesn't want to run. It's waiting for something to
happen, maybe an I operation to complete,
maybe a message to appear in its message queue.
Maybe it's waiting on some kernel object that signifies
something. When it gets that something,
it will move from the waiting state to the ready state and
hopefully to the running state as soon as possible.
Every thread has an access mode, which is either user mode or kernel mode.
We discussed those quite a bit in the first course in this series,
so you might want to refer to that.
But in general, with user mode, you have limited access to the system.
Anything you're trying to access beyond the process gets security checks.
On the other hand,
when you run in kernel mode, you have the full power of the kernel
and essentially can access anything on the machine.