linux mmap 详解

【Linux】Linux编程之 mmap解析 前言 虚拟内存系统通过将虚拟内存分割为称作虚拟页(Virtual Page,VP)大小固定的块,一般情况下,每个虚拟页的大小默认是4096字节。同样的,物理内存也被分割为物理页(Physical Page,PP),也为4096字节。 一、mmap基本原理和分类 在LINUX中我们可以使用mmap用来在进程虚拟内存地址空间中分配地址空间,创建和物理内存的映射关系。 映射关系可以分为两种 1、文件映射 磁盘文件映射进程的虚拟地址空间,使用文件内容初始化物理内存。 2、匿名映射 初始化全为0的内存空间。 而对于 阅读详情
一.前言
mmap的具体实现以前在学习内核时学习过,但是对于其中的很多函数是一知半解的,有些只能根据其函数名来猜测其具体的功能,在本文中,一起来重新深入理解其具体的实现。

二.mmap的用户层应用
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize); 
具体参数含义
start :  指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
length:  代表将文件中多大的部分映射到内存。
prot  :  映射区域的保护方式。可以为以下几种方式的组合:
                    PROT_EXEC 映射区域可被执行
                    PROT_READ 映射区域可被读取
                    PROT_WRITE 映射区域可被写入
                    PROT_NONE 映射区域不能存取
flags :  影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
                    MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
                    MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
                    MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
                    MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
                    MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
                    MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。
fd    :  要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,
          然后对该文件进行映射,可以同样达到匿名内存映射的效果。
offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是PAGE_SIZE的整数倍。

返回值:
      若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

错误代码:
            EBADF  参数fd 不是有效的文件描述词
            EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。
            EINVAL 参数start、length 或offset有一个不合法。
            EAGAIN 文件被锁住,或是有太多内存被锁住。
            ENOMEM 内存不足。
用户层的调用很简单,其具体功能就是直接将物理内存直接映射到用户虚拟内存,使用户空间可以直接对物理空间操作。但是对于内核层而言,其具体实现比较复杂。

三.mmap的内核实现
linux mmap 详解

谨以此文纪念过往的岁月

一.前言
mmap的具体实现以前在学习内核时学习过,但是对于其中的很多函数是一知半解的,有些只能根据其函数名来猜测其具体的功能,在本文中,一起来重新深入理解其
具体的实现。

二.mmap的用户层应用
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize); 
具体参数含义
start :  指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
length:  代表将文件中多大的部分映射到内存。
prot  :  映射区域的保护方式。可以为以下几种方式的组合:
                    PROT_EXEC 映射区域可被执行
                    PROT_READ 映射区域可被读取
                    PROT_WRITE 映射区域可被写入
                    PROT_NONE 映射区域不能存取
flags :  影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
                    MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
                    MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
                    MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
                    MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
                    MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
                    MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。
fd    :  要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,
          然后对该文件进行映射,可以同样达到匿名内存映射的效果。
offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是PAGE_SIZE的整数倍。

返回值:
      若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

错误代码:
            EBADF  参数fd 不是有效的文件描述词
            EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。
            EINVAL 参数start、length 或offset有一个不合法。
            EAGAIN 文件被锁住,或是有太多内存被锁住。
            ENOMEM 内存不足。
用户层的调用很简单,其具体功能就是直接将物理内存直接映射到用户虚拟内存,使用户空间可以直接对物理空间操作。但是对于内核层而言,其具体实现比较复杂。

三.mmap的内核实现
对于mmap的内核有了解的都会知道用户层的mmap到内核层的mmap其中多了一个参数vma_struct这个结构体,在开始时对于这个参数很疑惑就是这个参数的值是哪儿来的,
在这里我们会一一来讲述。
还是从do_mmap开始吧。
3.1 do_mmap
参数说明:
file  :就是用户层想要映射的file
addr  :欲映射的起始地址,即用户层的start
prot  :用户层传入的port
flag  :同上
offset:同上
从这里可以知道,这里面的参数几乎均是用户层传入的参数。
static inline unsigned long do_mmap(struct file *file, unsigned long addr,unsigned long len, unsigned long prot,
                                    unsigned long flag, unsigned long offset)
{
    unsigned long ret = -EINVAL;
    if ((offset + PAGE_ALIGN(len)) < offset)  --页对齐len,检测传入参数是否有误。
        goto out;
    if (!(offset & ~PAGE_MASK))           --检测offset是否页对齐。映射时只能映射页对齐的长度。
        ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);  
out:
    return ret;
}

3.2 do_mmap_pgoff
这个函数是巨大的。
unsigned long do_mmap_pgoff(struct file * file, unsigned long addr,unsigned long len, unsigned long prot,unsigned long flags, unsigned long pgoff)
{
    struct mm_struct * mm = current->mm;      --当前用户进程的mm
    struct inode *inode; 
    unsigned int vm_flags;
    int error;
    int accountable = 1;
    unsigned long reqprot = prot;

    if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))   --是否隐藏了可执行属性。
        if (!(file && (file->f_path.mnt->mnt_flags & MNT_NOEXEC)))
            prot |= PROT_EXEC;

    if (!len)         
        return -EINVAL;

    if (!(flags & MAP_FIXED))              -  
        addr = round_hint_to_min(addr);    --判断输入的欲映射的起始地址是否小于最小映射地址,如果小于,将addr修改为最小地址,不过前提是MAP_FIXED旗标没有设置。

    error = arch_mmap_check(addr, len, flags);   --不同平台对于mmap参数的不同检测。这里之间返回0
    if (error)
        return error;

    len = PAGE_ALIGN(len);        --检测len是否越界,len的范围在0~TASK_SIZE之间。
    if (!len || len > TASK_SIZE)
        return -ENOMEM;             --错误值为nomem

    if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)  --再次检测是否越界。我们这里不得不小心哪个晕头了传入一个莫名其妙的值
    return -EOVERFLOW;

    if (mm->map_count > sysctl_max_map_count)   --在一个进程中对于mmap个数是有限制的。超出了还是nomem的错误。
        return -ENOMEM;

    addr = get_unmapped_area(file, addr, len, pgoff, flags);  --获取没有映射的地址,这个是查询mm中空闲的内存地址,这个在下面理解。
    if (addr & ~PAGE_MASK)
        return addr;

    vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) | mm->def_flags |
               VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;      --设置vm_flags,根据传入的port和flags以及mm本身自有的旗标来设置。

    if (flags & MAP_LOCKED) {       
        if (!can_do_mlock())         
            return -EPERM;
        vm_flags |= VM_LOCKED;
    }

    if (vm_flags & VM_LOCKED) {
        unsigned long locked, lock_limit;
        locked = len >> PAGE_SHIFT;
        locked += mm->locked_vm;
        lock_limit = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur;
        lock_limit >>= PAGE_SHIFT;
        if (locked > lock_limit && !capable(CAP_IPC_LOCK))
            return -EAGAIN;
    }
  --关于锁定的内存区在以后学习中再看,这里就不细看。
    inode = file ? file->f_path.dentry->d_inode : NULL;  --判断是否匿名映射,如果不是则赋值inode

    if (file) {
        switch (flags & MAP_TYPE) {   --MAP_TYPE = 0x0F type的掩码
        case MAP_SHARED:
            if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE))   --file应该被打开并允许写入。
                return -EACCES;
            if (IS_APPEND(inode) && (file->f_mode & FMODE_WRITE))  --不能写入一个只允许写追加的文件
                return -EACCES;
            if (locks_verify_locked(inode))      --确保文件没有被强制锁定。
                return -EAGAIN;

            vm_flags |= VM_SHARED | VM_MAYSHARE;  --尝试允许其他进程共享。
            if (!(file->f_mode & FMODE_WRITE))    --如果file不允许写就算了,共享也没有用啊,因为file就一直固定死了,共享也没有意义。
                vm_flags &= ~(VM_MAYWRITE | VM_SHARED);
        case MAP_PRIVATE:
            if (!(file->f_mode & FMODE_READ))
                return -EACCES;
            if (file->f_path.mnt->mnt_flags & MNT_NOEXEC) {
                if (vm_flags & VM_EXEC)
                    return -EPERM;
                vm_flags &= ~VM_MAYEXEC;
            }
            if (is_file_hugepages(file))
                accountable = 0;

            if (!file->f_op || !file->f_op->mmap)
                return -ENODEV;
            break;

        default:
            return -EINVAL;
        }
    } else {
        switch (flags & MAP_TYPE) {
        case MAP_SHARED:
            pgoff = 0;
            vm_flags |= VM_SHARED | VM_MAYSHARE;
            break;
        case MAP_PRIVATE:
            pgoff = addr >> PAGE_SHIFT;
            break;
        default:
            return -EINVAL;
        }
    }
  --上面就是对一些旗标进行检测,防止出现旗标冲突,比如我欲映射的文件不允许写,而我映射的旗标却设定是可写并可以共享的,这个就冲突了。
    error = security_file_mmap(file, reqprot, prot, flags, addr, 0);   --这个函数就忽略了。
    if (error)
        return error;

    return mmap_region(file, addr, len, flags, vm_flags, pgoff,accountable);  --最后一个参数为是否为大页,如果是的就为0.其余的参数都好理解。
}

3.3 get_unmapped_area
这个是获取没有被映射的内存区
unsigned long get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags)
{
    unsigned long (*get_area)(struct file *, unsigned long,unsigned long, unsigned long, unsigned long);
    get_area = current->mm->get_unmapped_area;
    if (file && file->f_op && file->f_op->get_unmapped_area)
        get_area = file->f_op->get_unmapped_area;
    addr = get_area(file, addr, len, pgoff, flags);
    if (IS_ERR_VALUE(addr))
        return addr;

    if (addr > TASK_SIZE - len)
        return -ENOMEM;
    if (addr & ~PAGE_MASK)
        return -EINVAL;

    return arch_rebalance_pgtables(addr, len);
}
对于get_area函数我们以arch_get_unmapped_area为例来看如何查找一个空闲的mmap area
unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr,unsigned long len, unsigned long pgoff, unsigned long flags)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma;
    unsigned long start_addr;

    if (len > TASK_SIZE)
        return -ENOMEM;

    if (flags & MAP_FIXED)    --还记否这个MAP_FIXED是什么含义不?
        return addr;

    if (addr) {
        addr = PAGE_ALIGN(addr);
        vma = find_vma(mm, addr); --vma为NULL即addr的地址不在任一个VMA(vma->vm_start~vma->vm_end) addr的地址没有被映射,
                                    而且空洞足够我们这次的映射,那么返回addr以准备这次的映射
        if (TASK_SIZE - len >= addr &&(!vma || addr + len <= vma->vm_start))
            return addr;
    }
    if (len > mm->cached_hole_size) { --如果所需的长度大于当前vma之间的空洞长度
            start_addr = addr = mm->free_area_cache;
    } else {
            start_addr = addr = TASK_UNMAPPED_BASE;  --需要的长度小于当前空洞,为了不至于时间浪费,那么从0开始搜寻,
                                                       这里的搜寻基地址TASK_UNMAPPED_BASE很重要,用户mmap的地址的基地址必须在TASK_UNMAPPED_BASE之上,
                                                       但是一定这样严格 吗?看上面的if (addr)判断,如果用户给了一个地址在TASK_UNMAPPED_BASE之下,
                                                       映射实际上还是会发生的。
            mm->cached_hole_size = 0;
    }

full_search:
    for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
        if (TASK_SIZE - len < addr) {
            if (start_addr != TASK_UNMAPPED_BASE) {
                addr = TASK_UNMAPPED_BASE;
              start_addr = addr;
                mm->cached_hole_size = 0;
                goto full_search;
            }
            return -ENOMEM;
        }
    
        if (!vma || addr + len <= vma->vm_start) {        --如果第一次find_vma返回值即为NULL ,vma没有被映射并且空洞足够映射
                                                        !vma的条件只有可能在循环的第一次满足,在其后不可能满足,在其后的判断条件即为
                                                         vma->vma_end~vma->vma_next->vma_start之间的空洞大小大于所需要映射的长度即可,
                                                         下面判断条件中的addr为vma->vma_end,而vma->vm_start为vma->vma_next->vma_start
            mm->free_area_cache = addr + len;
            return addr;
        }
        if (addr + mm->cached_hole_size < vma->vm_start)  --在循环的第一次如果vma不为NULL,不会满足下面的条件,在以后循环中mm->cached_hole_size 
                                                            则为该次vma->vm_start 与上一次的vma->vm_end之间的差值

                mm->cached_hole_size = vma->vm_start - addr;
        addr = vma->vm_end;
    }
}
还记否以前看的红黑树,这里就现实的用了红黑树的算法。关于这个我们就不看了。
struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)
{
    struct vm_area_struct *vma = NULL;

    if (mm) {
        vma = mm->mmap_cache;
        if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
            struct rb_node * rb_node;
            rb_node = mm->mm_rb.rb_node;
            vma = NULL;
            while (rb_node) {
                struct vm_area_struct * vma_tmp;

                vma_tmp = rb_entry(rb_node,struct vm_area_struct, vm_rb);
                if (vma_tmp->vm_end > addr) {
                    vma = vma_tmp;
                    if (vma_tmp->vm_start <= addr)
                        break;
                    rb_node = rb_node->rb_left;
                } else
                    rb_node = rb_node->rb_right;
            }
            if (vma)
                mm->mmap_cache = vma;
        }
    }
    return vma;
}

3.4 mmap_region
unsigned long mmap_region(struct file *file, unsigned long addr,unsigned long len, unsigned long flags,
                                unsigned int vm_flags, unsigned long pgoff,int accountable)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma, *prev;
    struct vm_area_struct *merged_vma;
    int correct_wcount = 0;
    int error;
    struct rb_node **rb_link, *rb_parent;
    unsigned long charged = 0;
    struct inode *inode =  file ? file->f_path.dentry->d_inode : NULL;

    /* Clear old maps */
    error = -ENOMEM;
munmap_back:
    vma = find_vma_prepare(mm, addr, &prev, &rb_link, &rb_parent); --函数find_vma_prepare()与find_vma()基本相同,它扫描当前进程地址空间的vm_area_struct
                                                                     结构所形成的红黑树,试图找到结束地址高于addr的第一个区间;如果找到了一个虚拟区,
                                                                     说明addr所在的虚拟区已经在使用,也就是已经有映射存在,因此要调用do_munmap()
                                                                     把这个老的虚拟区从进程地址空间中撤销,如果撤销不成功,就返回一个负数;
                                                                     如果撤销成功,就继续查找,直到在红黑树中找不到addr所在的虚拟区
    if (vma && vma->vm_start < addr + len) {
        if (do_munmap(mm, addr, len))
            return -ENOMEM;
        goto munmap_back;
    }
    if (!may_expand_vm(mm, len >> PAGE_SHIFT))                   -- 页数和超过限定值返回 0 ,不超过返回1
        return -ENOMEM;

    if (flags & MAP_NORESERVE)                             -- 如果flags参数中没有设置MAP_NORESERVE标志,新的虚拟区含有私有的可写页,空闲页面数小于要映射的虚拟区
                                                              的大小;则函数终止并返回一个负数;其中函数security_vm_enough_memory()用来检查一个
                                                              进程的地址空间中是否有足够的内存来进行一个新的映射
        vm_flags |= VM_NORESERVE;

    if (accountable && (!(flags & MAP_NORESERVE) ||
                sysctl_overcommit_memory == OVERCOMMIT_NEVER)) {
        if (vm_flags & VM_SHARED) {
            /* Check memory availability in shmem_file_setup? */
            vm_flags |= VM_ACCOUNT;
        } else if (vm_flags & VM_WRITE) {
            charged = len >> PAGE_SHIFT;
            if (security_vm_enough_memory(charged))
                return -ENOMEM;
            vm_flags |= VM_ACCOUNT;
        }
    }
    if (!file && !(vm_flags & VM_SHARED)) { --如果是匿名映射(file为空),并且这个虚拟区是非共享的,则可以把这个虚拟区和与它紧挨的前一个虚拟区进行合并;
                                              虚拟区的合并是由vma_merge()函数实现的。如果合并成功,则转out处,请看后面out处的代码。
        vma = vma_merge(mm, prev, addr, addr + len, vm_flags,
                    NULL, NULL, pgoff, NULL);
        if (vma)
            goto out;
    }
    vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
    if (!vma) {
        error = -ENOMEM;
        goto unacct_error;
    }

    vma->vm_mm = mm;
    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;

    if (file) {
        error = -EINVAL;
        if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
            goto free_vma;
        if (vm_flags & VM_DENYWRITE) {
            error = deny_write_access(file);
            if (error)
                goto free_vma;
            correct_wcount = 1;
        }
        vma->vm_file = file;
        get_file(file);
        error = file->f_op->mmap(file, vma);    -- (⊙o⊙)哦 ,终于可以调用设备文件中真正的mmap
        if (error)
            goto unmap_and_free_vma;
        if (vm_flags & VM_EXECUTABLE)
            added_exe_file_vma(mm);
    } else if (vm_flags & VM_SHARED) {
        error = shmem_zero_setup(vma);
        if (error)
            goto free_vma;
    }
如果建立的是从文件到虚存区间的映射,则:
1.当参数flags中的VM_GROWSDOWN或VM_GROWSUP标志位为1时,说明这个区间可以向低地址或高地址扩展,但从文件映射的区间不能进行扩展,因此转到free_vma,释放给vm_area_struct分配的Slab,并返回一个错误;
2.当flags中的VM_DENYWRITE标志位为1时,就表示不允许通过常规的文件操作访问该文件,所以要调用deny_write_access()排斥常规的文件操作(参见第八章)。
3.get_file()函数的主要作用是递增file结构中的共享计数;
4.每个文件系统都有个fiel_operation数据结构,其中的函数指针mmap提供了用来建立从该类文件到虚存区间进行映射的操作,这是最具有实质意义的函数;对于大部分文件系统,这个函数为generic_file_mmap( )函数实现的,该函数执行以下操作:
        (1)初始化vm_area_struct结构中的vm_ops域。如果VM_SHARED标志为1,就把该域设置成file_shared_mmap,否则就把该域设置成file_private_mmap。从某种意义上说,这个步骤所做的事情类似于打开一个文件并初始化文件对象的方法。
        (2)从索引节点的i_mode域(参见第八章)检查要映射的文件是否是一个常规文件。如果是其他类型的文件(例如目录或套接字),就返回一个错误代码。
        (3)从索引节点的i_op域中检查是否定义了readpage( )的索引节点操作。如果没有定义,就返回一个错误代码。
        (4)调用update_atime( )函数把当前时间存放在该文件索引节点的i_atime域中,并将这个索引节点标记成脏。
5.如果flags参数中的MAP_SHARED标志位为1,则调用shmem_zero_setup()进行共享内存的映射。

    if ((vm_flags & (VM_SHARED|VM_ACCOUNT)) == (VM_SHARED|VM_ACCOUNT))
        vma->vm_flags &= ~VM_ACCOUNT;

    addr = vma->vm_start;
    pgoff = vma->vm_pgoff;
    vm_flags = vma->vm_flags;

    if (vma_wants_writenotify(vma))
        vma->vm_page_prot = vm_get_page_prot(vm_flags & ~VM_SHARED);

    merged_vma = NULL;
    if (file)
        merged_vma = vma_merge(mm, prev, addr, vma->vm_end,
            vma->vm_flags, NULL, file, pgoff, vma_policy(vma));
    if (merged_vma) {
        mpol_put(vma_policy(vma));
        kmem_cache_free(vm_area_cachep, vma);
        fput(file);
        if (vm_flags & VM_EXECUTABLE)
            removed_exe_file_vma(mm);
        vma = merged_vma;
    } else {
        vma_link(mm, vma, prev, rb_link, rb_parent);
        file = vma->vm_file;
    }
此时,把新建的虚拟区插入到进程的地址空间,这是由函数vma_link()完成的,该函数具有三方面的功能:
(1)把vma 插入到虚拟区链表中
(2)把vma插入到虚拟区形成的红黑树中
(3)把vam插入到索引节点(inode)共享链表中
函数atomic_inc(x)给*x加1,这是一个原子操作。在内核代码中,有很多地方调用了以atomic为前缀的函数。原子操作,在操作过程中不会被中断。

    if (correct_wcount)
        atomic_inc(&inode->i_writecount);
out:
    mm->total_vm += len >> PAGE_SHIFT;
    vm_stat_account(mm, vm_flags, file, len >> PAGE_SHIFT);
    if (vm_flags & VM_LOCKED) {
        long nr_pages = mlock_vma_pages_range(vma, addr, addr + len);
        if (nr_pages < 0)
            return nr_pages;    /* vma gone! */
        mm->locked_vm += (len >> PAGE_SHIFT) - nr_pages;
    } else if ((flags & MAP_POPULATE) && !(flags & MAP_NONBLOCK))
        make_pages_present(addr, addr + len);
    return addr;

unmap_and_free_vma:
    if (correct_wcount)
        atomic_inc(&inode->i_writecount);
    vma->vm_file = NULL;
    fput(file);

    unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end);
    charged = 0;
free_vma:
    kmem_cache_free(vm_area_cachep, vma);
unacct_error:
    if (charged)
        vm_unacct_memory(charged);
    return error;
}

ok!到此mmap的内核核心就可以了,关于具体的mmap的实现,以后再看。

四.总结
mmap的实质是什么,其实就是从每一个进程中的用户空间分配一段空间用于映射。这里面的机关重重,需要好好理解,不过谨记一点,进程的vma_struct是采用了红黑树
来管理的。对于每一段的内存区都会有一个vma_struct来描述,比如数据区,code区等等,以及mmap所需要的一段内存区。
对于mmap的内核有了解的都会知道用户层的mmap到内核层的mmap其中多了一个参数vma_struct这个结构体,在开始时对于这个参数很疑惑就是这个参数的值是哪儿来的,
在这里我们会一一来讲述。
还是从do_mmap开始吧。
3.1 do_mmap
参数说明:
file  :就是用户层想要映射的file
addr  :欲映射的起始地址,即用户层的start
prot  :用户层传入的port
flag  :同上
offset:同上
从这里可以知道,这里面的参数几乎均是用户层传入的参数。
static inline unsigned long do_mmap(struct file *file, unsigned long addr,unsigned long len, unsigned long prot,
                                    unsigned long flag, unsigned long offset)
{
    unsigned long ret = -EINVAL;
    if ((offset + PAGE_ALIGN(len)) < offset)  --页对齐len,检测传入参数是否有误。
        goto out;
    if (!(offset & ~PAGE_MASK))           --检测offset是否页对齐。映射时只能映射页对齐的长度。
        ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT);  
out:
    return ret;
}

3.2 do_mmap_pgoff
这个函数是巨大的。
unsigned long do_mmap_pgoff(struct file * file, unsigned long addr,unsigned long len, unsigned long prot,unsigned long flags, unsigned long pgoff)
{
    struct mm_struct * mm = current->mm;      --当前用户进程的mm
    struct inode *inode; 
    unsigned int vm_flags;
    int error;
    int accountable = 1;
    unsigned long reqprot = prot;

    if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))   --是否隐藏了可执行属性。
        if (!(file && (file->f_path.mnt->mnt_flags & MNT_NOEXEC)))
            prot |= PROT_EXEC;

    if (!len)         
        return -EINVAL;

    if (!(flags & MAP_FIXED))              -  
        addr = round_hint_to_min(addr);    --判断输入的欲映射的起始地址是否小于最小映射地址,如果小于,将addr修改为最小地址,不过前提是MAP_FIXED旗标没有设置。

    error = arch_mmap_check(addr, len, flags);   --不同平台对于mmap参数的不同检测。这里之间返回0
    if (error)
        return error;

    len = PAGE_ALIGN(len);        --检测len是否越界,len的范围在0~TASK_SIZE之间。
    if (!len || len > TASK_SIZE)
        return -ENOMEM;             --错误值为nomem

    if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)  --再次检测是否越界。我们这里不得不小心哪个晕头了传入一个莫名其妙的值
    return -EOVERFLOW;

    if (mm->map_count > sysctl_max_map_count)   --在一个进程中对于mmap个数是有限制的。超出了还是nomem的错误。
        return -ENOMEM;

    addr = get_unmapped_area(file, addr, len, pgoff, flags);  --获取没有映射的地址,这个是查询mm中空闲的内存地址,这个在下面理解。
    if (addr & ~PAGE_MASK)
        return addr;

    vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) | mm->def_flags |
               VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;      --设置vm_flags,根据传入的port和flags以及mm本身自有的旗标来设置。

    if (flags & MAP_LOCKED) {       
        if (!can_do_mlock())         
            return -EPERM;
        vm_flags |= VM_LOCKED;
    }

    if (vm_flags & VM_LOCKED) {
        unsigned long locked, lock_limit;
        locked = len >> PAGE_SHIFT;
        locked += mm->locked_vm;
        lock_limit = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur;
        lock_limit >>= PAGE_SHIFT;
        if (locked > lock_limit && !capable(CAP_IPC_LOCK))
            return -EAGAIN;
    }
  --关于锁定的内存区在以后学习中再看,这里就不细看。
    inode = file ? file->f_path.dentry->d_inode : NULL;  --判断是否匿名映射,如果不是则赋值inode

    if (file) {
        switch (flags & MAP_TYPE) {   --MAP_TYPE = 0x0F type的掩码
        case MAP_SHARED:
            if ((prot&PROT_WRITE) && !(file->f_mode&FMODE_WRITE))   --file应该被打开并允许写入。
                return -EACCES;
            if (IS_APPEND(inode) && (file->f_mode & FMODE_WRITE))  --不能写入一个只允许写追加的文件
                return -EACCES;
            if (locks_verify_locked(inode))      --确保文件没有被强制锁定。
                return -EAGAIN;

            vm_flags |= VM_SHARED | VM_MAYSHARE;  --尝试允许其他进程共享。
            if (!(file->f_mode & FMODE_WRITE))    --如果file不允许写就算了,共享也没有用啊,因为file就一直固定死了,共享也没有意义。
                vm_flags &= ~(VM_MAYWRITE | VM_SHARED);
        case MAP_PRIVATE:
            if (!(file->f_mode & FMODE_READ))
                return -EACCES;
            if (file->f_path.mnt->mnt_flags & MNT_NOEXEC) {
                if (vm_flags & VM_EXEC)
                    return -EPERM;
                vm_flags &= ~VM_MAYEXEC;
            }
            if (is_file_hugepages(file))
                accountable = 0;

            if (!file->f_op || !file->f_op->mmap)
                return -ENODEV;
            break;

        default:
            return -EINVAL;
        }
    } else {
        switch (flags & MAP_TYPE) {
        case MAP_SHARED:
            pgoff = 0;
            vm_flags |= VM_SHARED | VM_MAYSHARE;
            break;
        case MAP_PRIVATE:
            pgoff = addr >> PAGE_SHIFT;
            break;
        default:
            return -EINVAL;
        }
    }
  --上面就是对一些旗标进行检测,防止出现旗标冲突,比如我欲映射的文件不允许写,而我映射的旗标却设定是可写并可以共享的,这个就冲突了。
    error = security_file_mmap(file, reqprot, prot, flags, addr, 0);   --这个函数就忽略了。
    if (error)
        return error;

    return mmap_region(file, addr, len, flags, vm_flags, pgoff,accountable);  --最后一个参数为是否为大页,如果是的就为0.其余的参数都好理解。
}

3.3 get_unmapped_area
这个是获取没有被映射的内存区
unsigned long get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags)
{
    unsigned long (*get_area)(struct file *, unsigned long,unsigned long, unsigned long, unsigned long);
    get_area = current->mm->get_unmapped_area;
    if (file && file->f_op && file->f_op->get_unmapped_area)
        get_area = file->f_op->get_unmapped_area;
    addr = get_area(file, addr, len, pgoff, flags);
    if (IS_ERR_VALUE(addr))
        return addr;

    if (addr > TASK_SIZE - len)
        return -ENOMEM;
    if (addr & ~PAGE_MASK)
        return -EINVAL;

    return arch_rebalance_pgtables(addr, len);
}
对于get_area函数我们以arch_get_unmapped_area为例来看如何查找一个空闲的mmap area
unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr,unsigned long len, unsigned long pgoff, unsigned long flags)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma;
    unsigned long start_addr;

    if (len > TASK_SIZE)
        return -ENOMEM;

    if (flags & MAP_FIXED)    --还记否这个MAP_FIXED是什么含义不?
        return addr;

    if (addr) {
        addr = PAGE_ALIGN(addr);
        vma = find_vma(mm, addr); --vma为NULL即addr的地址不在任一个VMA(vma->vm_start~vma->vm_end) addr的地址没有被映射,而且空洞足够我们这次的映射,那么返回addr以准备这次的映射
        if (TASK_SIZE - len >= addr &&(!vma || addr + len <= vma->vm_start))
            return addr;
    }
    if (len > mm->cached_hole_size) { --如果所需的长度大于当前vma之间的空洞长度
            start_addr = addr = mm->free_area_cache;
    } else {
            start_addr = addr = TASK_UNMAPPED_BASE;  --需要的长度小于当前空洞,为了不至于时间浪费,那么从0开始搜寻,这里的搜寻基地址TASK_UNMAPPED_BASE很重要,用户mmap的地址的基地址必须在TASK_UNMAPPED_BASE之上,但是一定这样严格 吗?看上面的if (addr)判断,如果用户给了一个地址在TASK_UNMAPPED_BASE之下,  映射实际上还是会发生的。
            mm->cached_hole_size = 0;
    }

full_search:
    for (vma = find_vma(mm, addr); ; vma = vma->vm_next) {
        if (TASK_SIZE - len < addr) {
            if (start_addr != TASK_UNMAPPED_BASE) {
                addr = TASK_UNMAPPED_BASE;
              start_addr = addr;
                mm->cached_hole_size = 0;
                goto full_search;
            }
            return -ENOMEM;
        }
    
        if (!vma || addr + len <= vma->vm_start) {        --如果第一次find_vma返回值即为NULL ,vma没有被映射并且空洞足够映射  !vma的条件只有可能在循环的第一次满足,在其后不可能满足,在其后的判断条件即为,vma->vma_end~vma->vma_next->vma_start之间的空洞大小大于所需要映射的长度即可,下面判断条件中的addr为vma->vma_end,而vma->vm_start为vma->vma_next->vma_start
            mm->free_area_cache = addr + len;
            return addr;
        }
        if (addr + mm->cached_hole_size < vma->vm_start)  --在循环的第一次如果vma不为NULL,不会满足下面的条件,在以后循环中mm->cached_hole_size 则为该次vma->vm_start 与上一次的vma->vm_end之间的差值

                mm->cached_hole_size = vma->vm_start - addr;
        addr = vma->vm_end;
    }
}
还记否以前看的红黑树,这里就现实的用了红黑树的算法。关于这个我们就不看了。
struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)
{
    struct vm_area_struct *vma = NULL;

    if (mm) {
        vma = mm->mmap_cache;
        if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
            struct rb_node * rb_node;
            rb_node = mm->mm_rb.rb_node;
            vma = NULL;
            while (rb_node) {
                struct vm_area_struct * vma_tmp;

                vma_tmp = rb_entry(rb_node,struct vm_area_struct, vm_rb);
                if (vma_tmp->vm_end > addr) {
                    vma = vma_tmp;
                    if (vma_tmp->vm_start <= addr)
                        break;
                    rb_node = rb_node->rb_left;
                } else
                    rb_node = rb_node->rb_right;
            }
            if (vma)
                mm->mmap_cache = vma;
        }
    }
    return vma;
}

3.4 mmap_region
unsigned long mmap_region(struct file *file, unsigned long addr,unsigned long len, unsigned long flags,
                                unsigned int vm_flags, unsigned long pgoff,int accountable)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma, *prev;
    struct vm_area_struct *merged_vma;
    int correct_wcount = 0;
    int error;
    struct rb_node **rb_link, *rb_parent;
    unsigned long charged = 0;
    struct inode *inode =  file ? file->f_path.dentry->d_inode : NULL;

    /* Clear old maps */
    error = -ENOMEM;
munmap_back:
    vma = find_vma_prepare(mm, addr, &prev, &rb_link, &rb_parent); --函数find_vma_prepare()与find_vma()基本相同,它扫描当前进程地址空间的vm_area_struct结构所形成的红黑树,试图找到结束地址高于addr的第一个区间;如果找到了一个虚拟区,说明addr所在的虚拟区已经在使用,也就是已经有映射存在,因此要调用do_munmap() 把这个老的虚拟区从进程地址空间中撤销,如果撤销不成功,就返回一个负数;如果撤销成功,就继续查找,直到在红黑树中找不到addr所在的虚拟区
    if (vma && vma->vm_start < addr + len) {
        if (do_munmap(mm, addr, len))
            return -ENOMEM;
        goto munmap_back;
    }
    if (!may_expand_vm(mm, len >> PAGE_SHIFT))                   -- 页数和超过限定值返回 0 ,不超过返回1
        return -ENOMEM;

    if (flags & MAP_NORESERVE)                             -- 如果flags参数中没有设置MAP_NORESERVE标      志,新的虚拟区含有私有的可写页,空闲页面数小于要映射的虚拟区的大小;则函数终止并返回一个负数;其中函数security_vm_enough_memory()用来检查一个进程的地址空间中是否有足够的内存来进行一个新的映射
        vm_flags |= VM_NORESERVE;

    if (accountable && (!(flags & MAP_NORESERVE) ||
                sysctl_overcommit_memory == OVERCOMMIT_NEVER)) {
        if (vm_flags & VM_SHARED) {
            /* Check memory availability in shmem_file_setup? */
            vm_flags |= VM_ACCOUNT;
        } else if (vm_flags & VM_WRITE) {
            charged = len >> PAGE_SHIFT;
            if (security_vm_enough_memory(charged))
                return -ENOMEM;
            vm_flags |= VM_ACCOUNT;
        }
    }
    if (!file && !(vm_flags & VM_SHARED)) { --如果是匿名映射(file为空),并且这个虚拟区是非共享的,则可以把这个虚拟区和与它紧挨的前一个虚拟区进行合并;
                                              虚拟区的合并是由vma_merge()函数实现的。如果合并成功,则转out处,请看后面out处的代码。
        vma = vma_merge(mm, prev, addr, addr + len, vm_flags,
                    NULL, NULL, pgoff, NULL);
        if (vma)
            goto out;
    }
    vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
    if (!vma) {
        error = -ENOMEM;
        goto unacct_error;
    }

    vma->vm_mm = mm;
    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;

    if (file) {
        error = -EINVAL;
        if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
            goto free_vma;
        if (vm_flags & VM_DENYWRITE) {
            error = deny_write_access(file);
            if (error)
                goto free_vma;
            correct_wcount = 1;
        }
        vma->vm_file = file;
        get_file(file);
        error = file->f_op->mmap(file, vma);    -- (⊙o⊙)哦 ,终于可以调用设备文件中真正的mmap
        if (error)
            goto unmap_and_free_vma;
        if (vm_flags & VM_EXECUTABLE)
            added_exe_file_vma(mm);
    } else if (vm_flags & VM_SHARED) {
        error = shmem_zero_setup(vma);
        if (error)
            goto free_vma;
    }
如果建立的是从文件到虚存区间的映射,则:
1.当参数flags中的VM_GROWSDOWN或VM_GROWSUP标志位为1时,说明这个区间可以向低地址或高地址扩展,但从文件映射的区间不能进行扩展,因此转到free_vma,释放给vm_area_struct分配的Slab,并返回一个错误;
2.当flags中的VM_DENYWRITE标志位为1时,就表示不允许通过常规的文件操作访问该文件,所以要调用deny_write_access()排斥常规的文件操作(参见第八章)。
3.get_file()函数的主要作用是递增file结构中的共享计数;
4.每个文件系统都有个fiel_operation数据结构,其中的函数指针mmap提供了用来建立从该类文件到虚存区间进行映射的操作,这是最具有实质意义的函数;对于大部分文件系统,这个函数为generic_file_mmap( )函数实现的,该函数执行以下操作:
        (1)初始化vm_area_struct结构中的vm_ops域。如果VM_SHARED标志为1,就把该域设置成file_shared_mmap,否则就把该域设置成file_private_mmap。从某种意义上说,这个步骤所做的事情类似于打开一个文件并初始化文件对象的方法。
        (2)从索引节点的i_mode域(参见第八章)检查要映射的文件是否是一个常规文件。如果是其他类型的文件(例如目录或套接字),就返回一个错误代码。
        (3)从索引节点的i_op域中检查是否定义了readpage( )的索引节点操作。如果没有定义,就返回一个错误代码。
        (4)调用update_atime( )函数把当前时间存放在该文件索引节点的i_atime域中,并将这个索引节点标记成脏。
5.如果flags参数中的MAP_SHARED标志位为1,则调用shmem_zero_setup()进行共享内存的映射。

    if ((vm_flags & (VM_SHARED|VM_ACCOUNT)) == (VM_SHARED|VM_ACCOUNT))
        vma->vm_flags &= ~VM_ACCOUNT;

    addr = vma->vm_start;
    pgoff = vma->vm_pgoff;
    vm_flags = vma->vm_flags;

    if (vma_wants_writenotify(vma))
        vma->vm_page_prot = vm_get_page_prot(vm_flags & ~VM_SHARED);

    merged_vma = NULL;
    if (file)
        merged_vma = vma_merge(mm, prev, addr, vma->vm_end,
            vma->vm_flags, NULL, file, pgoff, vma_policy(vma));
    if (merged_vma) {
        mpol_put(vma_policy(vma));
        kmem_cache_free(vm_area_cachep, vma);
        fput(file);
        if (vm_flags & VM_EXECUTABLE)
            removed_exe_file_vma(mm);
        vma = merged_vma;
    } else {
        vma_link(mm, vma, prev, rb_link, rb_parent);
        file = vma->vm_file;
    }
此时,把新建的虚拟区插入到进程的地址空间,这是由函数vma_link()完成的,该函数具有三方面的功能:
(1)把vma 插入到虚拟区链表中
(2)把vma插入到虚拟区形成的红黑树中
(3)把vam插入到索引节点(inode)共享链表中
函数atomic_inc(x)给*x加1,这是一个原子操作。在内核代码中,有很多地方调用了以atomic为前缀的函数。原子操作,在操作过程中不会被中断。

    if (correct_wcount)
        atomic_inc(&inode->i_writecount);
out:
    mm->total_vm += len >> PAGE_SHIFT;
    vm_stat_account(mm, vm_flags, file, len >> PAGE_SHIFT);
    if (vm_flags & VM_LOCKED) {
        long nr_pages = mlock_vma_pages_range(vma, addr, addr + len);
        if (nr_pages < 0)
            return nr_pages;    /* vma gone! */
        mm->locked_vm += (len >> PAGE_SHIFT) - nr_pages;
    } else if ((flags & MAP_POPULATE) && !(flags & MAP_NONBLOCK))
        make_pages_present(addr, addr + len);
    return addr;

unmap_and_free_vma:
    if (correct_wcount)
        atomic_inc(&inode->i_writecount);
    vma->vm_file = NULL;
    fput(file);

    unmap_region(mm, vma, prev, vma->vm_start, vma->vm_end);
    charged = 0;
free_vma:
    kmem_cache_free(vm_area_cachep, vma);
unacct_error:
    if (charged)
        vm_unacct_memory(charged);
    return error;
}

ok!到此mmap的内核核心就可以了,关于具体的mmap的实现,以后再看。

四.总结
mmap的实质是什么,其实就是从每一个进程中的用户空间分配一段空间用于映射。这里面的机关重重,需要好好理解,不过谨记一点,进程的vma_struct是采用了红黑树
来管理的。对于每一段的内存区都会有一个vma_struct来描述,比如数据区,code区等等,以及mmap所需要的一段内存区。
Linux系统中的mmap底层详解 通过上图我们可以知道mmap是操作硬件设备的一种方法,所谓操作设备,比如IO端口(点亮一个LED)、LCD控制器、磁盘控制器,实际上就是往设备的物理地址上读写数据。但是,由于应用程序不能直接操作设备硬件地址,所以操作系统提供了这样一种机制——内存映射,把设备地址映射到进程虚拟地址,mmap就是实现内存映射的接口。操作设备还有很多方法,如ioctl、ioremap等。 阅读详情

相关推荐

Linux mmap内存映射

四、网上测试实例 (1)demo1 演示一下,将文件/tmp/file_mmap中的字符转成大写,分别使用mmap与read/write二种方法实现。 先创建/tmp/file_mmap文件,该文件写入www.baidu.com,使用strace统计系统调用。 /* @file: t_mmap.c */ #include <stdio.h> #include <ctype.h> #include <sys/mman.h> /mmap munmap/ #include &

uu_5201314的博客 2887

Linux线程(进程)数限制

Linux线程(进程)数限制

bobo now~ 2052

【Linux驱动编程】mmap方法

mmap常规的应用是将普通文件映射到用户进程空间,提高文件读写效率。linux的思想是“一切皆文件”,对于设备文件而言,同样可以实现mmap映射。一个设备,一般涉及到帧缓存会考虑实现映射接口,常见的LCD<font color=cloor> framebuffer</font>设备的显存空间,经过映射后,用户进程可以直接操作进程内存空间将LCD显示数据写入,提高刷新效率,节省CPU拷贝内存开销。

只要思想不滑坡,想法总比问题多。 4416

Linux下对mmap封装使用

mmap即memory map,是一种内存映射文件的技术。mmap可以将一个文件或者其它对象映射到进程的地址空间,进而实现磁盘地址和进程虚拟地址的一一对应关系。通过使用mmap技术,我们可以让不同进程通过映射到同一个普通文件的方式实现共享内存,普通文件被映射到进程地址空间当中之后,进程可以向访问普通内存一样对文件高效地进行一系列操作。mmap()必须以PAGE_SIZE(默认为4KB) 为单位进行映射,而内存也只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行内存对齐,强行以。

游戏行业资深搬砖人员 2835

linux内核那些事之mmap

《linux mmap系统调用》主要描述了用户空间内mmap的使用及其注意事项,mmap最终还是要进入到内核态,如果没有指定addr则由内核分配一段可用的vma,如果已经指定addr则内核按照对齐等要求对addr地址进行整理,根据需要内核生成一个vma,以记录跟踪用户空间的使用情况,最后根据生成的vma以及fd做映射。 mmap主要过程 用户空间mmap主要是通过glibc,由glibc通过系统调用最终进入到内核态__do_sys_mmap(),以内核5.8.10版本为例,主要流程图如下: 主要.

weixin_42730667的博客 1447

Ubuntu双系统引导修复实战:boot-repair工具详解

本文详细介绍了使用boot-repair工具修复Ubuntu双系统引导丢失问题的实战方法。当GRUB引导菜单因安装新系统或更新而损坏时,通过制作Ubuntu启动盘进入Live环境,安装并运行boot-repair,可一键完成系统修复引导,自动化执行GRUB重装与配置,解决无法进入Windows或Ubuntu的常见故障。

weixin_29224589的博客 356

Linux内存管理之mmap详解 (2

原文地址:Linux内存管理之mmap详解 作者:heavent2010 Linux内存管理之mmap详解 一. mmap系统调用 1. mmap系统调用         mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特

wocjj的专栏 1698

地理空间智能体(GI Agent)

地理空间智能体(Geospatial Intelligence Agent, GI Agent)是AI Agent技术与地理信息系统(GIS)深度融合的产物。其核心是以大型语言模型(LLM)为驱动,通过自然语言理解、任务分解和工具调用能力,实现地理空间任务的自动化执行。​关键特性​自主性​:独立规划任务流程(如空间数据检索、分析、可视化);​交互性​:支持自然语言对话交互,替代传统图形界面操作;​扩展性​:灵活集成第三方工具和API,支持跨平台协作。

百态·数智思维 | 聚焦大数据、量化与未来AI 2031

Linux内存分析 | mmap

转自 https://www.cnblogs.com/huxiao-tee/p/4660352.html 1 mmap基本概念 mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的...

JH_Zhai的博客 928

Gromacs实战:从蛋白质-小分子对接到分子动力学模拟全流程解析

本文详细解析了使用Gromacs进行蛋白质-小分子复合物分子动力学模拟的全流程。从处理AutoDock等对接软件的结果文件开始,逐步指导如何为蛋白质和小分子分别生成拓扑文件,并解决力场合并的核心挑战。文章涵盖了构建模拟盒子、添加溶剂离子、能量最小化、平衡模拟以及关键的轨迹分析步骤,为计算生物学和药物设计研究者提供了一套从静态对接到动态评估的完整实战指南。

weixin_29251337的博客 1218

基于IC动态加权的机器学习多因子选股策略:从模型融合到实战回测

在量化投资领域,多因子选股模型通过挖掘市场数据中的有效信号来预测股票收益。其核心原理在于利用统计学与机器学习算法,从海量因子中识别出与未来收益具有稳定相关性的特征。这一技术的核心价值在于将主观投资决策转化为系统化、可重复的客观过程,从而在控制风险的前提下追求超额收益。其典型应用场景包括股票阿尔法策略构建、Smart Beta产品设计以及资产配置中的股票筛选环节。本文聚焦于如何通过信息系数(IC)这一关键指标,动态融合Ridge回归、MLP神经网络和随机森林等多个预测模型,构建一个能够自适应市场风格轮动的选股

weixin_34216614的博客 450

Linux mmap原理

mmap是linux操作系统提供给用户空间调用的内存映射函数,很多人仅仅只是知道可以通过mmap完成进程间的内存共享和减少用户态到内核态的数据拷贝次数,但是并没有深入理解mmap在操作系统内部是如何实现的,原理是什么。linux段页式内存管理回顾mmap原理(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域。

m0_53157173的博客 3409

多相机标定与单应变换:用OpenCV实现BEV俯视全景图

多路监控画面分散在不同屏幕,难以快速理解全局空间关系。通过相机标定与畸变校正,利用单应矩阵将各视角图像投影到统一的地面坐标系,即可合成BEV鸟瞰全景图。该技术是车载360°环视系统和自动驾驶BEV感知的雏形,也适用于场地安防与移动机器人导航。基于Python+OpenCV的四路相机实现,完整覆盖标定、投影变换、多路羽化融合与实时优化,为多视角拼接和俯视全景图项目提供可复现的工程参考。

weixin_29056865的博客 242

linux mmap 作用,LINUX 中的mmap浅析

LINUX 中的mmap浅析一、mmap基本原理和分类在LINUX中我们可以使用mmap用来在进程虚拟地址空间中分配创建一片虚拟内存地址映射其可以是1、文件映射使用文件内容初始化内存2、匿名映射初始化全为0的内存空间(calloc也可以)下面配图来自UNIX系统编程手册而对于是否共享又分为1、私有映射(MAP_PRIVATE)多进程间数据共享,修改不反应到磁盘实际文件,私有写时复制实现2、共享映射...

weixin_29359001的博客 3489

图神经网络与大模型融合的理论综述:架构、机制与未来方向

然而,它们普遍缺乏对图结构数据的原生支持,无法直接处理边、节点之间的拓扑关系,这限制了其在关系密集型任务中的表现 [23]。2020年发布的BERT [16] 开启了自监督预训练时代,而2021年后,T5 [17]、PaLM [18]、LLaMA [19] 等模型相继推出,参数规模突破千亿级,展现出惊人的上下文学习(In-context Learning)与链式推理(Chain-of-Thought)能力 [20]。例如,将医学影像作为图节点,结合病理报告文本,实现更精准的疾病诊断 [58]。

andeyeluguo的博客 294

【报错解决】OpenClaw 报错 `openclaw: command not found`:全局命令未加入系统 PATH 的深度排查与修复指南

在国产AI工具链开发中,OpenClaw作为关键命令行工具常出现command not found报错。本文深度分析该问题的根源——Linux环境变量PATH未包含用户级安装目录(~/.local/bin)。通过四种解决方案(临时PATH修改、永久配置、软链接、虚拟环境处理)和排错Checklist,揭示"命令存在却找不到"的本质是路径配置问题,特别适用于Docker/国产Linux/非root环境。文中强调理解Linux工具链机制对国产AI开发的重要性,并指出这是开发者掌握环境

博客 5381

Linux mmap 详解

Linux mmap详解一、mmap()是什么二、mmap()原理三、mmap和常规文件操作的区别四、mmap相关函数五、mmap使用细节六、对mmap()返回地址的访问例子:情形一:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射5000字节到虚拟内存中。**情形二:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射15000字节到虚拟内存中,即映射...

Steve Guo 2410

升级最新IDEA版本(2022.1.1)

升级最新IDEA版本(2022.1.1)

软件老王 3万+

【Linux】mmap文件映射

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图所示:看到上面这张图,不知道大家有没有觉得很像System V的共享内存啊?

2301_80224556的博客 1465

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

linux函数 mmap详解

mmap() 可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存 地址,对文件的读写可以直接用指针来做而不需要read/write函数。man mmap。

叶新东老师的博客 1036

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

【Linux】mmap文件内存映射

【Linux】mmap文件内存映射:一文带你理解mmap的映射,教你使用mmap,写入映射,读入映射,简单模拟实现malloc

tan_run的博客 3394

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

linux mmap函数详解

linux mmap 详解 谨以此文纪念过往的岁月 一.前言 mmap的具体实现以前在学习内核时学习过,但是对于其中的很多函数是一知半解的,有些只能根据其函数名来猜测其具体的功能,在本文中,一起来重新深入理解其具体的实现。 二.mmap的用户层应用 void *mmap(void *start,size_t length,int prot,int flags,int fd,o

村夫的小田园 1075

【Linux】mmap 文件映射

mmap 文件映射

2203_76003626的博客 1085

【Linux】mmap分析

平时我们写Linux驱动和用户空间交互时,都是使用read或者write函数接口,并且在函数内使用或者。因为用户空间是不能直接访问内核空间数据的,只能先将数据拷贝过来,然后再操作。如果用户空间需要传几MB的数据给内核,那么原来的拷贝方式显然效率比较低,这个时候就需要使用mmap操作,简单来说就是让一块物理内存拥有两份映射,即拥有两个虚拟地址,一个在内核空间,一个在用户空间,这样用户空间和内核空间就可以访问同一块内存了。关系如下mmap。

目标:MVP 1033

linux mmap实例_linux下mmap详解与代码示例

在讲述文件映射的概念时, 不可避免的要牵涉到虚存(SVR 4的VM). 实际上, 文件映射是虚存的中心概念, 文件映射一方面给用户提供了一组措施, 好似用户将文件映射到自己地址空间的某个部分, 使用简单的内存访问指令读写文件;另一方面, 它也可以用于内核的基本组织模式, 在这种模式种, 内核将整个地址空间视为诸如文件之类的一组不同对象的映射. 中的传统文件访问方式是, 首先用open系统调用打开文...

weixin_32099979的博客 191

linux mmap详解

mmap基础概念 mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。如下图所示: 由上图可以看出,进程的虚拟地址空间,由多个虚拟内存区域构成。虚拟内存

liuwg1226的专栏 1169
上一篇: Linux驱动之设备模型(9)-platform
下一篇: 进程任务结构与初始化
白水煮鸡蛋
博客等级 码龄15年 106粉丝 46原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值