diff --git a/sys/kern/sysv_shm.c b/sys/kern/sysv_shm.c index 64ba74d..a599ffe 100644 --- a/sys/kern/sysv_shm.c +++ b/sys/kern/sysv_shm.c @@ -109,7 +109,6 @@ static int shmget_existing(struct thread *td, struct shmget_args *uap, #define SHMSEG_FREE 0x0200 #define SHMSEG_REMOVED 0x0400 #define SHMSEG_ALLOCATED 0x0800 -#define SHMSEG_WANTED 0x1000 static int shm_last_free, shm_nused, shmalloced; vm_size_t shm_committed; @@ -122,8 +121,7 @@ struct shmmap_state { static void shm_deallocate_segment(struct shmid_kernel *); static int shm_find_segment_by_key(key_t); -static struct shmid_kernel *shm_find_segment_by_shmid(int); -static struct shmid_kernel *shm_find_segment_by_shmidx(int); +static struct shmid_kernel *shm_find_segment(int, bool); static int shm_delete_mapping(struct vmspace *vm, struct shmmap_state *); static void shmrealloc(void); static int shminit(void); @@ -181,10 +179,15 @@ SYSCTL_INT(_kern_ipc, OID_AUTO, shm_use_phys, CTLFLAG_RWTUN, SYSCTL_INT(_kern_ipc, OID_AUTO, shm_allow_removed, CTLFLAG_RWTUN, &shm_allow_removed, 0, "Enable/Disable attachment to attached segments marked for removal"); -SYSCTL_PROC(_kern_ipc, OID_AUTO, shmsegs, CTLTYPE_OPAQUE | CTLFLAG_RD, - NULL, 0, sysctl_shmsegs, "", +SYSCTL_PROC(_kern_ipc, OID_AUTO, shmsegs, CTLTYPE_OPAQUE | CTLFLAG_RD | + CTLFLAG_MPSAFE, NULL, 0, sysctl_shmsegs, "", "Current number of shared memory segments allocated"); +static struct sx sysvshmsx; +#define SYSVSHM_LOCK() sx_xlock(&sysvshmsx) +#define SYSVSHM_UNLOCK() sx_xunlock(&sysvshmsx) +#define SYSVSHM_ASSERT_LOCKED() sx_assert(&sysvshmsx, SA_LOCKED) + static int shm_find_segment_by_key(key) key_t key; @@ -198,35 +201,24 @@ shm_find_segment_by_key(key) return (-1); } +/* + * Finds segment either by shmid if is_shmid is true, or by segnum if + * is_shmid is false. + */ static struct shmid_kernel * -shm_find_segment_by_shmid(int shmid) +shm_find_segment(int arg, bool is_shmid) { - int segnum; struct shmid_kernel *shmseg; + int segnum; - segnum = IPCID_TO_IX(shmid); + segnum = is_shmid ? IPCID_TO_IX(arg) : arg; if (segnum < 0 || segnum >= shmalloced) return (NULL); shmseg = &shmsegs[segnum]; if ((shmseg->u.shm_perm.mode & SHMSEG_ALLOCATED) == 0 || (!shm_allow_removed && (shmseg->u.shm_perm.mode & SHMSEG_REMOVED) != 0) || - shmseg->u.shm_perm.seq != IPCID_TO_SEQ(shmid)) - return (NULL); - return (shmseg); -} - -static struct shmid_kernel * -shm_find_segment_by_shmidx(int segnum) -{ - struct shmid_kernel *shmseg; - - if (segnum < 0 || segnum >= shmalloced) - return (NULL); - shmseg = &shmsegs[segnum]; - if ((shmseg->u.shm_perm.mode & SHMSEG_ALLOCATED) == 0 || - (!shm_allow_removed && - (shmseg->u.shm_perm.mode & SHMSEG_REMOVED) != 0)) + (is_shmid && shmseg->u.shm_perm.seq != IPCID_TO_SEQ(arg))) return (NULL); return (shmseg); } @@ -237,7 +229,7 @@ shm_deallocate_segment(shmseg) { vm_size_t size; - GIANT_REQUIRED; + SYSVSHM_ASSERT_LOCKED(); vm_object_deallocate(shmseg->object); shmseg->object = NULL; @@ -261,7 +253,7 @@ shm_delete_mapping(struct vmspace *vm, struct shmmap_state *shmmap_s) int segnum, result; vm_size_t size; - GIANT_REQUIRED; + SYSVSHM_ASSERT_LOCKED(); segnum = IPCID_TO_IX(shmmap_s->shmid); shmseg = &shmsegs[segnum]; @@ -299,7 +291,7 @@ sys_shmdt(td, uap) if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC)) return (ENOSYS); - mtx_lock(&Giant); + SYSVSHM_LOCK(); shmmap_s = p->p_vmspace->vm_shm; if (shmmap_s == NULL) { error = EINVAL; @@ -323,7 +315,7 @@ sys_shmdt(td, uap) #endif error = shm_delete_mapping(p->p_vmspace, shmmap_s); done2: - mtx_unlock(&Giant); + SYSVSHM_UNLOCK(); return (error); } @@ -353,29 +345,17 @@ kern_shmat(td, shmid, shmaddr, shmflg) if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC)) return (ENOSYS); - mtx_lock(&Giant); + SYSVSHM_LOCK(); shmmap_s = p->p_vmspace->vm_shm; if (shmmap_s == NULL) { shmmap_s = malloc(shminfo.shmseg * sizeof(struct shmmap_state), M_SHM, M_WAITOK); - - /* - * If malloc() above sleeps, the Giant lock is - * temporarily dropped, which allows another thread to - * allocate shmmap_state and set vm_shm. Recheck - * vm_shm and free the new shmmap_state if another one - * is already allocated. - */ - if (p->p_vmspace->vm_shm != NULL) { - free(shmmap_s, M_SHM); - shmmap_s = p->p_vmspace->vm_shm; - } else { - for (i = 0; i < shminfo.shmseg; i++) - shmmap_s[i].shmid = -1; - p->p_vmspace->vm_shm = shmmap_s; - } + for (i = 0; i < shminfo.shmseg; i++) + shmmap_s[i].shmid = -1; + KASSERT(p->p_vmspace->vm_shm == NULL, ("raced")); + p->p_vmspace->vm_shm = shmmap_s; } - shmseg = shm_find_segment_by_shmid(shmid); + shmseg = shm_find_segment(shmid, true); if (shmseg == NULL) { error = EINVAL; goto done2; @@ -439,7 +419,7 @@ kern_shmat(td, shmid, shmaddr, shmflg) shmseg->u.shm_nattch++; td->td_retval[0] = attach_va; done2: - mtx_unlock(&Giant); + SYSVSHM_UNLOCK(); return (error); } @@ -465,7 +445,7 @@ kern_shmctl(td, shmid, cmd, buf, bufsz) if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC)) return (ENOSYS); - mtx_lock(&Giant); + SYSVSHM_LOCK(); switch (cmd) { /* * It is possible that kern_shmctl is being called from the Linux ABI @@ -497,10 +477,7 @@ kern_shmctl(td, shmid, cmd, buf, bufsz) goto done2; } } - if (cmd == SHM_STAT) - shmseg = shm_find_segment_by_shmidx(shmid); - else - shmseg = shm_find_segment_by_shmid(shmid); + shmseg = shm_find_segment(shmid, cmd != SHM_STAT); if (shmseg == NULL) { error = EINVAL; goto done2; @@ -557,7 +534,7 @@ kern_shmctl(td, shmid, cmd, buf, bufsz) break; } done2: - mtx_unlock(&Giant); + SYSVSHM_UNLOCK(); return (error); } @@ -622,19 +599,8 @@ shmget_existing(td, uap, mode, segnum) struct shmid_kernel *shmseg; int error; + SYSVSHM_ASSERT_LOCKED(); shmseg = &shmsegs[segnum]; - if (shmseg->u.shm_perm.mode & SHMSEG_REMOVED) { - /* - * This segment is in the process of being allocated. Wait - * until it's done, and look the key up again (in case the - * allocation failed or it was freed). - */ - shmseg->u.shm_perm.mode |= SHMSEG_WANTED; - error = tsleep(shmseg, PLOCK | PCATCH, "shmget", 0); - if (error) - return (error); - return (EAGAIN); - } if ((uap->shmflg & (IPC_CREAT | IPC_EXCL)) == (IPC_CREAT | IPC_EXCL)) return (EEXIST); #ifdef MAC @@ -654,13 +620,13 @@ shmget_allocate_segment(td, uap, mode) struct shmget_args *uap; int mode; { - int i, segnum, shmid; + int i, segnum; size_t size; struct ucred *cred = td->td_ucred; struct shmid_kernel *shmseg; vm_object_t shm_object; - GIANT_REQUIRED; + SYSVSHM_ASSERT_LOCKED(); if (uap->size < shminfo.shmmin || uap->size > shminfo.shmmax) return (EINVAL); @@ -695,15 +661,7 @@ shmget_allocate_segment(td, uap, mode) } PROC_UNLOCK(td->td_proc); #endif - /* - * In case we sleep in malloc(), mark the segment present but deleted - * so that noone else tries to create the same key. - */ - shmseg->u.shm_perm.mode = SHMSEG_ALLOCATED | SHMSEG_REMOVED; - shmseg->u.shm_perm.key = uap->key; - shmseg->u.shm_perm.seq = (shmseg->u.shm_perm.seq + 1) & 0x7fff; - shmid = IXSEQ_TO_IPCID(segnum, shmseg->u.shm_perm); - + /* * We make sure that we have allocated a pager before we need * to. @@ -728,8 +686,9 @@ shmget_allocate_segment(td, uap, mode) shmseg->object = shm_object; shmseg->u.shm_perm.cuid = shmseg->u.shm_perm.uid = cred->cr_uid; shmseg->u.shm_perm.cgid = shmseg->u.shm_perm.gid = cred->cr_gid; - shmseg->u.shm_perm.mode = (shmseg->u.shm_perm.mode & SHMSEG_WANTED) | - (mode & ACCESSPERMS) | SHMSEG_ALLOCATED; + shmseg->u.shm_perm.mode = (mode & ACCESSPERMS) | SHMSEG_ALLOCATED; + shmseg->u.shm_perm.key = uap->key; + shmseg->u.shm_perm.seq = (shmseg->u.shm_perm.seq + 1) & 0x7fff; shmseg->cred = crhold(cred); shmseg->u.shm_segsz = uap->size; shmseg->u.shm_cpid = td->td_proc->p_pid; @@ -741,15 +700,8 @@ shmget_allocate_segment(td, uap, mode) shmseg->u.shm_ctime = time_second; shm_committed += btoc(size); shm_nused++; - if (shmseg->u.shm_perm.mode & SHMSEG_WANTED) { - /* - * Somebody else wanted this key while we were asleep. Wake - * them up now. - */ - shmseg->u.shm_perm.mode &= ~SHMSEG_WANTED; - wakeup(shmseg); - } - td->td_retval[0] = shmid; + td->td_retval[0] = IXSEQ_TO_IPCID(segnum, shmseg->u.shm_perm); + return (0); } @@ -770,15 +722,12 @@ sys_shmget(td, uap) if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC)) return (ENOSYS); - mtx_lock(&Giant); + SYSVSHM_LOCK(); mode = uap->shmflg & ACCESSPERMS; if (uap->key != IPC_PRIVATE) { - again: segnum = shm_find_segment_by_key(uap->key); if (segnum >= 0) { error = shmget_existing(td, uap, mode, segnum); - if (error == EAGAIN) - goto again; goto done2; } if ((uap->shmflg & IPC_CREAT) == 0) { @@ -788,7 +737,7 @@ sys_shmget(td, uap) } error = shmget_allocate_segment(td, uap, mode); done2: - mtx_unlock(&Giant); + SYSVSHM_UNLOCK(); return (error); } @@ -800,15 +749,16 @@ shmfork_myhook(p1, p2) size_t size; int i; - mtx_lock(&Giant); + SYSVSHM_LOCK(); size = shminfo.shmseg * sizeof(struct shmmap_state); shmmap_s = malloc(size, M_SHM, M_WAITOK); bcopy(p1->p_vmspace->vm_shm, shmmap_s, size); p2->p_vmspace->vm_shm = shmmap_s; - for (i = 0; i < shminfo.shmseg; i++, shmmap_s++) + for (i = 0; i < shminfo.shmseg; i++, shmmap_s++) { if (shmmap_s->shmid != -1) shmsegs[IPCID_TO_IX(shmmap_s->shmid)].u.shm_nattch++; - mtx_unlock(&Giant); + } + SYSVSHM_UNLOCK(); } static void @@ -817,16 +767,16 @@ shmexit_myhook(struct vmspace *vm) struct shmmap_state *base, *shm; int i; + SYSVSHM_LOCK(); if ((base = vm->vm_shm) != NULL) { vm->vm_shm = NULL; - mtx_lock(&Giant); for (i = 0, shm = base; i < shminfo.shmseg; i++, shm++) { if (shm->shmid != -1) shm_delete_mapping(vm, shm); } - mtx_unlock(&Giant); free(base, M_SHM); } + SYSVSHM_UNLOCK(); } static void @@ -919,6 +869,7 @@ shminit() shm_last_free = 0; shm_nused = 0; shm_committed = 0; + sx_init(&sysvshmsx, "sysvshmsx"); shmexit_hook = &shmexit_myhook; shmfork_hook = &shmfork_myhook; @@ -961,14 +912,19 @@ shmunload() free(shmsegs, M_SHM); shmexit_hook = NULL; shmfork_hook = NULL; + sx_destroy(&sysvshmsx); return (0); } static int sysctl_shmsegs(SYSCTL_HANDLER_ARGS) { + int error; - return (SYSCTL_OUT(req, shmsegs, shmalloced * sizeof(shmsegs[0]))); + SYSVSHM_LOCK(); + error = SYSCTL_OUT(req, shmsegs, shmalloced * sizeof(shmsegs[0])); + SYSVSHM_UNLOCK(); + return (error); } #if defined(__i386__) && (defined(COMPAT_FREEBSD4) || defined(COMPAT_43)) @@ -1000,8 +956,8 @@ oshmctl(struct thread *td, struct oshmctl_args *uap) if (!prison_allow(td->td_ucred, PR_ALLOW_SYSVIPC)) return (ENOSYS); - mtx_lock(&Giant); - shmseg = shm_find_segment_by_shmid(uap->shmid); + SYSVSHM_LOCK(); + shmseg = shm_find_segment(uap->shmid, true); if (shmseg == NULL) { error = EINVAL; goto done2; @@ -1034,7 +990,7 @@ oshmctl(struct thread *td, struct oshmctl_args *uap) break; } done2: - mtx_unlock(&Giant); + SYSVSHM_UNLOCK(); return (error); #else return (EINVAL); @@ -1066,9 +1022,9 @@ sys_shmsys(td, uap) if (uap->which < 0 || uap->which >= sizeof(shmcalls)/sizeof(shmcalls[0])) return (EINVAL); - mtx_lock(&Giant); + SYSVSHM_LOCK(); error = (*shmcalls[uap->which])(td, &uap->a2); - mtx_unlock(&Giant); + SYSVSHM_UNLOCK(); return (error); }