読者です 読者をやめる 読者になる 読者になる

Linux SystemV IPC: shmatとshmdtを読む

linux kernel

今日はshmat()とshmdt()を読んでみます。 shmat()はshmget()、shmdt()、shmctl()と違ってsyscall_defineは使われていなくて、ipc()からdo_shmat()を呼び出しています。

では、do_shmat()を見ていきます。
最初は変数定義が並んでいるだけなので飛ばします。

1050 long do_shmat(int shmid, char __user *shmaddr, int shmflg, ulong *raddr,
1051               unsigned long shmlba)
1052 {
1053         struct shmid_kernel *shp;
1054         unsigned long addr;
1055         unsigned long size;
1056         struct file *file;
1057         int    err;
1058         unsigned long flags;
1059         unsigned long prot;
1060         int acc_mode;
1061         struct ipc_namespace *ns;
1062         struct shm_file_data *sfd;
1063         struct path path;
1064         fmode_t f_mode;
1065         unsigned long populate = 0;

最初はユーザーランドから渡された引数のチェックです。最初のifはshat(2)の最初の引数shmidのチェックです。

1067         err = -EINVAL;
1068         if (shmid < 0)
1069                 goto out;

shmat(2)の第二引数shmaddrがNULLでなかった場合の処理です。中括弧内はshmflgの値に応じたエラーチェックとフラグの設定で、この辺はmanにあるとおりですね。

1070         else if ((addr = (ulong)shmaddr)) {
1071                 if (addr & (shmlba - 1)) {
1072                         if (shmflg & SHM_RND)
1073                                 addr &= ~(shmlba - 1);     /* round down */
1074                         else
1075 #ifndef __ARCH_FORCE_SHMLBA
1076                                 if (addr & ~PAGE_MASK)
1077 #endif
1078                                         goto out;
1079                 }
1080                 flags = MAP_SHARED | MAP_FIXED;

上記の条件に当てはまらなかった場合はここです。最初のif文はSHM_REMAPを使う場合はshmaddrがNULLではダメというmanの通りのチェックです。

1081         } else {
1082                 if ((shmflg & SHM_REMAP))
1083                         goto out;
1084 
1085                 flags = MAP_SHARED;
1086         }

ここまでの処理で設定したflags変数は後ほどmmpaするときに出てきます。 次に進んでこの部分ですが、ここもshmflgの内容に応じたフラグ類の設定をするだけです。

1088         if (shmflg & SHM_RDONLY) {
1089                 prot = PROT_READ;
1090                 acc_mode = S_IRUGO;
1091                 f_mode = FMODE_READ;
1092         } else {
1093                 prot = PROT_READ | PROT_WRITE;
1094                 acc_mode = S_IRUGO | S_IWUGO;
1095                 f_mode = FMODE_READ | FMODE_WRITE;
1096         }
1097         if (shmflg & SHM_EXEC) {
1098                 prot |= PROT_EXEC;
1099                 acc_mode |= S_IXUGO;
1100         }

まずカレントタスクが所属するipc namespaceの取得をします。

1102         /*
1103          * We cannot rely on the fs check since SYSV IPC does have an
1104          * additional creator id...
1105          */
1106         ns = current->nsproxy->ipc_ns;

そして、このnamespaceの中にshmidに該当する共有メモリのオブジェクト(struct shmid_kernel)を取得します。

1107         rcu_read_lock();
1108         shp = shm_obtain_object_check(ns, shmid);
1109         if (IS_ERR(shp)) {
1110                 err = PTR_ERR(shp);
1111                 goto out_unlock;
1112         }

ここはオブジェクトに設定されているパーミッションとshmat()でshmflgに設定した内容に応じて決定したパーミッションとの比較です。

1114         err = -EACCES;
1115         if (ipcperms(ns, &shp->shm_perm, acc_mode))
1116                 goto out_unlock;

security_XXXはselinux等のセキュリティ機構が使うので飛ばします。

1118         err = security_shm_shmat(shp, shmaddr, shmflg);
1119         if (err)
1120                 goto out_unlock;

ロックを取得して次のipc_valid_object()ですが、これはコメントのとおりで他の誰かがこのオブジェクトの削除中の場合はattachできないのでエラーになります。

1122         ipc_lock_object(&shp->shm_perm);
1123 
1124         /* check if shm_destroy() is tearing down shp */
1125         if (!ipc_valid_object(&shp->shm_perm)) {
1126                 ipc_unlock_object(&shp->shm_perm);
1127                 err = -EIDRM;
1128                 goto out_unlock;
1129         }

ここからは擬似ファイルに対する操作が入ってきます。まずpathですがこれはstruct pathです。ここでshmid_kernelより擬似ファイルのパスを取得してpath_get()でマウントポイントへの参照カウントとdentryの参照カウントを増やします。そして、shm_nattchのインクリメントでこの共有メモリの参照が増えます。sizeはファイルのサイズです。

1131         path = shp->shm_file->f_path;
1132         path_get(&path);
1133         shp->shm_nattch++;
1134         size = i_size_read(path.dentry->d_inode);

今まで取ったロックを解除します。

1135         ipc_unlock_object(&shp->shm_perm);
1136         rcu_read_unlock();

ここは見たままですね。sfdはstruct shm_file_dataという構造体です。

1138         err = -ENOMEM;
1139         sfd = kzalloc(sizeof(*sfd), GFP_KERNEL);
1140         if (!sfd) {
1141                 path_put(&path);
1142                 goto out_nattch;
1143         }

この構造体はipc/shm.cにて定義されているローカルな構造体で、このような中身になってます。

 50 struct shm_file_data {
 51         int id;
 52         struct ipc_namespace *ns;
 53         struct file *file;
 54         const struct vm_operations_struct *vm_ops;
 55 };

do_shmat()に戻って、次はfile(struct file)をアローケートします。alloc_file()はfile構造体の領域を確保して初期化して返してくれる関数です。

1145         file = alloc_file(&path, f_mode,
1146                           is_file_hugepages(shp->shm_file) ?
1147                                 &shm_file_operations_huge :
1148                                 &shm_file_operations);
1149         err = PTR_ERR(file);
1150         if (IS_ERR(file)) {
1151                 kfree(sfd);
1152                 path_put(&path);
1153                 goto out_nattch;
1154         }

ここは先ほどkzallocしたsfdの設定です。sfdはstruct fileのprivate_dataに代入されます。そして、sfdのメンバ変数を設定してきます。

1156         file->private_data = sfd;
1157         file->f_mapping = shp->shm_file->f_mapping;
1158         sfd->id = shp->shm_perm.id;
1159         sfd->ns = get_ipc_ns(ns);
1160         sfd->file = shp->shm_file;
1161         sfd->vm_ops = NULL;

ここは飛ばします。

1163         err = security_mmap_file(file, prot, flags);
1164         if (err)
1165                 goto out_fput;

ここからmmapを行うための処理になります。まずはロックを取ります。

1167         down_write(&current->mm->mmap_sem);

ここはshmat()でshmaddrにNULL以外を渡し、フラグにSHM_REMAPが付いていなかった場合ですね。find_vma_intersection()を呼んでいますがここでは適切な領域があるかだけのチェックをしていて戻り値は使いません。

1168         if (addr && !(shmflg & SHM_REMAP)) {
1169                 err = -EINVAL;
1170                 if (addr + size < addr)
1171                         goto invalid;
1172 
1173                 if (find_vma_intersection(current->mm, addr, addr + size))
1174                         goto invalid;
1175                 /*
1176                  * If shm segment goes below stack, make sure there is some
1177                  * space left for the stack to grow (at least 4 pages).
1178                  */
1179                 if (addr < current->mm->start_stack &&
1180                     addr > current->mm->start_stack - size - PAGE_SIZE * 5)
1181                         goto invalid;
1182         }

do_mmap_pgoff()mmapします。*raddrにaddrを代入しておきます。

1184         addr = do_mmap_pgoff(file, addr, size, prot, flags, 0, &populate);
1185         *raddr = addr;
1186         err = 0;
1187         if (IS_ERR_VALUE(addr))
1188                 err = (long)addr;

ここからはdo_shmat()終了処理に入っていきます。mmpaするのに取ったロックを解除

1189 invalid:
1190         up_write(&current->mm->mmap_sem);

do_mmap_pgoff()に渡した変数のpopulateが0じゃなければmm_populate()を呼ぶ必要があるようです。

1191         if (populate)
1192                 mm_populate(addr, populate);
1193 

struct fileが不要になったので解放。

1194 out_fput:
1195         fput(file);
1196

namespaceのロックを解除と別のロックの取得。

1197 out_nattch:
1198         down_write(&shm_ids(ns).rwsem);
1199         shp = shm_lock(ns, shmid);
1200         BUG_ON(IS_ERR(shp));

shm_nattchの値をデクリメントして元に戻す。

1201         shp->shm_nattch--;

shm_may_destroy()を呼び、結果として共有メモリをdestroyする必要があればdestroyする。

1202         if (shm_may_destroy(ns, shp))
1203                 shm_destroy(ns, shp);
1204         else
1205                 shm_unlock(shp);

さっき取ったロックを解除して終了。ユーザーランドにはアドレスが返りますがこれはdo_mmap_pgoff()の返り値になります。do_mmap_pgoff()の呼び出し後にraddr = addr;としてraddrに値を代入していたところがそれです。

1206         up_write(&shm_ids(ns).rwsem);
1207         return err;
1208 

ここからは途中でエラーが発生時の処理。

1209 out_unlock:
1210         rcu_read_unlock();
1211 out:
1212         return err;
1213 }

ここまでがshmat()の実装で、ここからはshmdt()を見てみましょう。   SYSCALL_DEFINE1が付いていますがipc()から呼ばれます。

1231 SYSCALL_DEFINE1(shmdt, char __user *, shmaddr)
1232 {
1233         struct mm_struct *mm = current->mm;
1234         struct vm_area_struct *vma;
1235         unsigned long addr = (unsigned long)shmaddr;
1236         int retval = -EINVAL;
1237 #ifdef CONFIG_MMU
1238         loff_t size = 0;
1239         struct vm_area_struct *next;
1240 #endif
1241 

最初にdetachするアドレスのチェックをして、問題なければmmap用のロックを取ります。

1242         if (addr & ~PAGE_MASK)
1243                 return retval;
1244 
1245         down_write(&mm->mmap_sem);

ここは結構長めのコメントが書いてあるのですが、それを飛ばしてコードだけ見ると該当アドレスに対するvmaを取得します。

1267         vma = find_vma(mm, addr);

そうしたら後はdo_munmap()でmmapした領域を解放してきます。ここではアドレスだけでなく、vmaに設定されているオペレーションが共有メモリのオペレーションかというのもチェック対象になってますね。

1270         while (vma) {
1271                 next = vma->vm_next;
1272 
1273                 /*
1274                  * Check if the starting address would match, i.e. it's
1275                  * a fragment created by mprotect() and/or munmap(), or it
1276                  * otherwise it starts at this address with no hassles.
1277                  */
1278                 if ((vma->vm_ops == &shm_vm_ops) &&
1279                         (vma->vm_start - addr)/PAGE_SIZE == vma->vm_pgoff) {
1280 
1281 
1282                         size = file_inode(vma->vm_file)->i_size;
1283                         do_munmap(mm, vma->vm_start, vma->vm_end - vma->vm_start);
1284                         /*
1285                          * We discovered the size of the shm segment, so
1286                          * break out of here and fall through to the next
1287                          * loop that uses the size information to stop
1288                          * searching for matching vma's.
1289                          */
1290                         retval = 0;
1291                         vma = next;
1292                         break;
1293                 }
1294                 vma = next;
1295         }

引き続きdo_munmapの処理が入ります。vma周りはよくわかっていないので今はこれが限度です\(^o^)/

1297         /*
1298          * We need look no further than the maximum address a fragment
1299          * could possibly have landed at. Also cast things to loff_t to
1300          * prevent overflows and make comparisons vs. equal-width types.
1301          */
1302         size = PAGE_ALIGN(size);
1303         while (vma && (loff_t)(vma->vm_end - addr) <= size) {
1304                 next = vma->vm_next;
1305 
1306                 /* finding a matching vma now does not alter retval */
1307                 if ((vma->vm_ops == &shm_vm_ops) &&
1308                         (vma->vm_start - addr)/PAGE_SIZE == vma->vm_pgoff)
1309 
1310                         do_munmap(mm, vma->vm_start, vma->vm_end - vma->vm_start);
1311                 vma = next;
1312         }

最後はロックを解放して終わりです。

1324         up_write(&mm->mmap_sem);
1325         return retval;
1326 }

えー、shmatはmmapするだけだったんですが、shmdtは単にmmpaしたアドレスをunmmapするだけではないので難しいですね(´・ω・`)

Unix/Linuxプログラミング理論と実践

Unix/Linuxプログラミング理論と実践