Chinaunix首页 | 论坛 | 博客
  • 博客访问: 110871
  • 博文数量: 40
  • 博客积分: 1650
  • 博客等级: 上尉
  • 技术积分: 420
  • 用 户 组: 普通用户
  • 注册时间: 2007-07-20 13:05
文章分类
文章存档

2011年(1)

2009年(1)

2008年(1)

2007年(37)

我的朋友

分类: LINUX

2007-07-24 17:29:56

系统V共享内存原理
系统V共享内存指的是把共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的进程通过共享该内存区域来获得访问权。系统V共享内存通过shmget获得或创建一个IPC共享内存区域,并返回相应的标识符。内核在保证shmget获得或创建一个共享内存区,初始化该共享内存区相应的 shmid_kernel结构注同时,还将在特殊文件系统shm中,创建并打开一个同名文件,并在内存中建立起该文件的相应dentry及inode结 构,新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。所有这一切都是系统调用shmget完成的。

注:每一个共享内存区都有一个控制结构struct shmid_kernel,shmid_kernel是共享内存区域中非常重要的一个数据结构,它是存储管理和文件系统结合起来的桥梁,定义如下:
struct shmid_kernel /* private to the kernel */
{
struct kern_ipc_perm shm_perm;
struct file * shm_file;
int id;
unsigned long shm_nattch;
unsigned long shm_segsz;
time_t shm_atim;
time_t shm_dtim;
time_t shm_ctim;
pid_t shm_cprid;
pid_t shm_lprid;
};
该结构中最重要的一个域应该是shm_file,它存储了将被映射文件的地址。每个共享内存区对象都对应特殊文件系统shm中的一个文件,一般情况下,特殊文件系统shm中的文件是不能用read()、write()等方法访问的,当采取共享内存的方式把其中的文件映射到进程地址空间后,可直接采用访问内存的方式对其访问。
正如消息队列 和信号灯一样,内核通过数据结构struct ipc_ids shm_ids维护系统中的所有共享内存区域。shm_ids的entries变量指向一个ipc_id结构数组,而每个ipc_id结构数组中有个指向kern_ipc_perm结构的指针。而kern_ipc_perm的宿主是 shmid_kernel结构,shmid_kernel是用来描述一个共享内存区域的,这样内核就能够控制系统中所有的共享区域。同时,在 shmid_kernel结构的file类型指针shm_file指向文件系统shm中相应的文件,这样,共享内存区域就与shm文件系统中的文件对应起来。

在创建了一个共享内存区域后,还要将它映射到进程地址空间,这通过系统调用shmat()完成此功能。由于在调用shmget()时,已经创建了文件系统shm中的一个同名文件与共享内存区域相对应,因此,调用shmat()的过程相当于映射文件系统shm中的同名文件过程。


#include
#include
#include
#include
#include
#include
#include
#include

#include "util.h"

struct shmid_kernel /* private to the kernel */
{    
    struct kern_ipc_perm    shm_perm;
    struct file *        shm_file;
    int            id;
    unsigned long        shm_nattch;
    unsigned long        shm_segsz;
    time_t            shm_atim;
    time_t            shm_dtim;
    time_t            shm_ctim;
    pid_t            shm_cprid;
    pid_t            shm_lprid;
};

#define shm_flags    shm_perm.mode

static struct file_operations shm_file_operations;
static struct vm_operations_struct shm_vm_ops;

static struct ipc_ids shm_ids;

#define shm_lock(id)    ((struct shmid_kernel*)ipc_lock(&shm_ids,id))
#define shm_unlock(id)    ipc_unlock(&shm_ids,id)
#define shm_lockall()    ipc_lockall(&shm_ids)
#define shm_unlockall()    ipc_unlockall(&shm_ids)
#define shm_get(id)    ((struct shmid_kernel*)ipc_get(&shm_ids,id))
#define shm_buildid(id, seq) \
    ipc_buildid(&shm_ids, id, seq)

static int newseg (key_t key, int shmflg, size_t size);
static void shm_open (struct vm_area_struct *shmd);
static void shm_close (struct vm_area_struct *shmd);

size_t    shm_ctlmax = SHMMAX;
size_t     shm_ctlall = SHMALL;
int     shm_ctlmni = SHMMNI;

static int shm_tot; /* total number of shared memory pages */

///初始化shm_ids
void __init shm_init (void)
{
    ipc_init_ids(&shm_ids, 1);
}
//查看id号是否和理
static inline int shm_checkid(struct shmid_kernel *s, int id)
{
    if (ipc_checkid(&shm_ids,&s->shm_perm,id))
        return -EIDRM;
    return 0;
}
//ipc_checkid 在util.h中
extern inline int ipc_checkid(struct ipc_ids* ids, struct kern_ipc_perm* ipcp, int uid)
{
    if(uid/SEQ_MULTIPLIER != ipcp->seq)
        return 1;
    return 0;
}
//根据id号删除对应共享内存
static inline struct shmid_kernel *shm_rmid(int id)
{
    return (struct shmid_kernel *)ipc_rmid(&shm_ids,id);
}
//根据id号添加共享内存
static inline int shm_addid(struct shmid_kernel *shp)
{
    return ipc_addid(&shm_ids, &shp->shm_perm, shm_ctlmni+1);
}

//每次调用shm_open时对shmid_kernel对象参数调正
static inline void shm_inc (int id) {
    struct shmid_kernel *shp;

    if(!(shp = shm_lock(id)))
        BUG();
    shp->shm_atim = CURRENT_TIME;
    shp->shm_lprid = current->pid;
    shp->shm_nattch++;
    shm_unlock(id);
}

/* This is called by fork, once for every shm attach. */
static void shm_open (struct vm_area_struct *shmd)
{
    shm_inc (shmd->vm_file->f_dentry->d_inode->i_ino);
}

/*
 释放shmid_kernel对象
 *
 * It has to be called with shp and shm_ids.sem locked,
 * but returns with shp unlocked and freed.
 */
static void shm_close (struct vm_area_struct *shmd)
{
    struct file * file = shmd->vm_file;
    int id = file->f_dentry->d_inode->i_ino;
    struct shmid_kernel *shp;

    down (&shm_ids.sem);
    /* remove from the list of attaches of the shm segment */
    if(!(shp = shm_lock(id)))
        BUG();
    shp->shm_lprid = current->pid;
    shp->shm_dtim = CURRENT_TIME;
    shp->shm_nattch--;
    if(shp->shm_nattch == 0 &&
       shp->shm_flags & SHM_DEST)
        shm_destroy (shp);
    else
        shm_unlock(id);
    up (&shm_ids.sem);
}

static int shm_mmap(struct file * file, struct vm_area_struct * vma)
{
    UPDATE_ATIME(file->f_dentry->d_inode);
    vma->vm_ops = &shm_vm_ops;
    shm_inc(file->f_dentry->d_inode->i_ino);
    return 0;
}

static struct file_operations shm_file_operations = {
    mmap:    shm_mmap
};

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:    shmem_nopage,
};

static int newseg (key_t key, int shmflg, size_t size)
{
    int error;
    struct shmid_kernel *shp;
    int numpages = (size + PAGE_SIZE -1) >> PAGE_SHIFT;
    struct file * file;
    char name[13];
    int id;

    if (size < SHMMIN || size > shm_ctlmax)
        return -EINVAL;

    if (shm_tot + numpages >= shm_ctlall)
        return -ENOSPC;

    shp = (struct shmid_kernel *) kmalloc (sizeof (*shp), GFP_USER);
    if (!shp)
        return -ENOMEM;
    sprintf (name, "SYSV%08x", key);
    file = shmem_file_setup(name, size);
    error = PTR_ERR(file);
    if (IS_ERR(file))
        goto no_file;

    error = -ENOSPC;
    id = shm_addid(shp);
    if(id == -1)
        goto no_id;
    shp->shm_perm.key = key;
    shp->shm_flags = (shmflg & S_IRWXUGO);
    shp->shm_cprid = current->pid;
    shp->shm_lprid = 0;
    shp->shm_atim = shp->shm_dtim = 0;
    shp->shm_ctim = CURRENT_TIME;
    shp->shm_segsz = size;
    shp->shm_nattch = 0;
    shp->id = shm_buildid(id,shp->shm_perm.seq);
    shp->shm_file = file;
    file->f_dentry->d_inode->i_ino = shp->id;
    file->f_op = &shm_file_operations;
    shm_tot += numpages;
    shm_unlock (id);
    return shp->id;

no_id:
    fput(file);
no_file:
    kfree(shp);
    return error;
}

asmlinkage long sys_shmget (key_t key, size_t size, int shmflg)
{
    struct shmid_kernel *shp;
    int err, id = 0;

    down(&shm_ids.sem);
    if (key == IPC_PRIVATE) {
        err = newseg(key, shmflg, size);
    } else if ((id = ipc_findkey(&shm_ids, key)) == -1) {
        if (!(shmflg & IPC_CREAT))
            err = -ENOENT;
        else
            err = newseg(key, shmflg, size);
    } else if ((shmflg & IPC_CREAT) && (shmflg & IPC_EXCL)) {
        err = -EEXIST;
    } else {
        shp = shm_lock(id);
        if(shp==NULL)
            BUG();
        if (shp->shm_segsz < size)
            err = -EINVAL;
        else if (ipcperms(&shp->shm_perm, shmflg))
            err = -EACCES;
        else
            err = shm_buildid(id, shp->shm_perm.seq);
        shm_unlock(id);
    }
    up(&shm_ids.sem);
    return err;
}

static inline unsigned long copy_shmid_to_user(void *buf, struct shmid64_ds *in, int version)
{
    switch(version) {
    case IPC_64:
        return copy_to_user(buf, in, sizeof(*in));
    case IPC_OLD:
        {
        struct shmid_ds out;

        ipc64_perm_to_ipc_perm(&in->shm_perm, &out.shm_perm);
        out.shm_segsz    = in->shm_segsz;
        out.shm_atime    = in->shm_atime;
        out.shm_dtime    = in->shm_dtime;
        out.shm_ctime    = in->shm_ctime;
        out.shm_cpid    = in->shm_cpid;
        out.shm_lpid    = in->shm_lpid;
        out.shm_nattch    = in->shm_nattch;

        return copy_to_user(buf, &out, sizeof(out));
        }
    default:
        return -EINVAL;
    }
}

struct shm_setbuf {
    uid_t    uid;
    gid_t    gid;
    mode_t    mode;
};    

static inline unsigned long copy_shmid_from_user(struct shm_setbuf *out, void *buf, int version)
{
    switch(version) {
    case IPC_64:
        {
        struct shmid64_ds tbuf;

        if (copy_from_user(&tbuf, buf, sizeof(tbuf)))
            return -EFAULT;

        out->uid    = tbuf.shm_perm.uid;
        out->gid    = tbuf.shm_perm.gid;
        out->mode    = tbuf.shm_flags;

        return 0;
        }
    case IPC_OLD:
        {
        struct shmid_ds tbuf_old;

        if (copy_from_user(&tbuf_old, buf, sizeof(tbuf_old)))
            return -EFAULT;

        out->uid    = tbuf_old.shm_perm.uid;
        out->gid    = tbuf_old.shm_perm.gid;
        out->mode    = tbuf_old.shm_flags;

        return 0;
        }
    default:
        return -EINVAL;
    }
}

static inline unsigned long copy_shminfo_to_user(void *buf, struct shminfo64 *in, int version)
{
    switch(version) {
    case IPC_64:
        return copy_to_user(buf, in, sizeof(*in));
    case IPC_OLD:
        {
        struct shminfo out;

        if(in->shmmax > INT_MAX)
            out.shmmax = INT_MAX;
        else
            out.shmmax = (int)in->shmmax;

        out.shmmin    = in->shmmin;
        out.shmmni    = in->shmmni;
        out.shmseg    = in->shmseg;
        out.shmall    = in->shmall;

        return copy_to_user(buf, &out, sizeof(out));
        }
    default:
        return -EINVAL;
    }
}

static void shm_get_stat (unsigned long *rss, unsigned long *swp)
{
    struct shmem_inode_info *info;
    int i;

    *rss = 0;
    *swp = 0;

    for(i = 0; i <= shm_ids.max_id; i++) {
        struct shmid_kernel* shp;
        struct inode * inode;

        shp = shm_get(i);
        if(shp == NULL)
            continue;
        inode = shp->shm_file->f_dentry->d_inode;
        info = SHMEM_I(inode);
        spin_lock (&info->lock);
        *rss += inode->i_mapping->nrpages;
        *swp += info->swapped;
        spin_unlock (&info->lock);
    }
}

asmlinkage long sys_shmctl (int shmid, int cmd, struct shmid_ds *buf)
{
    struct shm_setbuf setbuf;
    struct shmid_kernel *shp;
    int err, version;

    if (cmd < 0 || shmid < 0)
        return -EINVAL;

    version = ipc_parse_version(&cmd);

    switch (cmd) { /* replace with proc interface ? */
    case IPC_INFO:
    {
        struct shminfo64 shminfo;

        memset(&shminfo,0,sizeof(shminfo));
        shminfo.shmmni = shminfo.shmseg = shm_ctlmni;
        shminfo.shmmax = shm_ctlmax;
        shminfo.shmall = shm_ctlall;

        shminfo.shmmin = SHMMIN;
        if(copy_shminfo_to_user (buf, &shminfo, version))
            return -EFAULT;
        /* reading a integer is always atomic */
        err= shm_ids.max_id;
        if(err<0)
            err = 0;
        return err;
    }
    case SHM_INFO:
    {
        struct shm_info shm_info;

        memset(&shm_info,0,sizeof(shm_info));
        down(&shm_ids.sem);
        shm_lockall();
        shm_info.used_ids = shm_ids.in_use;
        shm_get_stat (&shm_info.shm_rss, &shm_info.shm_swp);
        shm_info.shm_tot = shm_tot;
        shm_info.swap_attempts = 0;
        shm_info.swap_successes = 0;
        err = shm_ids.max_id;
        shm_unlockall();
        up(&shm_ids.sem);
        if(copy_to_user (buf, &shm_info, sizeof(shm_info)))
            return -EFAULT;

        return err < 0 ? 0 : err;
    }
    case SHM_STAT:
    case IPC_STAT:
    {
        struct shmid64_ds tbuf;
        int result;
        memset(&tbuf, 0, sizeof(tbuf));
        shp = shm_lock(shmid);
        if(shp==NULL)
            return -EINVAL;
        if(cmd==SHM_STAT) {
            err = -EINVAL;
            if (shmid > shm_ids.max_id)
                goto out_unlock;
            result = shm_buildid(shmid, shp->shm_perm.seq);
        } else {
            err = shm_checkid(shp,shmid);
            if(err)
                goto out_unlock;
            result = 0;
        }
        err=-EACCES;
        if (ipcperms (&shp->shm_perm, S_IRUGO))
            goto out_unlock;
        kernel_to_ipc64_perm(&shp->shm_perm, &tbuf.shm_perm);
        tbuf.shm_segsz    = shp->shm_segsz;
        tbuf.shm_atime    = shp->shm_atim;
        tbuf.shm_dtime    = shp->shm_dtim;
        tbuf.shm_ctime    = shp->shm_ctim;
        tbuf.shm_cpid    = shp->shm_cprid;
        tbuf.shm_lpid    = shp->shm_lprid;
        tbuf.shm_nattch    = shp->shm_nattch;
        shm_unlock(shmid);
        if(copy_shmid_to_user (buf, &tbuf, version))
            return -EFAULT;
        return result;
    }
    case SHM_LOCK:
    case SHM_UNLOCK:
    {
/* Allow superuser to lock segment in memory */
/* Should the pages be faulted in here or leave it to user? */
/* need to determine interaction with current->swappable */
        if (!capable(CAP_IPC_LOCK))
            return -EPERM;

        shp = shm_lock(shmid);
        if(shp==NULL)
            return -EINVAL;
        err = shm_checkid(shp,shmid);
        if(err)
            goto out_unlock;
        if(cmd==SHM_LOCK) {
            shmem_lock(shp->shm_file, 1);
            shp->shm_flags |= SHM_LOCKED;
        } else {
            shmem_lock(shp->shm_file, 0);
            shp->shm_flags &= ~SHM_LOCKED;
        }
        shm_unlock(shmid);
        return err;
    }
    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.
         */
        down(&shm_ids.sem);
        shp = shm_lock(shmid);
        err = -EINVAL;
        if (shp == NULL)
            goto out_up;
        err = shm_checkid(shp, shmid);
        if(err)
            goto out_unlock_up;
        if (current->euid != shp->shm_perm.uid &&
            current->euid != shp->shm_perm.cuid &&
            !capable(CAP_SYS_ADMIN)) {
            err=-EPERM;
            goto out_unlock_up;
        }
        if (shp->shm_nattch){
            shp->shm_flags |= SHM_DEST;
            /* Do not find it any more */
            shp->shm_perm.key = IPC_PRIVATE;
            shm_unlock(shmid);
        } else
            shm_destroy (shp);
        up(&shm_ids.sem);
        return err;
    }

    case IPC_SET:
    {
        if(copy_shmid_from_user (&setbuf, buf, version))
            return -EFAULT;
        down(&shm_ids.sem);
        shp = shm_lock(shmid);
        err=-EINVAL;
        if(shp==NULL)
            goto out_up;
        err = shm_checkid(shp,shmid);
        if(err)
            goto out_unlock_up;
        err=-EPERM;
        if (current->euid != shp->shm_perm.uid &&
            current->euid != shp->shm_perm.cuid &&
            !capable(CAP_SYS_ADMIN)) {
            goto out_unlock_up;
        }

        shp->shm_perm.uid = setbuf.uid;
        shp->shm_perm.gid = setbuf.gid;
        shp->shm_flags = (shp->shm_flags & ~S_IRWXUGO)
            | (setbuf.mode & S_IRWXUGO);
        shp->shm_ctim = CURRENT_TIME;
        break;
    }

    default:
        return -EINVAL;
    }

    err = 0;
out_unlock_up:
    shm_unlock(shmid);
out_up:
    up(&shm_ids.sem);
    return err;
out_unlock:
    shm_unlock(shmid);
    return err;
}

/*
 * Fix shmaddr, allocate descriptor, map shm, add attach descriptor to lists.
 */
asmlinkage long sys_shmat (int shmid, char *shmaddr, int shmflg, ulong *raddr)
{
    struct shmid_kernel *shp;
    unsigned long addr;
    unsigned long size;
    struct file * file;
    int    err;
    unsigned long flags;
    unsigned long prot;
    unsigned long o_flags;
    int acc_mode;
    void *user_addr;

    if (shmid < 0)
        return -EINVAL;

    if ((addr = (ulong)shmaddr)) {
        if (addr & (SHMLBA-1)) {
            if (shmflg & SHM_RND)
                addr &= ~(SHMLBA-1);       /* round down */
            else
                return -EINVAL;
        }
        flags = MAP_SHARED | MAP_FIXED;
    } else {
        if ((shmflg & SHM_REMAP))
            return -EINVAL;

        flags = MAP_SHARED;
    }

    if (shmflg & SHM_RDONLY) {
        prot = PROT_READ;
        o_flags = O_RDONLY;
        acc_mode = S_IRUGO;
    } else {
        prot = PROT_READ | PROT_WRITE;
        o_flags = O_RDWR;
        acc_mode = S_IRUGO | S_IWUGO;
    }

    /*
     * We cannot rely on the fs check since SYSV IPC does have an
     * additional creator id...
     */
    shp = shm_lock(shmid);
    if(shp == NULL)
        return -EINVAL;
    err = shm_checkid(shp,shmid);
    if (err) {
        shm_unlock(shmid);
        return err;
    }
    if (ipcperms(&shp->shm_perm, acc_mode)) {
        shm_unlock(shmid);
        return -EACCES;
    }
    file = shp->shm_file;
    size = file->f_dentry->d_inode->i_size;
    shp->shm_nattch++;
    shm_unlock(shmid);

    down_write(¤t->mm->mmap_sem);
    if (addr && !(shmflg & SHM_REMAP)) {
        user_addr = ERR_PTR(-EINVAL);
        if (find_vma_intersection(current->mm, addr, addr + size))
            goto invalid;
        /*
         * If shm segment goes below stack, make sure there is some
         * space left for the stack to grow (at least 4 pages).
         */
        if (addr < current->mm->start_stack &&
            addr > current->mm->start_stack - size - PAGE_SIZE * 5)
            goto invalid;
    }
        
    user_addr = (void*) do_mmap (file, addr, size, prot, flags, 0);

invalid:
    up_write(¤t->mm->mmap_sem);

    down (&shm_ids.sem);
    if(!(shp = shm_lock(shmid)))
        BUG();
    shp->shm_nattch--;
    if(shp->shm_nattch == 0 &&
       shp->shm_flags & SHM_DEST)
        shm_destroy (shp);
    else
        shm_unlock(shmid);
    up (&shm_ids.sem);

    *raddr = (unsigned long) user_addr;
    err = 0;
    if (IS_ERR(user_addr))
        err = PTR_ERR(user_addr);
    return err;

}

/*
 * detach and kill segment if marked destroyed.
 * The work is done in shm_close.
 */
asmlinkage long sys_shmdt (char *shmaddr)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *shmd, *shmdnext;
    int retval = -EINVAL;

    down_write(&mm->mmap_sem);
    for (shmd = mm->mmap; shmd; shmd = shmdnext) {
        shmdnext = shmd->vm_next;
        if (shmd->vm_ops == &shm_vm_ops
            && shmd->vm_start - (shmd->vm_pgoff << PAGE_SHIFT) == (ulong) shmaddr) {
            do_munmap(mm, shmd->vm_start, shmd->vm_end - shmd->vm_start);
            retval = 0;
        }
    }
    up_write(&mm->mmap_sem);
    return retval;
}

#ifdef CONFIG_PROC_FS
static int sysvipc_shm_read_proc(char *buffer, char **start, off_t offset, int length, int *eof, void *data)
{
    off_t pos = 0;
    off_t begin = 0;
    int i, len = 0;

    down(&shm_ids.sem);
    len += sprintf(buffer, "       key      shmid perms       size  cpid  lpid nattch   uid   gid  cuid  cgid      atime      dtime      ctime\n");

    for(i = 0; i <= shm_ids.max_id; i++) {
        struct shmid_kernel* shp;

        shp = shm_lock(i);
        if(shp!=NULL) {
#define SMALL_STRING "%10d %10d  %4o %10u %5u %5u  %5d %5u %5u %5u %5u %10lu %10lu %10lu\n"
#define BIG_STRING   "%10d %10d  %4o %21u %5u %5u  %5d %5u %5u %5u %5u %10lu %10lu %10lu\n"
            char *format;

            if (sizeof(size_t) <= sizeof(int))
                format = SMALL_STRING;
            else
                format = BIG_STRING;
            len += sprintf(buffer + len, format,
                shp->shm_perm.key,
                shm_buildid(i, shp->shm_perm.seq),
                shp->shm_flags,
                shp->shm_segsz,
                shp->shm_cprid,
                shp->shm_lprid,
                shp->shm_nattch,
                shp->shm_perm.uid,
                shp->shm_perm.gid,
                shp->shm_perm.cuid,
                shp->shm_perm.cgid,
                shp->shm_atim,
                shp->shm_dtim,
                shp->shm_ctim);
            shm_unlock(i);

            pos += len;
            if(pos < offset) {
                len = 0;
                begin = pos;
            }
            if(pos > offset + length)
                goto done;
        }
    }
    *eof = 1;
done:
    up(&shm_ids.sem);
    *start = buffer + (offset - begin);
    len -= (offset - begin);
    if(len > length)
        len = length;
    if(len < 0)
        len = 0;
    return len;
}
#endif

 
阅读(1120) | 评论(0) | 转发(0) |
0

上一篇:linux IPC util.c分析

下一篇:虚函数剖析

给主人留下些什么吧!~~