<kernel v5.0>
Memory Mapped 파일 & anon 매핑
현재 유저 주소 공간의 vm(가상 메모리)에 file(디바이스 포함) 또는 anon 매핑/해제를 요청한다. 다음과 같은 api를 사용한다.
- mmap()
- 현재 유저 주소 공간의 vm(가상 메모리)에 file 또는 anon 매핑을 요청한다.
- munmap()
- 현재 유저 주소 공간의 vm(가상 메모리)에 file 또는 anon 매핑 해제를 요청한다.
- mmap2()
- 가장 마지막 인자가 페이지 단위의 offset을 사용한다는 것만 제외하고 mmap()과 동일하다.
- mmap_pgoff()
- 가장 마지막 인자가 페이지 단위의 offset을 사용한다는 것만 제외하고 mmap()과 동일하다.
mmap() – for user application
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
파일 및 디바이스를 메모리에 매핑한다. anon 매핑 속성을 주어 anon 메모리를 매핑할 수도 있다.
mmap2() – for user application
#include <sys/mman.h> void *mmap2(void *addr, size_t length, int prot, int flags, int fd, off_t pgoffset);
mmap_pgoff() – for user application
#include <sys/mman.h> void *mmap_pgoff(void *addr, size_t length, int prot, int flags, int fd, unsigned long pgoffset);
인수
- addr
- 매핑을 원하는 시작 가상 주소 값으로 커널이 이를 hint로 이용하여 적절한 주소를 찾으려고 한다. null이 입력되는 경우 커널이 빈 공간을 찾는다.
- length
- 매핑할 길이(bytes)
- prot
- 메모리 보호 속성
- PROT_EXEC: 페이지는 실행 가능하다.
- PROT_READ: 페이지는 읽기 가능하다.
- PROT_WRITE: 페이지는 쓰기 가능하다.
- PROT_NONE: 페이지는 접근할 할 수 없다.
- 메모리 보호 속성
- flags
- 플래그
- fd
- 파일 디스크립터
- offset
- 바이트 offset
- pgoff
- 페이지 offset
- file 매핑하는 경우 skip 할 페이지 수를 사용한다.
요청 플래그
- MAP_FIXED
- 지정된 시작 가상 주소로 매핑한다. 지정된 주소를 사용할 수 없는 경우 mmap()은 실패한다.
- 가상 시작 주소 addr는 페이지 사이즈의 배수여야 한다.
- 요청 영역이 기존 매핑과 중복되는 경우 중복되는 영역의 기존 매핑은 제거된다.
- MAP_ANONYMOUS
- 어떠한 파일하고도 연결되지 않고 0으로 초기화된 영역.
- fd는 무시되지만 어떤 경우에는 -1이 요구된다.
- offset는 0으로 한다.
- MAP_SHARED와 같이 사용될 수도 있다. (커널 v2.4)
- MAP_FILE
- 파일 매핑
- MAP_SHARED
- 다른 프로세스와 영역에 대해 공유 매핑.
- MAP_PRIVATE
- private COW(Copy On Write) 매핑을 만든다. 이 영역은 다른 프로세스와 공유되지 않는다.
- MAP_DENYWRITE
- 쓰기 금지된 매핑 영역
- MAP_EXECUTABLE
- 실행 가능한 매핑 영역
- MAP_GROWSDOWN
- 하향으로 자라는 스택에 사용된다.
- MAP_HUGETLB (커널 v2.6.32)
- huge page들을 사용하여 할당하게 한다.
- MAP_HUGE_2MB, MAP_HUGE_1GB (커널 v3.8)
- MAP_HUGETLB와 같이 사용되며 huge page를 선택할 수 있다.
- “/sys/kernel/mm/hugepages” 디렉토리에 사용할 수 있는 huge page 종류를 볼 수 있다.
- MAP_LOCKED
- mlock()과 동일하게 요청 영역은 물리메모리가 미리 할당되어 매핑된다.
- mlock()과 다르게 물리메모리를 미리 할당하고 매핑할 때 실패하더라도 곧바로 -ENOMEM 에러를 발생시키지 않는다.
- MAP_NONBLOCK
- 오직 MAP_POPULATE와 같이 사용될 때에만 의미가 있다.
- MAP_NORESERVE
- 이 영역은 swap 공간을 준비하지 않는다.
- MAP_POPULATE
- 매핑에 대한 페이지 테이블을 활성화(prefault)한다.
- private 매핑에서만 사용된다. (커널 v2.6.23)
- MAP_STACK (커널 v2.6.27)
- 프로세스 또는 스레드 스택을 할당한다.
- MAP_UNINITIALIZED (커널 v2.6.33)
- anonymous 페이지들을 클리어하지 않는다.
Memory Mapped Mapping 사례
private anon 예)
- mmap((void *) 0x200000000000ULL, 4096 * 30, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
- 첫 번째 인수 addr에 보통 null을 사용한다. 결과 확인을 위해 쉽게 하기 위해 주소를 지정하였다.
- anon vma 영역이 만들어지지만 Rss 값이 0으로 보이는 것과 같이실제 메모리가 매핑된 상태는 아니다. 추후 메모리에 액세스할 때 fault가 발생하고 fault 핸들러에 의해 물리 메모리가 할당된 후 매핑된다. (lazy allocation)
200000000000-20000001e000 rw-p 00000000 00:00 0 Size: 120 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Rss: 0 kB Pss: 0 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Dirty: 0 kB Referenced: 0 kB Anonymous: 0 kB LazyFree: 0 kB AnonHugePages: 0 kB ShmemPmdMapped: 0 kB Shared_Hugetlb: 0 kB Private_Hugetlb: 0 kB Swap: 0 kB SwapPss: 0 kB Locked: 0 kB THPeligible: 1 VmFlags: rd wr mr mw me ac
private file 예)
- mmap(NULL, 15275, PROT_READ, MAP_PRIVATE, fd, 0);
- fd는 abc.txt를 open한 file 디스크립터이다.
- file vma 영역이 만들어지지만 Rss 값이 0으로 보이는 것과 같이실제 메모리가 매핑된 상태는 아니다. 추후 메모리에 액세스할 때 fault가 발생하고 fault 핸들러에 의해 물리 메모리가 할당된 후 파일 내용이 읽히고 그 후 매핑된다. (lazy allocation)
ffffb2d14000-ffffb2d15000 r--p 00000000 fe:00 416016 /root/workspace/test/mmap/abc.txt Size: 4 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Rss: 0 kB Pss: 0 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Dirty: 0 kB Referenced: 0 kB Anonymous: 0 kB LazyFree: 0 kB AnonHugePages: 0 kB ShmemPmdMapped: 0 kB Shared_Hugetlb: 0 kB Private_Hugetlb: 0 kB Swap: 0 kB SwapPss: 0 kB Locked: 0 kB THPeligible: 0 VmFlags: rd mr mw me
locked private file 예)
- mmap(NULL, 15275, PROT_READ, MAP_PRIVATE | MAP_LOCKED, fd, 0);
- MAP_LOCKED를 사용하여 파일의 내용을 실제 메모리에 모두 선(pre) 매핑하였다. Size 항목과 Rss 항목이 동일함을 알 수 있다.
ffffba677000-ffffba67b000 r--p 00000000 fe:00 416016 /root/workspace/test/mmap/abc.txt Size: 16 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Rss: 16 kB Pss: 16 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 16 kB Private_Dirty: 0 kB Referenced: 16 kB Anonymous: 0 kB LazyFree: 0 kB AnonHugePages: 0 kB ShmemPmdMapped: 0 kB Shared_Hugetlb: 0 kB Private_Hugetlb: 0 kB Swap: 0 kB SwapPss: 0 kB Locked: 16 kB THPeligible: 0 VmFlags: rd mr mw me lo
ARM32용 Memory Mapped 매핑
다음 그림은 mmap syscall 호출에 따른 커널에서의 함수 호출 관계를 보여준다.
sys_mmap2() – ARM32
arch/arm/kernel/entry-common.S
/* * Note: off_4k (r5) is always units of 4K. If we can't do the requested * offset, we return EINVAL. */
sys_mmap2: streq r5, [sp, #4] beq sys_mmap_pgoff ENDPROC(sys_mmap2)
파일 및 디바이스를 메모리에 매핑한다. anon 매핑 속성을 주어 anon 메모리를 매핑할 수도 있다.
sys_mmap_pgoff() – Generic (ARM32, …)
mm/mmap.c
SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len, unsigned long, prot, unsigned long, flags, unsigned long, fd, unsigned long, pgoff) { return ksys_mmap_pgoff(addr, len, prot, flags, fd, pgoff); }
ARM64용 Memory Mapped 매핑
sys_mmap() – ARM64
arch/arm64/kernel/sys.c
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len, unsigned long, prot, unsigned long, flags, unsigned long, fd, off_t, off) { if (offset_in_page(off) != 0) return -EINVAL; return ksys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT); }
sys_mmap_pgoff() – ARM64
arch/arm64/kernel/sys.c
SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len, unsigned long, prot, unsigned long, flags, unsigned long, fd, unsigned long, pgoff) { return ksys_mmap_pgoff(addr, len, prot, flags, fd, pgoff); }
공통부 – Memory Mapped 매핑
ksys_mmap_pgoff()
mm/mmap.c
unsigned long ksys_mmap_pgoff(unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long fd, unsigned long pgoff) { struct file *file = NULL; unsigned long retval; if (!(flags & MAP_ANONYMOUS)) { audit_mmap_fd(fd, flags); file = fget(fd); if (!file) return -EBADF; if (is_file_hugepages(file)) len = ALIGN(len, huge_page_size(hstate_file(file))); retval = -EINVAL; if (unlikely(flags & MAP_HUGETLB && !is_file_hugepages(file))) goto out_fput; } else if (flags & MAP_HUGETLB) { struct user_struct *user = NULL; struct hstate *hs; hs = hstate_sizelog((flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK); if (!hs) return -EINVAL; len = ALIGN(len, huge_page_size(hs)); /* * VM_NORESERVE is used because the reservations will be * taken when vm_ops->mmap() is called * A dummy user value is used because we are not locking * memory so no accounting is necessary */ file = hugetlb_file_setup(HUGETLB_ANON_FILE, len, VM_NORESERVE, &user, HUGETLB_ANONHUGE_INODE, (flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK); if (IS_ERR(file)) return PTR_ERR(file); } flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff); out_fput: if (file) fput(file); return retval; }
file 디스크립터에서 pgoff 페이지 부터 요청 길이만큼 요청한 가상 주소에 prot 속성으로 매핑한다.
- 코드 라인 8~17에서 file 매핑인 경우 audit 설정하고, huge 페이지를 사용하는 파일인 경우 길이를 huge 페이지에 맞춰 정렬한다.
- 현재 태스크의 audit_context가 설정된 경우 audit_context에 요청 fd와 flags를 설정하고 타입으로 AUDIT_MMAP을 대입한다.
- file 디스크립터를 통해 file을 알아오는데 실패하는 경우 out 레이블을 통해 -EBADF 에러를 반환한다.
- hugetlbfs 또는 huge 페이지 매핑을 이용하는 공유 파일인 경우 길이를 huge 페이지 단위로 정렬한다.
- MAP_HUGETLB 플래그 요청을 하였지만 file이 huge 페이지 요청 타입이 아닌 경우 -EINVAL 에러를 반환한다.
- 코드 라인 18~39에서 MAP_HUGETLB 요청이 있는 경우 길이를 huge 페이지 단위로 정렬하여 HUGETLB_ANON_FILE 형태의 파일을 준비한다. 플래그에 기록된 huge 페이지 정보가 발견되지 않으면 -EINVAL 에러를 반환한다.
- 플래그의 bit26~bit31에 사이즈를 로그 단위로 변환하여 저장하였다.
- x86 예) 21 -> 2M huge page, 30 -> 1G huge page
- 플래그의 bit26~bit31에 사이즈를 로그 단위로 변환하여 저장하였다.
- 코드 라인 41~43에서 플래그에서 MAP_EXECUTABLE 및 MAP_DENYWRITE를 제외하고 매핑을 한다.
is_file_hugepages()
include/linux/hugetlb.h
static inline int is_file_hugepages(struct file *file) { if (file->f_op == &hugetlbfs_file_operations) return 1; if (is_file_shm_hugepages(file)) return 1; return 0; }
file이 hugetlbfs에서 사용하는 파일이거나 huge 페이지를 사용하는 공유 메모리 파일인 경우 1을 반환한다. 그 외에는 0을 반환한다.
Audit 관련 함수
audit_mmap_fd()
include/linux/audit.h
static inline void audit_mmap_fd(int fd, int flags) { if (unlikely(!audit_dummy_context())) __audit_mmap_fd(fd, flags); }
작은 확률로 현재 태스크의 audit_context가 설정된 경우 audit_context에 요청 fd와 flags를 설정하고 타입으로 AUDIT_MMAP을 대입한다.
audit_dummy_context()
include/linux/audit.h
static inline int audit_dummy_context(void) { void *p = current->audit_context; return !p || *(int *)p; }
현재 태스크의 audit_context가 설정된 경우 0을 반환하고, 설정되지 않은 경우 0이 아닌 값을 반환한다.
__audit_mmap_fd()
kernel/auditsc.c
void __audit_mmap_fd(int fd, int flags) { struct audit_context *context = current->audit_context; context->mmap.fd = fd; context->mmap.flags = flags; context->type = AUDIT_MMAP; }
현재 태스크의 audit_context에 요청 fd와 flags를 설정하고 타입으로 AUDIT_MMAP을 대입한다.
vm_mmap_pgoff()
mm/util.c
unsigned long vm_mmap_pgoff(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long pgoff) { unsigned long ret; struct mm_struct *mm = current->mm; unsigned long populate; LIST_HEAD(uf); ret = security_mmap_file(file, prot, flag); if (!ret) { if (down_write_killable(&mm->mmap_sem)) return -EINTR; ret = do_mmap_pgoff(file, addr, len, prot, flag, pgoff, &populate, &uf); up_write(&mm->mmap_sem); userfaultfd_unmap_complete(mm, &uf); if (populate) mm_populate(ret, populate); } return ret; }
@file을 @pgoff 페이지 부터 요청 길이만큼 가상 주소 @addr에 @prot 속성으로 매핑한다.
- 코드 라인 10에서 LSM 및 LIM을 통해 파일 매핑의 허가 여부를 알아온다. 0=성공
- LSM(Linux Security Module)과 LIM(Linux Integrity Module)의 hook api를 호출하여 한다.
- 코드 라인 10~15에서 mmap_sem write 세마포어 락을 사용하여 vm 매핑을 요청한다.
- 코드 라인 16에서 userfaultfd 언맵 완료를 지시한다.
- userfaultfd
- on demand paging을 사용하는 리눅스는 메모리 할당 요청 시 실제 매모리를 할당하지 않고, 유저 application이 해당 할당된 영역에 접근 시 fault가 발생 후 커널이 이에 대한 실제 메모리를 할당하여 매핑한다. 이러한 것을 유저 application에게 처리하도록 설계된 것이 userfaultfd이다.
- userfaultfd
- 코드 라인 17~18에서 매핑 영역을 활성화(물리 RAM을 매핑)한다.
do_mmap_pgoff()
mm/mmap.c
static inline unsigned long do_mmap_pgoff(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long pgoff, unsigned long *populate, struct list_head *uf) { return do_mmap(file, addr, len, prot, flags, 0, pgoff, populate, uf); }
do_mmap()
mm/mmap.c -1/3-
/* * The caller must hold down_write(¤t->mm->mmap_sem). */
unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, vm_flags_t vm_flags, unsigned long pgoff, unsigned long *populate, struct list_head *uf) { struct mm_struct *mm = current->mm; int pkey = 0; *populate = 0; if (!len) return -EINVAL; /* * Does the application expect PROT_READ to imply PROT_EXEC? * * (the exception is when the underlying filesystem is noexec * mounted, in which case we dont add PROT_EXEC.) */ if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC)) if (!(file && path_noexec(&file->f_path))) prot |= PROT_EXEC; /* force arch specific MAP_FIXED handling in get_unmapped_area */ if (flags & MAP_FIXED_NOREPLACE) flags |= MAP_FIXED; if (!(flags & MAP_FIXED)) addr = round_hint_to_min(addr); /* Careful about overflows.. */ len = PAGE_ALIGN(len); if (!len) return -ENOMEM; /* offset overflow? */ if ((pgoff + (len >> PAGE_SHIFT)) < pgoff) return -EOVERFLOW; /* Too many mappings? */ if (mm->map_count > sysctl_max_map_count) return -ENOMEM; /* Obtain the address to map to. we verify (or select) it and ensure * that it represents a valid section of the address space. */ addr = get_unmapped_area(file, addr, len, pgoff, flags); if (offset_in_page(addr)) return addr; if (flags & MAP_FIXED_NOREPLACE) { struct vm_area_struct *vma = find_vma(mm, addr); if (vma && vma->vm_start < addr + len) return -EEXIST; } if (prot == PROT_EXEC) { pkey = execute_only_pkey(mm); if (pkey < 0) pkey = 0; } /* Do simple checking here so the lower-level routines won't have * to. we assume access permissions have been handled by the open * of the memory object, so we don't do any here. */ vm_flags |= calc_vm_prot_bits(prot, pkey) | calc_vm_flag_bits(flags) | mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC; if (flags & MAP_LOCKED) if (!can_do_mlock()) return -EPERM; if (mlock_future_check(mm, vm_flags, len)) return -EAGAIN;
file을 pgoff 페이지 부터 요청 길이만큼 가상 주소 addr에 prot 속성으로 매핑한다. 실제 메모리가 매핑된 경우 출력 인수 populate에 길이를 대입한다.
- 코드 라인 12~13에서 길이가 0인 경우 -EINVAL 에러를 반환한다.
- 코드 라인 21~23에서 read 속성이 요청되는 경우 현재 태스크의 personality에 READ_IMPLIES_EXEC가 설정된 경우 exec 속성을 추가한다. 단 sysfs 및 proc 마운트 위의 file과 같이 마운트 플래그가 MNT_NOEXEC 설정이 있는경우는 제외한다.
- 코드 라인 26~27에서
- 코드 라인 29~30에서 고정 매핑을 요청한 경우가 아니면 페이지 정렬한 주소를 사용하되 mmap_min_addr 보다 작은 경우 mmap_min_addr 주소로 변경한다.
- 코드 라인 33~35에서 길이를 페이지 단위로 정렬한다. 단 0인 경우 -ENOMEM 에러를 반환한다.
- 코드 라인 38~39에서 pgoff 페이지+ len 페이지가 시스템 주소 범위를 초과하는 경우 -EOVERFLOW 에러를 반환한다.
- 코드 라인 42~43에서 현재 태스크의 메모리 디스크립터에 매핑된 수가 최대치 허용을 초과한 경우 -ENOMEM 에러를 반환한다.
- 기본 매핑 최대치: 65530 (“/proc/sys/vm/max_map_count”에서 설정)
- 코드 라인 48~50에서 매핑되지 않은 영역을 찾아 시작 가상 주소를 알아온다. 만일 에러인 경우 에러 코드를 반환한다.
- 코드 라인 52~57에서 MAP_FIXED_NOREPLACE 플래그 요청이 있는 경우 해당 주소에 대해 vma에 영역이 없는 경우에만 매핑할 수 있다. 따라서 이미 존재하는 경우 -EEXIST 에러를 반환한다.
- 코드 라인 59~63에서 실행 속성이 있는 경우 Protection Key를 확인한다.
- x86 및 powerpc 아키텍처에서만 지원하고 아직 ARM, ARM64에서는 사용되지 않고 있다.
- 코드 라인 69~70에서 vm_flags에 vm 플래그로 변환한 prot 플래그, vm 플래그로 변환한 flags, 메모리 디스크립터의 기본 플래그에 mayread, maywrite, mayexec를 추가한다.
- 코드 라인 72~74에서 MAP_LOCKED 플래그 요청이 있는 경우 mlock 최대치 제한 등으로 인해 수행할 수 없으면 -EPERM 에러를 반환한다.
- 코드 라인 76~77에서 VM_LOCKED 요청이 있고 기존 mlock 페이지에 요청 길이를 추가하여 mlock 페이지 최대치 제한에 걸리는 경우 -EAGAIN 에러를 반환한다.
mm/mmap.c -2/3-
if (file) { struct inode *inode = file_inode(file); unsigned long flags_mask; if (!file_mmap_ok(file, inode, pgoff, len)) return -EOVERFLOW; flags_mask = LEGACY_MAP_MASK | file->f_op->mmap_supported_flags; switch (flags & MAP_TYPE) { case MAP_SHARED: /* * Force use of MAP_SHARED_VALIDATE with non-legacy * flags. E.g. MAP_SYNC is dangerous to use with * MAP_SHARED as you don't know which consistency model * you will get. We silently ignore unsupported flags * with MAP_SHARED to preserve backward compatibility. */ flags &= LEGACY_MAP_MASK; /* fall through */ case MAP_SHARED_VALIDATE: if (flags & ~flags_mask) return -EOPNOTSUPP; if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE)) return -EACCES; /* * Make sure we don't allow writing to an append-only * file.. */ if (IS_APPEND(inode) && (file->f_mode & FMODE_WRITE)) return -EACCES; /* * Make sure there are no mandatory locks on the file. */ if (locks_verify_locked(file)) return -EAGAIN; vm_flags |= VM_SHARED | VM_MAYSHARE; if (!(file->f_mode & FMODE_WRITE)) vm_flags &= ~(VM_MAYWRITE | VM_SHARED); /* fall through */ case MAP_PRIVATE: if (!(file->f_mode & FMODE_READ)) return -EACCES; if (path_noexec(&file->f_path)) { if (vm_flags & VM_EXEC) return -EPERM; vm_flags &= ~VM_MAYEXEC; } if (!file->f_op->mmap) return -ENODEV; if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP)) return -EINVAL; break; default: return -EINVAL; }
- 코드 라인 1~8에서 file 매핑인 경우 inode 값을 가져온다.
- 코드 라인 10~42에서 공유 파일 매핑(MAP_SHARED) 플래그 요청인 경우 다음과 같이 처리한다.
- write 속성이 없는 파일에 write 요청을 한 경우 -EACCESS 에러를 반환한다.
- append only 파일인 경우 write 요청을 한 경우 -EACCESS 에러를 반환한다.
- mandatory 락이 걸려있는 파일인 경우 -EAGAIN 에러를 반환한다.
- vm_flags에 share 및 mayshare 속성을 추가한다.
- write 속성이 없는 파일인 경우 maywrite와 shared를 제거한다. 아래 private 요청을 계속 진행한다.
- 코드 라인 45~58에서 private 파일 매핑(MAP_PRIVATE) 플래그 요청인 경우 다음과 같이 처리한다.
- read 속성이 없는 파일인 경우 -EACCESS 에러를 반환한다.
- file이 마운트 곳의 마운트 플래그에 실행 금지가 설정된 경우 mayexec를 제거한다. 단 VM_EXEC 요청이 있는 경우에는 -EPERM 에러를 반환한다.
- “/proc 및 /sys”가 마운트 되어 있는 곳은 실행 파일이 있을 수 없다.
- 매핑이 없는 파일은 -ENODEV 에러를 반환한다.
- growsdown 및 growsup 요청된 경우 -EINVAL 에러를 반환한다.
mm/mmap.c -3/3-
} else { switch (flags & MAP_TYPE) { case MAP_SHARED: if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP)) return -EINVAL; /* * Ignore pgoff. */ pgoff = 0; vm_flags |= VM_SHARED | VM_MAYSHARE; break; case MAP_PRIVATE: /* * Set pgoff according to addr for anon_vma. */ pgoff = addr >> PAGE_SHIFT; break; default: return -EINVAL; } } /* * Set 'VM_NORESERVE' if we should not account for the * memory use of this mapping. */ if (flags & MAP_NORESERVE) { /* We honor MAP_NORESERVE if allowed to overcommit */ if (sysctl_overcommit_memory != OVERCOMMIT_NEVER) vm_flags |= VM_NORESERVE; /* hugetlb applies strict overcommit unless MAP_NORESERVE */ if (file && is_file_hugepages(file)) vm_flags |= VM_NORESERVE; } addr = mmap_region(file, addr, len, vm_flags, pgoff, uf); if (!IS_ERR_VALUE(addr) && ((vm_flags & VM_LOCKED) || (flags & (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE)) *populate = len; return addr; }
- 코드 라인 1~21에서 anon 매핑인 경우 다음 두 가지 요청을 수행한다.
- shared anon 매핑인 경우 pgoff=0, shared 및 maysahre 플래그를 추가한다. 단 growsdown 및 growsup 요청된 경우 -EINVAL 에러를 반환한다.
- private anon 매핑인 경우 pgoff에 가상 주소 페이지 번호를 대입한다.
- 코드 라인 27~35에서 no reserve 요청인 경우 over commit 모드가 OVERCOMMIT_NEVER가 아닌 경우 또는 또한 huge page 파일인 경우 vm_noreserve 플래그를 추가한다.
- 코드 라인 37에서 file을 pgoff 페이지 부터 가상 주소 addr에 길이 len 만큼 vm_flags 속성을 사용하여 vma를 구성하고 등록한다.
- 코드 라인 38~41에서 실제 메모리가 매핑된 경우 출력 인수 populate에 길이를 대입한다.
round_hint_to_min()
mm/mmap.c
/* * If a hint addr is less than mmap_min_addr change hint to be as * low as possible but still greater than mmap_min_addr */
static inline unsigned long round_hint_to_min(unsigned long hint) { hint &= PAGE_MASK; if (((void *)hint != NULL) && (hint < mmap_min_addr)) return PAGE_ALIGN(mmap_min_addr); return hint; }
가상 주소 hint를 페이지 단위로 절삭하고 반환한다. 단 그 주소가 mmap_min_addr 보다 낮은 경우 페이지 단위로 정렬한 mmap_min_addr 주소를 반환한다.
calc_vm_prot_bits()
include/linux/mman.h
/* * Combine the mmap "prot" argument into "vm_flags" used internally. */
static inline unsigned long calc_vm_prot_bits(unsigned long prot) { return _calc_vm_trans(prot, PROT_READ, VM_READ ) | _calc_vm_trans(prot, PROT_WRITE, VM_WRITE) | _calc_vm_trans(prot, PROT_EXEC, VM_EXEC) | arch_calc_vm_prot_bits(prot); }
prot 값에 PROT_READ, PROT_WRITE, PROT_EXEC 비트가 있는 경우 각각 VM_READ, VM_WRITE, VM_EXEC 플래그 속성으로 변환하여 반환한다.
- 특정 아키텍처에 맞게 속성을 추가할 수 있다. (arm은 추가 없음)
_calc_vm_trans()
include/linux/mman.h
/* * Optimisation macro. It is equivalent to: * (x & bit1) ? bit2 : 0 * but this version is faster. * ("bit1" and "bit2" must be single bits) */
#define _calc_vm_trans(x, bit1, bit2) \ ((!(bit1) || !(bit2)) ? 0 : \ ((bit1) <= (bit2) ? ((x) & (bit1)) * ((bit2) / (bit1)) \ : ((x) & (bit1)) / ((bit1) / (bit2))))
x 플래그에서 bi1 속성이 있는 경우 bit 속성으로 변환하여 반환한다. 없는 경우 0을 반환한다.
calc_vm_flag_bits()
include/linux/mman.h
/* * Combine the mmap "flags" argument into "vm_flags" used internally. */
static inline unsigned long calc_vm_flag_bits(unsigned long flags) { return _calc_vm_trans(flags, MAP_GROWSDOWN, VM_GROWSDOWN ) | _calc_vm_trans(flags, MAP_DENYWRITE, VM_DENYWRITE ) | _calc_vm_trans(flags, MAP_LOCKED, VM_LOCKED ) | _calc_vm_trans(flags, MAP_SYNC, VM_SYNC ); }
flags 값에 MAP_GROWSDOWN, MAP_DENYWRITE, MAP_LOCKED, MAP_SYNC 비트가 있는 경우 각각 VM_GROWSDOWN, VM_DENYWRITE, VM_LOCKED, VM_SYNC 플래그 속성으로 변환하여 반환한다.
VMA 영역 구성(확장/병합/신규)
mmap_region()
mm/mmap.c -1/3-
unsigned long mmap_region(struct file *file, unsigned long addr, unsigned long len, vm_flags_t vm_flags, unsigned long pgoff, struct list_head *uf) { struct mm_struct *mm = current->mm; struct vm_area_struct *vma, *prev; int error; struct rb_node **rb_link, *rb_parent; unsigned long charged = 0; /* Check against address space limit. */ if (!may_expand_vm(mm, vm_flags, len >> PAGE_SHIFT)) { unsigned long nr_pages; /* * MAP_FIXED may remove pages of mappings that intersects with * requested mapping. Account for the pages it would unmap. */ nr_pages = count_vma_pages_range(mm, addr, addr + len); if (!may_expand_vm(mm, vm_flags, (len >> PAGE_SHIFT) - nr_pages)) return -ENOMEM; } /* Clear old maps */ while (find_vma_links(mm, addr, addr + len, &prev, &rb_link, &rb_parent)) { if (do_munmap(mm, addr, len, uf)) return -ENOMEM; } /* * Private writable mapping: check memory availability */ if (accountable_mapping(file, vm_flags)) { charged = len >> PAGE_SHIFT; if (security_vm_enough_memory_mm(mm, charged)) return -ENOMEM; vm_flags |= VM_ACCOUNT; } /* * Can we just expand an old mapping? */ vma = vma_merge(mm, prev, addr, addr + len, vm_flags, NULL, file, pgoff, NULL, NULL_VM_UFFD_CTX); if (vma) goto out; /* * Determine the object being mapped and call the appropriate * specific mapper. the address has already been validated, but * not unmapped, but the maps are removed from the list. */ vma = vm_area_alloc(mm); if (!vma) { error = -ENOMEM; goto unacct_error; } vma->vm_start = addr; vma->vm_end = addr + len; vma->vm_flags = vm_flags; vma->vm_page_prot = vm_get_page_prot(vm_flags); vma->vm_pgoff = pgoff;
file을 pgoff 페이지 부터 요청 길이만큼 가상 주소 addr에 prot 속성으로 vma를 구성하고 등록한다. 기존 vma 영역을 확장하거나 병합할 수 있고, 없으면 신규로 생성한다.
- 코드 라인 12 ~24에서 len 페이지 만큼의 공간을 확장할 수 없는 경우 fix 매핑 여부에 따라 다음과 같이 처리한다.
- fix 매핑이 아닌 경우 -ENOMEM 에러를 반환한다.
- fix 매핑인 경우 겹치는 영역만큼은 뺴고 매핑할 계획이다. 기존 영역과 겹치지 않는 페이지 만큼이 확장 가능하지 않으면 -ENOMEM 에러를 반환환다.
- 코드 라인 27~31에서 요청 영역과 겹치는 기존 영역들을 언매핑한다.
- 코드 라인 36~41에서 vm 메모리 계량이 필요한 매핑인 경우 LSM을 통해 charged 페이지 만큼 vm 메모리 할당으로 commit하고 허용된 경우 VM_ACCOUNT 플래그를 추가한다.
- accountable 매핑 확인(private writable 매핑)
- huge 파일 매핑이 아니고 noreserve 및 shared가 없고 write 요청은 있는 경우이다.
- LSM 모듈에서 SELinux 모듈을 사용 여부에 따라 selinux_vm_enough_memory() 함수를 먼저 호출하여 admin 권한만큼의 추가 영역을 확보한 후 __vm_enough_memory() 함수를 호출하여 commit 량에 대해 commit 옵션에 따라 vm 허용치 제한을 통해 할당 여부를 반환한다.
- LSM 모듈에서 기본 Posix Capability 모듈만을 사용하는 경우 cap_vm_enough_memory() 함수를 먼저 호출하여 admin권한만큼의 추가 영역을 확보한 후 __vm_enough_memory() 함수를 호출하여 commit 량에 대해 commit 옵션에 따라 vm 허용치 제한을 통해 할당 여부를 반환한다.
- accountable 매핑 확인(private writable 매핑)
- 코드 라인 46~49에서 기존 영역과 병합하고, 성공한 경우 out 레이블로 이동한다.
- 코드 라인 56~66에서 새로운 vma(vm_area_struct 구조체)를 할당하고 구성한다.
mm/mmap.c -2/3-
. if (file) { if (vm_flags & VM_DENYWRITE) { error = deny_write_access(file); if (error) goto free_vma; } if (vm_flags & VM_SHARED) { error = mapping_map_writable(file->f_mapping); if (error) goto allow_write_and_free_vma; } /* ->mmap() can change vma->vm_file, but must guarantee that * vma_link() below can deny write-access if VM_DENYWRITE is set * and map writably if VM_SHARED is set. This usually means the * new file must not have been exposed to user-space, yet. */ vma->vm_file = get_file(file); error = call_mmap(file, vma); if (error) goto unmap_and_free_vma; /* Can addr have changed?? * * Answer: Yes, several device drivers can do it in their * f_op->mmap method. -DaveM * Bug: If addr is changed, prev, rb_link, rb_parent should * be updated for vma_link() */ WARN_ON_ONCE(addr != vma->vm_start); addr = vma->vm_start; vm_flags = vma->vm_flags; } else if (vm_flags & VM_SHARED) { error = shmem_zero_setup(vma); if (error) goto free_vma; } else { vma_set_anonymous(vma); } vma_link(mm, vma, prev, rb_link, rb_parent); /* Once vma denies write, undo our temporary denial count */ if (file) { if (vm_flags & VM_SHARED) mapping_unmap_writable(file->f_mapping); if (vm_flags & VM_DENYWRITE) allow_write_access(file); } file = vma->vm_file;
- 코드 라인 1~33에서 file 매핑인 경우 다음과 같이 처리한다.
- denywrite 요청이 있는 경우 file을 denywrite 상태로 만든다. 실패하는 경우 free_vma 레이블로 이동한다.
- shared 요청이 있는 경우 매핑 영역에 대해 다음 writable 파일 매핑 요청이 거절되게 설정한다. 실패하는 경우 allow_write_and_free_vma 레이블로 이동한다.
- file의 사용 카운터 f_count를 증가시키고 file을 vma_vm_file에 대입한다.
- vma 정보로 file 매핑을 수행한다. 실패하는 경우 unmap_and_free_vma 레이블로 이동한다.
- 코드 라인 34~37에서 shared anon 매핑을 준비한다. 실패하는 경우 free_vma 레이블로 이동한다.
- “/dev/zero” 파일을 vma->vm_file에 지정하고 vma->vm_ops에 전역 shmem_vm_ops를 대입한다.
- 코드 라인 38~40에서 private anon 매핑을 준비한다.
- 코드 라인 42에서 vma 정보를 추가한다.
- 코드 라인 44~49에서 파일 매핑인 경우 다음과 같이 처리한다.
- shared file 매핑인 경우 매핑 영역에 대해 writable 매핑이 가능하게 바꾼다.
- 그리고 denywrite 요청을 가진 file 매핑인 경우 file에 대해 writable 매핑이 가능하도록 설정한다.
mm/mmap.c -3/3-
out: perf_event_mmap(vma); vm_stat_account(mm, vm_flags, len >> PAGE_SHIFT); if (vm_flags & VM_LOCKED) { if ((vm_flags & VM_SPECIAL) || vma_is_dax(vma) || is_vm_hugetlb_page(vma) || vma == get_gate_vma(current->mm)) vma->vm_flags &= VM_LOCKED_CLEAR_MASK; else mm->locked_vm += (len >> PAGE_SHIFT); } if (file) uprobe_mmap(vma); /* * New (or expanded) vma always get soft dirty status. * Otherwise user-space soft-dirty page tracker won't * be able to distinguish situation when vma area unmapped, * then new mapped in-place (which must be aimed as * a completely new data area). */ vma->vm_flags |= VM_SOFTDIRTY; vma_set_page_prot(vma); return addr; unmap_and_free_vma: vma->vm_file = NULL; fput(file); /* Undo any partial mapping done by a device driver. */ unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end); charged = 0; if (vm_flags & VM_SHARED) mapping_unmap_writable(file->f_mapping); allow_write_and_free_vma: if (vm_flags & VM_DENYWRITE) allow_write_access(file); free_vma: vm_area_free(vma); unacct_error: if (charged) vm_unacct_memory(charged); return error; }
- 코드 라인 1~2에서 out: 레이블이다. mmap에 대한 performance events 정보를 출력한다.
- 코드 라인 4에서 메모리 디스크립터에 대해 가상 메모리에 대한 몇 개의 vm stat 카운터를 len 페이지만큼 추가한다.
- 코드 라인 5~12에서 mlock(VM_LOCKED) 요청인 경우 VM_SPECIAL, hugetlb 페이지 또는 gate vma 요청인 경우 VM_LOCKED 플래그를 삭제한다. 그렇지 않은 경우 mm->locked_vm에 len 페이지를 추가한다.
- 코드 라인 14~15에서 file 매핑인 경우 uprobe가 설정되어 있고 uprobe filter 체인이 걸려있는 경우 는 요청 주소에 break point 명령 코드로 업데이트한다.
- 코드 라인 24~28에서 softdirty 플래그를 추가하고 vma에 기록하고 정상적으로 가상 주소를 반환한다.
- 코드 라인 30~38에서 unmap_and_free_vma: 레이블이다. 여기에서는 매핑을 해제하는 루틴이 수행된다.
- 코드 라인 39~41에서 allow_write_and_free_vma: 레이블은 denywrite 요청이 있는 경우 inode의 i_writecount를 증가 시킨다.
- 코드 라인 42~43에서 free_vma: 레이블은 vma object를 할당 해제 한다.
- 코드 라인 44~47에서 unacct_error: 레이블은 vm 계량을 했었던(Private writable 매핑) 경우 commit 양을 되돌리기 위해 charged 만큼 다시 감소시킨다.
count_vma_pages_range()
mm/mmap.c
static unsigned long count_vma_pages_range(struct mm_struct *mm, unsigned long addr, unsigned long end) { unsigned long nr_pages = 0; struct vm_area_struct *vma; /* Find first overlaping mapping */ vma = find_vma_intersection(mm, addr, end); if (!vma) return 0; nr_pages = (min(end, vma->vm_end) - max(addr, vma->vm_start)) >> PAGE_SHIFT; /* Iterate over the rest of the overlaps */ for (vma = vma->vm_next; vma; vma = vma->vm_next) { unsigned long overlap_len; if (vma->vm_start > end) break; overlap_len = min(end, vma->vm_end) - vma->vm_start; nr_pages += overlap_len >> PAGE_SHIFT; } return nr_pages; }
요청 영역과 기존 vma 영역과 겹치는 페이지 수를 반환한다. 겹치는 영역이 없으면 0을 반환한다.
- 코드 라인 8~10에서 기존 vma 영역과 요청 영역이 겹치는 경우 관련 vma를 알아온다. 겹치는 영역이 없는 경우 0을 반환한다.
- 코드 라인 12~13에서 현재 vma에서 겹치는 페이지 수를 산출한다.
- 코드 라인 16~26에서 다음 vma 영역들과도 비교하여 겹치는 페이지 수를 산출한 후 반환한다.
accountable_mapping()
mm/mmap.c
/* * We account for memory if it's a private writeable mapping, * not hugepages and VM_NORESERVE wasn't set. */
static inline int accountable_mapping(struct file *file, vm_flags_t vm_flags) { /* * hugetlb has its own accounting separate from the core VM * VM_HUGETLB may not be set yet so we cannot check for that flag. */ if (file && is_file_hugepages(file)) return 0; return (vm_flags & (VM_NORESERVE | VM_SHARED | VM_WRITE)) == VM_WRITE; }
vm 메모리 계량이 필요한 매핑인지 확인한다.
- accountable 매핑 확인(private writable 매핑)
- huge 파일 매핑이 아니고 noreserve 및 shared가 없고 write 요청은 있는 경우이다.
writable 파일 매핑(1)
inode->i_writecount 값 상태
- 0(writable)
- write 권한이 허용되지 않은 상태로 새로운 writable 권한 요청이 가능한 상태
- 1(write)
- write 권한이 허용된 상태로 새로운 write 권한 요청은 금지된 상태
- 음수(denywrite)
- denywrite 요청에 의해 모든 write 권한 요청이 금지된 상태
get_write_access()
include/linux/fs.h
/* * get_write_access() gets write permission for a file. * put_write_access() releases this write permission. * This is used for regular files. * We cannot support write (and maybe mmap read-write shared) accesses and * MAP_DENYWRITE mmappings simultaneously. The i_writecount field of an inode * can have the following values: * 0: no writers, no VM_DENYWRITE mappings * < 0: (-i_writecount) vm_area_structs with VM_DENYWRITE set exist * > 0: (i_writecount) users are writing to the file. * * Normally we operate on that counter with atomic_{inc,dec} and it's safe * except for the cases where we don't hold i_writecount yet. Then we need to * use {get,deny}_write_access() - these functions check the sign and refuse * to do the change if sign is wrong. */
static inline int get_write_access(struct inode *inode) { return atomic_inc_unless_negative(&inode->i_writecount) ? 0 : -ETXTBSY; }
inode에 대해 write 권한을 요청한다. 성공하면 0을 반환하고, 실패하는 경우 -ETXTBSY 에러를 반환한다.
- inode->i_writecount를 음수(-)가 아닌한 증가시킨다. 성공한 경우 0을 반환하고 , 이미 음수(-)여서 증가가 불가능한 경우 -ETXTBSY 에러를 반환한다.
put_write_access()
include/linux/fs.h
static inline void put_write_access(struct inode * inode) { atomic_dec(&inode->i_writecount); }
inode에 대해 write 권한을 제거한다.
- inode->i_writecount를 감소시킨다.
deny_write_access()
include/linux/fs.h
static inline int deny_write_access(struct file *file) { struct inode *inode = file_inode(file); return atomic_dec_unless_positive(&inode->i_writecount) ? 0 : -ETXTBSY; }
요청 file을 denywrite 상태로 만들어 writable 매핑을 만들 수 없게 금지한다. 성공한 경우 0을 반환하고 그렇지 않은 경우 -ETXTBSY 에러를 반환한다.
- 요청 file의 inode->i_writecount를 양수(+)가 아닌한 감소시킨다. 성공한 경우 0을 반환하고 이미 양수(+)여서 감소가 불가능한 경우 -ETXTBSY 에러를 반환한다.
allow_write_access()
include/linux/fs.h
static inline void allow_write_access(struct file *file) { if (file) atomic_inc(&file_inode(file)->i_writecount); }
요청 파일에 대해 denywrite를 제거하여 새로운 writable 매핑을 허용할 수 있는 상태로 변경한다.
- 요청 file의 inode->i_writecount를 증가시킨다.
writable 파일 매핑(2)
mapping->i_mapwritable 값 상태 (VM_SHARED 카운터)
- 0(writable)
- 공유 write 매핑되어 있지 않은 상태로 새로운 writable 매핑 요청이 가능한 상태
- 양수(write)
- 공유 write 매핑되어 있는 상태로 새로운 write 매핑 요청은 금지된 상태
- 음수(denywrite)
- 공유 denywrite 요청에 의해 모든 write 매핑이 금지된 상태이다.
mapping_map_writable()
include/linux/fs.h
static inline int mapping_map_writable(struct address_space *mapping) { return atomic_inc_unless_negative(&mapping->i_mmap_writable) ? 0 : -EPERM; }
매핑 영역을 writable 공유 매핑 상태로 요청한다. 성공하면 0을 반환하고, 실패하는 경우 -EPERM을 반환한다.
- mapping->i_mmapwritable을 음수(-)가 아닌한 증가시킨다. 성공한 경우 0을 반환하고 , 이미 음수(-)여서 증가가 불가능한 경우 -EPERM 에러를 반환한다.
mapping_unmap_writable()
include/linux/fs.h
static inline void mapping_unmap_writable(struct address_space *mapping) { atomic_dec(&mapping->i_mmap_writable); }
writable 공유 매핑 영역을 언맵 요청하여 writable 상태로 변경한다. 다시 새로운 writable 매핑 요청을 받을 수 있는 상태이다.
- mapping->i_mmap_writable을 감소시킨다.
mapping_deny_writable()
include/linux/fs.h
static inline int mapping_deny_writable(struct address_space *mapping) { return atomic_dec_unless_positive(&mapping->i_mmap_writable) ? 0 : -EBUSY; }
요청 매핑 영역에 대해 denywritable 상태로 변경하여 writable 매핑을 만들 수 없게 금지한다. 성공하면 0을 반환하고, 실패하는 경우 -EBUSY를 반환한다.
- mapping->i_writecount를 양수(+)가 아닌한 감소시킨다. 성공한 경우 0을 반환하고 , 이미 양수(+)여서 감소가 불가능한 경우 -EPERM 에러를 반환한다.
mapping_allow_writable()
include/linux/fs.h
static inline void mapping_allow_writable(struct address_space *mapping) { atomic_inc(&mapping->i_mmap_writable); }
요청 매핑 영역에 대해 denywrite를 제거하여 새로운 writable 매핑을 받을 수 있는 상태로 변경한다.
vm_stat_account()
mm/mmap.c
void vm_stat_account(struct mm_struct *mm, vm_flags_t flags, long npages) { mm->total_vm += npages; if (is_exec_mapping(flags)) mm->exec_vm += npages; else if (is_stack_mapping(flags)) mm->stack_vm += npages; else if (is_data_mapping(flags)) mm->data_vm += npages; }
매핑 용도에 맞게 각각의 vm stat에 대해 pages 만큼 추가한다.
- 코드 라인 3에서 mm->total_vm 카운터에 페이지 수를 추가한다.
- 코드 라인 5~6에서 실행 코드인 경우 mm->exec_vm 카운터에 페이지 수를 추가한다.
- 코드 라인 7~8에서 stack 매핑인 경우 mm->stack_vm 카운터에 페이지 수를 추가한다.
- 코드 라인 9~10에서 데이터 매핑인 경우 mm->data_vm 카운터에 페이지 수를 추가한다.
VMA용 페이지 테이블 매핑 속성 지정
vma_set_page_prot()
mm/mmap.c
/* Update vma->vm_page_prot to reflect vma->vm_flags. */ void vma_set_page_prot(struct vm_area_struct *vma) { unsigned long vm_flags = vma->vm_flags; vma->vm_page_prot = vm_pgprot_modify(vma->vm_page_prot, vm_flags); if (vma_wants_writenotify(vma)) { vm_flags &= ~VM_SHARED; vm_page_prot = vm_pgprot_modify(vma->vm_page_prot, vm_flags); } /* remove_protection_ptes reads vma->vm_page_prot without mmap_sem */ WRITE_ONCE(vma->vm_page_prot, vm_page_prot); }
vma->vm_flags에 맞는 메모리 속성 값으로 vma->vm_page_prot 속성 값을 업데이트한다.
- 요청 vma 영역의 vm_page_prot 속성에 대해 아키텍처에 커스트마이즈된 캐시 변환이 필요한 경우 변환된 vm_flags 값을 저장한다. 매칭되지 않으면 그냥 vm_flags 속성을 저장한다.
- vma가 write notify를 사용하고자 하는 경우 shared 플래그를 제거한다.
- vma 영역이 shared 매핑이고 페이지들이 read only 설정이된 경우 write 이벤트를 트래킹하고자 할 때 true를 반환한다.
vm_pgprot_modify()
mm/mmap.c
static pgprot_t vm_pgprot_modify(pgprot_t oldprot, unsigned long vm_flags) { return pgprot_modify(oldprot, vm_get_page_prot(vm_flags)); }
oldprot에 해당하는 아키텍처별로 미리 정의된 프로토콜 변환이 있으면 변환하여 속성을 반환한다.
- arm에서는 요청하는 oldprot 속성이 noncache, writecombine, device가 있는 경우 각각 noncache, buffer, noncache 형태로 변환한다. 매칭되지 않는 속성은 newprot 속성을 변환없이 그대로 반환한다.
pgprot_modify()
include/asm-generic/pgtable.h
static inline pgprot_t pgprot_modify(pgprot_t oldprot, pgprot_t newprot) { if (pgprot_val(oldprot) == pgprot_val(pgprot_noncached(oldprot))) newprot = pgprot_noncached(newprot); if (pgprot_val(oldprot) == pgprot_val(pgprot_writecombine(oldprot))) newprot = pgprot_writecombine(newprot); if (pgprot_val(oldprot) == pgprot_val(pgprot_device(oldprot))) newprot = pgprot_device(newprot); return newprot; }
아키텍처에서 매핑 시 old 마스크사용하는 odl 캐시 속성을 new 캐시 속성에 맞게 변환한다.
- arm: no cache 또는 buffer
ARM32 매핑 속성
arch/arm/include/asm/pgtable.h
#define __pgprot_modify(prot,mask,bits) \ __pgprot((pgprot_val(prot) & ~(mask)) | (bits)) #define pgprot_noncached(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED) #define pgprot_writecombine(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE) #define pgprot_stronglyordered(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED) #ifdef CONFIG_ARM_DMA_MEM_BUFFERABLE #define pgprot_dmacoherent(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE | L_PTE_XN) #else #define pgprot_dmacoherent(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED | L_PTE_XN) #endif
arm 아키텍처에서 매핑 시 사용하는 캐시 속성을 선택한다. (uncached 또는 buffrable)
- noncache -> no cache 속성 사용
- writecombine -> buffer 속성 사용
- stronglyordered -> no cache 속성 사용
- dmacoherennt -> CONFIG_ARM_DMA_MEM_BUFFERABLE 커널 옵션에 따라 buffer 또는 no cache 사용
- armv6 또는 armv7에서 dma가 buffer 사용 가능하다.
ARM64 매핑 속성
arch/arm64/include/asm/pgtable.h
/* * Mark the prot value as uncacheable and unbufferable. */
#define pgprot_noncached(prot) \ __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRnE) | PTE_PXN | PTE_UXN) #define pgprot_writecombine(prot) \ __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_NC) | PTE_PXN | PTE_UXN) #define pgprot_device(prot) \ __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRE) | PTE_PXN | PTE_UXN) #define __HAVE_PHYS_MEM_ACCESS_PROT
arm64 아키텍처에서 매핑 시 사용하는 캐시 속성을 선택한다. (uncached 또는 buffrable)
- noncache -> nGnRnE 속성 사용
- writecombine -> no cache 속성 사용
- device -> nGnRE 속성 사용
vma_wants_writenotify()
mm/mmap.c
/* * Some shared mappigns will want the pages marked read-only * to track write events. If so, we'll downgrade vm_page_prot * to the private version (using protection_map[] without the * VM_SHARED bit). */
int vma_wants_writenotify(struct vm_area_struct *vma) { vm_flags_t vm_flags = vma->vm_flags; /* If it was private or non-writable, the write bit is already clear */ if ((vm_flags & (VM_WRITE|VM_SHARED)) != ((VM_WRITE|VM_SHARED))) return 0; /* The backer wishes to know when pages are first written to? */ if (vma->vm_ops && vma->vm_ops->page_mkwrite || vm_ops->pfn_mkwrite)) return 1; /* The open routine did something to the protections that pgprot_modify * won't preserve? */ if (pgprot_val(vma->vm_page_prot) != pgprot_val(vm_pgprot_modify(vma->vm_page_prot, vm_flags))) return 0; /* Do we need to track softdirty? */ if (IS_ENABLED(CONFIG_MEM_SOFT_DIRTY) && !(vm_flags & VM_SOFTDIRTY)) return 1; /* Specialty mapping? */ if (vm_flags & VM_PFNMAP) return 0; /* Can the mapping track the dirty pages? */ return vma->vm_file && vma->vm_file->f_mapping && mapping_cap_account_dirty(vma->vm_file->f_mapping); }
vma 영역이 shared 매핑이고 페이지들이 read only 설정이된 경우 write 이벤트를 트래킹하고자 할 때 true를 반환한다.
- 코드 라인 6~7에서 write 및 shared 요청이 없는 vma의 경우 false(0)를 반환한다.
- 코드 라인 10~11에서 vm_ops->mkwrite 콜백 함수가 지정된 경우 true(1)를 반환한다.
- 코드 라인 15~17에서 vm_page_prot 속성에 변화를 줄 필요가 없는 경우 false(0)를 반환한다.
- 코드 라인 20~21에서 CONFIG_MEM_SOFT_DIRTY 커널 옵션을 사용하면서 soft dirty 기능을 요청하지 않은 경우 true(1)를 반환한다.
- 코드 라인 24~25에서 pfnmap 매핑을 요청한 경우 false(0)을 반환한다.
- 코드 라인 28~29에서 file 매핑이면서 bdi 에 dirty capable 설정된 경우 true(1)를 반환한다. 그렇지 않으면 flase(0)을 반환한다.
참고
- User virtual maps (brk) | 문c
- Understanding glibc malloc | sploitfun
잘정리해주신 글을 보나다가 궁금한것이 생겨서 문의를 드립니다.
혹시 mmap 으로 할당받은 memory 와 malloc 으로 할당받은 memory 는 어떤 차이가 있나요?
안녕하세요? 궁그미님.
mmap() 함수는 주로 다음과 같은 처리를 수행합니다.
1) 유저 가상 공간에 파일을 매핑하여 직접 액세스할 때
2) 유저 가상 공간에 디바이스를 매핑하여 hw를 제어하고자 할 때
참고: https://slidesplayer.org/slide/15106509/
3) 유저 가상 공간에 메모리를 할당받고 싶을 때
– 이 방법은 주로 glibc등의 라이브러리에서 힙 매니저가 사용합니다. 힙이 부족할 때 MAP_ANONYMOUS 플래그를 사용하여 mmap syscall을 통해 커널에 빈 페이지 메모리를 요청할 떄 사용합니다. 그리고 일반 유저 application에서는 이러한 방법을 직접 사용하지 않고, glibc를 통해 힙에서 메모리를 할당받아오는 malloc()을 사용합니다.
감사합니다.
감사합니다.
vm_mmap_pgoff에서 do_mmap_pgoff를 호출할 때는 마지막 인자가 populate인데, do_mmap_pgoff의 선언에는 uf가 생겼습니다.
커널 버전이 여러개 섞여있는 건가요?
안녕하세요? 지형탁님.
vm_mmap_pgoff() 함수에 대해 기존 v4.9 코드가 일부 남아있었습니다. 해당 코드의 버전을 v5.0 코드로 수정하였습니다.
감사합니다.
감사히 잘 보았습니다.
한가지 궁금한점이 있습니다. user영역에서 mmap()함수 호출할 때 MAP_POPULATE flag를 설정한다면
lazy allocation이 발생하지 않는건가요?
안녕하세요?
네. 맞습니다. page fault가 발생하지 않도록 미리 페이지 테이블을 구성합니다. 즉 메모리를 할당하고 미리 매핑합니다.
감사합니다.