共有メモリ(shmget)
共有メモリの実態はファイルでした。すなわち共有メモリを新規に取得する場合、そのファイルを作成する事になります。カーネルにとってファイルを作成するというのは、inodeを作成する事に他なりません。
shmgetシステムコールは第一引数のkeyがIPC_PRIVATEの時に、新規に共有メモリが割り当てられます。この割り当ての処理を行うのが、struct ipc_ops shm_opsでセットするnewseg()です。ipcget()でkey=IPC_PRIVATEなら、newseg()がコールされます。
#define SHMALL (SHMMAX/PAGE_SIZE*(SHMMNI/16))について言えば、
0x2000000/4096*4096/16=0x222222ページ(ざっと8G以上となります。なお共有メモリはスワップ対象です。)
次にstruct shmid_kernel *shpをipc_addi()で、プロセスのネームスペース下のipcネームスペース下の共有メモリを管理している、struct ipc_idsに登録します。ここでの登録はradix-treeのような階層構造をなしていて、ちょっとごちゃごちゃしています。
ipcスロットに登録できたら、struct shmid_kernel *shpに他のもろもろの設定を施してお終いです。
shmem_file_setu()では、共有メモリのファイルを作成することにありますが、共有メモリだから特別の処理はないようです。なお作成されるvfsmntはshm_mntで、これはinit_tmpfs(void)で、
shm_mnt = vfs_kern_mount(&tmpfs_fs_type, MS_NOUSER, tmpfs_fs_type.name, NULL);
としてtmpfsにマウントしています。
shmgetシステムコールは第一引数のkeyがIPC_PRIVATEの時に、新規に共有メモリが割り当てられます。この割り当ての処理を行うのが、struct ipc_ops shm_opsでセットするnewseg()です。ipcget()でkey=IPC_PRIVATEなら、newseg()がコールされます。
SYSCALL_DEFINE3(shmget, key_t, key, size_t, size, int, shmflg) { struct ipc_namespace *ns; struct ipc_ops shm_ops; struct ipc_params shm_params; ns = current->nsproxy->ipc_ns; shm_ops.getnew = newseg; shm_ops.associate = shm_security; shm_ops.more_checks = shm_more_checks; shm_params.key = key; shm_params.flg = shmflg; shm_params.u.size = size; return ipcget(ns, &shm_ids(ns), &shm_ops, &shm_params); }int numpages = (size + PAGE_SIZE -1) >> PAGE_SHIFTで、割り当てサイズのページ数を求め、サイズ/ページ数のチェックを行います。その時の条件は以下のようになっています。すなわち1つの割り当て可能の共有メモリのサイズは0より大きく、0x2000000小さくて、全ての共有メモリとして割り当てたページ数はSHMALLを超えてはならないと言うことです。
#define SHMALL (SHMMAX/PAGE_SIZE*(SHMMNI/16))について言えば、
0x2000000/4096*4096/16=0x222222ページ(ざっと8G以上となります。なお共有メモリはスワップ対象です。)
#define SHMMAX 0x2000000 /* max shared seg size (bytes) */ #define SHMMIN 1 /* min shared seg size (bytes) */ #define SHMMNI 4096 /* max num of segs system wide */ #ifdef __KERNEL__ #define SHMALL (SHMMAX/PAGE_SIZE*(SHMMNI/16)) /* max shm system wide (pages) */ #else #define SHMALL (SHMMAX/getpagesize()*(SHMMNI/16)) #endif void shm_init_ns(struct ipc_namespace *ns) { ns->shm_ctlmax = SHMMAX; ns->shm_ctlall = SHMALL; ns->shm_ctlmni = SHMMNI; ns->shm_tot = 0; ipc_init_ids(&ns->ids[IPC_SHM_IDS]); }サイズに掛かるチェックがOKなら、共有メモリの実態と言うべきstruct shmid_kernel *shpに各種パラメタを設定し、hugetlb_file_setup()/shmem_file_setup()をコールします。この関数で共有メモリのファイルを作成し、そのファイル構造体をshp->shm_fileにセットします。
次にstruct shmid_kernel *shpをipc_addi()で、プロセスのネームスペース下のipcネームスペース下の共有メモリを管理している、struct ipc_idsに登録します。ここでの登録はradix-treeのような階層構造をなしていて、ちょっとごちゃごちゃしています。
ipcスロットに登録できたら、struct shmid_kernel *shpに他のもろもろの設定を施してお終いです。
static int newseg(struct ipc_namespace *ns, struct ipc_params *params) { key_t key = params->key; int shmflg = params->flg; size_t size = params->u.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 > ns->shm_ctlmax) return -EINVAL; if (ns->shm_tot + numpages > ns->shm_ctlall) return -ENOSPC; shp = ipc_rcu_alloc(sizeof(*shp)); if (!shp) return -ENOMEM; shp->shm_perm.key = key; shp->shm_perm.mode = (shmflg & S_IRWXUGO); shp->mlock_user = NULL; shp->shm_perm.security = NULL; error = security_shm_alloc(shp); if (error) { ipc_rcu_putref(shp); return error; } sprintf (name, "SYSV%08x", key); if (shmflg & SHM_HUGETLB) { file = hugetlb_file_setup(name, size); shp->mlock_user = current->user; } else { int acctflag = VM_ACCOUNT; if ((shmflg & SHM_NORESERVE) && sysctl_overcommit_memory != OVERCOMMIT_NEVER) acctflag = 0; file = shmem_file_setup(name, size, acctflag); } error = PTR_ERR(file); if (IS_ERR(file)) goto no_file; id = ipc_addid(&shm_ids(ns), &shp->shm_perm, ns->shm_ctlmni); if (id < 0) { error = id; goto no_id; } shp->shm_cprid = task_tgid_vnr(current); shp->shm_lprid = 0; shp->shm_atim = shp->shm_dtim = 0; shp->shm_ctim = get_seconds(); shp->shm_segsz = size; shp->shm_nattch = 0; shp->shm_file = file; file->f_dentry->d_inode->i_ino = shp->shm_perm.id; ns->shm_tot += numpages; error = shp->shm_perm.id; shm_unlock(shp); return error; no_id: fput(file); no_file: security_shm_free(shp); ipc_rcu_putref(shp); return error; }
補足
return値がipcスロットに登録できた場合と、そうでない場合の値の取得が異なっています。ipc_addid()がエラーの場合、その返り値idをerror = idとして、return値としていますが、登録できた場合、error = shp->shm_perm.idをreturn値としています。ipc_addid()で返してくる値はipcのスロット位置なのですが、ipc_addid()内ではこのスロット位置にオフセットを加えたもの(SEQ_MULTIPLIER * seq + id)を、shp->shm_perm.idに設定しています。seqはipcスロットを割り当て毎に増加していきます。アプリケーションこの値を共有メモリ識別子として使うわけですが、近寄った値故、アプリケーションで間違って使わないようにとの配慮だそうです。shmem_file_setu()では、共有メモリのファイルを作成することにありますが、共有メモリだから特別の処理はないようです。なお作成されるvfsmntはshm_mntで、これはinit_tmpfs(void)で、
shm_mnt = vfs_kern_mount(&tmpfs_fs_type, MS_NOUSER, tmpfs_fs_type.name, NULL);
としてtmpfsにマウントしています。
struct file *shmem_file_setup(char *name, loff_t size, unsigned long flags) { int error; struct file *file; struct inode *inode; struct dentry *dentry, *root; struct qstr this; : this.name = name; this.len = strlen(name); this.hash = 0; /* will go */ root = shm_mnt->mnt_root; dentry = d_alloc(root, &this); if (!dentry) goto put_memory; error = -ENFILE; file = get_empty_filp(); if (!file) goto put_dentry; error = -ENOSPC; inode = shmem_get_inode(root->d_sb, S_IFREG | S_IRWXUGO, 0); if (!inode) goto close_file; SHMEM_I(inode)->flags = flags & VM_ACCOUNT; d_instantiate(dentry, inode); inode->i_size = size; inode->i_nlink = 0; /* It is unlinked */ init_file(file, shm_mnt, dentry, FMODE_WRITE | FMODE_READ, &shmem_file_operations); return file; : }