๏ปฟ# mlock / munlock ๐
๊ด๋ จ ์์ค:mm/mlock.c,mm/swap.c,include/linux/mm.h
๊ฐ์ (Overview)
mlock์ ์ฌ์ฉ์ ๊ณต๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ํ์ด์ง๋ฅผ ๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ์ ๊ณ ์ ํ์ฌ ์ปค๋์ ํ์ด์ง ํ์(kswapd, direct reclaim)๋ ์ค์ ์์์ผ๋ก๋ถํฐ ๋ณดํธํ๋ ์์คํ
ํธ์ถ์
๋๋ค. ์ค์๊ฐ ์์คํ
, ์ ์ง์ฐ(latency) ์ ํ๋ฆฌ์ผ์ด์
, ์ํธํ ํค ๋ฑ์์ ์ค์ํ ๋ณดํธ ๋ฉ์ปค๋์ฆ์ผ๋ก, mlock, mlock2, mlockall๊ณผ ํด์ ํจ์ munlock, munlockall์ ์ ๊ณตํฉ๋๋ค.
์ปค๋ ๋ด๋ถ์ ์ผ๋ก mlock๋ ํ์ด์ง๋ folio์ PG_mlocked ํ๋๊ทธ๋ฅผ ์ค์ ํ๊ณ , LRU(active/inactive)์์ unevictable ๋ชฉ๋ก์ผ๋ก ์ด๋์ํต๋๋ค. folio์ mlock_count ํ๋๋ ๊ฐ์ folio๊ฐ ์ฌ๋ฌ VMA์์ mlock๋ ์ ์๋ ๊ฒฝ์ฐ๋ฅผ ์ถ์ ํฉ๋๋ค. VM_LOCKED ํ๋๊ทธ๋ VMA์, VM_LOCKONFAULT(mlock2)์ ํ์ด์ง ํดํธ ์์ ์ ๊ณ ์ ํ๋ ์ง์ฐ ๋ชจ๋๋ฅผ ๋ํ๋
๋๋ค.
์ด ๊ณผ์ ์ ๋์๊ด์์ ์์ฃผ ์ฐ๋ ์ฑ
์ ๋ฐ๋ฉ ๊ธ์ง ํ์์ ๋ถ์ฌ ๋๋ ๊ฒ๊ณผ ๋น์ทํฉ๋๋ค. ์ฑ
์ ์์ ์น์ฐ๋ ๊ฒ์ด ์๋๋ผ, ํ์ ๋ด๋น์๊ฐ ๋จผ์ ๊ฐ์ ธ๊ฐ ๋ฒ๋ฆฌ์ง ๋ชปํ๊ฒ ํ์๋ง ๋จ๊ธฐ๊ณ ํ์ํ ๋๋ ๊ทธ๋๋ก ๊บผ๋ด ์ฝ๊ฒ ํ๋ ๋ฐฉ์์
๋๋ค.
// mm/mlock.c:1-29 โ ํค๋ ๋ฐ ๊ธฐ๋ณธ ๊ตฌ์กฐ
#include <linux/capability.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/sched/user.h>
#include <linux/swap.h>
#include <linux/pagewalk.h>
#include <linux/mempolicy.h>
#include <linux/rmap.h>
๋น ๋ฅธ ์ ๊ฒ ๋ช ๋ น
# ํ์ฌ ํ๋ก์ธ์ค์ locked ๋ฉ๋ชจ๋ฆฌ์ ๊ธฐ๋ณธ ์ํ ํ์ธ
grep -E '^VmLck|^VmRSS|^VmSize' /proc/self/status
# ์์คํ
์ ์ฒด unevictable / mlocked ํต๊ณ
grep -E '^Unevictable:|^Mlocked:' /proc/meminfo
# VMA ๋จ์ lock ํ๋๊ทธ ํ์ธ
grep -E 'VmFlags:.*\blo\b' /proc/<PID>/smaps
# RLIMIT_MEMLOCK ํ๋ ํ์ธ
ulimit -l
# mlock / munlock / rescued / stranded ํต๊ณ
grep -E 'nr_mlock|unevictable_pgs_(mlocked|munlocked|rescued|stranded)' /proc/vmstat
# ํ๋ก์ธ์ค ์์ฝ ํต๊ณ์์ Locked ํ์ธ
grep -E '^Locked:|^Rss:' /proc/<PID>/smaps_rollup
# mlock ๋์๊ณผ ํจ๊ป ์์ฃผ ๋ณด๋ per-process ํญ๋ชฉ
grep -E '^VmLck|^VmPin' /proc/<PID>/status
# lock ํ๋์ ์ค์ NUMA ๋ฐฐ์น ๊ฐ์ด ๋ณด๊ธฐ
cat /proc/<PID>/numa_maps | head -20
ํต์ฌ ์๋ฃ๊ตฌ์กฐ
struct mlock_fbatch
Per-CPU ๋ฐฐ์น ๊ตฌ์กฐ์ฒด๋ก, mlock/munlock ์ฐ์ฐ์ ๋ชจ์๋ค๊ฐ ํ ๋ฒ์ ์ฒ๋ฆฌํ์ฌ lock ์ค๋ฒํค๋๋ฅผ ์ค์ ๋๋ค.
// mm/mlock.c:31-38 โ mlock_fbatch ์ ์
struct mlock_fbatch {
local_lock_t lock;
struct folio_batch fbatch;
};
static DEFINE_PER_CPU(struct mlock_fbatch, mlock_fbatch) = {
.lock = INIT_LOCAL_LOCK(lock),
};
folio ํฌ์ธํฐ ํ๋๊ทธ ์ธ์ฝ๋ฉ
mlock_fbatch ๋ด์์ folio ํฌ์ธํฐ์ ํ์ ๋นํธ์ ์์
์ ํ์ ์ธ์ฝ๋ฉํฉ๋๋ค. LRU_FOLIO(0x1)๋ ๊ธฐ์กด LRU์ folio, NEW_FOLIO(0x2)๋ ์๋ก ํ ๋น๋ folio๋ฅผ ๋ํ๋
๋๋ค. ๋ ๋ค ์๋๋ฉด munlock์
๋๋ค.
// mm/mlock.c:167-177 โ folio ํฌ์ธํฐ ํ์ ๋นํธ ์ธ์ฝ๋ฉ
#define LRU_FOLIO 0x1
#define NEW_FOLIO 0x2
static inline struct folio *mlock_lru(struct folio *folio)
{
return (struct folio *)((unsigned long)folio + LRU_FOLIO);
}
static inline struct folio *mlock_new(struct folio *folio)
{
return (struct folio *)((unsigned long)folio + NEW_FOLIO);
}
vm_area_struct ์ฃผ์ ํ๋๊ทธ
// include/linux/mm.h:420-575 โ VM_LOCKED ๊ด๋ จ ํ๋๊ทธ
#define VM_LOCKED INIT_VM_FLAG(LOCKED)
#define VM_LOCKONFAULT INIT_VM_FLAG(LOCKONFAULT)
#define VM_LOCKED_MASK (VM_LOCKED | VM_LOCKONFAULT)
VM_LOCKED: VMA์ ๋ชจ๋ ํ์ด์ง๋ฅผ ๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ์ ๊ณ ์ VM_LOCKONFAULT: ํ์ด์ง ํดํธ ์์ ์๋ง ๊ณ ์ (์ง์ฐ ๋ชจ๋)VM_IO: mlock_vma_pages_range ์ค ๋์ ๋ง์ด๊ทธ๋ ์ด์
๋ฐฉ์ง์ฉ ์ผ์์ ํ๋๊ทธํต์ฌ ํจ์
mlock_folio โ ๊ธฐ์กด LRU folio ๊ณ ์
// mm/mlock.c:242-261 โ mlock_folio()
void mlock_folio(struct folio *folio)
{
struct folio_batch *fbatch;
local_lock(&mlock_fbatch.lock);
fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
if (!folio_test_set_mlocked(folio)) {
int nr_pages = folio_nr_pages(folio);
zone_stat_mod_folio(folio, NR_MLOCK, nr_pages);
__count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
}
folio_get(folio);
if (!folio_batch_add(fbatch, mlock_lru(folio)) ||
!folio_may_be_lru_cached(folio) || lru_cache_disabled())
mlock_folio_batch(fbatch);
local_unlock(&mlock_fbatch.lock);
}
folio_test_set_mlocked: atomic set & test๋ก ์ฒ์ mlock์ธ์ง ํ์ธ (NR_MLOCK ํต๊ณ ๊ฐฑ์ )mlock_new_folio โ ์๋ก ํ ๋น๋ folio ๊ณ ์
VM_LOCKED VMA์์ ์ folio๊ฐ LRU์ ๋ค์ด๊ฐ๊ธฐ ์ ์ ์ฐ๋ ๊ฒฝ๋ก์
๋๋ค. ์์ง LRU์ ์ฌ๋ผ์ค๊ธฐ ์ ์ด๋ผ __mlock_new_folio()๊ฐ PG_mlocked์ mlock_count๋ฅผ ๋จผ์ ์ธํ
ํ๊ณ unevictable ์ํ๋ก ๋ฃ์ต๋๋ค.
// mm/mlock.c:263-284 โ mlock_new_folio()
void mlock_new_folio(struct folio *folio)
{
struct folio_batch *fbatch;
int nr_pages = folio_nr_pages(folio);
local_lock(&mlock_fbatch.lock);
fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
folio_set_mlocked(folio);
zone_stat_mod_folio(folio, NR_MLOCK, nr_pages);
__count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
folio_get(folio);
if (!folio_batch_add(fbatch, mlock_new(folio)) ||
!folio_may_be_lru_cached(folio) || lru_cache_disabled())
mlock_folio_batch(fbatch);
local_unlock(&mlock_fbatch.lock);
}
mlock_new()๋ก ํ์ํ ๋ค ๋ฐฐ์น์ ๋ฃ์ต๋๋ค.swap.c์ folio_add_lru_vma()๋ VM_LOCKED VMA๋ฉด ์ด ๊ฒฝ๋ก๋ก ๋ฐ๋ก ๋ณด๋
๋๋ค.__mlock_folio โ LRU์์ unevictable๋ก ์ด๋
// mm/mlock.c:61-101 โ __mlock_folio()
static struct lruvec *__mlock_folio(struct folio *folio, struct lruvec *lruvec)
{
if (!folio_test_clear_lru(folio))
return lruvec;
lruvec = folio_lruvec_relock_irq(folio, lruvec);
if (unlikely(folio_evictable(folio))) {
if (folio_test_unevictable(folio)) {
lruvec_del_folio(lruvec, folio);
folio_clear_unevictable(folio);
lruvec_add_folio(lruvec, folio);
__count_vm_events(UNEVICTABLE_PGRESCUED,
folio_nr_pages(folio));
}
goto out;
}
if (folio_test_unevictable(folio)) {
if (folio_test_mlocked(folio))
folio->mlock_count++;
goto out;
}
lruvec_del_folio(lruvec, folio);
folio_clear_active(folio);
folio_set_unevictable(folio);
folio->mlock_count = !!folio_test_mlocked(folio);
lruvec_add_folio(lruvec, folio);
__count_vm_events(UNEVICTABLE_PGCULLED, folio_nr_pages(folio));
out:
folio_set_lru(folio);
return lruvec;
}
mlock_count ์ค๋ณต mlock ์ถ์ (์ด๋ฏธ unevictable์ด๋ฉด ์นด์ดํธ๋ง ์ฆ๊ฐ)UNEVICTABLE_PGRESCUED: evictable์ธ๋ฐ mlocked๊ฐ ํด์ ๋ ์ผ์ด์ค__munlock_folio โ munlock ์ฒ๋ฆฌ
// mm/mlock.c:122-162 โ __munlock_folio()
static struct lruvec *__munlock_folio(struct folio *folio, struct lruvec *lruvec)
{
int nr_pages = folio_nr_pages(folio);
bool isolated = false;
if (!folio_test_clear_lru(folio))
goto munlock;
isolated = true;
lruvec = folio_lruvec_relock_irq(folio, lruvec);
if (folio_test_unevictable(folio)) {
if (folio->mlock_count)
folio->mlock_count--;
if (folio->mlock_count)
goto out;
}
munlock:
if (folio_test_clear_mlocked(folio)) {
__zone_stat_mod_folio(folio, NR_MLOCK, -nr_pages);
if (isolated || !folio_test_unevictable(folio))
__count_vm_events(UNEVICTABLE_PGMUNLOCKED, nr_pages);
else
__count_vm_events(UNEVICTABLE_PGSTRANDED, nr_pages);
}
if (isolated && folio_test_unevictable(folio) && folio_evictable(folio)) {
lruvec_del_folio(lruvec, folio);
folio_clear_unevictable(folio);
lruvec_add_folio(lruvec, folio);
__count_vm_events(UNEVICTABLE_PGRESCUED, nr_pages);
}
out:
if (isolated)
folio_set_lru(folio);
return lruvec;
}
mlock_count ๊ฐ์ ํ 0์ด ๋๋ฉด ์ค์ mlock ํด์ UNEVICTABLE_PGSTRANDED: LRU์์ ๊ฒฉ๋ฆฌ๋์ง ์์ ์ํ์์ mlocked ํด์ mlock_fixup โ VMA ๋จ์ mlock/munlock ์ฒ๋ฆฌ
// mm/mlock.c:466-512 โ mlock_fixup()
static int mlock_fixup(struct vma_iterator *vmi, struct vm_area_struct *vma,
struct vm_area_struct **prev, unsigned long start,
unsigned long end, vm_flags_t newflags)
{
struct mm_struct *mm = vma->vm_mm;
int nr_pages;
int ret = 0;
vm_flags_t oldflags = vma->vm_flags;
if (newflags == oldflags || (oldflags & VM_SPECIAL) ||
is_vm_hugetlb_page(vma) || vma == get_gate_vma(current->mm) ||
vma_is_dax(vma) || vma_is_secretmem(vma) || (oldflags & VM_DROPPABLE))
goto out;
vma = vma_modify_flags(vmi, *prev, vma, start, end, &newflags);
if (IS_ERR(vma)) {
ret = PTR_ERR(vma);
goto out;
}
nr_pages = (end - start) >> PAGE_SHIFT;
if (!(newflags & VM_LOCKED))
nr_pages = -nr_pages;
else if (oldflags & VM_LOCKED)
nr_pages = 0;
mm->locked_vm += nr_pages;
if ((newflags & VM_LOCKED) && (oldflags & VM_LOCKED)) {
vma_start_write(vma);
vm_flags_reset(vma, newflags);
} else {
mlock_vma_pages_range(vma, start, end, newflags);
}
out:
*prev = vma;
return ret;
}
locked_vm ์นด์ดํธ ๊ด๋ฆฌ (RLIMIT_MEMLOCK ์ฒดํฌ์ฉ)mlock_vma_pages_range โ ํ์ด์ง ์ํฌ์ ์์ VM_IO ์ฒ๋ฆฌ
mlock_fixup()๊ฐ VMA ํ๋๊ทธ๋ฅผ ๋ฐ๊พผ ๋ค ์ค์ ํ์ด์ง๋ฅผ ์ํํ๋ ๊ฒฝ๋ก์
๋๋ค. VM_LOCKED๋ฅผ ์ผ ์ํ์์ ๋ค๋ฅธ ๊ฒฝ๋ก๊ฐ ๊ฐ์ folio๋ฅผ ๋ค์ ์ธ์ง ์๋๋ก, ์ํ ๋์๋ง VM_IO๋ฅผ ๋ง๋ถ์๋ค๊ฐ ๋๋๋ฉด ์ ๊ฑฐํฉ๋๋ค.
// mm/mlock.c:423-455 โ mlock_vma_pages_range()
static void mlock_vma_pages_range(struct vm_area_struct *vma,
unsigned long start, unsigned long end, vm_flags_t newflags)
{
static const struct mm_walk_ops mlock_walk_ops = {
.pmd_entry = mlock_pte_range,
.walk_lock = PGWALK_WRLOCK_VERIFY,
};
if (newflags & VM_LOCKED)
newflags |= VM_IO;
vma_start_write(vma);
vm_flags_reset_once(vma, newflags);
lru_add_drain();
walk_page_range(vma->vm_mm, start, end, &mlock_walk_ops, NULL);
lru_add_drain();
if (newflags & VM_IO) {
newflags &= ~VM_IO;
vm_flags_reset_once(vma, newflags);
}
}
VM_IO๋ ์ฌ๊ธฐ์๋ง ์ ๊น ๋ถ๋ ๋ณด์กฐ ํ๋๊ทธ์
๋๋ค.walk_page_range()๊ฐ mlock_pte_range()๋ฅผ ๋ถ๋ฅด๋ฉฐ THP์ PTE ๊ฒฝ๋ก๋ฅผ ๋๋๋๋ค.VM_IO๋ฅผ ์ง์ ์๋ VMA ์ํ๋ก ๋๋ฆฝ๋๋ค.do_mlock โ ์์คํ ํธ์ถ ์ง์ ์
// mm/mlock.c:612-657 โ do_mlock()
static __must_check int do_mlock(unsigned long start, size_t len, vm_flags_t flags)
{
unsigned long locked;
unsigned long lock_limit;
int error = -ENOMEM;
start = untagged_addr(start);
if (!can_do_mlock())
return -EPERM;
len = PAGE_ALIGN(len + (offset_in_page(start)));
start &= PAGE_MASK;
lock_limit = rlimit(RLIMIT_MEMLOCK);
lock_limit >>= PAGE_SHIFT;
locked = len >> PAGE_SHIFT;
if (mmap_write_lock_killable(current->mm))
return -EINTR;
locked += current->mm->locked_vm;
if ((locked > lock_limit) && (!capable(CAP_IPC_LOCK))) {
locked -= count_mm_mlocked_page_nr(current->mm, start, len);
}
if ((locked <= lock_limit) || capable(CAP_IPC_LOCK))
error = apply_vma_lock_flags(start, len, flags);
mmap_write_unlock(current->mm);
if (error)
return error;
error = __mm_populate(start, len, 0);
if (error)
return __mlock_posix_error_return(error);
return 0;
}
can_do_mlock: RLIMIT_MEMLOCK != 0 ๋๋ CAP_IPC_LOCK capability ํ์ธ__mm_populate: ์ค์ ํ์ด์ง๋ฅผ ๋ฌผ๋ฆฌ ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ฆผ (demand paging ํด์)ํธ์ถ ํ๋ฆ
mlock(start, len)
โ do_mlock(start, len, VM_LOCKED)
โ can_do_mlock() [๊ถํ ์ฒดํฌ]
โ mmap_write_lock_killable() [mmap_lock ํ๋]
โ apply_vma_lock_flags(start, len, flags)
โ for_each_vma_range()
โ mlock_fixup()
โ vma_modify_flags() [VMA ๋ถํ /๋ณํฉ]
โ mm->locked_vm ๊ฐฑ์
โ mlock_vma_pages_range()
โ walk_page_range(mlock_pte_range)
โ mlock_pte_range()
โ [THP] pmd_folio() โ mlock_folio()
โ [PTE] vm_normal_folio() โ mlock_folio() / munlock_folio()
โ mmap_write_unlock()
โ __mm_populate() [ํ์ด์ง ์ค์ ํ ๋น]
์๋ก ์๊ธด folio๋ `mm/swap.c:523-530`์ `folio_add_lru_vma()`์์ `VM_LOCKED` VMA์ธ์ง ๋ณด๊ณ `mlock_new_folio()`๋ก ๋ค์ด๊ฐ๋ฉฐ, ๊ทธ ๋ฐ์ folio๋ ์ผ๋ฐ LRU ๊ฒฝ๋ก๋ฅผ ํ๋๋ค.
munlock(start, len)
โ apply_vma_lock_flags(start, len, 0)
โ mlock_fixup() โ mlock_vma_pages_range()
โ munlock_folio()
mlockall(flags)
โ do_mlockall โ apply_mlockall_flags()
โ mm->def_flags |= VM_LOCKED [MCL_FUTURE ์]
โ for_each_vma() โ mlock_fixup()
โ mm_populate() [MCL_CURRENT ์]
์กฐ๊ฑด๋ณ ๋น๊ต
| ๋น๊ต ํญ๋ชฉ | mlock | mlock2 | mlockall |
|---|---|---|---|
| ๋ฒ์ | ํน์ ์ฃผ์ ๋ฒ์ | ํน์ ์ฃผ์ ๋ฒ์ | ํ๋ก์ธ์ค ์ ์ฒด VMA |
| ์ง์ฐ ๋ชจ๋ | ์์ | `MLOCK_ONFAULT` ๊ฐ๋ฅ | `MCL_ONFAULT` ๊ฐ๋ฅ |
| ๋ฏธ๋ ํ ๋น | ์์ | ์์ | `MCL_FUTURE` ์ง์ |
| ํ์ฌ ํ ๋น | ํญ์ | ํญ์ | `MCL_CURRENT` (๊ธฐ๋ณธ๊ฐ) |
| lock_limit ์ฒดํฌ | `mm->locked_vm + len` | `mm->locked_vm + len` | `total_vm <= lock_limit` |
| ๋น๊ต ํญ๋ชฉ | VM_LOCKED | VM_LOCKONFAULT | VM_LOCKED ํด์ |
|---|---|---|---|
| ํ์ด์ง ๊ณ ์ ์์ | VMA ํ๊ทธ ์ | ํ์ด์ง ํดํธ ์ | ํด์ ์ unevictable ํด์ |
| __mm_populate | ํ์ | ๋ถํ์ (ํดํธ ์) | ๋ถํ์ |
| memcg ํ์ | ์ฐจ๋จ | ์ฐจ๋จ | ํ์ฉ |
| unevictable LRU | ์ฆ์ ์ด๋ | ํดํธ ์ ์ด๋ | active/inactive๋ก ๋ณต๊ท |
| ๋น๊ต ํญ๋ชฉ | ํน์ VMA ์ฒ๋ฆฌ | ๊ฒฐ๊ณผ |
|---|---|---|
| VM_SPECIAL ( vdso, vsyscall ๋ฑ) | mlock ๋ฌด์ | `goto out` |
| HugeTLB VMA | mlock ๋ฌด์ | `goto out` |
| DAX VMA | mlock ๋ฌด์ | `goto out` |
| secretmem VMA | mlock ๋ฌด์ | `goto out` |
| VM_DROPPABLE VMA | mlock ๋ฌด์ | `goto out` |
| VM_LOCKED + VM_LOCKED (์ด๋ฏธ ๊ณ ์ ) | ํ์ด์ง ์ํฌ ์์ด ํ๋๊ทธ๋ง ๊ฐฑ์ | `vm_flags_reset` |
| ๋น๊ต ํญ๋ชฉ | __mlock_folio | __munlock_folio | __mlock_new_folio |
|---|---|---|---|
| LRU ์ํ | active/inactive โ unevictable | unevictable โ active/inactive | LRU์ ์์ โ unevictable |
| mlock_count | ์ฆ๊ฐ (์ด๋ฏธ unevictable ์) | ๊ฐ์ (0์ด ๋๋ฉด ํด์ ) | `!!PG_mlocked` |
| ํต๊ณ ์ด๋ฒคํธ | PGCULLED, PGRESCUED | PGMUNLOCKED, PGSTRANDED | PGCULLED |