| /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| * (the "License"); you may not use this file except in compliance with | |
| * the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /* | |
| time-sem.c has the basics of the semaphores we use in http_main.c. It's | |
| intended for timing differences between various methods on an | |
| architecture. In practice we've found many things affect which semaphore | |
| to be used: | |
| - NFS filesystems absolutely suck for fcntl() and flock() | |
| - uslock absolutely sucks on single-processor IRIX boxes, but | |
| absolutely rocks on multi-processor boxes. The converse | |
| is true for fcntl. sysvsem seems a moderate balance. | |
| - Under Solaris you can't have too many processes use SEM_UNDO, there | |
| might be a tuneable somewhere that increases the limit from 29. | |
| We're not sure what the tunable is, so there's a define | |
| NO_SEM_UNDO which can be used to simulate us trapping/blocking | |
| signals to be able to properly release the semaphore on a clean | |
| child death. You'll also need to define NEED_UNION_SEMUN | |
| under solaris. | |
| You'll need to define USE_SHMGET_SCOREBOARD if anonymous shared mmap() | |
| doesn't work on your system (i.e. linux). | |
| argv[1] is the #children, argv[2] is the #iterations per child | |
| You should run each over many different #children inputs, and choose | |
| #iter such that the program runs for at least a second or so... or even | |
| longer depending on your patience. | |
| compile with: | |
| gcc -o time-FCNTL -Wall -O time-sem.c -DUSE_FCNTL_SERIALIZED_ACCEPT | |
| gcc -o time-FLOCK -Wall -O time-sem.c -DUSE_FLOCK_SERIALIZED_ACCEPT | |
| gcc -o time-SYSVSEM -Wall -O time-sem.c -DUSE_SYSVSEM_SERIALIZED_ACCEPT | |
| gcc -o time-SYSVSEM2 -Wall -O time-sem.c -DUSE_SYSVSEM_SERIALIZED_ACCEPT -DNO_SEM_UNDO | |
| gcc -o time-PTHREAD -Wall -O time-sem.c -DUSE_PTHREAD_SERIALIZED_ACCEPT -lpthread | |
| gcc -o time-USLOCK -Wall -O time-sem.c -DUSE_USLOCK_SERIALIZED_ACCEPT | |
| not all versions work on all systems. | |
| */ | |
| #include <errno.h> | |
| #include <sys/time.h> | |
| #include <unistd.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <stdlib.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <fcntl.h> | |
| #include <sys/wait.h> | |
| #include <sys/mman.h> | |
| #include <signal.h> | |
| #if defined(USE_FCNTL_SERIALIZED_ACCEPT) | |
| static struct flock lock_it; | |
| static struct flock unlock_it; | |
| static int fcntl_fd=-1; | |
| #define accept_mutex_child_init() | |
| #define accept_mutex_cleanup() | |
| /* | |
| * Initialize mutex lock. | |
| * Must be safe to call this on a restart. | |
| */ | |
| void | |
| accept_mutex_init(void) | |
| { | |
| lock_it.l_whence = SEEK_SET; /* from current point */ | |
| lock_it.l_start = 0; /* -"- */ | |
| lock_it.l_len = 0; /* until end of file */ | |
| lock_it.l_type = F_WRLCK; /* set exclusive/write lock */ | |
| lock_it.l_pid = 0; /* pid not actually interesting */ | |
| unlock_it.l_whence = SEEK_SET; /* from current point */ | |
| unlock_it.l_start = 0; /* -"- */ | |
| unlock_it.l_len = 0; /* until end of file */ | |
| unlock_it.l_type = F_UNLCK; /* set exclusive/write lock */ | |
| unlock_it.l_pid = 0; /* pid not actually interesting */ | |
| printf("opening test-lock-thing in current directory\n"); | |
| fcntl_fd = open("test-lock-thing", O_CREAT | O_WRONLY | O_EXCL, 0644); | |
| if (fcntl_fd == -1) | |
| { | |
| perror ("open"); | |
| fprintf (stderr, "Cannot open lock file: %s\n", "test-lock-thing"); | |
| exit (1); | |
| } | |
| unlink("test-lock-thing"); | |
| } | |
| void accept_mutex_on(void) | |
| { | |
| int ret; | |
| while ((ret = fcntl(fcntl_fd, F_SETLKW, &lock_it)) < 0 && errno == EINTR) | |
| continue; | |
| if (ret < 0) { | |
| perror ("fcntl lock_it"); | |
| exit(1); | |
| } | |
| } | |
| void accept_mutex_off(void) | |
| { | |
| if (fcntl (fcntl_fd, F_SETLKW, &unlock_it) < 0) | |
| { | |
| perror ("fcntl unlock_it"); | |
| exit(1); | |
| } | |
| } | |
| #elif defined(USE_FLOCK_SERIALIZED_ACCEPT) | |
| #include <sys/file.h> | |
| static int flock_fd=-1; | |
| #define FNAME "test-lock-thing" | |
| /* | |
| * Initialize mutex lock. | |
| * Must be safe to call this on a restart. | |
| */ | |
| void accept_mutex_init(void) | |
| { | |
| printf("opening " FNAME " in current directory\n"); | |
| flock_fd = open(FNAME, O_CREAT | O_WRONLY | O_EXCL, 0644); | |
| if (flock_fd == -1) | |
| { | |
| perror ("open"); | |
| fprintf (stderr, "Cannot open lock file: %s\n", "test-lock-thing"); | |
| exit (1); | |
| } | |
| } | |
| void accept_mutex_child_init(void) | |
| { | |
| flock_fd = open(FNAME, O_WRONLY, 0600); | |
| if (flock_fd == -1) { | |
| perror("open"); | |
| exit(1); | |
| } | |
| } | |
| void accept_mutex_cleanup(void) | |
| { | |
| unlink(FNAME); | |
| } | |
| void accept_mutex_on(void) | |
| { | |
| int ret; | |
| while ((ret = flock(flock_fd, LOCK_EX)) < 0 && errno == EINTR) | |
| continue; | |
| if (ret < 0) { | |
| perror ("flock(LOCK_EX)"); | |
| exit(1); | |
| } | |
| } | |
| void accept_mutex_off(void) | |
| { | |
| if (flock (flock_fd, LOCK_UN) < 0) | |
| { | |
| perror ("flock(LOCK_UN)"); | |
| exit(1); | |
| } | |
| } | |
| #elif defined (USE_SYSVSEM_SERIALIZED_ACCEPT) | |
| #include <sys/types.h> | |
| #include <sys/ipc.h> | |
| #include <sys/sem.h> | |
| static int sem_id = -1; | |
| #ifdef NO_SEM_UNDO | |
| static sigset_t accept_block_mask; | |
| static sigset_t accept_previous_mask; | |
| #endif | |
| #define accept_mutex_child_init() | |
| #define accept_mutex_cleanup() | |
| void accept_mutex_init(void) | |
| { | |
| #ifdef NEED_UNION_SEMUN | |
| /* believe it or not, you need to define this under solaris */ | |
| union semun { | |
| int val; | |
| struct semid_ds *buf; | |
| ushort *array; | |
| }; | |
| #endif | |
| union semun ick; | |
| sem_id = semget(999, 1, IPC_CREAT | 0666); | |
| if (sem_id < 0) { | |
| perror ("semget"); | |
| exit (1); | |
| } | |
| ick.val = 1; | |
| if (semctl(sem_id, 0, SETVAL, ick) < 0) { | |
| perror ("semctl"); | |
| exit(1); | |
| } | |
| #ifdef NO_SEM_UNDO | |
| sigfillset(&accept_block_mask); | |
| sigdelset(&accept_block_mask, SIGHUP); | |
| sigdelset(&accept_block_mask, SIGTERM); | |
| sigdelset(&accept_block_mask, SIGUSR1); | |
| #endif | |
| } | |
| void accept_mutex_on() | |
| { | |
| struct sembuf op; | |
| #ifdef NO_SEM_UNDO | |
| if (sigprocmask(SIG_BLOCK, &accept_block_mask, &accept_previous_mask)) { | |
| perror("sigprocmask(SIG_BLOCK)"); | |
| exit (1); | |
| } | |
| op.sem_flg = 0; | |
| #else | |
| op.sem_flg = SEM_UNDO; | |
| #endif | |
| op.sem_num = 0; | |
| op.sem_op = -1; | |
| if (semop(sem_id, &op, 1) < 0) { | |
| perror ("accept_mutex_on"); | |
| exit (1); | |
| } | |
| } | |
| void accept_mutex_off() | |
| { | |
| struct sembuf op; | |
| op.sem_num = 0; | |
| op.sem_op = 1; | |
| #ifdef NO_SEM_UNDO | |
| op.sem_flg = 0; | |
| #else | |
| op.sem_flg = SEM_UNDO; | |
| #endif | |
| if (semop(sem_id, &op, 1) < 0) { | |
| perror ("accept_mutex_off"); | |
| exit (1); | |
| } | |
| #ifdef NO_SEM_UNDO | |
| if (sigprocmask(SIG_SETMASK, &accept_previous_mask, NULL)) { | |
| perror("sigprocmask(SIG_SETMASK)"); | |
| exit (1); | |
| } | |
| #endif | |
| } | |
| #elif defined (USE_PTHREAD_SERIALIZED_ACCEPT) | |
| /* note: pthread mutexes aren't released on child death, hence the | |
| * signal goop ... in a real implementation we'd do special things | |
| * during hup, term, usr1. | |
| */ | |
| #include <pthread.h> | |
| static pthread_mutex_t *mutex; | |
| static sigset_t accept_block_mask; | |
| static sigset_t accept_previous_mask; | |
| #define accept_mutex_child_init() | |
| #define accept_mutex_cleanup() | |
| void accept_mutex_init(void) | |
| { | |
| pthread_mutexattr_t mattr; | |
| int fd; | |
| fd = open ("/dev/zero", O_RDWR); | |
| if (fd == -1) { | |
| perror ("open(/dev/zero)"); | |
| exit (1); | |
| } | |
| mutex = (pthread_mutex_t *)mmap ((caddr_t)0, sizeof (*mutex), | |
| PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); | |
| if (mutex == (void *)(caddr_t)-1) { | |
| perror ("mmap"); | |
| exit (1); | |
| } | |
| close (fd); | |
| if (pthread_mutexattr_init(&mattr)) { | |
| perror ("pthread_mutexattr_init"); | |
| exit (1); | |
| } | |
| if (pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED)) { | |
| perror ("pthread_mutexattr_setpshared"); | |
| exit (1); | |
| } | |
| if (pthread_mutex_init(mutex, &mattr)) { | |
| perror ("pthread_mutex_init"); | |
| exit (1); | |
| } | |
| sigfillset(&accept_block_mask); | |
| sigdelset(&accept_block_mask, SIGHUP); | |
| sigdelset(&accept_block_mask, SIGTERM); | |
| sigdelset(&accept_block_mask, SIGUSR1); | |
| } | |
| void accept_mutex_on() | |
| { | |
| if (sigprocmask(SIG_BLOCK, &accept_block_mask, &accept_previous_mask)) { | |
| perror("sigprocmask(SIG_BLOCK)"); | |
| exit (1); | |
| } | |
| if (pthread_mutex_lock (mutex)) { | |
| perror ("pthread_mutex_lock"); | |
| exit (1); | |
| } | |
| } | |
| void accept_mutex_off() | |
| { | |
| if (pthread_mutex_unlock (mutex)) { | |
| perror ("pthread_mutex_unlock"); | |
| exit (1); | |
| } | |
| if (sigprocmask(SIG_SETMASK, &accept_previous_mask, NULL)) { | |
| perror("sigprocmask(SIG_SETMASK)"); | |
| exit (1); | |
| } | |
| } | |
| #elif defined (USE_USLOCK_SERIALIZED_ACCEPT) | |
| #include <ulocks.h> | |
| static usptr_t *us = NULL; | |
| static ulock_t uslock = NULL; | |
| #define accept_mutex_child_init() | |
| #define accept_mutex_cleanup() | |
| void accept_mutex_init(void) | |
| { | |
| ptrdiff_t old; | |
| /* default is 8 */ | |
| #define CONF_INITUSERS_MAX 15 | |
| if ((old = usconfig(CONF_INITUSERS, CONF_INITUSERS_MAX)) == -1) { | |
| perror("usconfig"); | |
| exit(-1); | |
| } | |
| if ((old = usconfig(CONF_LOCKTYPE, US_NODEBUG)) == -1) { | |
| perror("usconfig"); | |
| exit(-1); | |
| } | |
| if ((old = usconfig(CONF_ARENATYPE, US_SHAREDONLY)) == -1) { | |
| perror("usconfig"); | |
| exit(-1); | |
| } | |
| if ((us = usinit("/dev/zero")) == NULL) { | |
| perror("usinit"); | |
| exit(-1); | |
| } | |
| if ((uslock = usnewlock(us)) == NULL) { | |
| perror("usnewlock"); | |
| exit(-1); | |
| } | |
| } | |
| void accept_mutex_on() | |
| { | |
| switch(ussetlock(uslock)) { | |
| case 1: | |
| /* got lock */ | |
| break; | |
| case 0: | |
| fprintf(stderr, "didn't get lock\n"); | |
| exit(-1); | |
| case -1: | |
| perror("ussetlock"); | |
| exit(-1); | |
| } | |
| } | |
| void accept_mutex_off() | |
| { | |
| if (usunsetlock(uslock) == -1) { | |
| perror("usunsetlock"); | |
| exit(-1); | |
| } | |
| } | |
| #endif | |
| #ifndef USE_SHMGET_SCOREBOARD | |
| static void *get_shared_mem(apr_size_t size) | |
| { | |
| void *result; | |
| /* allocate shared memory for the shared_counter */ | |
| result = (unsigned long *)mmap ((caddr_t)0, size, | |
| PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); | |
| if (result == (void *)(caddr_t)-1) { | |
| perror ("mmap"); | |
| exit (1); | |
| } | |
| return result; | |
| } | |
| #else | |
| #include <sys/types.h> | |
| #include <sys/ipc.h> | |
| #ifdef HAVE_SYS_MUTEX_H | |
| #include <sys/mutex.h> | |
| #endif | |
| #include <sys/shm.h> | |
| static void *get_shared_mem(apr_size_t size) | |
| { | |
| key_t shmkey = IPC_PRIVATE; | |
| int shmid = -1; | |
| void *result; | |
| #ifdef MOVEBREAK | |
| char *obrk; | |
| #endif | |
| if ((shmid = shmget(shmkey, size, IPC_CREAT | SHM_R | SHM_W)) == -1) { | |
| perror("shmget"); | |
| exit(1); | |
| } | |
| #ifdef MOVEBREAK | |
| /* | |
| * Some SysV systems place the shared segment WAY too close | |
| * to the dynamic memory break point (sbrk(0)). This severely | |
| * limits the use of malloc/sbrk in the program since sbrk will | |
| * refuse to move past that point. | |
| * | |
| * To get around this, we move the break point "way up there", | |
| * attach the segment and then move break back down. Ugly | |
| */ | |
| if ((obrk = sbrk(MOVEBREAK)) == (char *) -1) { | |
| perror("sbrk"); | |
| } | |
| #endif | |
| #define BADSHMAT ((void *)(-1)) | |
| if ((result = shmat(shmid, 0, 0)) == BADSHMAT) { | |
| perror("shmat"); | |
| } | |
| /* | |
| * We must avoid leaving segments in the kernel's | |
| * (small) tables. | |
| */ | |
| if (shmctl(shmid, IPC_RMID, NULL) != 0) { | |
| perror("shmctl(IPC_RMID)"); | |
| } | |
| if (result == BADSHMAT) /* now bailout */ | |
| exit(1); | |
| #ifdef MOVEBREAK | |
| if (obrk == (char *) -1) | |
| return; /* nothing else to do */ | |
| if (sbrk(-(MOVEBREAK)) == (char *) -1) { | |
| perror("sbrk 2"); | |
| } | |
| #endif | |
| return result; | |
| } | |
| #endif | |
| #ifdef _POSIX_PRIORITY_SCHEDULING | |
| /* don't ask */ | |
| #define _P __P | |
| #include <sched.h> | |
| #define YIELD sched_yield() | |
| #else | |
| #define YIELD do { struct timeval zero; zero.tv_sec = zero.tv_usec = 0; select(0,0,0,0,&zero); } while(0) | |
| #endif | |
| void main (int argc, char **argv) | |
| { | |
| int num_iter; | |
| int num_child; | |
| int i; | |
| struct timeval first; | |
| struct timeval last; | |
| long ms; | |
| int pid; | |
| unsigned long *shared_counter; | |
| if (argc != 3) { | |
| fprintf (stderr, "Usage: time-sem num-child num iter\n"); | |
| exit (1); | |
| } | |
| num_child = atoi (argv[1]); | |
| num_iter = atoi (argv[2]); | |
| /* allocate shared memory for the shared_counter */ | |
| shared_counter = get_shared_mem(sizeof(*shared_counter)); | |
| /* initialize counter to 0 */ | |
| *shared_counter = 0; | |
| accept_mutex_init (); | |
| /* parent grabs mutex until done spawning children */ | |
| accept_mutex_on (); | |
| for (i = 0; i < num_child; ++i) { | |
| pid = fork(); | |
| if (pid == 0) { | |
| /* child, do our thing */ | |
| accept_mutex_child_init(); | |
| for (i = 0; i < num_iter; ++i) { | |
| unsigned long tmp; | |
| accept_mutex_on (); | |
| tmp = *shared_counter; | |
| YIELD; | |
| *shared_counter = tmp + 1; | |
| accept_mutex_off (); | |
| } | |
| exit (0); | |
| } else if (pid == -1) { | |
| perror ("fork"); | |
| accept_mutex_off (); | |
| exit (1); | |
| } | |
| } | |
| /* a quick test to see that nothing is screwed up */ | |
| if (*shared_counter != 0) { | |
| puts ("WTF! shared_counter != 0 before the children have been started!"); | |
| accept_mutex_off (); | |
| exit (1); | |
| } | |
| gettimeofday (&first, NULL); | |
| /* launch children into action */ | |
| accept_mutex_off (); | |
| for (i = 0; i < num_child; ++i) { | |
| if (wait(NULL) == -1) { | |
| perror ("wait"); | |
| } | |
| } | |
| gettimeofday (&last, NULL); | |
| if (*shared_counter != num_child * num_iter) { | |
| printf ("WTF! shared_counter != num_child * num_iter!\n" | |
| "shared_counter = %lu\nnum_child = %d\nnum_iter=%d\n", | |
| *shared_counter, | |
| num_child, num_iter); | |
| } | |
| last.tv_sec -= first.tv_sec; | |
| ms = last.tv_usec - first.tv_usec; | |
| if (ms < 0) { | |
| --last.tv_sec; | |
| ms += 1000000; | |
| } | |
| last.tv_usec = ms; | |
| printf ("%8lu.%06lu\n", last.tv_sec, last.tv_usec); | |
| accept_mutex_cleanup(); | |
| exit(0); | |
| } | |