summaryrefslogtreecommitdiff
path: root/kern/mm.c
blob: a1dfea66fc12c3958a3a700472baab646f59bc3e (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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/** @file mm.c
 *
 *  @brief memory management functions.
 *
 *  @author Hanjie Wu (hanjiew)
 *  @bug No functional bugs
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <assert.h>
#include <common_kern.h>
#include <x86/asm.h>

#include <asm_instr.h>
#include <common.h>
#include <mm.h>
#include <paging.h>
#include <sched.h>
#include <sync.h>

/** header of a free page */
typedef struct page_head_s {
    int size;
    pa_t prev;
    pa_t next;
} page_head_t;

/** footer of a free page */
typedef struct page_foot_s {
    char padding[PAGE_SIZE - sizeof(int)];
    int size;
} page_foot_t;

/** total user pages */
static int num_user_pages;

/** bitmap to track page usage */
static unsigned char* user_page_bitmap;

/** bins from 4k to 1m, which is normal request size */
#define NUM_BINS 9
/** minimum size of last bin */
#define LAST_BIN_SIZE (1 << (NUM_BINS - 1))
/** bins for free chunks */
static pa_t bins[NUM_BINS];

mutex_t mm_lock = MUTEX_INIT;

/**
 * @brief is a page inuse
 * @param pn physical page number
 * @return 1 inuse, 0 free
 */
static int is_page_inuse(int pn) {
    return (user_page_bitmap[(pn + 1) / 8] & (1 << ((pn + 1) % 8))) != 0;
}

/**
 * @brief mark a page inuse
 * @param pn physical page number
 */
static void set_page_inuse(int pn) {
    user_page_bitmap[(pn + 1) / 8] |= (1 << ((pn + 1) % 8));
}

/**
 * @brief mark a page free
 * @param pn physical page number
 */
static void set_page_free(int pn) {
    user_page_bitmap[(pn + 1) / 8] &= (~(1 << ((pn + 1) % 8)));
}

/**
 * @brief convert physical address to page number
 * @param pa physical address
 * @return physical page number
 */
static int pa_to_pn(pa_t pa) {
    return (pa - USER_MEM_START) / PAGE_SIZE;
}

/**
 * @brief convert page number to physical address
 * @param pn physical page number
 * @return physical address
 */
static int pn_to_pa(int pn) {
    return pn * PAGE_SIZE + USER_MEM_START;
}

/**
 * @brief find which bin a free chunk belongs to
 * @param size size of chunk
 * @return bin
 */
static int find_bin(int size) {
    if (size >= LAST_BIN_SIZE) {
        return NUM_BINS - 1;
    }
    int i;
    for (i = NUM_BINS - 2; i > 0; i--) {
        if ((size & (1 << i)) != 0) {
            return i;
        }
    }
    return 0;
}

/**
 * @brief add a chunk to bins
 * @param pn physical page number
 * @param size size of chunk
 */
static void add_to_bins(int pn, int size);

/**
 * @brief remove a chunk from bins
 * @param pn physical page number
 */
static void remove_from_bins(int pn);

/**
 * @brief allocate a memory from a bin
 * @param bin bin
 * @param size size of chunk
 * @return address of chunk, BAD_PA on failure
 */
static pa_t alloc_from_bin(pa_t* bin, int size);

void mm_init() {
    num_user_pages = machine_phys_frames() - (USER_MEM_START / PAGE_SIZE);
    assert(num_user_pages > 0);
    user_page_bitmap = smalloc((num_user_pages + 2) / 8);
    assert(user_page_bitmap != NULL);
    memset(user_page_bitmap, 0, (num_user_pages + 2) / 8);
    memset(bins, 0, sizeof(bins));

    set_page_inuse(-1);
    set_page_inuse(num_user_pages);
    add_to_bins(pa_to_pn(USER_MEM_START), num_user_pages);
}

pa_t alloc_user_pages(int num_pages) {
    mutex_lock(&mm_lock);
    int bn = find_bin(num_pages);
    while (bn < NUM_BINS) {
        pa_t result = alloc_from_bin(&bins[bn], num_pages);
        if (result != BAD_PA) {
            mutex_unlock(&mm_lock);
            return result;
        }
        bn++;
    }
    mutex_unlock(&mm_lock);
    return BAD_PA;
}

void free_user_pages(pa_t pa, int num_pages) {
    mutex_lock(&mm_lock);
    int pn = pa_to_pn(pa);
    int final_pn = pn, final_size = num_pages;

    /* merge with prev */
    int prev_inuse = is_page_inuse(pn - 1);
    if (prev_inuse == 0) {
        int old_if = save_clear_if();
        page_foot_t* prev_foot =
            (page_foot_t*)map_phys_page(pn_to_pa(pn - 1), NULL);
        int prev_size = prev_foot->size;
        restore_if(old_if);
        int prev_pn = pn - prev_size;
        remove_from_bins(prev_pn);
        final_pn = prev_pn;
        final_size += prev_size;
    }

    /* merge with next */
    int next_pn = pn + num_pages;
    int next_inuse = is_page_inuse(next_pn);
    if (next_inuse == 0) {
        int old_if = save_clear_if();
        page_head_t* next_head =
            (page_head_t*)map_phys_page(pn_to_pa(next_pn), NULL);
        int next_size = next_head->size;
        restore_if(old_if);
        remove_from_bins(next_pn);
        final_size += next_size;
    }

    add_to_bins(final_pn, final_size);
    mutex_unlock(&mm_lock);
}

va_t map_phys_page(pa_t pa, pa_t* old_pa) {
    pte_t* pte = get_mapped_phys_page_pte();
    if (old_pa != NULL) {
        *old_pa = get_page_base(*pte);
    }
    *pte = make_pte(pa, 0, PTE_SUPERVISOR, PTE_RW, PTE_PRESENT);
    va_t va = get_mapped_phys_page();
    invlpg(va);
    return va;
}

/**
 * @brief insert a chunk to a bin
 * @param bin bin
 * @param this_chunk chunk
 * @param size size of chunk
 */
static void bin_insert(pa_t* bin, pa_t this_chunk, int size) {
    int old_if = save_clear_if();
    if (*bin == BAD_PA) {
        page_head_t* this_head = (page_head_t*)map_phys_page(this_chunk, NULL);
        this_head->next = this_head->prev = this_chunk;
        this_head->size = size;
    } else {
        pa_t head_chunk = *bin;
        page_head_t* head_head = (page_head_t*)map_phys_page(head_chunk, NULL);
        pa_t tail_chunk = head_head->prev;
        head_head->prev = this_chunk;
        page_head_t* this_head = (page_head_t*)map_phys_page(this_chunk, NULL);
        this_head->next = head_chunk;
        this_head->prev = tail_chunk;
        this_head->size = size;
        page_head_t* tail_head = (page_head_t*)map_phys_page(tail_chunk, NULL);
        tail_head->next = this_chunk;
    }
    restore_if(old_if);
    *bin = this_chunk;
}

/**
 * @brief remove a chunk from a bin
 * @param bin bin
 * @param this_chunk chunk
 */
static void bin_delete(pa_t* bin, pa_t this_chunk) {
    int old_if = save_clear_if();
    page_head_t* this_head = (page_head_t*)map_phys_page(this_chunk, NULL);
    pa_t next_chunk = this_head->next;
    if (*bin == this_chunk) {
        *bin = (next_chunk == this_chunk ? BAD_PA : next_chunk);
    }
    if (next_chunk != this_chunk) {
        pa_t prev_chunk = this_head->prev;
        page_head_t* next_head = (page_head_t*)map_phys_page(next_chunk, NULL);
        next_head->prev = prev_chunk;
        page_head_t* prev_head = (page_head_t*)map_phys_page(prev_chunk, NULL);
        prev_head->next = next_chunk;
    }
    restore_if(old_if);
}

static void add_to_bins(int pn, int size) {
    set_page_free(pn);
    set_page_free(pn + (size - 1));
    pa_t this_chunk = pn_to_pa(pn);
    pa_t* bin = &bins[find_bin(size)];

    bin_insert(bin, this_chunk, size);

    int old_if = save_clear_if();
    page_foot_t* this_foot =
        (page_foot_t*)map_phys_page(pn_to_pa(pn + (size - 1)), NULL);
    this_foot->size = size;
    restore_if(old_if);
}

static void remove_from_bins(int pn) {
    pa_t this_chunk = pn_to_pa(pn);
    int old_if = save_clear_if();
    page_head_t* this_head = (page_head_t*)map_phys_page(this_chunk, NULL);
    int size = this_head->size;
    restore_if(old_if);
    pa_t* bin = &bins[find_bin(size)];
    bin_delete(bin, this_chunk);
}

static pa_t alloc_from_bin(pa_t* bin, int size) {
    if (*bin == BAD_PA) {
        return BAD_PA;
    }
    pa_t r_pa = BAD_PA;
    pa_t r_pn = 0;
    pa_t this_chunk = *bin, end = *bin;
    do {
        int old_if = save_clear_if();
        page_head_t* this_head = (page_head_t*)map_phys_page(this_chunk, NULL);
        int this_size = this_head->size;
        pa_t next_chunk = this_head->next;
        restore_if(old_if);
        if (this_size > size) {
            bin_delete(bin, this_chunk);
            r_pa = this_chunk;
            r_pn = pa_to_pn(r_pa);
            add_to_bins(r_pn + size, this_size - size);
            break;
        } else if (this_size == size) {
            bin_delete(bin, this_chunk);
            r_pa = this_chunk;
            r_pn = pa_to_pn(r_pa);
            break;
        }
        this_chunk = next_chunk;
    } while (this_chunk != end);
    if (r_pa == BAD_PA) {
        return BAD_PA;
    }
    set_page_inuse(r_pn);
    set_page_inuse(r_pn + (size - 1));
    return r_pa;
}