Skip to content

Commit dbef1f2

Browse files
committed
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.
1 parent c49705a commit dbef1f2

File tree

4 files changed

+141
-23
lines changed

4 files changed

+141
-23
lines changed

arch/x86/pagetables.c

Lines changed: 15 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,18 @@ void *vmap_4k(cr3_t *cr3_ptr, void *va, mfn_t mfn, unsigned long l1_flags,
420421
return va;
421422
}
422423

424+
void *vmap_pmm_refill_nolock(void *va, mfn_t mfn, unsigned long l1_flags) {
425+
unsigned long _va = _ul(va) & PAGE_ORDER_TO_MASK(PAGE_ORDER_4K);
426+
427+
dprintk("%s: va: 0x%p mfn: 0x%lx\n", __func__, va, mfn);
428+
429+
BUG_ON(!vmap_lock);
430+
va = _vmap_4k(&cr3, _ptr(_va), mfn, l1_flags, false);
431+
BUG_ON(!vmap_lock);
432+
433+
return va;
434+
}
435+
423436
static inline void init_tmp_mapping(void) {
424437
pte_t *tab = get_l1_table(_tmp_mapping);
425438
_tmp_mapping_entry = (pgentry_t *) l1_table_entry(tab, _tmp_mapping);

include/arch/x86/pagetable.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,8 @@ static inline void *vmap_kern_4k(void *va, mfn_t mfn, unsigned long l1_flags) {
360360
return vmap_4k(&cr3, va, mfn, l1_flags, false);
361361
}
362362

363+
void *vmap_pmm_refill_nolock(void *va, mfn_t mfn, unsigned long l1_flags);
364+
363365
static inline void *vmap_user(void *va, mfn_t mfn, unsigned int order,
364366
#if defined(__x86_64__)
365367
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: 122 additions & 21 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 void pmm_lock() {
69+
spin_lock(&lock);
70+
spin_lock(&priority_lock);
71+
}
72+
73+
static 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));
@@ -109,7 +138,49 @@ 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 {
141+
/* switch to special refilling mode to avoid deadlock with paging */
142+
spin_unlock(&priority_lock);
143+
144+
/* only paging is allowed to use pmm with only the priority_lock */
112145
array = vmap_kern_4k(mfn_to_virt_map(frame->mfn), frame->mfn, L1_PROT);
146+
147+
/* switch back to normal mode */
148+
spin_lock(&priority_lock);
149+
150+
if (!array)
151+
goto error;
152+
}
153+
154+
dprintk("%s: allocated new frames array: %p\n", __func__, array);
155+
156+
init_frames_array(array);
157+
158+
return array;
159+
error:
160+
panic("PMM: Unable to allocate new page for frame array");
161+
UNREACHABLE();
162+
}
163+
164+
static frames_array_t *new_frames_array_nolock(void) {
165+
frames_array_t *array;
166+
frame_t *frame;
167+
168+
frame = reserve_frame(get_first_frame(free_frames, PAGE_ORDER_4K));
169+
if (!frame)
170+
goto error;
171+
172+
if (!boot_flags.virt)
173+
array = (frames_array_t *) mfn_to_virt_kern(frame->mfn);
174+
else {
175+
/* switch to special refilling mode to avoid deadlock with paging */
176+
spin_unlock(&priority_lock);
177+
178+
/* only paging is allowed to use pmm with only the priority_lock */
179+
array = vmap_pmm_refill_nolock(mfn_to_virt_map(frame->mfn), frame->mfn, L1_PROT);
180+
181+
/* switch back to normal mode */
182+
spin_lock(&priority_lock);
183+
113184
if (!array)
114185
goto error;
115186
}
@@ -426,31 +497,31 @@ static frame_t *_find_mfn_frame(list_head_t *list, mfn_t mfn, unsigned int order
426497
frame_t *find_free_mfn_frame(mfn_t mfn, unsigned int order) {
427498
frame_t *frame;
428499

429-
spin_lock(&lock);
500+
pmm_lock();
430501
frame = _find_mfn_frame(free_frames, mfn, order);
431-
spin_unlock(&lock);
502+
pmm_unlock();
432503

433504
return frame;
434505
}
435506

436507
frame_t *find_busy_mfn_frame(mfn_t mfn, unsigned int order) {
437508
frame_t *frame;
438509

439-
spin_lock(&lock);
510+
pmm_lock();
440511
frame = _find_mfn_frame(busy_frames, mfn, order);
441-
spin_unlock(&lock);
512+
pmm_unlock();
442513

443514
return frame;
444515
}
445516

446517
frame_t *find_mfn_frame(mfn_t mfn, unsigned int order) {
447518
frame_t *frame;
448519

449-
spin_lock(&lock);
520+
pmm_lock();
450521
frame = _find_mfn_frame(busy_frames, mfn, order);
451522
if (!frame)
452523
frame = _find_mfn_frame(free_frames, mfn, order);
453-
spin_unlock(&lock);
524+
pmm_unlock();
454525

455526
return frame;
456527
}
@@ -471,31 +542,31 @@ static frame_t *_find_paddr_frame(list_head_t *list, paddr_t paddr) {
471542
frame_t *find_free_paddr_frame(paddr_t paddr) {
472543
frame_t *frame;
473544

474-
spin_lock(&lock);
545+
pmm_lock();
475546
frame = _find_paddr_frame(free_frames, paddr);
476-
spin_unlock(&lock);
547+
pmm_unlock();
477548

478549
return frame;
479550
}
480551

481552
frame_t *find_busy_paddr_frame(paddr_t paddr) {
482553
frame_t *frame;
483554

484-
spin_lock(&lock);
555+
pmm_lock();
485556
frame = _find_paddr_frame(busy_frames, paddr);
486-
spin_unlock(&lock);
557+
pmm_unlock();
487558

488559
return frame;
489560
}
490561

491562
frame_t *find_paddr_frame(paddr_t paddr) {
492563
frame_t *frame;
493564

494-
spin_lock(&lock);
565+
pmm_lock();
495566
frame = _find_paddr_frame(busy_frames, paddr);
496567
if (!frame)
497568
frame = _find_paddr_frame(free_frames, paddr);
498-
spin_unlock(&lock);
569+
pmm_unlock();
499570

500571
return frame;
501572
}
@@ -599,20 +670,20 @@ static void try_create_4k_frames(void) {
599670
* This function does not split larger frames.
600671
*/
601672
frame_t *get_free_frames_cond(free_frames_cond_t cb) {
602-
spin_lock(&lock);
673+
pmm_lock();
603674
try_create_4k_frames();
604675
for_each_order (order) {
605676
frame_t *frame;
606677

607678
list_for_each_entry (frame, &free_frames[order], list) {
608679
if (cb(frame)) {
609680
reserve_frame(frame);
610-
spin_unlock(&lock);
681+
pmm_unlock();
611682
return frame;
612683
}
613684
}
614685
}
615-
spin_unlock(&lock);
686+
pmm_unlock();
616687

617688
return NULL;
618689
}
@@ -623,22 +694,22 @@ frame_t *get_free_frames(unsigned int order) {
623694
if (order > MAX_PAGE_ORDER)
624695
return NULL;
625696

626-
spin_lock(&lock);
697+
pmm_lock();
627698
if (order == PAGE_ORDER_4K)
628699
try_create_4k_frames();
629700

630701
while (list_is_empty(&free_frames[order])) {
631702
BUG_ON(order == PAGE_ORDER_4K);
632703
frame = find_larger_frame(free_frames, order);
633704
if (!frame) {
634-
spin_unlock(&lock);
705+
pmm_unlock();
635706
return NULL;
636707
}
637708
split_frame(frame);
638709
}
639710

640711
frame = reserve_frame(get_first_frame(free_frames, order));
641-
spin_unlock(&lock);
712+
pmm_unlock();
642713

643714
return frame;
644715
}
@@ -648,7 +719,7 @@ void put_free_frames(mfn_t mfn, unsigned int order) {
648719

649720
ASSERT(order <= MAX_PAGE_ORDER);
650721

651-
spin_lock(&lock);
722+
pmm_lock();
652723
frame = _find_mfn_frame(busy_frames, mfn, order);
653724
if (!frame) {
654725
warning("PMM: unable to find frame: %lx, order: %u among busy frames", mfn,
@@ -660,7 +731,7 @@ void put_free_frames(mfn_t mfn, unsigned int order) {
660731
merge_frames(frame);
661732

662733
unlock:
663-
spin_unlock(&lock);
734+
pmm_unlock();
664735
}
665736

666737
void map_frames_array(void) {
@@ -674,3 +745,33 @@ void map_frames_array(void) {
674745
BUG_ON(!vmap_kern_4k(va, mfn, L1_PROT));
675746
}
676747
}
748+
749+
/* functions for paging to avoid deadlocks */
750+
751+
frame_t *get_free_frame_norefill(void) {
752+
frame_t *frame;
753+
754+
spin_lock(&priority_lock);
755+
frame = reserve_frame(get_first_frame(free_frames, PAGE_ORDER_4K));
756+
spin_unlock(&priority_lock);
757+
758+
/* we ran out of reserved frames. increase MIN_NUM_4K_FRAMES */
759+
BUG_ON(!frame);
760+
761+
return frame;
762+
}
763+
764+
void refill_from_paging(void) {
765+
spin_lock(&priority_lock);
766+
767+
/* ensure enough space to refill 4K frames without frame array allocation */
768+
if (total_free_frames < MIN_NOREFILL_FREE_FRAMES_THRESHOLD)
769+
new_frames_array_nolock();
770+
/* if this fails, increase MIN_NUM_4K_FRAMES to allow for multiple array refills */
771+
BUG_ON(total_free_frames < MIN_NOREFILL_FREE_FRAMES_THRESHOLD);
772+
773+
/* refill the 4K frames */
774+
try_create_4k_frames();
775+
776+
spin_unlock(&priority_lock);
777+
}

0 commit comments

Comments
 (0)