Linux SystemV IPC: shmgetの実装を読む

今日はipc/shm.cにあるshmget()の実装部分であるnewseg()を読んでみる。昨日の「 Linuxカーネル:SystemV IPC get系操作の共通実装部分を読む - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモ」でも書いた通りSystemV IPCのxxxget()はipcget()という共通関数を使い、固有の部分はstruct ipc_opsのメンバ変数の関数ポインタに関数を登録する形で行っています。

まずは最初はパラメーターのチェックなので特にこれと言った処理は無いですね。

482 static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
483 {
484         key_t key = params->key;
485         int shmflg = params->flg;
486         size_t size = params->u.size;
487         int error;
488         struct shmid_kernel *shp;
489         size_t numpages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
490         struct file *file;
491         char name[13];
492         int id;
493         vm_flags_t acctflag = 0;
494 
495         if (size < SHMMIN || size > ns->shm_ctlmax)
496                 return -EINVAL;
497 
498         if (numpages << PAGE_SHIFT < size)
499                 return -ENOSPC;
500 
501         if (ns->shm_tot + numpages < ns->shm_tot ||
502                         ns->shm_tot + numpages > ns->shm_ctlall)
503                 return -ENOSPC;
504 

次にstruct shmid_kernel型の変数のメモリ確保。ぱっと見た感じだとstruct shmid_kernelのサイズ分を確保しているように見えますが、実際にはsizeof(struct ipc_rcu)+sizeof(*shp)のサイズで確保されます。

505         shp = ipc_rcu_alloc(sizeof(*shp));
506         if (!shp)
507                 return -ENOMEM;
508 

これはどういう事かというと、確保したのは下図のような形になり、呼び出し元がrcu関連のことを気にしないようになってます。

|--------------| <--- allocした領域の先頭アドレス
|struct        |
| ipc_rcu      |
|              |
|--------------| <--- ipc_rcu_alloc()が返すのはここのアドレス
|struct        |
| shmid_kernel |
|              |
|              |
|              |
|              |
|--------------|

shmid_kernelはこのような構造体です。これがSystemV IPCの共有メモリを管理する構造体です。

  9 struct shmid_kernel /* private to the kernel */
 10 {       
 11         struct kern_ipc_perm    shm_perm;
 12         struct file             *shm_file;
 13         unsigned long           shm_nattch;
 14         unsigned long           shm_segsz;
 15         time_t                  shm_atim;
 16         time_t                  shm_dtim;
 17         time_t                  shm_ctim;
 18         pid_t                   shm_cprid;
 19         pid_t                   shm_lprid;
 20         struct user_struct      *mlock_user;
 21 
 22         /* The task created the shm object.  NULL if the task is dead. */
 23         struct task_struct      *shm_creator;
 24         struct list_head        shm_clist;      /* list by creator */
 25 };

shpに値を設定。

509         shp->shm_perm.key = key;
510         shp->shm_perm.mode = (shmflg & S_IRWXUGO);
511         shp->mlock_user = NULL;
512 
513         shp->shm_perm.security = NULL;

security_shm_alloc()はselinux等のセキュリティ機能を使って無ければ関係ないので飛ばします。

514         error = security_shm_alloc(shp);
515         if (error) {
516                 ipc_rcu_putref(shp, ipc_rcu_free);
517                 return error;
518         }

名前の設定。

520         sprintf(name, "SYSV%08x", key);

この名前はlsofで見た時に↓のように見えます。IPC_PRIVATEを指定しているので数値は全部0ですね。

41997 a.out     29678                   root  DEL       REG                0,4             27394068 /SYSV00000000

次はフラグにSHM_HUGETLBが指定されたかどうかでの場合分けで、最終的にはhugetlb_file_setup()を呼ぶかshmem_file_setup()を呼ぶかの違いです。shmem_file_setup()を後で読んでみます。

521         if (shmflg & SHM_HUGETLB) {
522                 struct hstate *hs;
523                 size_t hugesize;
524 
525                 hs = hstate_sizelog((shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK);
526                 if (!hs) {
527                         error = -EINVAL;
528                         goto no_file;
529                 }
530                 hugesize = ALIGN(size, huge_page_size(hs));
531 
532                 /* hugetlb_file_setup applies strict accounting */
533                 if (shmflg & SHM_NORESERVE)
534                         acctflag = VM_NORESERVE;
535                 file = hugetlb_file_setup(name, hugesize, acctflag,
536                                   &shp->mlock_user, HUGETLB_SHMFS_INODE,
537                                 (shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK);
538         } else {
539                 /*
540                  * Do not allow no accounting for OVERCOMMIT_NEVER, even
541                  * if it's asked for.
542                  */
543                 if  ((shmflg & SHM_NORESERVE) &&
544                                 sysctl_overcommit_memory != OVERCOMMIT_NEVER)
545                         acctflag = VM_NORESERVE;
546                 file = shmem_file_setup(name, size, acctflag);
547         }

ここからはまたshpのメンバ変数に対する値の設定です。

558         shp->shm_cprid = task_tgid_vnr(current);
559         shp->shm_lprid = 0;
560         shp->shm_atim = shp->shm_dtim = 0;
561         shp->shm_ctim = get_seconds();
562         shp->shm_segsz = size;
563         shp->shm_nattch = 0;
564         shp->shm_file = file;
565         shp->shm_creator = current;

shpはstruct task_structのメンバ変数にあるsysvshm構造体のshm_clistにリストでつなぎます。これでカレントタスクにある共有メモリはshm_clistから辿れるということですね。

566         list_add(&shp->shm_clist, &current->sysvshm.shm_clist);
567 

上のほうで作ったfile構造体のf_inodeに値を設定。

568         /*
569          * shmid gets reported as "inode#" in /proc/pid/maps.
570          * proc-ps tools use this. Changing this will break them.
571          */
572         file_inode(file)->i_ino = shp->shm_perm.id;
573 

ipc_namespacesにあるshm_totにはコメントが無いので何の略なのかさっぱりわからないのですが、include/uapi/linux/shm.hにあるstruct shm_infoを見るとtotal allocated shm と書かれています。何はともあれ、このipc_namespacesで使用しているページ数が入ります。

574         ns->shm_tot += numpages;

返り値にshp->shm_perm.idを設定して不要になったオブジェクトとロックを解放してnewseg()は終了です。

575         error = shp->shm_perm.id;
576 
577         ipc_unlock_object(&shp->shm_perm);
578         rcu_read_unlock();
579         return error;
580 

ここからはエラー発生時の終了処理なので省略。

581 no_id:
582         if (is_file_hugepages(file) && shp->mlock_user)
583                 user_shm_unlock(size, shp->mlock_user);
584         fput(file);
585 no_file:
586         ipc_rcu_putref(shp, shm_rcu_free);
587         return error;
588 }

では先ほど飛ばしたshmem_file_setup()を見ます。hugetlbのほうは見ません。shmem_file_setup()を見ると言ってもこれは__shmem_file_setup()を呼ぶだけです。 そして、この関数は本当にファイルを作るだけなのでそんなに読まなくても良い気が。 やっているのは擬似ファイル用のdentryの取得、inodeの確保(これはshmem_get_inode()でやってます)、ファイルの実体(struct file)の作成と言ったところです。このファイルの操作は通常通りstruct file_operationsにて設定し、共有メモリ用の操作関数はshmem_file_operationsにまとまっています。
内容はこのような構造体です。

3084 static const struct file_operations shmem_file_operations = {
3085         .mmap           = shmem_mmap,
3086 #ifdef CONFIG_TMPFS
3087         .llseek         = shmem_file_llseek,
3088         .read           = new_sync_read,
3089         .write          = new_sync_write,
3090         .read_iter      = shmem_file_read_iter,
3091         .write_iter     = generic_file_write_iter,
3092         .fsync          = noop_fsync,
3093         .splice_read    = shmem_file_splice_read,
3094         .splice_write   = iter_file_splice_write,
3095         .fallocate      = shmem_fallocate,
3096 #endif
3097 };

Amazon Web Services 基礎からのネットワーク&サーバー構築

Amazon Web Services 基礎からのネットワーク&サーバー構築