๏ปฟ# process_vm_readv/writev ๐ฅ
๊ด๋ จ ์์ค:mm/process_vm_access.c,include/linux/uio.h,include/linux/mm_types.h
๊ฐ์ (Overview)
process_vm_readv๊ณผ process_vm_writev๋ ํ๋ก์ธ์ค ๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ง์ ์ ๊ทผ์ ์ํ ์์คํ
ํธ์ถ์
๋๋ค. ๊ธฐ์กด์ ptrace(PTRACE_PEEKDATA)๋ ํ์ดํ/์์ผ์ ํตํ ๊ฐ์ ์ ์ก๊ณผ ๋ฌ๋ฆฌ, ์ปค๋์ด ๋ ํ๋ก์ธ์ค์ ๊ฐ์ ์ฃผ์ ๊ณต๊ฐ์ ์ง์ ์ ๊ทผํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํฉ๋๋ค. ์ด๋ฅผ ํตํด ์๋๋ฐฉ ํ๋ก์ธ์ค์ ๊ฐ์ ์ฃผ์๋ฅผ ํ๋์ ์์คํ
ํธ์ถ๋ก ์ฌ๋ฌ ๋ฒ ์ฝ๊ฑฐ๋ ์ธ ์ ์์ด ์ฑ๋ฅ๊ณผ ํธ์์ฑ์ด ํฅ์๋ฉ๋๋ค.
์์คํ
ํธ์ถ์ ์ฌ์ฉ์ ์ ๊ณต iovec ๋ฐฐ์ด์ ํตํด ๋ก์ปฌ๊ณผ ์๊ฒฉ ์ฃผ์ ์์ญ์ ์ง์ ํ๋ฉฐ, ์ปค๋ ๋ด๋ถ์์ pin_user_pages_remote()๋ฅผ ์ฌ์ฉํ์ฌ ์๊ฒฉ ํ๋ก์ธ์ค์ ํ์ด์ง๋ฅผ ๊ณ ์ (pinning)ํ ํ ๋ณต์ฌํฉ๋๋ค. ์ด ๊ณผ์ ์์ mm_access()๋ฅผ ํตํด ๋์ ํ๋ก์ธ์ค์ mm_struct์ ์ ๊ทผ ๊ถํ์ ์ป์ด์ผ ํ๋ฉฐ, PTRACE_MODE_ATTACH_REALCREDS ํ๋๊ทธ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
/* mm/process_vm_access.c โ ์์คํ
ํธ์ถ ์ ์ (๋ผ์ธ 292-305) */
SYSCALL_DEFINE6(process_vm_readv, pid_t, pid, const struct iovec __user *, lvec,
unsigned long, liovcnt, const struct iovec __user *, rvec,
unsigned long, riovcnt, unsigned long, flags)
{
return process_vm_rw(pid, lvec, liovcnt, rvec, riovcnt, flags, 0);
}
SYSCALL_DEFINE6(process_vm_writev, pid_t, pid,
const struct iovec __user *, lvec,
unsigned long, liovcnt, const struct iovec __user *, rvec,
unsigned long, riovcnt, unsigned long, flags)
{
return process_vm_rw(pid, lvec, liovcnt, rvec, riovcnt, flags, 1);
}
๋น ๋ฅธ ์ ๊ฒ ๋ช ๋ น
# ๋์ ํ๋ก์ธ์ค์ ๋ฉ๋ชจ๋ฆฌ ๋งคํ ํ์ธ
cat /proc/<pid>/maps
# process_vm_readv/writev ์์คํ
ํธ์ถ ์ถ์
strace -e process_vm_readv,process_vm_writev <command>
# ํ์ฌ ํ๋ก์ธ์ค์ RSS ํ์ธ
cat /proc/self/status | grep VmRSS
# ์ปค๋ ๋ก๊ทธ์์ ๊ด๋ จ fault ๋ฉ์์ง ํ์ธ
dmesg | grep -i "fault\|page"
# ์์คํ
ํธ์ถ ํต๊ณ ํ์ธ
cat /proc/self/syscall
# ๋์ ํ๋ก์ธ์ค์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ชจ๋ํฐ๋ง
top -p <pid>
# iovec ๊ตฌ์กฐ์ฒด ํ์ธ (๋๋ฒ๊น
์ฉ)
strace -v -e process_vm_readv <command>
# ์ปค๋ ๋ฉ๋ชจ๋ฆฌ ํ ๋น ํต๊ณ
cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable"
# ํ๋ก์ธ์ค์ ์ด๋ฆฐ ํ์ผ ๋์คํฌ๋ฆฝํฐ ํ์ธ
ls -la /proc/<pid>/fd
# ๋์ ํ๋ก์ธ์ค์ mm_struct ์ฃผ์ ํ์ธ (crash ๋๊ตฌ ์ฌ์ฉ ์)
crash> task_struct.mm <pid>
ํต์ฌ ์๋ฃ๊ตฌ์กฐ
struct iov_iter โ I/O ๋ฒกํฐ ๋ฐ๋ณต์
๋ค์ํ ์ ํ์ ๋ฒกํฐ(iov, kvec, bvec ๋ฑ)๋ฅผ ์ถ์ํํ์ฌ ํต์ผ๋ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค. process_vm_access.c์์๋ ๋ก์ปฌ๊ณผ ์๊ฒฉ ์ฃผ์ ์์ญ์ ์ํํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
/* include/linux/uio.h:43-83 */
struct iov_iter {
u8 iter_type; /* ๋ฐ๋ณต์ ์ ํ (ITER_IOVEC ๋ฑ) */
bool nofault; /* ํดํธ ์์ด ์ฒ๋ฆฌํ ์ง ์ฌ๋ถ */
bool data_source; /* ๋ฐ์ดํฐ ์์ค ๋ฐฉํฅ (์ฝ๊ธฐ/์ฐ๊ธฐ) */
size_t iov_offset; /* ํ์ฌ ๋ฒกํฐ ๋ด ์คํ์
*/
union {
struct iovec __ubuf_iovec;
struct {
union {
const struct iovec *__iov; /* iovec ๋ฐฐ์ด ํฌ์ธํฐ */
const struct kvec *kvec;
const struct bio_vec *bvec;
const struct folio_queue *folioq;
struct xarray *xarray;
void __user *ubuf;
};
size_t count; /* ๋จ์ ๋ฐ์ดํธ ์ */
};
};
union {
unsigned long nr_segs; /* ์ธ๊ทธ๋จผํธ ์ */
u8 folioq_slot;
loff_t xarray_start;
};
};
struct mm_struct โ ํ๋ก์ธ์ค ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ตฌ์กฐ์ฒด
ํ๋ก์ธ์ค์ ๊ฐ์ ์ฃผ์ ๊ณต๊ฐ, ํ์ด์ง ํ
์ด๋ธ, VMA ๋ชฉ๋ก ๋ฑ์ ๊ด๋ฆฌํ๋ ํต์ฌ ๊ตฌ์กฐ์ฒด์
๋๋ค. process_vm_rw_core()์์ mm_access()๋ฅผ ํตํด ํ๋ํฉ๋๋ค.
/* include/linux/mm_types.h:1123-1240 (์ผ๋ถ) */
struct mm_struct {
struct {
struct {
atomic_t mm_count; /* mm_struct ์ฐธ์กฐ ์นด์ดํธ */
} ____cacheline_aligned_in_smp;
struct maple_tree mm_mt; /* VMA ์ ์ฅ (maple tree) */
unsigned long mmap_base; /* mmap ์์ญ ๊ธฐ์ค ์ฃผ์ */
unsigned long mmap_legacy_base; /* ์๋์ชฝ ์ฑ์ฅ mmap ๊ธฐ์ค ์ฃผ์ */
unsigned long task_size; /* ์ฌ์ฉ์ ์ฃผ์ ๊ณต๊ฐ ํฌ๊ธฐ */
pgd_t *pgd; /* ํ์ด์ง ๊ธ๋ก๋ฒ ๋๋ ํ ๋ฆฌ */
atomic_t mm_users; /* ์ฌ์ฉ์ ์ (mmget/mmput) */
int map_count; /* VMA ๊ฐ์ */
spinlock_t page_table_lock; /* ํ์ด์ง ํ
์ด๋ธ ๋ณดํธ */
struct rw_semaphore mmap_lock; /* mmap ๋ณดํธ (์ฝ๊ธฐ/์ฐ๊ธฐ ์ ๊ธ) */
unsigned long total_vm; /* ์ด ๋งคํ๋ ํ์ด์ง ์ */
unsigned long locked_vm; /* ๊ณ ์ ๋ ํ์ด์ง ์ */
atomic64_t pinned_vm; /* ์๊ตฌ ๊ณ ์ ํ์ด์ง ์ */
};
/* ... */
};
struct task_struct โ ํ์คํฌ ๊ตฌ์กฐ์ฒด (๋ฐ์ท)
process_vm_rw_core()์์ find_get_task_by_vpid()๋ฅผ ํตํด ๋์ ํ๋ก์ธ์ค์ task_struct๋ฅผ ํ๋ํฉ๋๋ค. ์ด๋ฅผ ํตํด mm_struct์ ์ ๊ทผํ ์ ์์ต๋๋ค.
/* include/linux/sched.h:952-959 */
struct sched_info sched_info;
struct list_head tasks;
struct plist_node pushable_tasks;
struct rb_node pushable_dl_tasks;
struct mm_struct *mm;
struct mm_struct *active_mm;
struct iovec โ ์ฌ์ฉ์ ๊ณต๊ฐ I/O ๋ฒกํฐ
์ฌ์ฉ์ ๊ณต๊ฐ์์ process_vm_readv/writev์ ์ ๋ฌ๋๋ ์ฃผ์-๊ธธ์ด ์์
๋๋ค.
/* include/uapi/linux/uio.h */
struct iovec {
void __user *iov_base; /* ์์ ์ฃผ์ */
size_t iov_len; /* ๊ธธ์ด (๋ฐ์ดํธ) */
};
PVM_MAX_* ๋งคํฌ๋ก โ ๋ด๋ถ ์ ํ๊ฐ
/* mm/process_vm_access.c:56-59 */
#define PVM_MAX_KMALLOC_PAGES 2
#define PVM_MAX_USER_PAGES (PVM_MAX_KMALLOC_PAGES * PAGE_SIZE / sizeof(struct page *))
/* mm/process_vm_access.c:136 */
#define PVM_MAX_PP_ARRAY_COUNT 16
ํต์ฌ ํจ์
1. SYSCALL_DEFINE6(process_vm_readv/writev) โ ์์คํ ํธ์ถ ์ง์ ์
process_vm_rw()๋ฅผ ํธ์ถํ๊ณ vm_write ํ๋๊ทธ๋ก ์ฝ๊ธฐ(0) ๋๋ ์ฐ๊ธฐ(1)๋ฅผ ์ง์ ํฉ๋๋ค.
pid, lvec(๋ก์ปฌ), liovcnt, rvec(์๊ฒฉ), riovcnt, flagsflags != 0์ด๋ฉด -EINVAL ๋ฐํ2. process_vm_rw() โ iovec ๊ฒ์ฆ ๋ฐ ์ฝ์ด ํธ์ถ
์ฌ์ฉ์ ์ ๊ณต iovec ๋ฐฐ์ด์ ์ปค๋ ๊ณต๊ฐ์ผ๋ก ๊ฐ์ ธ์ค๊ณ import_iovec()๋ก iov_iter๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
/* mm/process_vm_access.c:254-290 */
static ssize_t process_vm_rw(pid_t pid,
const struct iovec __user *lvec,
unsigned long liovcnt,
const struct iovec __user *rvec,
unsigned long riovcnt,
unsigned long flags, int vm_write)
{
/* ... */
int dir = vm_write ? ITER_SOURCE : ITER_DEST;
if (flags != 0)
return -EINVAL;
/* ๋ก์ปฌ iovec์ iov_iter๋ก ๋ณํ */
rc = import_iovec(dir, lvec, liovcnt, UIO_FASTIOV, &iov_l, &iter);
if (rc < 0)
return rc;
if (!iov_iter_count(&iter))
goto free_iov_l;
/* ์๊ฒฉ iovec์ ์ปค๋ ๊ณต๊ฐ์ผ๋ก ๋ณต์ฌ */
iov_r = iovec_from_user(rvec, riovcnt, UIO_FASTIOV, iovstack_r,
in_compat_syscall());
if (IS_ERR(iov_r)) {
rc = PTR_ERR(iov_r);
goto free_iov_l;
}
rc = process_vm_rw_core(pid, &iter, iov_r, riovcnt, flags, vm_write);
/* ... */
}
- flags != 0: -EINVAL ๋ฐํ (ํ์ฌ ๋ฏธ์ฌ์ฉ)
- iov_iter_count(&iter) == 0: ๋ฐ๋ก ๋ฐํ
- import_iovec() ์คํจ: ์ค๋ฅ ์ฝ๋ ๋ฐํ
3. process_vm_rw_core() โ ์ฝ์ด ์ฒ๋ฆฌ ๋ฃจํด
๋์ ํ๋ก์ธ์ค๋ฅผ ์ฐพ๊ณ , mm_struct์ ์ ๊ทผํ ํ, ๋ชจ๋ ์๊ฒฉ ๋ฒกํฐ์ ๋ํด process_vm_rw_single_vec()์ ํธ์ถํฉ๋๋ค.
/* mm/process_vm_access.c:151-238 (์ผ๋ถ) */
static ssize_t process_vm_rw_core(pid_t pid, struct iov_iter *iter,
const struct iovec *rvec,
unsigned long riovcnt,
unsigned long flags, int vm_write)
{
struct task_struct *task;
struct page *pp_stack[PVM_MAX_PP_ARRAY_COUNT];
struct page **process_pages = pp_stack;
struct mm_struct *mm;
/* ... */
/* ๋์ ํ๋ก์ธ์ค ์ฐพ๊ธฐ */
task = find_get_task_by_vpid(pid);
if (!task) {
rc = -ESRCH;
goto free_proc_pages;
}
/* mm_struct ์ ๊ทผ ๊ถํ ํ๋ */
mm = mm_access(task, PTRACE_MODE_ATTACH_REALCREDS);
if (IS_ERR(mm)) {
rc = PTR_ERR(mm);
if (rc == -EACCES)
rc = -EPERM; /* EACCES๋ฅผ EPERM์ผ๋ก ๋ณํ */
goto put_task_struct;
}
/* ๊ฐ ์๊ฒฉ ๋ฒกํฐ์ ๋ํด ์ฒ๋ฆฌ */
for (i = 0; i < riovcnt && iov_iter_count(iter) && !rc; i++)
rc = process_vm_rw_single_vec(
(unsigned long)rvec[i].iov_base, rvec[i].iov_len,
iter, process_pages, mm, task, vm_write);
/* ์ ์ก๋ ๋ฐ์ดํธ ์ ๊ณ์ฐ */
total_len -= iov_iter_count(iter);
if (total_len)
rc = total_len;
mmput(mm);
put_task_struct:
put_task_struct(task);
free_proc_pages:
if (process_pages != pp_stack)
kfree(process_pages);
return rc;
}
- find_get_task_by_vpid() ์คํจ: -ESRCH
- mm_access() ์คํจ: -EACCES โ -EPERM ๋ณํ
- nr_pages == 0: ๋ฐ๋ก ๋ฐํ
4. process_vm_rw_single_vec() โ ๋จ์ผ ๋ฒกํฐ ์ฒ๋ฆฌ
ํ๋์ ์๊ฒฉ ๋ฒกํฐ์ ๋ํด ํ์ด์ง๋ฅผ ๊ณ ์ (pinning)ํ๊ณ ๋ณต์ฌํฉ๋๋ค. pin_user_pages_remote()๋ฅผ ์ฌ์ฉํ์ฌ ์๊ฒฉ ํ๋ก์ธ์ค์ ํ์ด์ง๋ฅผ ์์ ํ๊ฒ ๊ณ ์ ํฉ๋๋ค.
/* mm/process_vm_access.c:73-132 (์ผ๋ถ) */
static int process_vm_rw_single_vec(unsigned long addr,
unsigned long len,
struct iov_iter *iter,
struct page **process_pages,
struct mm_struct *mm,
struct task_struct *task,
int vm_write)
{
unsigned long pa = addr & PAGE_MASK;
unsigned long start_offset = addr - pa;
/* ... */
if (vm_write)
flags |= FOLL_WRITE;
while (!rc && nr_pages && iov_iter_count(iter)) {
int pinned_pages = min_t(unsigned long, nr_pages, PVM_MAX_USER_PAGES);
/* ์๊ฒฉ ํ๋ก์ธ์ค์ ํ์ด์ง ๊ณ ์ */
mmap_read_lock(mm);
pinned_pages = pin_user_pages_remote(mm, pa, pinned_pages,
flags, process_pages,
&locked);
if (locked)
mmap_read_unlock(mm);
if (pinned_pages <= 0)
return -EFAULT;
/* ํ์ด์ง ๋ณต์ฌ */
rc = process_vm_rw_pages(process_pages,
start_offset, bytes, iter,
vm_write);
/* ์ฐ๊ธฐ ์ ํ์ด์ง ๋๋ฌ์ด ํ๋๊ทธ ์ค์ */
unpin_user_pages_dirty_lock(process_pages, pinned_pages,
vm_write);
}
return rc;
}
- vm_write == 1: FOLL_WRITE ํ๋๊ทธ ์ถ๊ฐ
- pinned_pages <= 0: -EFAULT
- locked == 1: mmap_read_unlock() ํธ์ถ
5. process_vm_rw_pages() โ ์ค์ ํ์ด์ง ๋ณต์ฌ
๊ณ ์ ๋ ํ์ด์ง๋ค์ ๋ด์ฉ์ copy_page_to_iter() ๋๋ copy_page_from_iter()๋ฅผ ํตํด ๋ณต์ฌํฉ๋๋ค.
/* mm/process_vm_access.c:27-53 */
static int process_vm_rw_pages(struct page **pages,
unsigned offset,
size_t len,
struct iov_iter *iter,
int vm_write)
{
while (len && iov_iter_count(iter)) {
struct page *page = *pages++;
size_t copy = PAGE_SIZE - offset;
size_t copied;
if (copy > len)
copy = len;
if (vm_write)
copied = copy_page_from_iter(page, offset, copy, iter);
else
copied = copy_page_to_iter(page, offset, copy, iter);
len -= copied;
if (copied < copy && iov_iter_count(iter))
return -EFAULT;
offset = 0;
}
return 0;
}
- vm_write == 1: copy_page_from_iter() (์๊ฒฉ โ ๋ก์ปฌ)
- vm_write == 0: copy_page_to_iter() (๋ก์ปฌ โ ์๊ฒฉ)
- copied < copy: -EFAULT
ํธ์ถ ํ๋ฆ
process_vm_readv / process_vm_writev
โ
โโโ process_vm_rw()
โ
โโโ import_iovec() โ ๋ก์ปฌ iovec โ iov_iter ๋ณํ
โโโ iovec_from_user() โ ์๊ฒฉ iovec์ ์ปค๋ ๊ณต๊ฐ์ผ๋ก ๋ณต์ฌ
โ
โโโ process_vm_rw_core()
โ
โโโ find_get_task_by_vpid() โ ๋์ ํ๋ก์ธ์ค ์ฐพ๊ธฐ
โโโ mm_access() โ mm_struct ์ ๊ทผ ๊ถํ ํ๋
โ
โโโ process_vm_rw_single_vec() โ ๊ฐ ์๊ฒฉ ๋ฒกํฐ๋ณ ๋ฐ๋ณต
โ
โโโ pin_user_pages_remote() โ ์๊ฒฉ ํ์ด์ง ๊ณ ์
โโโ process_vm_rw_pages() โ ์ค์ ๋ฐ์ดํฐ ๋ณต์ฌ
โ โโโ copy_page_to_iter() (์ฝ๊ธฐ)
โ โโโ copy_page_from_iter() (์ฐ๊ธฐ)
โโโ unpin_user_pages_dirty_lock() โ ํ์ด์ง ๊ณ ์ ํด์
์กฐ๊ฑด๋ณ ๋น๊ต
| ํญ๋ชฉ | process_vm_readv | process_vm_writev |
|---|---|---|
| **๋ฐฉํฅ** | ์๊ฒฉ โ ๋ก์ปฌ | ๋ก์ปฌ โ ์๊ฒฉ |
| **vm_write** | 0 | 1 |
| **FOLL_WRITE** | ๋ฏธ์ค์ | ์ค์ |
| ** iov_iter ๋ฐฉํฅ** | ITER_DEST | ITER_SOURCE |
| **ํ์ด์ง ๋๋ฌ์** | ์ค์ ์ ํจ | unpin ์ ๋๋ฌ์ด ํ๋๊ทธ ์ค์ |
| **EACCES ์ฒ๋ฆฌ** | EACCES โ EPERM ๋ณํ | EACCES โ EPERM ๋ณํ |
| ํญ๋ชฉ | process_vm_readv/writev | ptrace(PTRACE_PEEKDATA) | ํ์ดํ |
|---|---|---|---|
| **๋์ ์ ๊ทผ** | ๋จ์ผ ์์คํ ํธ์ถ๋ก ๋ค์ ๋ฒกํฐ ์ฒ๋ฆฌ | 1ํ ํธ์ถ๋น 1 ์๋ | ์๋ฐฉํฅ ๊ฐ๋ฅ |
| **์ฑ๋ฅ** | ๋์ (ํ์ด์ง ๊ณ ์ ํ ๋ณต์ฌ) | ๋ฎ์ (์ปจํ ์คํธ ์ค์์น ๋ฐ๋ณต) | ์ค๊ฐ |
| **๊ถํ** | PTRACE_MODE_ATTACH_REALCREDS | PTRACE_MODE_ATTACH | ๋ ํ๋ก์ธ์ค๊ฐ ํ์ดํ ํ์ |
| **๋์ ํ๋ก์ธ์ค** | PID๋ก ์ง์ | PID๋ก ์ง์ | ํ์ผ ๋์คํฌ๋ฆฝํฐ ํ์ |
| ์กฐ๊ฑด | ๊ฒฐ๊ณผ |
|---|---|
| `flags != 0` | `-EINVAL` ๋ฐํ |
| `find_get_task_by_vpid()` ์คํจ | `-ESRCH` ๋ฐํ |
| `mm_access()` ์คํจ ์ `-EACCES` | `-EPERM`์ผ๋ก ๋ณํ |
| `pin_user_pages_remote() <= 0` | `-EFAULT` ๋ฐํ |
| `nr_pages == 0` | ๋ฐ๋ก 0 ๋ฐํ |
| `iov_iter_count(&iter) == 0` | ๋ฐ๋ก ๋ฐํ |
์ผ์ ๋น์
process_vm_readv/writev๋ ๋ ์ด์์ง ์ฌ์ด์ ์ง์ ํต๋ก๋ฅผ ๋ซ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค. ๊ธฐ์กด ๋ฐฉ๋ฒ(ํ์ดํ/์์ผ)์ ์ฐ์ฒด๊ตญ(์ปค๋)์ ํตํด ํธ์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ฐฉ์์ด๋ผ๋ฉด, ์ด ์์คํ
ํธ์ถ์ ์ด์์ง ๋ฌธ์ ์ด๊ณ ์๋๋ฐฉ ์ ๋ฐ์์ ์ง์ ๋ฌผ๊ฑด์ ๊ฐ์ ธ์ค๊ฑฐ๋ ๋ฃ์ด๋๋ ๋ฐฉ์์
๋๋ค. ๋จ, ๋ณด์(๊ถํ ๊ฒ์ฌ)์ด ํ์ํ๋ฉฐ, ํ ๋ฒ์ ๊ฐ์ ธ์ฌ ์ ์๋ ๋ฌผ๊ฑด์ ์(ํ์ด์ง ์)์ ์ ํ์ด ์์ต๋๋ค.