Lock is basically just a semaphore whose initial counter is 1. lock_acquire
is
like P
, while lock_release
is like V
. You probably want to go over
my previous post about semaphore
Lock's holder
However, since only one can hold the lock at any given time, that guy is considered to be the holder of this lock. While in semaphore, we don't have such a holder concept since multiple thread can "hold" the semaphore at the same time.
Thus we need to store the holder information in our lock structure, along with the
conventional spin lock and wait channel. Intuitively, you may tempted to use the
thread name (curthread->t_name
) as the thread's identifier. Nevertheless, same
with the case in real world, the thread's name isn't necessarily unique. The
OS161 doesn't forbidden us to create a bunch of threads with the same name.
There is a global variable defined in $OS161_SRC/kern/include/current.h
named curthread
, which is a pointer to the kernel data structure of current
thread. Two different threads definitely have different thread structures
(hence different pointers), which makes the pointer to thread structure a good
enough thread identifier.
Reentrant Lock
Another trick thing is to decide whether we support reentrant lock or not. Basically, a process can acquire a reentrant lock multiple times without blocking itself.
At first glance, you may wonder what kind of dumb thread would acquire a lock
multiple times anyway? Well, that kind of thread does exist, and they may not be
dumb at all. Reentrant lock is useful when it's difficult for a thread to track
whether it has grabbed the lock. Suppose we have multiple threads that traverse
a graph simultaneously, and each thread need to first grab the lock of a node
before it can visit that node. If the graph has a circle or there are multiple
paths leads to the same node, then it's possible that a thread visit the same
node twice. Although there is a function named lock_do_i_hold
that can tell
whether a thread holds a lock or not, unfortunately it's not a public interface of lock.
In OS161, it's OK that you choose to not support reentrant lock, so when you detect a thread try to acquire a lock while it's the lock's holder, just panic. But if you want to support reentrant lock, you need to make sure a thread won't accidentally loose a lock. For example,
void A(void) {
lock_acquire(lock1);
B();
lock_release(lock1);
}
void B(void) {
lock_acquire(lock1);
printf("Hello world!");
lock_release(lock1);
}
In this case, the thread is supposed to still hold the lock after B returns.
The simplest way would be, keep a counter (initial value 0) for each lock. When a thread acquires a lock, increase that counter. When it release the lock, decrease the counter, only actually release a lock when the counter reaches 0.