summaryrefslogtreecommitdiff
path: root/src/kernel/spinlock.cc
blob: 55dfc6b3a80f7da54539549517d4981cf813d795 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#include "kernel/spinlock.h"
#include "kernel/riscv.h"
#include "kernel/cpu.h"
#include "kernel/proc_def.h"
#include "drivers/uart.h"

extern struct cpu cpus[];

namespace ProcManager
{
    void sleep(void *chan, Spinlock *lk);
    void wakeup(void *chan);
}

// Interrupt Depth Management
void push_off(void);
void pop_off(void);

// // Get the pointer of the current CPU
// // Depends on r_tp() in riscv.h to read the tp register
// struct cpu *mycpu()
// {
//     int id = r_tp();
//     return &cpus[id];
// }

// Get the current process
// Must be called with interrupts disabled, or read atomically in a safe manner
struct Proc *myproc()
{
    push_off();
    struct cpu *c = mycpu();
    struct Proc *p = c->proc;
    pop_off();
    return p;
}

// Spinlock Implementation

void Spinlock::init(const char *lock_name)
{
    name = lock_name;
    locked = 0;
    cpu = nullptr;
}

void Spinlock::acquire()
{
    push_off(); // Disable interrupts: prevent deadlocks (interrupt handler requests a lock that is already held)

    if (holding())
    {
        Drivers::uart_puts("panic: recursive acquire ");
        Drivers::uart_puts(name);
        Drivers::uart_puts("\n");
        while (1)
            ;
    }

    // Atomic Exchange
    // __ATOMIC_ACQUIRE: Ensures that reads and writes after the acquire are not reordered before the acquire
    while (__atomic_exchange_n(&locked, 1, __ATOMIC_ACQUIRE))
    {
        // Busy-wait (Spinning)
        // Optimization: Read-only, no writes, reducing cache coherence traffic
        while (__atomic_load_n(&locked, __ATOMIC_RELAXED) == 1)
        {
            asm volatile("nop"); // RISC-V indicates that the CPU is in a spin state
        }
    }

    // Record holder
    cpu = mycpu();
}

void Spinlock::release()
{
    if (!holding())
    {
        Drivers::uart_puts("panic: release not holding\n");
        while (1)
            ;
    }

    cpu = nullptr;

    // Atomic Store
    // __ATOMIC_RELEASE: Ensures all reads and writes before the Release are completed
    __atomic_store_n(&locked, 0, __ATOMIC_RELEASE);

    pop_off();
}

// Check if the current CPU holds the lock
bool Spinlock::holding()
{
    // Interrupt checks must be disabled, otherwise a process switch may occur causing CPU fluctuations.
    int r;
    push_off();
    r = (locked && cpu == mycpu());
    pop_off();
    return r;
}


// Mutex Implementation (Sleep Locks)

void Mutex::init(const char *lock_name)
{
    name = lock_name;
    locked = 0;
    pid = 0;
    lk.init("mutex_internal");
}

void Mutex::acquire()
{
    lk.acquire(); // Acquire the internal lock to protect the locked field

    while (locked)
    {
        // Lock is held, preparing to sleep
        // Atomicity contract of sleep():
        // 1. Mark the current process as SLEEPING
        // 2. Release lk (so others can operate on locked)
        // 3. Switch CPU (context switch)
        // 4. After being woken up, reacquire the lk lock and return
        ProcManager::sleep(this, &lk);
    }

    locked = 1;
    pid = myproc()->pid;

    lk.release();
}

void Mutex::release()
{
    lk.acquire();

    locked = 0;
    pid = 0;

    // Wake up all processes sleeping on this lock (chan = this)
    ProcManager::wakeup(this);

    lk.release();
}

bool Mutex::holding()
{
    lk.acquire(); // Must use a lock when reading to prevent race conditions.
    bool r = locked && (pid == myproc()->pid);
    lk.release();
    return r;
}

// Interrupt Depth Management

void push_off(void)
{
    int old = intr_get();
    intr_off();

    struct cpu *c = mycpu();

    if (c->noff == 0)
        c->intena = old;

    c->noff += 1;
}

void pop_off(void)
{
    struct cpu *c = mycpu();

    if (intr_get())
    {
        Drivers::uart_puts("panic: pop_off - interruptible\n");
        while (1)
            ;
    }
    if (c->noff < 1)
    {
        Drivers::uart_puts("panic: pop_off count\n");
        while (1)
            ;
    }

    c->noff -= 1;

    // An interrupt is only truly enabled when the nesting level returns to zero and the outermost layer allows interrupts.
    if (c->noff == 0 && c->intena)
        intr_on();
}