共有メモリ(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;
:
}






