Skip to content

Commit 5a1e856

Browse files
sparchatuswipawel
authored andcommitted
pagetables,pmm: fix deadlock
1. paging gets exclusive pmm access during array refill. 2. pmm can call back into paging while holding the paging lock. 3. paging performs pmm refill if needed to ensure reserved frames. Signed-off-by: Sandro Rüegge <rueegges@ethz.ch>
1 parent c08a11a commit 5a1e856

File tree

4 files changed

+147
-25
lines changed

4 files changed

+147
-25
lines changed

arch/x86/pagetables.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ static mfn_t get_cr3_mfn(cr3_t *cr3_entry) {
200200
void *cr3_mapped = NULL;
201201

202202
if (mfn_invalid(cr3_entry->mfn)) {
203-
frame_t *frame = get_free_frame();
203+
frame_t *frame = get_free_frame_norefill();
204204
BUG_ON(!frame);
205205
frame->flags.pagetable = 1;
206206

@@ -243,7 +243,7 @@ static mfn_t get_pgentry_mfn(mfn_t tab_mfn, pt_index_t index, unsigned long flag
243243

244244
mfn = mfn_from_pgentry(*entry);
245245
if (mfn_invalid(mfn)) {
246-
frame_t *frame = get_free_frame();
246+
frame_t *frame = get_free_frame_norefill();
247247
BUG_ON(!frame);
248248
frame->flags.pagetable = 1;
249249

@@ -308,6 +308,7 @@ static void *_vmap(cr3_t *cr3_ptr, void *va, mfn_t mfn, unsigned int order,
308308
invlpg(va);
309309

310310
done:
311+
refill_from_paging();
311312
return va;
312313
}
313314

@@ -420,6 +421,19 @@ void *vmap_4k(cr3_t *cr3_ptr, void *va, mfn_t mfn, unsigned long l1_flags,
420421
return va;
421422
}
422423

424+
void *vmap_4k_nolock(cr3_t *cr3_ptr, void *va, mfn_t mfn, unsigned long l1_flags,
425+
bool propagate_user) {
426+
unsigned long _va = _ul(va) & PAGE_ORDER_TO_MASK(PAGE_ORDER_4K);
427+
428+
dprintk("%s: va: 0x%p mfn: 0x%lx\n", __func__, va, mfn);
429+
430+
ASSERT(vmap_lock);
431+
va = _vmap_4k(&cr3, _ptr(_va), mfn, l1_flags, propagate_user);
432+
ASSERT(vmap_lock);
433+
434+
return va;
435+
}
436+
423437
static inline void init_tmp_mapping(void) {
424438
pte_t *tab = get_l1_table(_tmp_mapping);
425439
_tmp_mapping_entry = (pgentry_t *) l1_table_entry(tab, _tmp_mapping);

include/arch/x86/pagetable.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ extern void *vmap_2m(cr3_t *cr3_ptr, void *va, mfn_t mfn, unsigned long l2_flags
178178
bool propagate_user);
179179
extern void *vmap_4k(cr3_t *cr3_ptr, void *va, mfn_t mfn, unsigned long l1_flags,
180180
bool propagate_user);
181+
/* this function may only be called while already holding the vmap_lock */
182+
extern void *vmap_4k_nolock(cr3_t *cr3_ptr, void *va, mfn_t mfn, unsigned long l1_flags,
183+
bool propagate_user);
181184

182185
extern int vunmap_kern(void *va, mfn_t *mfn, unsigned int *order);
183186
extern int vunmap_user(void *va, mfn_t *mfn, unsigned int *order);
@@ -360,6 +363,11 @@ static inline void *vmap_kern_4k(void *va, mfn_t mfn, unsigned long l1_flags) {
360363
return vmap_4k(&cr3, va, mfn, l1_flags, false);
361364
}
362365

366+
/* Same as vmap_kern_4k but does not take vmap_lock. Callers must hold vmap_lock! */
367+
static inline void *vmap_kern_4k_nolock(void *va, mfn_t mfn, unsigned long l1_flags) {
368+
return vmap_4k_nolock(&cr3, va, mfn, l1_flags, false);
369+
}
370+
363371
static inline void *vmap_user(void *va, mfn_t mfn, unsigned int order,
364372
#if defined(__x86_64__)
365373
unsigned long l4_flags,

include/mm/pmm.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ extern void init_pmm(void);
7373

7474
extern frame_t *get_free_frames_cond(free_frames_cond_t cb);
7575
extern frame_t *get_free_frames(unsigned int order);
76+
extern frame_t *get_free_frame_norefill(void);
7677
extern void put_free_frames(mfn_t mfn, unsigned int order);
7778
extern void reclaim_frame(mfn_t mfn, unsigned int order);
7879

@@ -84,6 +85,7 @@ extern frame_t *find_busy_paddr_frame(paddr_t paddr);
8485
extern frame_t *find_paddr_frame(paddr_t paddr);
8586

8687
extern void map_frames_array(void);
88+
extern void refill_from_paging(void);
8789

8890
/* Static definitions */
8991

mm/pmm.c

Lines changed: 121 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,39 @@ static frames_array_t early_frames;
4141
static list_head_t free_frames[MAX_PAGE_ORDER + 1];
4242
static list_head_t busy_frames[MAX_PAGE_ORDER + 1];
4343

44-
#define MIN_NUM_4K_FRAMES 2
44+
/*
45+
* Worst case: pmm wants to refill while paging just started on another thread
46+
* 1 for pmm refill attempt (new_frames_array)
47+
* 3 for paging currently ongoing
48+
* 4 for refill_from_paging (1 array frame, 3 for mapping it)
49+
*/
50+
#define MIN_NUM_4K_FRAMES (1 + 3 + (1 + 3))
51+
/* enough array frames to refill 4K frames in the worst case without needing refill */
52+
#define MIN_NOREFILL_FREE_FRAMES_THRESHOLD \
53+
(MIN_FREE_FRAMES_THRESHOLD + MIN_NUM_4K_FRAMES + (MAX_PAGE_ORDER - PAGE_ORDER_4K))
4554
static size_t frames_count[MAX_PAGE_ORDER + 1];
4655

56+
/* first lock to serialize normal access to pmm (i.e. not array frame refill) */
4757
static spinlock_t lock = SPINLOCK_INIT;
58+
/**
59+
* second lock to give paging priority access to pmm during array frame refill
60+
*
61+
* Ensure that get_free_frame_norefill and refill_from_paging are only called while
62+
* holding the paging lock.
63+
*/
64+
static spinlock_t priority_lock = SPINLOCK_INIT;
65+
66+
static void try_create_4k_frames(void);
67+
68+
static inline void pmm_lock() {
69+
spin_lock(&lock);
70+
spin_lock(&priority_lock);
71+
}
72+
73+
static inline void pmm_unlock() {
74+
spin_unlock(&priority_lock);
75+
spin_unlock(&lock);
76+
}
4877

4978
void display_frames_count(void) {
5079
printk("Avail memory frames: (total size: %lu MB)\n", total_phys_memory / MB(1));
@@ -98,7 +127,7 @@ static inline void init_frames_array(frames_array_t *array) {
98127
total_free_frames += array->meta.free_count;
99128
}
100129

101-
static frames_array_t *new_frames_array(void) {
130+
static frames_array_t *_new_frames_array(bool nolock) {
102131
frames_array_t *array;
103132
frame_t *frame;
104133

@@ -109,7 +138,21 @@ static frames_array_t *new_frames_array(void) {
109138
if (!boot_flags.virt)
110139
array = (frames_array_t *) mfn_to_virt_kern(frame->mfn);
111140
else {
112-
array = vmap_kern_4k(mfn_to_virt_map(frame->mfn), frame->mfn, L1_PROT);
141+
/* switch to special refilling mode to avoid deadlock with paging */
142+
spin_unlock(&priority_lock);
143+
144+
/* if we are comming from paging then we have to to this mapping without taking
145+
* the lock again */
146+
if (nolock) {
147+
array = vmap_kern_4k_nolock(mfn_to_virt_map(frame->mfn), frame->mfn, L1_PROT);
148+
}
149+
else {
150+
array = vmap_kern_4k(mfn_to_virt_map(frame->mfn), frame->mfn, L1_PROT);
151+
}
152+
153+
/* switch back to normal mode */
154+
spin_lock(&priority_lock);
155+
113156
if (!array)
114157
goto error;
115158
}
@@ -124,6 +167,14 @@ static frames_array_t *new_frames_array(void) {
124167
UNREACHABLE();
125168
}
126169

170+
static inline frames_array_t *new_frames_array() {
171+
return _new_frames_array(false);
172+
}
173+
174+
static inline frames_array_t *new_frames_array_nolock() {
175+
return _new_frames_array(true);
176+
}
177+
127178
static void del_frames_array(frames_array_t *array) {
128179
ASSERT(array);
129180

@@ -426,31 +477,31 @@ static frame_t *_find_mfn_frame(list_head_t *list, mfn_t mfn, unsigned int order
426477
frame_t *find_free_mfn_frame(mfn_t mfn, unsigned int order) {
427478
frame_t *frame;
428479

429-
spin_lock(&lock);
480+
pmm_lock();
430481
frame = _find_mfn_frame(free_frames, mfn, order);
431-
spin_unlock(&lock);
482+
pmm_unlock();
432483

433484
return frame;
434485
}
435486

436487
frame_t *find_busy_mfn_frame(mfn_t mfn, unsigned int order) {
437488
frame_t *frame;
438489

439-
spin_lock(&lock);
490+
pmm_lock();
440491
frame = _find_mfn_frame(busy_frames, mfn, order);
441-
spin_unlock(&lock);
492+
pmm_unlock();
442493

443494
return frame;
444495
}
445496

446497
frame_t *find_mfn_frame(mfn_t mfn, unsigned int order) {
447498
frame_t *frame;
448499

449-
spin_lock(&lock);
500+
pmm_lock();
450501
frame = _find_mfn_frame(busy_frames, mfn, order);
451502
if (!frame)
452503
frame = _find_mfn_frame(free_frames, mfn, order);
453-
spin_unlock(&lock);
504+
pmm_unlock();
454505

455506
return frame;
456507
}
@@ -471,31 +522,31 @@ static frame_t *_find_paddr_frame(list_head_t *list, paddr_t paddr) {
471522
frame_t *find_free_paddr_frame(paddr_t paddr) {
472523
frame_t *frame;
473524

474-
spin_lock(&lock);
525+
pmm_lock();
475526
frame = _find_paddr_frame(free_frames, paddr);
476-
spin_unlock(&lock);
527+
pmm_unlock();
477528

478529
return frame;
479530
}
480531

481532
frame_t *find_busy_paddr_frame(paddr_t paddr) {
482533
frame_t *frame;
483534

484-
spin_lock(&lock);
535+
pmm_lock();
485536
frame = _find_paddr_frame(busy_frames, paddr);
486-
spin_unlock(&lock);
537+
pmm_unlock();
487538

488539
return frame;
489540
}
490541

491542
frame_t *find_paddr_frame(paddr_t paddr) {
492543
frame_t *frame;
493544

494-
spin_lock(&lock);
545+
pmm_lock();
495546
frame = _find_paddr_frame(busy_frames, paddr);
496547
if (!frame)
497548
frame = _find_paddr_frame(free_frames, paddr);
498-
spin_unlock(&lock);
549+
pmm_unlock();
499550

500551
return frame;
501552
}
@@ -599,20 +650,20 @@ static void try_create_4k_frames(void) {
599650
* This function does not split larger frames.
600651
*/
601652
frame_t *get_free_frames_cond(free_frames_cond_t cb) {
602-
spin_lock(&lock);
653+
pmm_lock();
603654
try_create_4k_frames();
604655
for_each_order (order) {
605656
frame_t *frame;
606657

607658
list_for_each_entry (frame, &free_frames[order], list) {
608659
if (cb(frame)) {
609660
reserve_frame(frame);
610-
spin_unlock(&lock);
661+
pmm_unlock();
611662
return frame;
612663
}
613664
}
614665
}
615-
spin_unlock(&lock);
666+
pmm_unlock();
616667

617668
return NULL;
618669
}
@@ -623,22 +674,22 @@ frame_t *get_free_frames(unsigned int order) {
623674
if (order > MAX_PAGE_ORDER)
624675
return NULL;
625676

626-
spin_lock(&lock);
677+
pmm_lock();
627678
if (order == PAGE_ORDER_4K)
628679
try_create_4k_frames();
629680

630681
while (list_is_empty(&free_frames[order])) {
631682
BUG_ON(order == PAGE_ORDER_4K);
632683
frame = find_larger_frame(free_frames, order);
633684
if (!frame) {
634-
spin_unlock(&lock);
685+
pmm_unlock();
635686
return NULL;
636687
}
637688
split_frame(frame);
638689
}
639690

640691
frame = reserve_frame(get_first_frame(free_frames, order));
641-
spin_unlock(&lock);
692+
pmm_unlock();
642693

643694
return frame;
644695
}
@@ -648,7 +699,7 @@ void put_free_frames(mfn_t mfn, unsigned int order) {
648699

649700
ASSERT(order <= MAX_PAGE_ORDER);
650701

651-
spin_lock(&lock);
702+
pmm_lock();
652703
frame = _find_mfn_frame(busy_frames, mfn, order);
653704
if (!frame) {
654705
warning("PMM: unable to find frame: %lx, order: %u among busy frames", mfn,
@@ -660,7 +711,7 @@ void put_free_frames(mfn_t mfn, unsigned int order) {
660711
merge_frames(frame);
661712

662713
unlock:
663-
spin_unlock(&lock);
714+
pmm_unlock();
664715
}
665716

666717
void map_frames_array(void) {
@@ -674,3 +725,50 @@ void map_frames_array(void) {
674725
BUG_ON(!vmap_kern_4k(va, mfn, L1_PROT));
675726
}
676727
}
728+
729+
/* functions for paging to avoid deadlocks */
730+
731+
frame_t *get_free_frame_norefill(void) {
732+
frame_t *frame;
733+
734+
spin_lock(&priority_lock);
735+
frame = reserve_frame(get_first_frame(free_frames, PAGE_ORDER_4K));
736+
spin_unlock(&priority_lock);
737+
738+
/* we ran out of reserved frames. increase MIN_NUM_4K_FRAMES */
739+
BUG_ON(!frame);
740+
741+
return frame;
742+
}
743+
744+
/**
745+
* Function to refill 4k pages after using get_free_frame_norefill. Caller
746+
* needs to hold the vmap_lock!
747+
*/
748+
void refill_from_paging(void) {
749+
/* avoid recursive refilling after paging; variable protected by vmap_lock */
750+
static bool refill_from_paging_ongoing = false;
751+
752+
/* avoid refill_from_paging being called as a result of refill_from_paging */
753+
if (refill_from_paging_ongoing)
754+
return;
755+
756+
refill_from_paging_ongoing = true;
757+
758+
spin_lock(&priority_lock);
759+
760+
/* ensure enough space to refill 4K frames without frame array allocation */
761+
if (total_free_frames < MIN_NOREFILL_FREE_FRAMES_THRESHOLD)
762+
new_frames_array_nolock();
763+
/* if this fails, increase MIN_NUM_4K_FRAMES to allow for multiple array refills
764+
* and change the "if" to a "while" above.
765+
*/
766+
BUG_ON(total_free_frames < MIN_NOREFILL_FREE_FRAMES_THRESHOLD);
767+
768+
/* refill the 4K frames */
769+
try_create_4k_frames();
770+
771+
spin_unlock(&priority_lock);
772+
773+
refill_from_paging_ongoing = false;
774+
}

0 commit comments

Comments
 (0)