/dev/zero详解

我们常用/dev/zero读0写空,它是如何实现的,我们通过问题一探究竟

*内核代码/dev/zero

*用户程序可以通过open & mmap将/dev/zero映射到一个虚拟的内存空间;在读这段虚拟地址时 会发生page_fault,这种page_fault如何处理的?会申请新的内存吗?

1、先看/dev/zero的mmap在内核如何处理的

static int mmap_zero(struct file *file, struct vm_area_struct *vma)
{
    vma_set_anonymous(vma); 
}
static inline void vma_set_anonymous(struct vm_area_struct *vma)
{
        vma->vm_ops = NULL;
}

内核处理非常简单,只是将这段虚拟地址的vma->vm_ops置null,即访问/dev/zero的page_fault要走anon(即匿名页)的处理;

神奇吧,本来是文件页的处理 却走的是匿名页的流程。

2、再看page_fault时 的处理

arm64的异常处理:el1_abort-->do_mem_abort-->do_translation_fault-->do_page_fault

走到common的处理:handle_mm_fault-->__handle_mm_fault-->handle_pte_fault
{
...
                if (vma_is_anonymous(vmf->vma))
                        return do_anonymous_page(vmf);
...
}

由于mmap /dev/zero时已经vma_set_anonymous,这里page_fault走到匿名页的处理

static vm_fault_t do_anonymous_page(struct vm_fault *vmf)
{
...
        /* Use the zero-page for reads */
        if (!(vmf->flags & FAULT_FLAG_WRITE) &&
                        !mm_forbids_zeropage(vma->vm_mm)) {
                entry = pte_mkspecial(pfn_pte(my_zero_pfn(vmf->address),
                                                vmf->vma_page_prot));
...
}

可以看到,用户态的虚拟地址 都被映射到到了一个特殊的page上,即zero_pfn;随后的访问都是读这个zero_page;

3、什么是zero_page

zero_page是linux的COW(copy-on-write)的基础,也是/dev/zero等初始化成0的基础;

arm64的定义:
/* Empty_zero_page is a special page that is used for zero-initialized data
 * and COW.
 */
unsigned long empty_zero_page[PAGE_SIZE / sizeof(unsigned long)] __page_aligned_bss;
EXPORT_SYMBOL(empty_zero_page);

/*
 * ZERO_PAGE is a global shared page that is always zero: used
 * for zero-mapped memory areas etc..
 */
extern unsigned long empty_zero_page[PAGE_SIZE / sizeof(unsigned long)];
#define ZERO_PAGE(vaddr)        phys_to_page(__pa_symbol(empty_zero_page))

static int __init init_zero_pfn(void)
{
        zero_pfn = page_to_pfn(ZERO_PAGE(0));
        return 0;
}
early_initcall(init_zero_pfn);

static inline unsigned long my_zero_pfn(unsigned long addr)
{
        extern unsigned long zero_pfn;
        return zero_pfn;
}

# cat /proc/kallsyms |grep empty_zero_page
ffffffdb674fe000 B empty_zero_page

内核定义了一个静态数组empty_zero_page,大小一个page(即4K),且地址需要4K对齐(否则虚拟地址映射时、需要搞2个pte和其映射);早期内核是通过memblock reserve了一块4K大小的内存做zero_page。

COW机制简单点说,就是用户程序申请的内存 在只读的情况下、都只会和这个zero_page建立映射、内核并不会为其申请的内存,而发生write时、内核才会申请内存 copy相关内容过来、并建立映射。

zero_page机制节省了很多内存。

4、回答提出的问题,open & mmap将/dev/zero后并读这段虚拟地址时 会发生page_fault,走的是匿名页的处理流程,并且不会申请内存、只会和zero_page建立映射。

*再提出一个问题,用户程序通过open & read时,为什么读到的都是0?

1、这个和zero_page就没什么关系了,看看内核如何处理read_zero的:

static ssize_t read_zero(struct file *file, char __user *buf,
                         size_t count, loff_t *ppos)
{
        size_t cleared = 0;

        while (count) {
                size_t chunk = min_t(size_t, count, PAGE_SIZE);
                size_t left;

                left = clear_user(buf + cleared, chunk);
                if (unlikely(left)) {
                        cleared += (chunk - left);
                        if (!cleared)
                                return -EFAULT;
                        break;
                }
                cleared += chunk;
                count -= chunk;

                if (signal_pending(current))
                        break;
                cond_resched();
        }

        return cleared;
}

2、内核read_zero会将用户的buf通过clear_user直接清零,就是这么简单。

static inline unsigned long __must_check __clear_user(void __user *to, unsigned long n)
{
        if (access_ok(to, n)) {
                uaccess_enable_not_uao();
                n = __arch_clear_user(__uaccess_mask_ptr(to), n);
                uaccess_disable_not_uao();
        }
        return n;
}
#define clear_user      __clear_user

SYM_FUNC_START(__arch_clear_user)
        mov     x2, x1                  // save the size for fixup return
        subs    x1, x1, #8
        b.mi    2f
1:
uao_user_alternative 9f, str, sttr, xzr, x0, 8
        subs    x1, x1, #8
        b.pl    1b
2:      adds    x1, x1, #4
        b.mi    3f
uao_user_alternative 9f, str, sttr, wzr, x0, 4
        sub     x1, x1, #4
3:      adds    x1, x1, #2
        b.mi    4f
uao_user_alternative 9f, strh, sttrh, wzr, x0, 2
        sub     x1, x1, #2
4:      adds    x1, x1, #1
        b.mi    5f
uao_user_alternative 9f, strb, sttrb, wzr, x0, 0
5:      mov     x0, #0
        ret
SYM_FUNC_END(__arch_clear_user)
EXPORT_SYMBOL(__arch_clear_user)

        .macro uao_user_alternative l, inst, alt_inst, reg, addr, post_inc
8888:           \alt_inst       \reg, [\addr];
                add             \addr, \addr, \post_inc;

这里提一点,由于安全问题、内核态是不允许直接访问用户态地址的(当然 更不允许用户态访问内核态地址),arm64是通过SET_PSTATE_PAN(privilege-accsee-non)禁止的,如要访问需要需要先enbale、访问结束再disable;

static inline void uaccess_enable_not_uao(void)
{
        __uaccess_enable(ARM64_ALT_PAN_NOT_UAO);
}
 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值