内存在父子进程间的共享时间及范围

一、内存共享
共享内存是linux下父子进程除了文件描述符之外的另一个更加具有亲缘性的共享机制了。和文件描述符共享不同,这种内存的共享它的共享性更强,因为内存的范围很大,共享的粒度可以精确到不同的数据结构。这个说法可能有些危言耸听,而且大家会觉得这个不是事实。老实说,它的确只是部分事实。
在unix类系统中,进程的创建通常大家认为都是分为两步,就是fork+exec的模式,但是之后的exec并不是必须的,简单的一个fork就已经派生出了一个新的进程,它在内核中有自己的task_struct结构,有自己的寄存器组、地址空间布局等信息。在fork之后,新fork的进程可以执行很多的事情,如果说一个程序的功能比较简单,但是使用多任务比较有优势,那么这种只执行fork而不执行exec的模式就是一个很好的选择。事实上,在网络传输中经常使用的rsync就是由这种模式组成。它在fork之后就没有执行exec,而是把网络的接受层数据和真正的解码比较进程独立开来,这样的好处就是对CPU消耗较多的任务和对IO消耗就多的进程分开接受调度。如果这两个操作在同一个进程中串行处理,那么CPU和IO这两个可以并行的资源使用操作将会被串行化,在进行IO操作的时候就不能进行CPU操作,反之亦然。
二、fork对共享内存的处理
当fork执行之后,内核将会开始为新创建的进程设置一系列新的属性,这个就包括了新的内存布局信息的分配,由于这里主要讨论的就是共享内存的操作,所以我们就只讨论这个相关内容。在fork进入内核之后
sys_fork--->>>>do_fork--->>>copy_process---->>>copy_mm---->>>dup_mm---->>>dup_mmap---->>>copy_page_range---->>>>copy_pud_range---->>>copy_pmd_range---->>>copy_pte_range---->>>copy_one_pte
    /*
     * If it's a COW mapping, write protect it both
     * in the parent and the child
     */
    if (is_cow_mapping(vm_flags)) {
        ptep_set_wrprotect(src_mm, addr, src_pte);
        pte = pte_wrprotect(pte);
    }

static inline int is_cow_mapping(unsigned int flags)
{
    return (flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE;
}
大家注意一下,这里那些页面是私有的,私有的特征是非共享的可写内存区间。我们要知道,整个内存的地址空间中每个可用的区间都是一个这样的vma,而每个vma中真正分配过物理页面的page都是经过这个检测的。这是一个归纳的说法,可能没什么特殊的,举个例子来说一下。大家最常用的malloc的底层就是由libc中通过mmap向操作系统批量申请的地址空间,只是这个空间的mmap时它mmap的是私有的,这一点就导致is_cow_mapping中VM_SHARED是没有置位,因此函数返回值为true;对于代码段中的空间,它的VM_SHARED是满足的,所以函数返回false,进而导致父进程和子进程直接共享页面,不会设置COW属性。
三、当有进程(父进程或 子进程)写入共享COW空间
当有进程向私有可写内存中写入(修改内存内容)时,此时触发内存保护异常处理程序,此时就可以在处理程序中将页面执行“fork”,分裂之前让两个页面拥有相同的内容,然后取消页面的不可写属性,这样在中断异常返回之后就可以正确修改内存内容了。
handle_pte_fault--->>>do_wp_page
        goto oom;
    if (old_page == ZERO_PAGE(address)) {
        new_page = alloc_zeroed_user_highpage(vma, address);
        if (!new_page)
            goto oom;
    } else {
        new_page = alloc_page_vma(GFP_HIGHUSER, vma, address); 分配一个新的物理页面
        if (!new_page)
            goto oom;
        cow_user_page(new_page, old_page, address, vma);将原始页面内容拷贝到新的页面中
    }
四、shmget共享内存特性
在共享内存中,有传统的sysv的shmget创建的,还有新的libc中实现的shmopen方法创建的共享内存,由于shm文件创建之后还是要通过mmap啦映射文件,而映射的时候是需要明确指定这个共享内存是共享的还是私有的。而对于shmget这个操作来说,从api来说是没有办法直接看到了这个共享内存的属性到底是怎样的。
使用shmget的原因也比较多,可能这种调用方式操作比较少一些,或者是网络系统中比较常用shmget函数族,而嵌入式中比较常用的是shmopen类函数。
sys_ipc--->>>do_shmat
    if (shmid < 0)
        goto out;
    else if ((addr = (ulong)shmaddr)) {
        if (addr & (SHMLBA-1)) {
            if (shmflg & SHM_RND)
                addr &= ~(SHMLBA-1);       /* round down */
            else
#ifndef __ARCH_FORCE_SHMLBA
                if (addr & ~PAGE_MASK)
#endif
                    goto out;
        }
        flags = MAP_SHARED | MAP_FIXED;
    } else {
        if ((shmflg & SHM_REMAP))
            goto out;

        flags = MAP_SHARED;
    }
    if (shmflg & SHM_RDONLY) {
        prot = PROT_READ;
        acc_mode = S_IRUGO;
        f_mode = FMODE_READ;
    } else {
        prot = PROT_READ | PROT_WRITE;
        acc_mode = S_IRUGO | S_IWUGO;
        f_mode = FMODE_READ | FMODE_WRITE;
    }
大家可以看到,通过shmat附加上的共享内存是不满足is_cow_mapping的,所以在执行了fork之后,通过shmat附加的进程的地址空间是可以在子进程和父进程之间共享的,这意味着两个进程都可以同时来修改共享内存中的内容。大家不要小看这个特性,因为对于一个环形队列来说,如果一个进程只进行写入、另一个只进行读出,那么此时是不需要锁这种互斥机制的,关于这一点可以参考内核中的kfifo.c函数的实现。
五、shmat中attach的进程
当共享内存在创建之后,即使所有的进程都已经退出,共享内存依然是存在的。这里可以看出shm共享内存的一个重要属性,那就是它虽然能够被进程当做内存来使用,但是它却可以超越进程的生命周期,在进程退出之后依然存在。如果想要当所有进程都退出之后共享内存自动删除,可以在shm创建之后立即通过shmctl来删除这篇共享内存。但是这里的删除只是逻辑删除,只要有一个进程还在使用者这篇共享内存,那么即使执行了删除操作,该共享内存依然存在。
那么我们如何知道哪些进程attach了这个共享内存呢?这个的确是一个问题,现在没有办法,因为共享内存的所有使用者如果要保存起来,那么需要使用一个链表结构,这个结构还要最好嵌入到task_struct结构,这样就比较浪费。

[root@Harry ~]# cat /proc/sysvipc/shm 
       key      shmid perms       size  cpid  lpid nattch   uid   gid  cuid  cgid      atime      dtime      ctime
         0      98304  1600     393216  1734 18563      2     0     0     0     0 1354376227 1354376227 1351430952
         0     131073  1600     393216  1750 18563      2     0     0     0     
其中的lpid应该是最后一个修改该资源的结构,包括附加、修改等,当然这个对于共享着很多的进程资源来说,如果要知道它所有的附加者是不太可能实现的。

posted on 2019-03-07 10:13  tsecer  阅读(1146)  评论(0编辑  收藏  举报

导航