ipc.c
上传用户:blenddy
上传日期:2007-01-07
资源大小:6495k
文件大小:19k
源码类别:

数据库系统

开发平台:

Unix_Linux

  1.  /*-------------------------------------------------------------------------
  2.  *
  3.  * ipc.c
  4.  *   POSTGRES inter-process communication definitions.
  5.  *
  6.  * Copyright (c) 1994, Regents of the University of California
  7.  *
  8.  *
  9.  * IDENTIFICATION
  10.  *   $Header: /usr/local/cvsroot/pgsql/src/backend/storage/ipc/ipc.c,v 1.37 1999/05/25 16:11:07 momjian Exp $
  11.  *
  12.  * NOTES
  13.  *
  14.  *   Currently, semaphores are used (my understanding anyway) in two
  15.  *   different ways:
  16.  * 1. as mutexes on machines that don't have test-and-set (eg.
  17.  *    mips R3000).
  18.  * 2. for putting processes to sleep when waiting on a lock
  19.  *    and waking them up when the lock is free.
  20.  *   The number of semaphores in (1) is fixed and those are shared
  21.  *   among all backends. In (2), there is 1 semaphore per process and those
  22.  *   are not shared with anyone else.
  23.  *   -ay 4/95
  24.  *
  25.  *-------------------------------------------------------------------------
  26.  */
  27. #include <sys/types.h>
  28. #include <sys/file.h>
  29. #include <stdio.h>
  30. #include <string.h>
  31. #include <errno.h>
  32. #include "postgres.h"
  33. #include "storage/ipc.h"
  34. #include "storage/s_lock.h"
  35. /* In Ultrix, sem.h and shm.h must be included AFTER ipc.h */
  36. #include <sys/sem.h>
  37. #include <sys/shm.h>
  38. #include "utils/memutils.h"
  39. #include "libpq/libpq.h"
  40. #include "utils/trace.h"
  41. #if defined(solaris_sparc)
  42. #include <string.h>
  43. #include <sys/ipc.h>
  44. #endif
  45. static int UsePrivateMemory = 0;
  46. static void IpcMemoryDetach(int status, char *shmaddr);
  47. /* ----------------------------------------------------------------
  48.  * exit() handling stuff
  49.  * ----------------------------------------------------------------
  50.  */
  51. #define MAX_ON_EXITS 20
  52. static struct ONEXIT
  53. {
  54. void (*function) ();
  55. caddr_t arg;
  56. } on_proc_exit_list[MAX_ON_EXITS], on_shmem_exit_list[MAX_ON_EXITS];
  57. static int on_proc_exit_index,
  58. on_shmem_exit_index;
  59. typedef struct _PrivateMemStruct
  60. {
  61. int id;
  62. char    *memptr;
  63. } PrivateMem;
  64. PrivateMem IpcPrivateMem[16];
  65. static int
  66. PrivateMemoryCreate(IpcMemoryKey memKey,
  67. uint32 size)
  68. {
  69. static int memid = 0;
  70. UsePrivateMemory = 1;
  71. IpcPrivateMem[memid].id = memid;
  72. IpcPrivateMem[memid].memptr = malloc(size);
  73. if (IpcPrivateMem[memid].memptr == NULL)
  74. elog(ERROR, "PrivateMemoryCreate: not enough memory to malloc");
  75. MemSet(IpcPrivateMem[memid].memptr, 0, size); /* XXX PURIFY */
  76. return memid++;
  77. }
  78. static char *
  79. PrivateMemoryAttach(IpcMemoryId memid)
  80. {
  81. return IpcPrivateMem[memid].memptr;
  82. }
  83. /* ----------------------------------------------------------------
  84.  * proc_exit
  85.  *
  86.  * this function calls all the callbacks registered
  87.  * for it (to free resources) and then calls exit.
  88.  * This should be the only function to call exit().
  89.  * -cim 2/6/90
  90.  * ----------------------------------------------------------------
  91.  */
  92. static int proc_exit_inprogress = 0;
  93. void
  94. proc_exit(int code)
  95. {
  96. int i;
  97. TPRINTF(TRACE_VERBOSE, "proc_exit(%d) [#%d]", code, proc_exit_inprogress);
  98. /*
  99.  * If proc_exit is called too many times something bad is happenig, so
  100.  * exit immediately.
  101.  */
  102. if (proc_exit_inprogress > 9)
  103. {
  104. elog(ERROR, "infinite recursion in proc_exit");
  105. goto exit;
  106. }
  107. /* ----------------
  108.  * if proc_exit_inprocess is true, then it means that we
  109.  * are being invoked from within an on_exit() handler
  110.  * and so we return immediately to avoid recursion.
  111.  * ----------------
  112.  */
  113. if (proc_exit_inprogress++)
  114. return;
  115. /* do our shared memory exits first */
  116. shmem_exit(code);
  117. /* ----------------
  118.  * call all the callbacks registered before calling exit().
  119.  * ----------------
  120.  */
  121. for (i = on_proc_exit_index - 1; i >= 0; --i)
  122. (*on_proc_exit_list[i].function) (code, on_proc_exit_list[i].arg);
  123. exit:
  124. TPRINTF(TRACE_VERBOSE, "exit(%d)", code);
  125. exit(code);
  126. }
  127. /* ------------------
  128.  * Run all of the on_shmem_exit routines but don't exit in the end.
  129.  * This is used by the postmaster to re-initialize shared memory and
  130.  * semaphores after a backend dies horribly
  131.  * ------------------
  132.  */
  133. static int shmem_exit_inprogress = 0;
  134. void
  135. shmem_exit(int code)
  136. {
  137. int i;
  138. TPRINTF(TRACE_VERBOSE, "shmem_exit(%d) [#%d]",
  139. code, shmem_exit_inprogress);
  140. /*
  141.  * If shmem_exit is called too many times something bad is happenig,
  142.  * so exit immediately.
  143.  */
  144. if (shmem_exit_inprogress > 9)
  145. {
  146. elog(ERROR, "infinite recursion in shmem_exit");
  147. exit(-1);
  148. }
  149. /* ----------------
  150.  * if shmem_exit_inprocess is true, then it means that we
  151.  * are being invoked from within an on_exit() handler
  152.  * and so we return immediately to avoid recursion.
  153.  * ----------------
  154.  */
  155. if (shmem_exit_inprogress++)
  156. return;
  157. /* ----------------
  158.  * call all the callbacks registered before calling exit().
  159.  * ----------------
  160.  */
  161. for (i = on_shmem_exit_index - 1; i >= 0; --i)
  162. (*on_shmem_exit_list[i].function) (code, on_shmem_exit_list[i].arg);
  163. on_shmem_exit_index = 0;
  164. shmem_exit_inprogress = 0;
  165. }
  166. /* ----------------------------------------------------------------
  167.  * on_proc_exit
  168.  *
  169.  * this function adds a callback function to the list of
  170.  * functions invoked by proc_exit(). -cim 2/6/90
  171.  * ----------------------------------------------------------------
  172.  */
  173. int
  174. on_proc_exit(void (*function) (), caddr_t arg)
  175. {
  176. if (on_proc_exit_index >= MAX_ON_EXITS)
  177. return -1;
  178. on_proc_exit_list[on_proc_exit_index].function = function;
  179. on_proc_exit_list[on_proc_exit_index].arg = arg;
  180. ++on_proc_exit_index;
  181. return 0;
  182. }
  183. /* ----------------------------------------------------------------
  184.  * on_shmem_exit
  185.  *
  186.  * this function adds a callback function to the list of
  187.  * functions invoked by shmem_exit(). -cim 2/6/90
  188.  * ----------------------------------------------------------------
  189.  */
  190. int
  191. on_shmem_exit(void (*function) (), caddr_t arg)
  192. {
  193. if (on_shmem_exit_index >= MAX_ON_EXITS)
  194. return -1;
  195. on_shmem_exit_list[on_shmem_exit_index].function = function;
  196. on_shmem_exit_list[on_shmem_exit_index].arg = arg;
  197. ++on_shmem_exit_index;
  198. return 0;
  199. }
  200. /* ----------------------------------------------------------------
  201.  * on_exit_reset
  202.  *
  203.  * this function clears all proc_exit() registered functions.
  204.  * ----------------------------------------------------------------
  205.  */
  206. void
  207. on_exit_reset(void)
  208. {
  209. on_shmem_exit_index = 0;
  210. on_proc_exit_index = 0;
  211. }
  212. /****************************************************************************/
  213. /*  IPCPrivateSemaphoreKill(status, semId) */
  214. /* */
  215. /****************************************************************************/
  216. static void
  217. IPCPrivateSemaphoreKill(int status,
  218. int semId) /* caddr_t */
  219. {
  220. union semun semun;
  221. semctl(semId, 0, IPC_RMID, semun);
  222. }
  223. /****************************************************************************/
  224. /*  IPCPrivateMemoryKill(status, shmId) */
  225. /* */
  226. /****************************************************************************/
  227. static void
  228. IPCPrivateMemoryKill(int status,
  229.  int shmId) /* caddr_t */
  230. {
  231. if (UsePrivateMemory)
  232. {
  233. /* free ( IpcPrivateMem[shmId].memptr ); */
  234. }
  235. else
  236. {
  237. if (shmctl(shmId, IPC_RMID, (struct shmid_ds *) NULL) < 0)
  238. {
  239. elog(NOTICE, "IPCPrivateMemoryKill: shmctl(%d, %d, 0) failed: %m",
  240.  shmId, IPC_RMID);
  241. }
  242. }
  243. }
  244. /****************************************************************************/
  245. /*  IpcSemaphoreCreate(semKey, semNum, permission, semStartValue) */
  246. /* */
  247. /*   - returns a semaphore identifier: */
  248. /* */
  249. /* if key doesn't exist: return a new id,      status:= IpcSemIdNotExist    */
  250. /* if key exists:  return the old id,    status:= IpcSemIdExist */
  251. /* if semNum > MAX :  return # of argument, status:=IpcInvalidArgument */
  252. /* */
  253. /****************************************************************************/
  254. /*
  255.  * Note:
  256.  * XXX This should be split into two different calls. One should
  257.  * XXX be used to create a semaphore set. The other to "attach" a
  258.  * XXX existing set.  It should be an error for the semaphore set
  259.  * XXX to to already exist or for it not to, respectively.
  260.  *
  261.  * Currently, the semaphore sets are "attached" and an error
  262.  * is detected only when a later shared memory attach fails.
  263.  */
  264. IpcSemaphoreId
  265. IpcSemaphoreCreate(IpcSemaphoreKey semKey,
  266.    int semNum,
  267.    int permission,
  268.    int semStartValue,
  269.    int removeOnExit,
  270.    int *status)
  271. {
  272. int i;
  273. int errStatus;
  274. int semId;
  275. u_short array[IPC_NMAXSEM];
  276. union semun semun;
  277. /* get a semaphore if non-existent */
  278. /* check arguments */
  279. if (semNum > IPC_NMAXSEM || semNum <= 0)
  280. {
  281. *status = IpcInvalidArgument;
  282. return 2; /* returns the number of the invalid
  283.  * argument   */
  284. }
  285. semId = semget(semKey, 0, 0);
  286. if (semId == -1)
  287. {
  288. *status = IpcSemIdNotExist; /* there doesn't exist a semaphore */
  289. #ifdef DEBUG_IPC
  290. EPRINTF("calling semget with %d, %d , %dn",
  291. semKey,
  292. semNum,
  293. IPC_CREAT | permission);
  294. #endif
  295. semId = semget(semKey, semNum, IPC_CREAT | permission);
  296. if (semId < 0)
  297. {
  298. EPRINTF("IpcSemaphoreCreate: semget failed (%s) "
  299. "key=%d, num=%d, permission=%o",
  300. strerror(errno), semKey, semNum, permission);
  301. proc_exit(3);
  302. }
  303. for (i = 0; i < semNum; i++)
  304. array[i] = semStartValue;
  305. semun.array = array;
  306. errStatus = semctl(semId, 0, SETALL, semun);
  307. if (errStatus == -1)
  308. {
  309. EPRINTF("IpcSemaphoreCreate: semctl failed (%s) id=%d",
  310. strerror(errno), semId);
  311. }
  312. if (removeOnExit)
  313. on_shmem_exit(IPCPrivateSemaphoreKill, (caddr_t) semId);
  314. }
  315. else
  316. {
  317. /* there is a semaphore id for this key */
  318. *status = IpcSemIdExist;
  319. }
  320. #ifdef DEBUG_IPC
  321. EPRINTF("nIpcSemaphoreCreate, status %d, returns %dn",
  322. *status,
  323. semId);
  324. fflush(stdout);
  325. fflush(stderr);
  326. #endif
  327. return semId;
  328. }
  329. /****************************************************************************/
  330. /*  IpcSemaphoreSet() - sets the initial value of the semaphore */
  331. /* */
  332. /* note: the xxx_return variables are only used for debugging. */
  333. /****************************************************************************/
  334. #ifdef NOT_USED
  335. static int IpcSemaphoreSet_return;
  336. void
  337. IpcSemaphoreSet(int semId, int semno, int value)
  338. {
  339. int errStatus;
  340. union semun semun;
  341. semun.val = value;
  342. errStatus = semctl(semId, semno, SETVAL, semun);
  343. IpcSemaphoreSet_return = errStatus;
  344. if (errStatus == -1)
  345. {
  346. EPRINTF("IpcSemaphoreSet: semctl failed (%s) id=%d",
  347. strerror(errno), semId);
  348. }
  349. }
  350. #endif
  351. /****************************************************************************/
  352. /*  IpcSemaphoreKill(key) - removes a semaphore */
  353. /* */
  354. /****************************************************************************/
  355. void
  356. IpcSemaphoreKill(IpcSemaphoreKey key)
  357. {
  358. int semId;
  359. union semun semun;
  360. /* kill semaphore if existent */
  361. semId = semget(key, 0, 0);
  362. if (semId != -1)
  363. semctl(semId, 0, IPC_RMID, semun);
  364. }
  365. /****************************************************************************/
  366. /*  IpcSemaphoreLock(semId, sem, lock) - locks a semaphore */
  367. /* */
  368. /* note: the xxx_return variables are only used for debugging. */
  369. /****************************************************************************/
  370. static int IpcSemaphoreLock_return;
  371. void
  372. IpcSemaphoreLock(IpcSemaphoreId semId, int sem, int lock)
  373. {
  374. extern int errno;
  375. int errStatus;
  376. struct sembuf sops;
  377. sops.sem_op = lock;
  378. sops.sem_flg = 0;
  379. sops.sem_num = sem;
  380. /* ----------------
  381.  * Note: if errStatus is -1 and errno == EINTR then it means we
  382.  *   returned from the operation prematurely because we were
  383.  *   sent a signal.  So we try and lock the semaphore again.
  384.  *   I am not certain this is correct, but the semantics aren't
  385.  *   clear it fixes problems with parallel abort synchronization,
  386.  *   namely that after processing an abort signal, the semaphore
  387.  *   call returns with -1 (and errno == EINTR) before it should.
  388.  *   -cim 3/28/90
  389.  * ----------------
  390.  */
  391. do
  392. {
  393. errStatus = semop(semId, &sops, 1);
  394. } while (errStatus == -1 && errno == EINTR);
  395. IpcSemaphoreLock_return = errStatus;
  396. if (errStatus == -1)
  397. {
  398. EPRINTF("IpcSemaphoreLock: semop failed (%s) id=%d",
  399. strerror(errno), semId);
  400. proc_exit(255);
  401. }
  402. }
  403. /****************************************************************************/
  404. /*  IpcSemaphoreUnlock(semId, sem, lock) - unlocks a semaphore */
  405. /* */
  406. /* note: the xxx_return variables are only used for debugging. */
  407. /****************************************************************************/
  408. static int IpcSemaphoreUnlock_return;
  409. void
  410. IpcSemaphoreUnlock(IpcSemaphoreId semId, int sem, int lock)
  411. {
  412. extern int errno;
  413. int errStatus;
  414. struct sembuf sops;
  415. sops.sem_op = -lock;
  416. sops.sem_flg = 0;
  417. sops.sem_num = sem;
  418. /* ----------------
  419.  * Note: if errStatus is -1 and errno == EINTR then it means we
  420.  *   returned from the operation prematurely because we were
  421.  *   sent a signal.  So we try and lock the semaphore again.
  422.  *   I am not certain this is correct, but the semantics aren't
  423.  *   clear it fixes problems with parallel abort synchronization,
  424.  *   namely that after processing an abort signal, the semaphore
  425.  *   call returns with -1 (and errno == EINTR) before it should.
  426.  *   -cim 3/28/90
  427.  * ----------------
  428.  */
  429. do
  430. {
  431. errStatus = semop(semId, &sops, 1);
  432. } while (errStatus == -1 && errno == EINTR);
  433. IpcSemaphoreUnlock_return = errStatus;
  434. if (errStatus == -1)
  435. {
  436. EPRINTF("IpcSemaphoreUnlock: semop failed (%s) id=%d",
  437. strerror(errno), semId);
  438. proc_exit(255);
  439. }
  440. }
  441. int
  442. IpcSemaphoreGetCount(IpcSemaphoreId semId, int sem)
  443. {
  444. int semncnt;
  445. union semun dummy; /* for Solaris */
  446. semncnt = semctl(semId, sem, GETNCNT, dummy);
  447. return semncnt;
  448. }
  449. int
  450. IpcSemaphoreGetValue(IpcSemaphoreId semId, int sem)
  451. {
  452. int semval;
  453. union semun dummy; /* for Solaris */
  454. semval = semctl(semId, sem, GETVAL, dummy);
  455. return semval;
  456. }
  457. /****************************************************************************/
  458. /*  IpcMemoryCreate(memKey) */
  459. /* */
  460. /*   - returns the memory identifier, if creation succeeds */
  461. /* returns IpcMemCreationFailed, if failure */
  462. /****************************************************************************/
  463. IpcMemoryId
  464. IpcMemoryCreate(IpcMemoryKey memKey, uint32 size, int permission)
  465. {
  466. IpcMemoryId shmid;
  467. if (memKey == PrivateIPCKey)
  468. {
  469. /* private */
  470. shmid = PrivateMemoryCreate(memKey, size);
  471. }
  472. else
  473. shmid = shmget(memKey, size, IPC_CREAT | permission);
  474. if (shmid < 0)
  475. {
  476. EPRINTF("IpcMemoryCreate: shmget failed (%s) "
  477. "key=%d, size=%d, permission=%o",
  478. strerror(errno), memKey, size, permission);
  479. return IpcMemCreationFailed;
  480. }
  481. /* if (memKey == PrivateIPCKey) */
  482. on_shmem_exit(IPCPrivateMemoryKill, (caddr_t) shmid);
  483. return shmid;
  484. }
  485. /****************************************************************************/
  486. /* IpcMemoryIdGet(memKey, size) returns the shared memory Id */
  487. /* or IpcMemIdGetFailed */
  488. /****************************************************************************/
  489. IpcMemoryId
  490. IpcMemoryIdGet(IpcMemoryKey memKey, uint32 size)
  491. {
  492. IpcMemoryId shmid;
  493. shmid = shmget(memKey, size, 0);
  494. if (shmid < 0)
  495. {
  496. EPRINTF("IpcMemoryIdGet: shmget failed (%s) "
  497. "key=%d, size=%d, permission=%o",
  498. strerror(errno), memKey, size, 0);
  499. return IpcMemIdGetFailed;
  500. }
  501. return shmid;
  502. }
  503. /****************************************************************************/
  504. /* IpcMemoryDetach(status, shmaddr) removes a shared memory segment */
  505. /* from a backend address space */
  506. /* (only called by backends running under the postmaster) */
  507. /****************************************************************************/
  508. static void
  509. IpcMemoryDetach(int status, char *shmaddr)
  510. {
  511. if (shmdt(shmaddr) < 0)
  512. elog(NOTICE, "IpcMemoryDetach: shmdt(0x%x): %m", shmaddr);
  513. }
  514. /****************************************************************************/
  515. /* IpcMemoryAttach(memId)   returns the adress of shared memory */
  516. /*   or IpcMemAttachFailed */
  517. /* */
  518. /* CALL IT:  addr = (struct <MemoryStructure> *) IpcMemoryAttach(memId); */
  519. /* */
  520. /****************************************************************************/
  521. char *
  522. IpcMemoryAttach(IpcMemoryId memId)
  523. {
  524. char    *memAddress;
  525. if (UsePrivateMemory)
  526. memAddress = (char *) PrivateMemoryAttach(memId);
  527. else
  528. memAddress = (char *) shmat(memId, 0, 0);
  529. /* if ( *memAddress == -1) { XXX ??? */
  530. if (memAddress == (char *) -1)
  531. {
  532. EPRINTF("IpcMemoryAttach: shmat failed (%s) id=%d",
  533. strerror(errno), memId);
  534. return IpcMemAttachFailed;
  535. }
  536. if (!UsePrivateMemory)
  537. on_shmem_exit(IpcMemoryDetach, (caddr_t) memAddress);
  538. return (char *) memAddress;
  539. }
  540. /****************************************************************************/
  541. /* IpcMemoryKill(memKey) removes a shared memory segment */
  542. /* (only called by the postmaster and standalone backends) */
  543. /****************************************************************************/
  544. void
  545. IpcMemoryKill(IpcMemoryKey memKey)
  546. {
  547. IpcMemoryId shmid;
  548. if (!UsePrivateMemory && (shmid = shmget(memKey, 0, 0)) >= 0)
  549. {
  550. if (shmctl(shmid, IPC_RMID, (struct shmid_ds *) NULL) < 0)
  551. {
  552. elog(NOTICE, "IpcMemoryKill: shmctl(%d, %d, 0) failed: %m",
  553.  shmid, IPC_RMID);
  554. }
  555. }
  556. }
  557. #ifdef HAS_TEST_AND_SET
  558. /* ------------------
  559.  * use hardware locks to replace semaphores for sequent machines
  560.  * to avoid costs of swapping processes and to provide unlimited
  561.  * supply of locks.
  562.  * ------------------
  563.  */
  564. /* used in spin.c */
  565. SLock    *SLockArray = NULL;
  566. static SLock **FreeSLockPP;
  567. static int *UnusedSLockIP;
  568. static slock_t *SLockMemoryLock;
  569. static IpcMemoryId SLockMemoryId = -1;
  570. struct ipcdummy
  571. { /* to get alignment/size right */
  572. SLock    *free;
  573. int unused;
  574. slock_t memlock;
  575. SLock slocks[MAX_SPINS + 1];
  576. };
  577. #define SLOCKMEMORYSIZE sizeof(struct ipcdummy)
  578. void
  579. CreateAndInitSLockMemory(IPCKey key)
  580. {
  581. int id;
  582. SLock    *slckP;
  583. SLockMemoryId = IpcMemoryCreate(key,
  584. SLOCKMEMORYSIZE,
  585. 0700);
  586. AttachSLockMemory(key);
  587. *FreeSLockPP = NULL;
  588. *UnusedSLockIP = (int) FIRSTFREELOCKID;
  589. for (id = 0; id < (int) FIRSTFREELOCKID; id++)
  590. {
  591. slckP = &(SLockArray[id]);
  592. S_INIT_LOCK(&(slckP->locklock));
  593. slckP->flag = NOLOCK;
  594. slckP->nshlocks = 0;
  595. S_INIT_LOCK(&(slckP->shlock));
  596. S_INIT_LOCK(&(slckP->exlock));
  597. S_INIT_LOCK(&(slckP->comlock));
  598. slckP->next = NULL;
  599. }
  600. return;
  601. }
  602. void
  603. AttachSLockMemory(IPCKey key)
  604. {
  605. struct ipcdummy *slockM;
  606. if (SLockMemoryId == -1)
  607. SLockMemoryId = IpcMemoryIdGet(key, SLOCKMEMORYSIZE);
  608. if (SLockMemoryId == -1)
  609. elog(FATAL, "SLockMemory not in shared memory");
  610. slockM = (struct ipcdummy *) IpcMemoryAttach(SLockMemoryId);
  611. if (slockM == IpcMemAttachFailed)
  612. elog(FATAL, "AttachSLockMemory: could not attach segment");
  613. FreeSLockPP = (SLock **) &(slockM->free);
  614. UnusedSLockIP = (int *) &(slockM->unused);
  615. SLockMemoryLock = (slock_t *) &(slockM->memlock);
  616. S_INIT_LOCK(SLockMemoryLock);
  617. SLockArray = (SLock *) &(slockM->slocks[0]);
  618. return;
  619. }
  620. #ifdef NOT_USED
  621. bool
  622. LockIsFree(int lockid)
  623. {
  624. return SLockArray[lockid].flag == NOLOCK;
  625. }
  626. #endif
  627. #endif  /* HAS_TEST_AND_SET */
  628. #ifdef NOT_USED
  629. static void
  630. IpcConfigTip(void)
  631. {
  632. fprintf(stderr, "This type of error is usually caused by impropern");
  633. fprintf(stderr, "shared memory or System V IPC semaphore configuration.n");
  634. fprintf(stderr, "See the FAQ for more detailed informationn");
  635. }
  636. #endif