共有メモリ
共有メモリは、複数のプロセスでメモリ空間を共有する物で、想像するに、同じページフレームを、各プロセスの仮想メモリに割り当てればいいだけの事では。と思っていましたが、その実装はvfsと密接に結びついており(vfsそのものを利用)、たぶんvfs下のセキュリティ/ページ管理等のメカニズムを利用することにあるのでは勝手に解釈することにしました。
実装のイメージですが、例えばramfs上に、あるサイズのファイルを作成し、そのファイル名でもって、プロセス間でメモリマップ(mmap)するという処理を、カーネル内部で行っていると言えばいいでしょうか。
イメージを掴む為には、共有メモリを獲得するshmgetシステムコールより、shmatシステムコールの方がいいかと思います。なお、shmgetではIPC-IDを取得するわけですが、内部的には、tmpfsに仮想のファイルを作成し、それをプロセスのネームスペース下で管理するipc用ネームスペース下に、管理する事にあります。(各構造体の繋がりはよくわかりません。)
shmatシステムコールはdo_shmat()が主たる処理となります。ulong *raddrが共有メモリを割り当てた仮想空間アドレスになります。ユーザプロセスはこのアドレスで共有メモリを参照する事ができます。
最初に引数にかかる、以降の処理でのフラグの設定を行っています。通常har __user *shmaddrはNULLです。この場合空いているアドレス空間を割り当てます。NULLでない場合、そのアドレスをページ単位に丸める処理を行っています。そして読み込み/書き込み等により適切なフラグを設定しています。本処理での本質的な処理は、flags = MAP_SHAREDとなっていることです。
ns = current->nsproxy->ipc_nsでプロセスのネームスペース下のipcネームスペースを取得しています。プロセスを作成する時、CLONE_NEWNS属性を設定しない限り、このネームスペースは親のネームスペースを引き継ぎます。(従ってinitのネームスペースを引き継ぐことになり、全プロセスは同じネームスペースを有する事になります。)
その配下で管理しているipcの共有メモリを、shm_lock_check()でユーザ引数のshmidをキーとして取得します。この関数で取得した下記にしめすshmid_kernelの構造体が、共有メモリ本体とも言えます。
このstruct fileからdenry/mnt情報を取得し、それでもってshmatシステムコールを呼び出したプロセスのstruct fileを取得し、そこに必要とされる情報を設定します。
そして、このfileをメモリマップすることで共有メモリとして使用できるわけです。その処理が以降です。まず、マップアドレスが指定してあると、その空間が未使用かどうかをfind_vma_intersection()でチェックします。次のチェックはスタック領域とに余裕を持たせる意味だと思います。
最後にこれらの引数で、do_mmap()でメモリマップし、マップしたアドレスをulong *raddrに設定しています。
実装のイメージですが、例えばramfs上に、あるサイズのファイルを作成し、そのファイル名でもって、プロセス間でメモリマップ(mmap)するという処理を、カーネル内部で行っていると言えばいいでしょうか。
イメージを掴む為には、共有メモリを獲得するshmgetシステムコールより、shmatシステムコールの方がいいかと思います。なお、shmgetではIPC-IDを取得するわけですが、内部的には、tmpfsに仮想のファイルを作成し、それをプロセスのネームスペース下で管理するipc用ネームスペース下に、管理する事にあります。(各構造体の繋がりはよくわかりません。)
shmatシステムコールはdo_shmat()が主たる処理となります。ulong *raddrが共有メモリを割り当てた仮想空間アドレスになります。ユーザプロセスはこのアドレスで共有メモリを参照する事ができます。
最初に引数にかかる、以降の処理でのフラグの設定を行っています。通常har __user *shmaddrはNULLです。この場合空いているアドレス空間を割り当てます。NULLでない場合、そのアドレスをページ単位に丸める処理を行っています。そして読み込み/書き込み等により適切なフラグを設定しています。本処理での本質的な処理は、flags = MAP_SHAREDとなっていることです。
ns = current->nsproxy->ipc_nsでプロセスのネームスペース下のipcネームスペースを取得しています。プロセスを作成する時、CLONE_NEWNS属性を設定しない限り、このネームスペースは親のネームスペースを引き継ぎます。(従ってinitのネームスペースを引き継ぐことになり、全プロセスは同じネームスペースを有する事になります。)
その配下で管理しているipcの共有メモリを、shm_lock_check()でユーザ引数のshmidをキーとして取得します。この関数で取得した下記にしめすshmid_kernelの構造体が、共有メモリ本体とも言えます。
struct shm_file_data { int id; struct ipc_namespace *ns; struct file *file; const struct vm_operations_struct *vm_ops; };この構造体で注目すべきメンバーはstruct file *fileです。このメンバには、shmgetシステムコールを呼び出した時に作成されたVFSのファイル、そして呼び出したプロセスにより取得されたファイルのstruct fileが設定されています。
このstruct fileからdenry/mnt情報を取得し、それでもってshmatシステムコールを呼び出したプロセスのstruct fileを取得し、そこに必要とされる情報を設定します。
そして、このfileをメモリマップすることで共有メモリとして使用できるわけです。その処理が以降です。まず、マップアドレスが指定してあると、その空間が未使用かどうかをfind_vma_intersection()でチェックします。次のチェックはスタック領域とに余裕を持たせる意味だと思います。
最後にこれらの引数で、do_mmap()でメモリマップし、マップしたアドレスをulong *raddrに設定しています。
long do_shmat(int shmid, char __user *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; int acc_mode; unsigned long user_addr; struct ipc_namespace *ns; struct shm_file_data *sfd; struct path path; mode_t f_mode; err = -EINVAL; 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; } if (shmflg & SHM_EXEC) { prot |= PROT_EXEC; acc_mode |= S_IXUGO; } ns = current->nsproxy->ipc_ns; shp = shm_lock_check(ns, shmid); if (IS_ERR(shp)) { err = PTR_ERR(shp); goto out; } err = -EACCES; if (ipcperms(&shp->shm_perm, acc_mode)) goto out_unlock; err = security_shm_shmat(shp, shmaddr, shmflg); if (err) goto out_unlock; path.dentry = dget(shp->shm_file->f_path.dentry); path.mnt = shp->shm_file->f_path.mnt; shp->shm_nattch++; size = i_size_read(path.dentry->d_inode); shm_unlock(shp); err = -ENOMEM; sfd = kzalloc(sizeof(*sfd), GFP_KERNEL); if (!sfd) goto out_put_dentry; file = alloc_file(path.mnt, path.dentry, f_mode, &shm_file_operations); if (!file) goto out_free; file->private_data = sfd; file->f_mapping = shp->shm_file->f_mapping; sfd->id = shp->shm_perm.id; sfd->ns = get_ipc_ns(ns); sfd->file = shp->shm_file; sfd->vm_ops = NULL; down_write(¤t->mm->mmap_sem); if (addr && !(shmflg & SHM_REMAP)) { err = -EINVAL; if (find_vma_intersection(current->mm, addr, addr + size)) goto invalid; if (addr < current->mm->start_stack && addr > current->mm->start_stack - size - PAGE_SIZE * 5) goto invalid; } user_addr = do_mmap (file, addr, size, prot, flags, 0); *raddr = user_addr; : }なお、alloc_file()でfileを取得する時のfile_operationsは、以下のように、.mmapしかインプリメントされていません。
static const struct file_operations shm_file_operations = { .mmap = shm_mmap, .fsync = shm_fsync, .release = shm_release, .get_unmapped_area = shm_get_unmapped_area, };