sysv_shm.c revision 1.16 1 /*
2 * Copyright (c) 1994 Charles Hannum.
3 * Copyright (c) 1994 Adam Glass
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. The name of the Author may not be used to endorse or promote products
12 * derived from this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY Adam Glass ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL Adam Glass BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27 #include <sys/types.h>
28 #include <sys/param.h>
29 #include <sys/kernel.h>
30 #include <sys/shm.h>
31 #include <sys/proc.h>
32 #include <sys/uio.h>
33 #include <sys/time.h>
34 #include <sys/malloc.h>
35 #include <sys/mman.h>
36 #include <sys/systm.h>
37 #include <sys/stat.h>
38
39 #include <vm/vm.h>
40 #include <vm/vm_map.h>
41 #include <vm/vm_map.h>
42 #include <vm/vm_kern.h>
43
44 /*
45 * Provides the following externally accessible functions:
46 *
47 * shminit(void); initialization
48 * shmexit(struct proc *) cleanup
49 * shmfork(struct proc *, struct proc *, int) fork handling
50 * shmsys(arg1, arg2, arg3, arg4); shm{at,ctl,dt,get}(arg2, arg3, arg4)
51 *
52 * Structures:
53 * shmsegs (an array of 'struct shmid_ds')
54 * per proc array of 'struct shmmap_state'
55 */
56
57 int shmat(), shmctl(), shmdt(), shmget();
58 int (*shmcalls[])() = { shmat, shmctl, shmdt, shmget };
59
60 #define SHMSEG_FREE 0x0200
61 #define SHMSEG_REMOVED 0x0400
62 #define SHMSEG_ALLOCATED 0x0800
63 #define SHMSEG_WANTED 0x1000
64
65 vm_map_t sysvshm_map;
66 int shm_last_free, shm_nused, shm_committed;
67
68 struct shm_handle {
69 vm_offset_t kva;
70 };
71
72 struct shmmap_state {
73 vm_offset_t va;
74 int shmid;
75 };
76
77 static void shm_deallocate_segment __P((struct shmid_ds *));
78 static int shm_find_segment_by_key __P((key_t));
79 static struct shmid_ds *shm_find_segment_by_shmid __P((int));
80 static int shm_delete_mapping __P((struct proc *, struct shmmap_state *));
81
82 static int
83 shm_find_segment_by_key(key)
84 key_t key;
85 {
86 int i;
87
88 for (i = 0; i < shminfo.shmmni; i++)
89 if ((shmsegs[i].shm_perm.mode & SHMSEG_ALLOCATED) &&
90 shmsegs[i].shm_perm.key == key)
91 return i;
92 return -1;
93 }
94
95 static struct shmid_ds *
96 shm_find_segment_by_shmid(shmid)
97 int shmid;
98 {
99 int segnum;
100 struct shmid_ds *shmseg;
101
102 segnum = IPCID_TO_IX(shmid);
103 if (segnum < 0 || segnum >= shminfo.shmmni)
104 return NULL;
105 shmseg = &shmsegs[segnum];
106 if ((shmseg->shm_perm.mode & (SHMSEG_ALLOCATED | SHMSEG_REMOVED))
107 != SHMSEG_ALLOCATED ||
108 shmseg->shm_perm.seq != IPCID_TO_SEQ(shmid))
109 return NULL;
110 return shmseg;
111 }
112
113 static vm_offset_t
114 shm_find_space(p, size)
115 struct proc *p;
116 size_t size;
117 {
118 vm_offset_t low_end, range, current;
119 int result;
120
121 low_end = (vm_offset_t)p->p_vmspace->vm_daddr +
122 (p->p_vmspace->vm_dsize << PGSHIFT);
123 range = (USRSTACK - low_end);
124
125 /* XXXX totally bogus */
126 /* current = range *3/4 + low_end */
127 current = ((range&1)<<1 + range)>>2 + range>>1 + low_end;
128 #if 0
129 result = vm_map_find(&p->p_vmspace->vm_map, NULL, 0, ¤t, size,
130 TRUE);
131 if (result)
132 return NULL;
133 #endif
134 return current;
135 }
136
137 static void
138 shm_deallocate_segment(shmseg)
139 struct shmid_ds *shmseg;
140 {
141 struct shm_handle *shm_handle;
142 size_t size;
143
144 shm_handle = shmseg->shm_internal;
145 size = (shmseg->shm_segsz + CLOFSET) & ~CLOFSET;
146 vm_deallocate(sysvshm_map, shm_handle->kva, size);
147 free((caddr_t)shm_handle, M_SHM);
148 shmseg->shm_internal = NULL;
149 shm_committed -= btoc(size);
150 shmseg->shm_perm.mode = SHMSEG_FREE;
151 }
152
153 static int
154 shm_delete_mapping(p, shmmap_s)
155 struct proc *p;
156 struct shmmap_state *shmmap_s;
157 {
158 struct shmid_ds *shmseg;
159 int segnum, result;
160 size_t size;
161
162 segnum = IPCID_TO_IX(shmmap_s->shmid);
163 shmseg = &shmsegs[segnum];
164 size = (shmseg->shm_segsz + CLOFSET) & ~CLOFSET;
165 result = vm_deallocate(&p->p_vmspace->vm_map, shmmap_s->va, size);
166 if (result != KERN_SUCCESS)
167 return EINVAL;
168 shmmap_s->shmid = -1;
169 shmseg->shm_dtime = time.tv_sec;
170 if ((--shmseg->shm_nattch <= 0) &&
171 (shmseg->shm_perm.mode & SHMSEG_REMOVED)) {
172 shm_deallocate_segment(shmseg);
173 shm_last_free = segnum;
174 }
175 return 0;
176 }
177
178 struct shmdt_args {
179 void *shmaddr;
180 };
181 int
182 shmdt(p, uap, retval)
183 struct proc *p;
184 struct shmdt_args *uap;
185 int *retval;
186 {
187 struct shmmap_state *shmmap_s;
188 int i;
189
190 shmmap_s = (struct shmmap_state *)p->p_vmspace->vm_shm;
191 for (i = 0; i < shminfo.shmseg; i++, shmmap_s++)
192 if (shmmap_s->shmid != -1 &&
193 shmmap_s->va == (vm_offset_t)uap->shmaddr)
194 break;
195 if (i == shminfo.shmseg)
196 return EINVAL;
197 return shm_delete_mapping(p, shmmap_s);
198 }
199
200 struct shmat_args {
201 int shmid;
202 void *shmaddr;
203 int shmflg;
204 };
205 int
206 shmat(p, uap, retval)
207 struct proc *p;
208 struct shmat_args *uap;
209 int *retval;
210 {
211 int error, i, flags;
212 struct ucred *cred = p->p_ucred;
213 struct shmid_ds *shmseg;
214 struct shmmap_state *shmmap_s = NULL;
215 vm_offset_t attach_va;
216 vm_prot_t prot;
217 vm_size_t size;
218
219 shmmap_s = (struct shmmap_state *)p->p_vmspace->vm_shm;
220 if (shmmap_s == NULL) {
221 size = shminfo.shmseg * sizeof(struct shmmap_state);
222 shmmap_s = malloc(size, M_SHM, M_WAITOK);
223 bzero((caddr_t)shmmap_s, size);
224 p->p_vmspace->vm_shm = (caddr_t)shmmap_s;
225 }
226 shmseg = shm_find_segment_by_shmid(uap->shmid);
227 if (shmseg == NULL)
228 return EINVAL;
229 if (error = ipcperm(cred, &shmseg->shm_perm,
230 (uap->shmflg & SHM_RDONLY) ? IPC_R : IPC_R|IPC_W))
231 return error;
232 for (i = 0; i < shminfo.shmseg; i++) {
233 if (shmmap_s->shmid == -1)
234 break;
235 shmmap_s++;
236 }
237 if (i >= shminfo.shmseg)
238 return EMFILE;
239 size = (shmseg->shm_segsz + CLOFSET) & ~CLOFSET;
240 prot = VM_PROT_READ;
241 if ((uap->shmflg & SHM_RDONLY) == 0)
242 prot |= VM_PROT_WRITE;
243 flags = MAP_ANON | MAP_SHARED;
244 if (uap->shmaddr) {
245 flags |= MAP_FIXED;
246 if (uap->shmflg & SHM_RND)
247 attach_va = (vm_offset_t)uap->shmaddr & ~(SHMLBA-1);
248 else if (((vm_offset_t)uap->shmaddr & (SHMLBA-1)) == 0)
249 attach_va = (vm_offset_t)uap->shmaddr;
250 else
251 return EINVAL;
252 } else {
253 attach_va = shm_find_space(p, shmseg->shm_segsz);
254 if (attach_va == NULL)
255 return ENOMEM;
256 }
257 error = vm_mmap(&p->p_vmspace->vm_map, &attach_va, size, prot,
258 VM_PROT_DEFAULT, flags, uap->shmid, 0);
259 if (error)
260 return error;
261 shmmap_s->va = attach_va;
262 shmmap_s->shmid = uap->shmid;
263 shmseg->shm_lpid = p->p_pid;
264 shmseg->shm_atime = time.tv_sec;
265 shmseg->shm_nattch++;
266 *retval = attach_va;
267 return 0;
268 }
269
270 struct shmctl_args {
271 int shmid;
272 int cmd;
273 struct shmat_ds *ubuf;
274 };
275 int
276 shmctl(p, uap, retval)
277 struct proc *p;
278 struct shmctl_args *uap;
279 int *retval;
280 {
281 int error, segnum;
282 struct ucred *cred = p->p_ucred;
283 struct shmid_ds inbuf;
284 struct shmid_ds *shmseg;
285
286 shmseg = shm_find_segment_by_shmid(uap->shmid);
287 if (shmseg == NULL)
288 return EINVAL;
289 switch (uap->cmd) {
290 case IPC_STAT:
291 if (error = ipcperm(cred, &shmseg->shm_perm, IPC_R))
292 return error;
293 if (error = copyout((caddr_t)shmseg, uap->ubuf, sizeof(inbuf)))
294 return error;
295 break;
296 case IPC_SET:
297 if (error = ipcperm(cred, &shmseg->shm_perm, IPC_M))
298 return error;
299 if (error = copyin(uap->ubuf, (caddr_t)&inbuf, sizeof(inbuf)))
300 return error;
301 shmseg->shm_perm.uid = inbuf.shm_perm.uid;
302 shmseg->shm_perm.gid = inbuf.shm_perm.gid;
303 shmseg->shm_perm.mode =
304 (shmseg->shm_perm.mode & ~ACCESSPERMS) |
305 (inbuf.shm_perm.mode & ACCESSPERMS);
306 shmseg->shm_ctime = time.tv_sec;
307 break;
308 case IPC_RMID:
309 if (error = ipcperm(cred, &shmseg->shm_perm, IPC_M))
310 return error;
311 shmseg->shm_perm.key = IPC_PRIVATE;
312 shmseg->shm_perm.mode |= SHMSEG_REMOVED;
313 if (shmseg->shm_nattch <= 0) {
314 shm_deallocate_segment(shmseg);
315 shm_last_free = IPCID_TO_IX(uap->shmid);
316 }
317 break;
318 #if 0
319 case SHM_LOCK:
320 case SHM_UNLOCK:
321 #endif
322 default:
323 return EINVAL;
324 }
325 return 0;
326 }
327
328 struct shmget_args {
329 key_t key;
330 size_t size;
331 int shmflg;
332 };
333 static int
334 shmget_existing(p, uap, mode, segnum, retval)
335 struct proc *p;
336 struct shmget_args *uap;
337 int mode;
338 int segnum;
339 int *retval;
340 {
341 struct shmid_ds *shmseg;
342 struct ucred *cred = p->p_ucred;
343 int error;
344
345 shmseg = &shmsegs[segnum];
346 if (shmseg->shm_perm.mode & SHMSEG_REMOVED) {
347 /*
348 * This segment is in the process of being allocated. Wait
349 * until it's done, and look the key up again (in case the
350 * allocation failed or it was freed).
351 */
352 shmseg->shm_perm.mode |= SHMSEG_WANTED;
353 if (error =
354 tsleep((caddr_t)shmseg, PLOCK | PCATCH, "shmget", 0))
355 return error;
356 return EAGAIN;
357 }
358 if (error = ipcperm(cred, &shmseg->shm_perm, mode))
359 return error;
360 if (uap->size && uap->size > shmseg->shm_segsz)
361 return EINVAL;
362 if (uap->shmflg & (IPC_CREAT | IPC_EXCL) == (IPC_CREAT | IPC_EXCL))
363 return EEXIST;
364 *retval = IXSEQ_TO_IPCID(segnum, shmseg->shm_perm);
365 return 0;
366 }
367
368 static int
369 shmget_allocate_segment(p, uap, mode, retval)
370 struct proc *p;
371 struct shmget_args *uap;
372 int mode;
373 int *retval;
374 {
375 int i, segnum, result, shmid, size;
376 struct ucred *cred = p->p_ucred;
377 struct shmid_ds *shmseg;
378 struct shm_handle *shm_handle;
379
380 if (uap->size < shminfo.shmmin || uap->size > shminfo.shmmax)
381 return EINVAL;
382 if (shm_nused >= shminfo.shmmni) /* any shmids left? */
383 return ENOSPC;
384 size = (uap->size + CLOFSET) & ~CLOFSET;
385 if (shm_committed + btoc(size) > shminfo.shmall)
386 return ENOMEM;
387 if (shm_last_free < 0) {
388 for (i = 0; i < shminfo.shmmni; i++)
389 if (shmsegs[i].shm_perm.mode & SHMSEG_FREE)
390 break;
391 if (i == shminfo.shmmni)
392 panic("shmseg free count inconsistent");
393 segnum = i;
394 } else {
395 segnum = shm_last_free;
396 shm_last_free = -1;
397 }
398 shmseg = &shmsegs[segnum];
399 /*
400 * In case we sleep in malloc(), mark the segment present but deleted
401 * so that noone else tries to create the same key.
402 */
403 shmseg->shm_perm.mode = SHMSEG_ALLOCATED | SHMSEG_REMOVED;
404 shmseg->shm_perm.key = uap->key;
405 shmseg->shm_perm.seq = (shmseg->shm_perm.seq + 1) & 0x7fff;
406 shm_handle = (struct shm_handle *)
407 malloc(sizeof(struct shm_handle), M_SHM, M_WAITOK);
408 shmid = IXSEQ_TO_IPCID(segnum, shmseg->shm_perm);
409 result = vm_mmap(sysvshm_map, &shm_handle->kva, size, VM_PROT_ALL,
410 VM_PROT_DEFAULT, MAP_ANON, shmid, 0);
411 if (result != KERN_SUCCESS) {
412 shmseg->shm_perm.mode = SHMSEG_FREE;
413 shm_last_free = segnum;
414 free((caddr_t)shm_handle, M_SHM);
415 /* Just in case. */
416 wakeup((caddr_t)shmseg);
417 return ENOMEM;
418 }
419 shmseg->shm_internal = shm_handle;
420 shmseg->shm_perm.cuid = shmseg->shm_perm.uid = cred->cr_uid;
421 shmseg->shm_perm.cgid = shmseg->shm_perm.gid = cred->cr_gid;
422 shmseg->shm_perm.mode = (shmseg->shm_perm.mode & SHMSEG_WANTED) |
423 (mode & ACCESSPERMS) | SHMSEG_ALLOCATED;
424 shmseg->shm_segsz = uap->size;
425 shmseg->shm_cpid = p->p_pid;
426 shmseg->shm_lpid = shmseg->shm_nattch = 0;
427 shmseg->shm_atime = shmseg->shm_dtime = 0;
428 shmseg->shm_ctime = time.tv_sec;
429 shm_committed += btoc(size);
430 shm_nused++;
431 if (shmseg->shm_perm.mode & SHMSEG_WANTED) {
432 /*
433 * Somebody else wanted this key while we were asleep. Wake
434 * them up now.
435 */
436 shmseg->shm_perm.mode &= ~SHMSEG_WANTED;
437 wakeup((caddr_t)shmseg);
438 }
439 *retval = shmid;
440 return 0;
441 }
442
443 int
444 shmget(p, uap, retval)
445 struct proc *p;
446 struct shmget_args *uap;
447 int *retval;
448 {
449 int segnum, mode, error;
450 struct shmid_ds *shmseg;
451
452 mode = uap->shmflg & ACCESSPERMS;
453 if (uap->key != IPC_PRIVATE) {
454 again:
455 segnum = shm_find_segment_by_key(uap->key);
456 if (segnum >= 0) {
457 error = shmget_existing(p, uap, mode, segnum, retval);
458 if (error == EAGAIN)
459 goto again;
460 return error;
461 }
462 if ((uap->shmflg & IPC_CREAT) == 0)
463 return ENOENT;
464 }
465 return shmget_allocate_segment(p, uap, mode, retval);
466 }
467
468 struct shmsys_args {
469 u_int which;
470 };
471 int
472 shmsys(p, uap, retval)
473 struct proc *p;
474 struct shmsys_args *uap;
475 int *retval;
476 {
477
478 if (uap->which >= sizeof(shmcalls)/sizeof(shmcalls[0]))
479 return EINVAL;
480 return ((*shmcalls[uap->which])(p, &uap[1], retval));
481 }
482
483 void
484 shmfork(p1, p2, isvfork)
485 struct proc *p1, *p2;
486 int isvfork;
487 {
488 struct shmmap_state *shmmap_s;
489 size_t size;
490 int i;
491
492 size = shminfo.shmseg * sizeof(struct shmmap_state);
493 shmmap_s = malloc(size, M_SHM, M_WAITOK);
494 bcopy((caddr_t)p1->p_vmspace->vm_shm, (caddr_t)shmmap_s, size);
495 p2->p_vmspace->vm_shm = (caddr_t)shmmap_s;
496 for (i = 0; i < shminfo.shmseg; i++, shmmap_s++)
497 if (shmmap_s->shmid != -1)
498 shmsegs[IPCID_TO_IX(shmmap_s->shmid)].shm_nattch++;
499 }
500
501 void
502 shmexit(p)
503 struct proc *p;
504 {
505 struct shmmap_state *shmmap_s;
506 struct shmid_ds *shmseg;
507 int i;
508
509 shmmap_s = (struct shmmap_state *)p->p_vmspace->vm_shm;
510 for (i = 0; i < shminfo.shmseg; i++, shmmap_s++)
511 if (shmmap_s->shmid != -1)
512 shm_delete_mapping(p, shmmap_s);
513 free((caddr_t)p->p_vmspace->vm_shm, M_SHM);
514 p->p_vmspace->vm_shm = NULL;
515 }
516
517 void
518 shminit()
519 {
520 int i;
521 vm_offset_t garbage1, garbage2;
522
523 /* actually this *should* be pageable. SHM_{LOCK,UNLOCK} */
524 sysvshm_map = kmem_suballoc(kernel_map, &garbage1, &garbage2,
525 shminfo.shmall * NBPG, FALSE);
526 for (i = 0; i < shminfo.shmmni; i++) {
527 shmsegs[i].shm_perm.mode = SHMSEG_FREE;
528 shmsegs[i].shm_perm.seq = 0;
529 }
530 shm_last_free = 0;
531 shm_nused = 0;
532 shm_committed = 0;
533 }
534