注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

shm类共享内存再说明  

2012-07-29 15:52:48|  分类: Linux内核 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、shmget
这种shmget是相对比较原始的一种早期的sysv进程间通讯机制,也就是基于内存的一种共享内存实现方法。说它相对原始,这个相对相对的就是现在很多posix中说明的shm_open操作,这种操作和open一样,它返回的是一个文件描述符,然后可以通过mmap来讲这个内存映射入自己的地址空间,然后作为内存来使用。这种shm_open机制的优点就是它可以让用户清晰的看到自己在系统创建的共享内存文件,甚至可以通过lsof来看到这个共享内存文件是被谁使用的,这些都是shmget所不具备的一个功能。
但是在于一些比较传统的软件中,shmget的接口可能是相对简单一些,也比较熟悉一些,或者开销会小一些?但是不论如何,这种格式的共享内存使用方法都依然是存在的,所以这里再次回顾一些这种内存的使用方法。
二、IPC_PRIVATE标志的意义
我们看一下内核中对于这种格式的使用方法,但是暂时不清楚这种共享内存的典型应用场景,这里只是分析一下它的特征,可能只是为了在父子进程间共享的一种共享内存吧,跟管道类似。它在内核的创建接口为
asmlinkage long sys_shmget (key_t key, size_t size, int shmflg)
{
    struct shmid_kernel *shp;
    int err, id = 0;
    struct ipc_namespace *ns;

    ns = current->nsproxy->ipc_ns;

    mutex_lock(&shm_ids(ns).mutex);
    if (key == IPC_PRIVATE) {
        err = newseg(ns, key, shmflg, size);看一下if的条件,知道这个key的值一定是IPC_PRIVATE,而这个值是一个常量零,这个零值是保证这个共享内存只在进程内可见的关键
} else if ((id = ipc_findkey(&shm_ids(ns), key)) == -1) {能够走入这一分支,说明key的值一定不为零
#define IPC_PRIVATE ((__kernel_key_t) 0) 

对于系统可见的共享内存,这个共享的唯一标识就是这个key标识,所以在shm_get中,,它执行的操作为
int ipc_findkey(struct ipc_ids* ids, key_t key)
{
    int id;
    struct kern_ipc_perm* p;
    int max_id = ids->max_id;

    /*
     * rcu_dereference() is not needed here
     * since ipc_ids.mutex is held
     */
    for (id = 0; id <= max_id; id++) {
        p = ids->entries->p[id];
        if(p==NULL)
            continue;
        if (key == p->key)对于非零的key值,一定是不可能匹配到key值为零的IPC_PRIVATE标识共享内存的
            return id;
    }
    return -1;
}
三、如何知道系统中所有的shm
这个对于一些通用的系统,是可以通过proc/sysvipc文件夹的文件来直接显示出来,但是这种proc系统是可以配置的,所以并不是一个常在的功能。而更加通用的功能就是使用这些通讯机制内置的命令。具体来说就是通过SHM_INFO来获得系统中所有的shm的数量,然后通过IPC_STAT来对这些所有可能的共享内存进行遍历。这是一个比较笨的方法,但是总是可用。
这里要注意的是,这里的shmctl的IPC_STAT参数的数值和shmget返回的值是不同的,STAT传入的参数值是从-、0开始的、连续的系统中所有的共享内存的编号,可以认为是一个数组的下表。而shmget返回的值的则非常不连续,具体如何不连续之后再说。
可以通过
strace ipcs
来看到对于shmctl的调用情况。
四、shmget、key值以及shmctl值
key值是通过shmget的时候传递给系统的一个参数,但是系统并没有把这个key值作为shm的唯一表示,而是返回了另一个新的值 ,这个值就是所谓的shmid。
为什么需要这么做呢?我们可以想象,key值是非常随意的,可能的范围从0到最大的整数,而系统中同时存在的共享内存的数量一定是非常有限的,如果使用用户传入的key作为下标,那么此时当用户使用这个key的话,那么内核需要维护这种大范围key反向找到一个结构的方法,此时处理起来就比较麻烦。
所以正常来说,为每个key分配一个数组的下表,和文件描述符一样,可以每次返回的是一连串连续的数值,可以从0开始,然后依次递增。但是共享内存是和文件不同的资源,文件是进程内共享的一种资源,而shm则是系统级共享的。这样一个进程的错误可能会影响其它无辜的共享内存。例如删除一个shm的时候如果传入的id错误,那么可能回到导致一些正常的文件被删除。总之来说,就是这种机制不利于检测错误。
所以内核中是在这个类似数组下标的机制之上乘上了一个大的整数,从而让连续两次分配的SHMID之间的差别非常大,这样如果用户传入的ID不符合这种规则,就可以检测出是用户态程序出错了。
我们看一下这个id的生成过程
#define shm_buildid(ns, id, seq)    \
    ipc_buildid(&shm_ids(ns), id, seq)

int ipc_buildid(struct ipc_ids* ids, int id, int seq)
{
    return SEQ_MULTIPLIER*seq + id;
}
这里的seq是一个数值,它在系统启动之后会在每分配一个新的shm的时候递增,因此它的范围可以为0--2^32的取值范围;而id则是一个数组的下表,这个范围相对比较小。那么具体是多少呢?它是动态变化的,所以大家不用太纠结,但是一定不会太大。
在执行shmat的时候,它对于这个ID值的合法性检测为
int ipc_checkid(struct ipc_ids* ids, struct kern_ipc_perm* ipcp, int uid)
{
    if(uid/SEQ_MULTIPLIER != ipcp->seq)
        return 1;
    return 0;
}
这是一个反向的过程,不解释。
五、RMID
共享内存的创建也没什么难点,但是它的删除就比较麻烦了,因为一个共享内存是一个资源,它放在那里,就是等着被人来attach,如果当前没有人来attach,并不能保证它就应该被删除。就像一个文件,那个文件当前没有人打开,它就应该被删除吗?
它同样是通过shmctl来完成的,删除的命令就是IPC_RMID。我们看内核中对该命令的实现
case IPC_RMID:
    {
        /*
         *    We cannot simply remove the file. The SVID states
         *    that the block remains until the last person
         *    detaches from it, then is deleted. A shmat() on
         *    an RMID segment is legal in older Linux and if
         *    we change it apps break.
..
         *
         *    Instead we set a destroyed flag, and then blow
         *    the name away when the usage hits zero.
         */
……
        do_shm_rmid(ns, shp);

static void do_shm_rmid(struct ipc_namespace *ns, struct shmid_kernel *shp)
{
    if (shp->shm_nattch){
        shp->shm_perm.mode |= SHM_DEST;
        /* Do not find it any more */
        shp->shm_perm.key = IPC_PRIVATE;
        shm_unlock(shp);
    } else
        shm_destroy(ns, shp);
}
对于这种共享内存的关闭,它有一个自己的接口
static struct vm_operations_struct shm_vm_ops = {
    .open    = shm_open,    /* callback for a new vm-area open */
    .close    = shm_close,    /* callback for when the vm-area is released */
    .nopage    = shm_nopage,
#if defined(CONFIG_NUMA)
    .set_policy = shm_set_policy,
    .get_policy = shm_get_policy,
#endif
};
static void shm_close(struct vm_area_struct *vma)
{
    struct file * file = vma->vm_file;
    struct shm_file_data *sfd = shm_file_data(file);
    struct shmid_kernel *shp;
    struct ipc_namespace *ns = sfd->ns;

    mutex_lock(&shm_ids(ns).mutex);
    /* remove from the list of attaches of the shm segment */
    shp = shm_lock(ns, sfd->id);
    BUG_ON(!shp);
    shp->shm_lprid = current->tgid;
    shp->shm_dtim = get_seconds();
    shp->shm_nattch--;
    if(shp->shm_nattch == 0 &&
       shp->shm_perm.mode & SHM_DEST
)
        shm_destroy(ns, shp);
    else
        shm_unlock(shp);
    mutex_unlock(&shm_ids(ns).mutex);
}
六、我系统的例子
[root@Harry etc]# ipcs

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      大家可以看到,其中的大部分的shmid的差值都是32769,这个值就是buildid中的32768+1,具体怎么推算,有兴趣的同学研究一下
0x00000000 98304      root       600        393216     2          dest        
0x00000000 131073     root       600        393216     2          dest        
0x00000000 163842     root       600        393216     2          dest        
0x00000000 196611     root       600        393216     2          dest        
0x00000000 229380     root       600        393216     2          dest        
0x00000000 262149     root       600        393216     2          dest        
0x00000000 294918     root       600        393216     2          dest        
0x00000000 327687     root       600        393216     2          dest        
0x00000000 360456     root       600        393216     2          dest        
0x00000000 393225     root       600        393216     2          dest        
0x00000000 425994     root       600        393216     2          dest        
0x00000000 458763     root       600        393216     2          dest        
0x00000000 491532     root       600        393216     2          dest        
0x00000000 524301     root       600        393216     2          dest        
0x00000000 557070     root       600        393216     2          dest        
0x00000000 589839     root       600        393216     2          dest        
0x00000000 622608     root       600        393216     2          dest        
0x00000000 655377     root       600        393216     2          dest        

------ Semaphore Arrays --------
key        semid      owner      perms      nsems    

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages   

[root@Harry etc]#
  评论这张
 
阅读(1092)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017