fix sem_open and sem_close to obey posix semantics
authorRich Felker <dalias@aerifal.cx>
Fri, 11 Mar 2011 02:34:19 +0000 (21:34 -0500)
committerRich Felker <dalias@aerifal.cx>
Fri, 11 Mar 2011 02:34:19 +0000 (21:34 -0500)
multiple opens of the same named semaphore must return the same
pointer, and only the last close can unmap it. thus the ugly global
state keeping track of mappings. the maximum number of distinct named
semaphores that can be opened is limited sufficiently small that the
linear searches take trivial time, especially compared to the syscall
overhead of these functions.

include/limits.h
src/conf/sysconf.c
src/thread/sem_close.c [deleted file]
src/thread/sem_open.c

index e12819bab3a94cc63240ce5bd4033d407654b556..55ad82315b39193f845a7cd512c5a689fc97471a 100644 (file)
@@ -30,6 +30,7 @@
 #define PTHREAD_STACK_MIN PAGE_SIZE
 #define PTHREAD_DESTRUCTOR_ITERATIONS 4
 #define SEM_VALUE_MAX 0x7fffffff
+#define SEM_NSEMS_MAX 256
 
 /* Arbitrary numbers... */
 
index cdaeb2a646b5588442195193a4be7590a979f5aa..a660257d6fbf7db265e4f44121a1f7273ea105d0 100644 (file)
@@ -40,8 +40,8 @@ long sysconf(int name)
                [_SC_VERSION] = VER,
                [_SC_PAGE_SIZE] = PAGE_SIZE,
                [_SC_RTSIG_MAX] = 63, /* ?? */
-               [_SC_SEM_NSEMS_MAX] = _POSIX_SEM_NSEMS_MAX,
-               [_SC_SEM_VALUE_MAX] = _POSIX_SEM_VALUE_MAX,
+               [_SC_SEM_NSEMS_MAX] = SEM_NSEMS_MAX,
+               [_SC_SEM_VALUE_MAX] = OFLOW,
                [_SC_SIGQUEUE_MAX] = -1,
                [_SC_TIMER_MAX] = -1,
                [_SC_BC_BASE_MAX] = _POSIX2_BC_BASE_MAX,
@@ -215,8 +215,8 @@ long sysconf(int name)
        } else if (values[name] == VER) {
                return _POSIX_VERSION;
        } else if (values[name] == OFLOW) {
-               return ARG_MAX;
-       } else {
-               return values[name];
+               if (name == _SC_ARG_MAX) return ARG_MAX;
+               if (name == _SC_SEM_VALUE_MAX) return SEM_VALUE_MAX;
        }
+       return values[name];
 }
diff --git a/src/thread/sem_close.c b/src/thread/sem_close.c
deleted file mode 100644 (file)
index 036ee54..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-#include <semaphore.h>
-#include <sys/mman.h>
-
-int sem_close(sem_t *sem)
-{
-       return munmap(sem, sizeof *sem);
-}
index 55d5ef6b7808bae8e070e1d979d0b67083407375..6fff71a8512090eb588bb0329e30a51917ca8242 100644 (file)
@@ -9,32 +9,32 @@
 #include <time.h>
 #include <stdio.h>
 #include <sys/stat.h>
+#include <stdlib.h>
+#include <pthread.h>
 
-static void *find_map(int fd)
+static struct {
+       ino_t ino;
+       sem_t *sem;
+       int refcnt;
+} *semtab;
+
+static int semcnt;
+static pthread_spinlock_t lock;
+static pthread_once_t once;
+
+static void init()
 {
-       char c;
-       struct stat st;
-       FILE *f;
-       void *addr;
-       unsigned long long off, ino;
-       char buf[100];
+       semtab = calloc(sizeof *semtab, SEM_NSEMS_MAX);
+}
 
-       if (fstat(fd, &st) < 0) return 0;
-       if (!(f = fopen("/proc/self/maps", "rb"))) return 0;
-       
-       while (fgets(buf, sizeof buf, f)) {
-               sscanf(buf, "%lx-%*lx %*s %llx %*x:%*x %llu /dev/shm%c",
-                       (long *)&addr, &off, &ino, &c);
-               while (!strchr(buf, '\n') && fgets(buf, sizeof buf, f));
-               if (c!='/') continue;
-               c = 0;
-               if (!off && st.st_ino == ino) {
-                       fclose(f);
-                       return addr;
-               }
-       }
-       fclose(f);
-       return 0;
+static sem_t *find_map(ino_t ino)
+{
+       int i;
+       for (i=0; i<SEM_NSEMS_MAX && semtab[i].ino != ino; i++);
+       if (i==SEM_NSEMS_MAX) return 0;
+       if (semtab[i].refcnt == INT_MAX) return (sem_t *)-1;
+       semtab[i].refcnt++;
+       return semtab[i].sem;
 }
 
 sem_t *sem_open(const char *name, int flags, ...)
@@ -47,6 +47,8 @@ sem_t *sem_open(const char *name, int flags, ...)
        void *map;
        char tmp[64];
        struct timespec ts;
+       struct stat st;
+       int i;
 
        while (*name=='/') name++;
        if (strchr(name, '/')) {
@@ -54,6 +56,12 @@ sem_t *sem_open(const char *name, int flags, ...)
                return SEM_FAILED;
        }
 
+       pthread_once(&once, init);
+       if (!semtab) {
+               errno = ENOMEM;
+               return SEM_FAILED;
+       }
+
        if (flags & O_CREAT) {
                va_start(ap, flags);
                mode = va_arg(ap, mode_t) & 0666;
@@ -81,6 +89,8 @@ sem_t *sem_open(const char *name, int flags, ...)
        flags &= ~O_ACCMODE;
        flags |= O_RDWR;
 
+       pthread_spin_lock(&lock);
+
        for (;;) {
                if (!(flags & O_EXCL)) {
                        fd = shm_open(name, flags&~O_CREAT, mode);
@@ -90,9 +100,21 @@ sem_t *sem_open(const char *name, int flags, ...)
                                        close(tfd);
                                        unlink(tmp);
                                }
-                               if (fd < 0) return SEM_FAILED;
-                               if ((map = find_map(fd)))
+                               if (fstat(fd, &st) < 0) {
+                                       close(fd);
+                                       fd = -1;
+                               }
+                               if (fd < 0) {
+                                       pthread_spin_unlock(&lock);
+                                       return SEM_FAILED;
+                               }
+                               if ((map = find_map(st.st_ino))) {
+                                       pthread_spin_unlock(&lock);
+                                       close(fd);
+                                       if (map == (sem_t *)-1)
+                                               return SEM_FAILED;
                                        return map;
+                               }
                                break;
                        }
                }
@@ -109,8 +131,40 @@ sem_t *sem_open(const char *name, int flags, ...)
                        return SEM_FAILED;
                }
        }
+       if (fstat(fd, &st) < 0) {
+               pthread_spin_unlock(&lock);
+               close(fd);
+               return SEM_FAILED;
+       }
+       if (semcnt == SEM_NSEMS_MAX) {
+               pthread_spin_unlock(&lock);
+               close(fd);
+               errno = EMFILE;
+               return SEM_FAILED;
+       }
+       for (i=0; i<SEM_NSEMS_MAX && semtab[i].sem; i++);
        map = mmap(0, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        close(fd);
-       if (map == MAP_FAILED) return SEM_FAILED;
+       if (map == MAP_FAILED) {
+               pthread_spin_unlock(&lock);
+               return SEM_FAILED;
+       }
+       semtab[i].ino = st.st_ino;
+       semtab[i].sem = map;
+       semtab[i].refcnt = 1;
+       pthread_spin_unlock(&lock);
        return map;
 }
+
+int sem_close(sem_t *sem)
+{
+       int i;
+       pthread_spin_lock(&lock);
+       for (i=0; i<SEM_NSEMS_MAX && semtab[i].sem != sem; i++);
+       if (!--semtab[i].refcnt) {
+               semtab[i].sem = 0;
+               semtab[i].ino = 0;
+       }
+       pthread_spin_unlock(&lock);
+       return munmap(sem, sizeof *sem);
+}