Skip to content

Commit 6b3fff3

Browse files
Nicolas Pitrecarlescufi
authored andcommitted
kernel: mmu: make demand paging work on SMP
This is the minimum for demand paging to work on SMP systems. Signed-off-by: Nicolas Pitre <[email protected]>
1 parent a206ab0 commit 6b3fff3

File tree

1 file changed

+85
-44
lines changed

1 file changed

+85
-44
lines changed

kernel/mmu.c

Lines changed: 85 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -662,15 +662,16 @@ void *k_mem_map_phys_guard(uintptr_t phys, size_t size, uint32_t flags, bool is_
662662
arch_mem_map(dst, phys, size, flags);
663663
}
664664

665-
if (!uninit) {
665+
out:
666+
k_spin_unlock(&z_mm_lock, key);
667+
668+
if (dst != NULL && !uninit) {
666669
/* If we later implement mappings to a copy-on-write
667670
* zero page, won't need this step
668671
*/
669672
memset(dst, 0, size);
670673
}
671674

672-
out:
673-
k_spin_unlock(&z_mm_lock, key);
674675
return dst;
675676
}
676677

@@ -1236,14 +1237,18 @@ static inline void do_backing_store_page_out(uintptr_t location)
12361237
#endif /* CONFIG_DEMAND_PAGING_TIMING_HISTOGRAM */
12371238
}
12381239

1239-
/* Current implementation relies on interrupt locking to any prevent page table
1240-
* access, which falls over if other CPUs are active. Addressing this is not
1241-
* as simple as using spinlocks as regular memory reads/writes constitute
1242-
* "access" in this sense.
1243-
*
1244-
* Current needs for demand paging are on uniprocessor systems.
1240+
#if defined(CONFIG_SMP) && defined(CONFIG_DEMAND_PAGING_ALLOW_IRQ)
1241+
/*
1242+
* SMP support is very simple. Some resources such as the scratch page could
1243+
* be made per CPU, backing store driver execution be confined to the faulting
1244+
* CPU, statistics be made to cope with access concurrency, etc. But in the
1245+
* end we're dealing with memory transfer to/from some external storage which
1246+
* is inherently slow and whose access is most likely serialized anyway.
1247+
* So let's simply enforce global demand paging serialization across all CPUs
1248+
* with a mutex as there is no real gain from added parallelism here.
12451249
*/
1246-
BUILD_ASSERT(!IS_ENABLED(CONFIG_SMP));
1250+
static K_MUTEX_DEFINE(z_mm_paging_lock);
1251+
#endif
12471252

12481253
static void virt_region_foreach(void *addr, size_t size,
12491254
void (*func)(void *))
@@ -1333,16 +1338,21 @@ static int do_mem_evict(void *addr)
13331338
bool dirty;
13341339
struct k_mem_page_frame *pf;
13351340
uintptr_t location;
1336-
int key, ret;
1341+
k_spinlock_key_t key;
13371342
uintptr_t flags, phys;
1343+
int ret;
13381344

13391345
#if CONFIG_DEMAND_PAGING_ALLOW_IRQ
13401346
__ASSERT(!k_is_in_isr(),
13411347
"%s is unavailable in ISRs with CONFIG_DEMAND_PAGING_ALLOW_IRQ",
13421348
__func__);
1349+
#ifdef CONFIG_SMP
1350+
k_mutex_lock(&z_mm_paging_lock, K_FOREVER);
1351+
#else
13431352
k_sched_lock();
1353+
#endif
13441354
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
1345-
key = irq_lock();
1355+
key = k_spin_lock(&z_mm_lock);
13461356
flags = arch_page_info_get(addr, &phys, false);
13471357
__ASSERT((flags & ARCH_DATA_PAGE_NOT_MAPPED) == 0,
13481358
"address %p isn't mapped", addr);
@@ -1362,19 +1372,23 @@ static int do_mem_evict(void *addr)
13621372

13631373
__ASSERT(ret == 0, "failed to prepare page frame");
13641374
#ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ
1365-
irq_unlock(key);
1375+
k_spin_unlock(&z_mm_lock, key);
13661376
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
13671377
if (dirty) {
13681378
do_backing_store_page_out(location);
13691379
}
13701380
#ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ
1371-
key = irq_lock();
1381+
key = k_spin_lock(&z_mm_lock);
13721382
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
13731383
page_frame_free_locked(pf);
13741384
out:
1375-
irq_unlock(key);
1385+
k_spin_unlock(&z_mm_lock, key);
13761386
#ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ
1387+
#ifdef CONFIG_SMP
1388+
k_mutex_unlock(&z_mm_paging_lock);
1389+
#else
13771390
k_sched_unlock();
1391+
#endif
13781392
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
13791393
return ret;
13801394
}
@@ -1400,11 +1414,12 @@ int k_mem_page_out(void *addr, size_t size)
14001414

14011415
int k_mem_page_frame_evict(uintptr_t phys)
14021416
{
1403-
int key, ret;
1417+
k_spinlock_key_t key;
14041418
struct k_mem_page_frame *pf;
14051419
bool dirty;
14061420
uintptr_t flags;
14071421
uintptr_t location;
1422+
int ret;
14081423

14091424
__ASSERT(page_frames_initialized, "%s called on 0x%lx too early",
14101425
__func__, phys);
@@ -1417,9 +1432,13 @@ int k_mem_page_frame_evict(uintptr_t phys)
14171432
__ASSERT(!k_is_in_isr(),
14181433
"%s is unavailable in ISRs with CONFIG_DEMAND_PAGING_ALLOW_IRQ",
14191434
__func__);
1435+
#ifdef CONFIG_SMP
1436+
k_mutex_lock(&z_mm_paging_lock, K_FOREVER);
1437+
#else
14201438
k_sched_lock();
1439+
#endif
14211440
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
1422-
key = irq_lock();
1441+
key = k_spin_lock(&z_mm_lock);
14231442
pf = k_mem_phys_to_page_frame(phys);
14241443
if (!k_mem_page_frame_is_mapped(pf)) {
14251444
/* Nothing to do, free page */
@@ -1436,19 +1455,23 @@ int k_mem_page_frame_evict(uintptr_t phys)
14361455
}
14371456

14381457
#ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ
1439-
irq_unlock(key);
1458+
k_spin_unlock(&z_mm_lock, key);
14401459
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
14411460
if (dirty) {
14421461
do_backing_store_page_out(location);
14431462
}
14441463
#ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ
1445-
key = irq_lock();
1464+
k_spin_unlock(&z_mm_lock, key);
14461465
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
14471466
page_frame_free_locked(pf);
14481467
out:
1449-
irq_unlock(key);
1468+
k_spin_unlock(&z_mm_lock, key);
14501469
#ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ
1470+
#ifdef CONFIG_SMP
1471+
k_mutex_unlock(&z_mm_paging_lock);
1472+
#else
14511473
k_sched_unlock();
1474+
#endif
14521475
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
14531476
return ret;
14541477
}
@@ -1549,12 +1572,13 @@ static inline struct k_mem_page_frame *do_eviction_select(bool *dirty)
15491572
static bool do_page_fault(void *addr, bool pin)
15501573
{
15511574
struct k_mem_page_frame *pf;
1552-
int key, ret;
1575+
k_spinlock_key_t key;
15531576
uintptr_t page_in_location, page_out_location;
15541577
enum arch_page_location status;
15551578
bool result;
15561579
bool dirty = false;
1557-
struct k_thread *faulting_thread = _current_cpu->current;
1580+
struct k_thread *faulting_thread;
1581+
int ret;
15581582

15591583
__ASSERT(page_frames_initialized, "page fault at %p happened too early",
15601584
addr);
@@ -1568,13 +1592,11 @@ static bool do_page_fault(void *addr, bool pin)
15681592
*/
15691593

15701594
#ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ
1571-
/* We lock the scheduler so that other threads are never scheduled
1572-
* during the page-in/out operation.
1573-
*
1574-
* We do however re-enable interrupts during the page-in/page-out
1575-
* operation if and only if interrupts were enabled when the exception
1576-
* was taken; in this configuration page faults in an ISR are a bug;
1577-
* all their code/data must be pinned.
1595+
/*
1596+
* We do re-enable interrupts during the page-in/page-out operation
1597+
* if and only if interrupts were enabled when the exception was
1598+
* taken; in this configuration page faults in an ISR are a bug; all
1599+
* their code/data must be pinned.
15781600
*
15791601
* If interrupts were disabled when the exception was taken, the
15801602
* arch code is responsible for keeping them that way when entering
@@ -1584,20 +1606,35 @@ static bool do_page_fault(void *addr, bool pin)
15841606
* entire operation. This is far worse for system interrupt latency
15851607
* but requires less pinned pages and ISRs may also take page faults.
15861608
*
1587-
* Support for allowing k_mem_paging_backing_store_page_out() and
1609+
* On UP we lock the scheduler so that other threads are never
1610+
* scheduled during the page-in/out operation. Support for
1611+
* allowing k_mem_paging_backing_store_page_out() and
15881612
* k_mem_paging_backing_store_page_in() to also sleep and allow
15891613
* other threads to run (such as in the case where the transfer is
1590-
* async DMA) is not implemented. Even if limited to thread context,
1591-
* arbitrary memory access triggering exceptions that put a thread to
1592-
* sleep on a contended page fault operation will break scheduling
1593-
* assumptions of cooperative threads or threads that implement
1594-
* crticial sections with spinlocks or disabling IRQs.
1614+
* async DMA) is not supported on UP. Even if limited to thread
1615+
* context, arbitrary memory access triggering exceptions that put
1616+
* a thread to sleep on a contended page fault operation will break
1617+
* scheduling assumptions of cooperative threads or threads that
1618+
* implement critical sections with spinlocks or disabling IRQs.
1619+
*
1620+
* On SMP, though, exclusivity cannot be assumed solely from being
1621+
* a cooperative thread. Another thread with any prio may be running
1622+
* on another CPU so exclusion must already be enforced by other
1623+
* means. Therefore trying to prevent scheduling on SMP is pointless,
1624+
* and k_sched_lock() is equivalent to a no-op on SMP anyway.
1625+
* As a result, sleeping/rescheduling in the SMP case is fine.
15951626
*/
1596-
k_sched_lock();
15971627
__ASSERT(!k_is_in_isr(), "ISR page faults are forbidden");
1628+
#ifdef CONFIG_SMP
1629+
k_mutex_lock(&z_mm_paging_lock, K_FOREVER);
1630+
#else
1631+
k_sched_lock();
1632+
#endif
15981633
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
15991634

1600-
key = irq_lock();
1635+
key = k_spin_lock(&z_mm_lock);
1636+
faulting_thread = _current_cpu->current;
1637+
16011638
status = arch_page_location_get(addr, &page_in_location);
16021639
if (status == ARCH_PAGE_LOCATION_BAD) {
16031640
/* Return false to treat as a fatal error */
@@ -1628,7 +1665,7 @@ static bool do_page_fault(void *addr, bool pin)
16281665
__ASSERT(status == ARCH_PAGE_LOCATION_PAGED_OUT,
16291666
"unexpected status value %d", status);
16301667

1631-
paging_stats_faults_inc(faulting_thread, key);
1668+
paging_stats_faults_inc(faulting_thread, key.key);
16321669

16331670
pf = free_page_frame_list_get();
16341671
if (pf == NULL) {
@@ -1645,7 +1682,7 @@ static bool do_page_fault(void *addr, bool pin)
16451682
__ASSERT(ret == 0, "failed to prepare page frame");
16461683

16471684
#ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ
1648-
irq_unlock(key);
1685+
k_spin_unlock(&z_mm_lock, key);
16491686
/* Interrupts are now unlocked if they were not locked when we entered
16501687
* this function, and we may service ISRs. The scheduler is still
16511688
* locked.
@@ -1657,7 +1694,7 @@ static bool do_page_fault(void *addr, bool pin)
16571694
do_backing_store_page_in(page_in_location);
16581695

16591696
#ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ
1660-
key = irq_lock();
1697+
key = k_spin_lock(&z_mm_lock);
16611698
k_mem_page_frame_clear(pf, K_MEM_PAGE_FRAME_BUSY);
16621699
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
16631700
k_mem_page_frame_clear(pf, K_MEM_PAGE_FRAME_MAPPED);
@@ -1672,9 +1709,13 @@ static bool do_page_fault(void *addr, bool pin)
16721709
k_mem_paging_eviction_add(pf);
16731710
}
16741711
out:
1675-
irq_unlock(key);
1712+
k_spin_unlock(&z_mm_lock, key);
16761713
#ifdef CONFIG_DEMAND_PAGING_ALLOW_IRQ
1714+
#ifdef CONFIG_SMP
1715+
k_mutex_unlock(&z_mm_paging_lock);
1716+
#else
16771717
k_sched_unlock();
1718+
#endif
16781719
#endif /* CONFIG_DEMAND_PAGING_ALLOW_IRQ */
16791720

16801721
return result;
@@ -1722,10 +1763,10 @@ bool k_mem_page_fault(void *addr)
17221763
static void do_mem_unpin(void *addr)
17231764
{
17241765
struct k_mem_page_frame *pf;
1725-
unsigned int key;
1766+
k_spinlock_key_t key;
17261767
uintptr_t flags, phys;
17271768

1728-
key = irq_lock();
1769+
key = k_spin_lock(&z_mm_lock);
17291770
flags = arch_page_info_get(addr, &phys, false);
17301771
__ASSERT((flags & ARCH_DATA_PAGE_NOT_MAPPED) == 0,
17311772
"invalid data page at %p", addr);
@@ -1736,7 +1777,7 @@ static void do_mem_unpin(void *addr)
17361777
k_mem_paging_eviction_add(pf);
17371778
}
17381779
}
1739-
irq_unlock(key);
1780+
k_spin_unlock(&z_mm_lock, key);
17401781
}
17411782

17421783
void k_mem_unpin(void *addr, size_t size)

0 commit comments

Comments
 (0)