Addition of c3po library, including revamp of Make system for user libraries.
authorKevin Klues <klueska@cs.berkeley.edu>
Tue, 22 Feb 2011 02:54:01 +0000 (18:54 -0800)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:58 +0000 (17:35 -0700)
The c3po library is currently only functional in single core process mode, with
threads that run to completion or yield the processor voluntarily. No AIO
functionality is working yet either. I plan to soon get it working in
multi-core mode so that its threads can be multiplexed on multiple vcores as
well as get the AIO stuff working.  One encouraging thing, however, is that we
are in fact using the linked stack stuff, and the capriccio scheduler code with
only MINOR modifications, and everything seems to be working properly.  A test
application called c3po_test.c has been placed under tests/c3po/ and is based
on a modified version of pthread_test.c.

As it pertains to the Make system revamp, all user libraries are now self
contained in that they include a top level Makefile instead of a Makefrag
dependent on the ros build environment.  In GNUmakefile, these libraries are
built with a recusrive make, rather than including a Makefrag and invoking them
that way.  This change required a small change to the cross compiler Makefile,
but should NOT require a rebuild as it was just modified to be aware of the
reorganization; no actual header files were modified.

I also separated the pthread commands out of libparlib, so they are only now
contained in libpthread and not both.  If you build an app that relies on the
pthread library you now need to icnlude both -lpthread and -lparlib in order to
use them.  This change was necessary now that we are introducing the
libc3po library that implements the pthread functions differently.  To use
c3po, simply include -lc3po and -lparlib together instead of -lpthread.

While making these changes, I also revamped the Make system as it relates to
building tests.  By default, all of the test files in the tests/ directory are
compiled together with -lpthread and -lparlib.  Take a look at the changes to
see how I added a c3po subfolder that allows you to compile other tests with
different Makefile flags.  This process can be emulated to include other
subfolders taht group test files together that require any combiantion of
flags, or settings.

There is also a new install-tests Make target that will put all of the binaries
made during a 'make test' into the bin directory of the first entry in
INITRAMFS_PATHS.

119 files changed:
GNUmakefile
tests/Makefrag
tests/c3po/Makefrag [new file with mode: 0644]
tests/c3po/c3po_test.c [new file with mode: 0644]
tools/compilers/gcc-glibc/Makefile
user/Makefrag [deleted file]
user/c3po/Makefile [new file with mode: 0644]
user/c3po/aio/Makefrag [new file with mode: 0644]
user/c3po/aio/blocking_io.c [new file with mode: 0644]
user/c3po/aio/blocking_io.h [new file with mode: 0644]
user/c3po/aio/check_syscall [new file with mode: 0755]
user/c3po/aio/diskio_aio.c [new file with mode: 0644]
user/c3po/aio/diskio_blocking.c [new file with mode: 0644]
user/c3po/aio/diskio_immediate.c [new file with mode: 0644]
user/c3po/aio/diskio_kthread.c [new file with mode: 0644]
user/c3po/aio/io_internal.h [new file with mode: 0644]
user/c3po/aio/sockio_epoll.c [new file with mode: 0644]
user/c3po/aio/sockio_poll.c [new file with mode: 0644]
user/c3po/coro/Makefrag [new file with mode: 0644]
user/c3po/coro/coro.c [new file with mode: 0644]
user/c3po/coro/coro.h [new file with mode: 0644]
user/c3po/include/README [new file with mode: 0644]
user/c3po/include/bits/pthreadtypes.h [new file with mode: 0644]
user/c3po/include/capriccio.h [new file with mode: 0644]
user/c3po/include/fptrcheck.h [new file with mode: 0644]
user/c3po/include/pthread.h [new file with mode: 0644]
user/c3po/include/semaphore.h [new file with mode: 0644]
user/c3po/include/stackbounds.h [new file with mode: 0644]
user/c3po/include/stacklink.h [new file with mode: 0644]
user/c3po/stack/Makefrag [new file with mode: 0644]
user/c3po/stack/fptr.c [new file with mode: 0644]
user/c3po/stack/stack.c [new file with mode: 0644]
user/c3po/threads/Makefrag [new file with mode: 0644]
user/c3po/threads/blocking_graph.c [new file with mode: 0644]
user/c3po/threads/blocking_graph.h [new file with mode: 0644]
user/c3po/threads/events.c [new file with mode: 0644]
user/c3po/threads/mutex.c [new file with mode: 0644]
user/c3po/threads/pthread.c [new file with mode: 0644]
user/c3po/threads/pthreadtest.c [new file with mode: 0644]
user/c3po/threads/readproc.c [new file with mode: 0644]
user/c3po/threads/readproc.h [new file with mode: 0644]
user/c3po/threads/resource_stats.c [new file with mode: 0644]
user/c3po/threads/resource_stats.h [new file with mode: 0644]
user/c3po/threads/sched_global_rr.c [new file with mode: 0644]
user/c3po/threads/sched_graph_priority.c [new file with mode: 0644]
user/c3po/threads/sched_graph_rr.c [new file with mode: 0644]
user/c3po/threads/semaphore.h [new file with mode: 0644]
user/c3po/threads/threadlib.c [new file with mode: 0644]
user/c3po/threads/threadlib.h [new file with mode: 0644]
user/c3po/threads/threadlib_internal.h [new file with mode: 0644]
user/c3po/util/Makefrag [new file with mode: 0644]
user/c3po/util/atomic.h [new file with mode: 0644]
user/c3po/util/clock.c [new file with mode: 0644]
user/c3po/util/clock.h [new file with mode: 0644]
user/c3po/util/config.c [new file with mode: 0644]
user/c3po/util/config.h [new file with mode: 0644]
user/c3po/util/debug.c [new file with mode: 0644]
user/c3po/util/debug.h [new file with mode: 0644]
user/c3po/util/libperfctr.h [new file with mode: 0644]
user/c3po/util/linked_list.c [new file with mode: 0644]
user/c3po/util/linked_list.h [new file with mode: 0644]
user/c3po/util/object_pool.c [new file with mode: 0644]
user/c3po/util/object_pool.h [new file with mode: 0644]
user/c3po/util/occ_list.c [new file with mode: 0644]
user/c3po/util/occ_list.h [new file with mode: 0644]
user/c3po/util/plhash.c [new file with mode: 0644]
user/c3po/util/plhash.h [new file with mode: 0644]
user/c3po/util/timing.c [new file with mode: 0644]
user/c3po/util/timing.h [new file with mode: 0644]
user/c3po/util/util.h [new file with mode: 0644]
user/c3po/util/utiltest.c [new file with mode: 0644]
user/include/arc.h [deleted file]
user/include/bthread.h [deleted file]
user/include/event.h [deleted file]
user/include/glibc-tls.h [deleted file]
user/include/i686/arch.h [deleted file]
user/include/i686/atomic.h [deleted file]
user/include/i686/bitmask.h [deleted file]
user/include/i686/vcore.h [deleted file]
user/include/mcs.h [deleted file]
user/include/parlib.h [deleted file]
user/include/pool.h [deleted file]
user/include/pthread.h [deleted file]
user/include/rassert.h [deleted file]
user/include/rstdio.h [deleted file]
user/include/sparc/arch.h [deleted file]
user/include/sparc/atomic.h [deleted file]
user/include/sparc/bitmask.h [deleted file]
user/include/sparc/vcore.h [deleted file]
user/include/timing.h [deleted file]
user/include/vcore.h [deleted file]
user/parlib/Makefile [new file with mode: 0644]
user/parlib/Makefrag [deleted file]
user/parlib/include/arc.h [new file with mode: 0644]
user/parlib/include/arch [new symlink]
user/parlib/include/bthread.h [new file with mode: 0644]
user/parlib/include/event.h [new file with mode: 0644]
user/parlib/include/glibc-tls.h [new file with mode: 0644]
user/parlib/include/i686/arch.h [new file with mode: 0644]
user/parlib/include/i686/atomic.h [new file with mode: 0644]
user/parlib/include/i686/bitmask.h [new file with mode: 0644]
user/parlib/include/i686/vcore.h [new file with mode: 0644]
user/parlib/include/mcs.h [new file with mode: 0644]
user/parlib/include/parlib.h [new file with mode: 0644]
user/parlib/include/pool.h [new file with mode: 0644]
user/parlib/include/pthread.h [new file with mode: 0644]
user/parlib/include/rassert.h [new file with mode: 0644]
user/parlib/include/rstdio.h [new file with mode: 0644]
user/parlib/include/sparc/arch.h [new file with mode: 0644]
user/parlib/include/sparc/atomic.h [new file with mode: 0644]
user/parlib/include/sparc/bitmask.h [new file with mode: 0644]
user/parlib/include/sparc/vcore.h [new file with mode: 0644]
user/parlib/include/timing.h [new file with mode: 0644]
user/parlib/include/vcore.h [new file with mode: 0644]
user/parlib/pthread.c [deleted file]
user/pthread/Makefile [new file with mode: 0644]
user/pthread/Makefrag [deleted file]
user/pthread/pthread.c [new file with mode: 0644]
user/pthread/pthread.h [new file with mode: 0644]

index 716c468..d5a6e9f 100644 (file)
@@ -33,7 +33,7 @@ $(TARGET_ARCH):
 # Default values for configurable Make system variables
 COMPILER := GCC
 OBJDIR := obj
-V := @
+V ?= @
 
 # Make sure that 'all' is the first target when not erroring out
 realall: symlinks
@@ -87,7 +87,7 @@ endif
 endif
 
 # Default programs for compilation
-USER_CFLAGS += -O2
+USER_CFLAGS += -O2 -std=gnu99
 ifeq ($(COMPILER),IVY)
 KERN_CFLAGS += --deputy \
                --no-rc-sharc \
@@ -133,9 +133,6 @@ LDFLAGS := -nostdlib
 # List of directories that the */Makefrag makefile fragments will add to
 OBJDIRS :=
 
-# List of directories that the */Makefrag makefile fragments will add to
-ROS_USER_LIBS :=
-
 ROS_ARCH_DIR ?= $(TARGET_ARCH)
 
 arch:
@@ -149,7 +146,6 @@ symlinks: error
 
 # Include Makefrags for subdirectories
 ifneq ($(TARGET_ARCH),)
-include user/Makefrag
 include tests/Makefrag
 include kern/Makefrag
 endif
@@ -164,10 +160,16 @@ realtests: $(TESTS_EXECS)
 #      @mkdir -p fs/$(TARGET_ARCH)/tests
 #      cp -R $(OBJDIR)/$(TESTS_DIR)/* $(TOP_DIR)/fs/$(TARGET_ARCH)/tests
 
-install-libs: $(ROS_USER_LIBS)
-       cp $(ROS_USER_LIBS) $(GCC_ROOT)/$(TARGET_ARCH)-ros/lib
-       cp $(ROS_USER_LIBS) $(TOP_DIR)/fs/$(TARGET_ARCH)/lib
-       cp -R $(USER_DIR)/include/* $(GCC_ROOT)/$(TARGET_ARCH)-ros/include
+install-libs: 
+       @cd user/parlib; $(MAKE); $(MAKE) install
+       @cd user/pthread; $(MAKE); $(MAKE) install
+       @cd user/c3po; $(MAKE); $(MAKE) install
+
+userclean:
+       @cd user/parlib; $(MAKE) clean;
+       @cd user/pthread; $(MAKE) clean;
+       @cd user/c3po; $(MAKE) clean;
+       @rm -rf $(OBJDIR)/$(TESTS_DIR)
 .PHONY: tests
 endif
 
@@ -200,10 +202,6 @@ docs:
 doxyclean:
        rm -rf $(DOXYGEN_DIR)/rosdoc
 
-# For deleting the build
-userclean:
-       @rm -rf $(OBJDIR)/user
-       
 clean:
        @rm -rf $(OBJDIR)
        @echo All clean and pretty!
index 587095a..dc37c16 100644 (file)
@@ -1,31 +1,36 @@
 TESTS_DIR = tests
+
 OBJDIRS += $(TESTS_DIR)
 
-TESTS_CFLAGS += $(USER_CFLAGS)  \
-                -I$(USER_DIR)/include
+TESTS_CFLAGS += $(USER_CFLAGS)
 
 ALL_TEST_FILES = $(shell ls $(TESTS_DIR)/*.c)
 
-TESTS_LDDIRS := -L$(OBJDIR)/$(USER_PARLIB_DIR)
-
-TESTS_LDLIBS := -lparlib
+TESTS_LDLIBS := -lpthread -lparlib
 
 TESTS_SRCS := $(ALL_TEST_FILES)
 
-TESTS_EXECS  := $(patsubst $(TESTS_DIR)/%.c, \
+TESTS_EXECS  = $(patsubst $(TESTS_DIR)/%.c, \
                            $(OBJDIR)/$(TESTS_DIR)/%, \
                            $(TESTS_SRCS))
 
-TESTS_LDDEPENDS := $(TESTS_DIR)/%.c \
-                   $(OBJDIR)/$(USER_PARLIB_DIR)/libparlib.a
+TESTS_LDDEPENDS := $(TESTS_DIR)/%.c 
 
+include $(TESTS_DIR)/c3po/Makefrag
 STATIC := $(findstring static,$(TESTS_CFLAGS))
 $(OBJDIR)/$(TESTS_DIR)/%: $(TESTS_LDDEPENDS)
        @echo + cc [TESTS] $<
        @mkdir -p $(@D)
        $(V)$(CC) $(TESTS_CFLAGS) -o $@ $(TESTS_LDFLAGS) \
-                 $(TESTS_LDDIRS) $< $(TESTS_LDLIBS)
+                 $< $(TESTS_LDLIBS)
        @if [ "$(STATIC)" != "static" ]; then \
                $(OBJDUMP) -S $@ > $@.asm; \
                $(NM) -n $@ > $@.sym; \
        fi
+
+install-tests: $(TESTS_EXECS)
+       @echo + install [TESTS] $(firstword $(INITRAMFS_PATHS))/bin/
+       $(V)for i in "$(TESTS_EXECS)"; \
+       do \
+         cp $$i $(firstword $(INITRAMFS_PATHS))/bin/; \
+       done;
diff --git a/tests/c3po/Makefrag b/tests/c3po/Makefrag
new file mode 100644 (file)
index 0000000..63a8fd2
--- /dev/null
@@ -0,0 +1,27 @@
+C3PO_TESTS_DIR = $(TESTS_DIR)/c3po
+
+C3PO_TESTS_CFLAGS += $(TESTS_CFLAGS) \
+                     -I$(GCC_ROOT)/$(TARGET_ARCH)-ros/sys-include/c3po
+
+ALL_C3PO_TEST_FILES = $(shell ls $(C3PO_TESTS_DIR)/*.c)
+
+C3PO_TESTS_LDLIBS := -lc3po -lparlib -lm
+
+C3PO_TESTS_SRCS := $(ALL_C3PO_TEST_FILES)
+
+TESTS_EXECS  += $(patsubst $(C3PO_TESTS_DIR)/%.c, \
+                      $(OBJDIR)/$(C3PO_TESTS_DIR)/%, \
+                      $(C3PO_TESTS_SRCS))
+
+C3PO_TESTS_LDDEPENDS := $(C3PO_TESTS_DIR)/%.c 
+
+STATIC := static #$(findstring static,$(C3PO_TESTS_CFLAGS))
+$(OBJDIR)/$(C3PO_TESTS_DIR)/%: $(C3PO_TESTS_LDDEPENDS)
+       @echo + cc [C3PO_TESTS] $<
+       @mkdir -p $(@D)
+       $(V)$(CC) $(C3PO_TESTS_CFLAGS) -o $@ $(C3PO_TESTS_LDFLAGS) \
+                 $(C3PO_TESTS_LDDIRS) $< $(C3PO_TESTS_LDLIBS)
+       @if [ "$(STATIC)" != "static" ]; then \
+               $(OBJDUMP) -S $@ > $@.asm; \
+               $(NM) -n $@ > $@.sym; \
+       fi
diff --git a/tests/c3po/c3po_test.c b/tests/c3po/c3po_test.c
new file mode 100644 (file)
index 0000000..9e2fbe0
--- /dev/null
@@ -0,0 +1,80 @@
+#include <rstdio.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <parlib.h>
+#include <unistd.h>
+
+pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+#define vcore_id() 0
+//#define printf_safe(...) {}
+#define printf_safe(...) \
+       pthread_mutex_lock(&lock); \
+       printf(__VA_ARGS__); \
+       pthread_mutex_unlock(&lock);
+
+pthread_t t1;
+pthread_t t2;
+pthread_t t3;
+
+#define NUM_TEST_THREADS 1000
+
+pthread_t my_threads[NUM_TEST_THREADS];
+void *my_retvals[NUM_TEST_THREADS];
+
+__thread int my_id;
+void *yield_thread(void* arg)
+{      
+       for (int i = 0; i < 10; i++) {
+               printf_safe("[A] pthread %d on vcore %d\n", pthread_self(), vcore_id());
+               pthread_yield();
+               printf_safe("[A] pthread %d returned from yield on vcore %d\n",
+                           pthread_self(), vcore_id());
+       }
+       return (void*)(pthread_self());
+}
+
+void *hello_thread(void* arg)
+{      
+       printf_safe("[A] pthread %d on vcore %d\n", pthread_self(), vcore_id());
+       return (void*)(pthread_self());
+}
+
+int main(int argc, char** argv) 
+{
+       void *retval1 = 0;
+       void *retval2 = 0;
+       void *retval3 = 0;
+
+       /* yield test */
+       printf_safe("[A] About to create thread 1\n");
+       pthread_create(&t1, NULL, &yield_thread, NULL);
+       printf_safe("[A] About to create thread 2\n");
+       pthread_create(&t2, NULL, &yield_thread, NULL);
+       printf_safe("[A] About to create thread 3\n");
+       pthread_create(&t3, NULL, &yield_thread, NULL);
+       /* join on them */
+       printf_safe("[A] About to join on thread 1\n");
+       pthread_join(t1, &retval1);
+       printf_safe("[A] Successfully joined on thread 1 (retval: %p)\n", retval1);
+       printf_safe("[A] About to join on thread 2\n");
+       pthread_join(t2, &retval2);
+       printf_safe("[A] Successfully joined on thread 2 (retval: %p)\n", retval2);
+       printf_safe("[A] About to join on thread 3\n");
+       pthread_join(t3, NULL);
+       printf_safe("[A] Successfully joined on thread 3 (retval: %p)\n", retval3);
+
+       /* create and join on hellos */
+       while (1) {
+               for (int i = 1; i < NUM_TEST_THREADS; i++) {
+                       printf_safe("[A] About to create thread %d\n", i);
+                       pthread_create(&my_threads[i], NULL, &hello_thread, NULL);
+               }
+               for (int i = 1; i < NUM_TEST_THREADS; i++) {
+                       printf_safe("[A] About to join on thread %d\n", i);
+                       pthread_join(my_threads[i], &my_retvals[i]);
+                       printf_safe("[A] Successfully joined on thread %d (retval: %p)\n", i,
+                                   my_retvals[i]);
+               }
+               break;
+       }
+} 
index a3d876e..479edd9 100644 (file)
@@ -230,7 +230,9 @@ $(BINARY_PREFIX)gcc-stage2-builddir: gcc-$(GCC_VERSION)
        mkdir $(INSTDIR)/$(ARCH)-ros/sys-include/ros/arch
        cp -r $(ROSDIR)/kern/arch/$(ROS_ARCH_DIR)/ros/* \
           $(INSTDIR)/$(ARCH)-ros/sys-include/ros/arch/
-       cp -r $(ROSDIR)/user/include/* \
+       cp -r $(ROSDIR)/user/parlib/include/* \
+          $(INSTDIR)/$(ARCH)-ros/sys-include/
+       cp -r $(ROSDIR)/user/pthread/*.h \
           $(INSTDIR)/$(ARCH)-ros/sys-include/
        rm -rf $(INSTDIR)/$(ARCH)-ros/sys-include/arch
        ln -s $(ARCH) $(INSTDIR)/$(ARCH)-ros/sys-include/arch 
diff --git a/user/Makefrag b/user/Makefrag
deleted file mode 100644 (file)
index df61466..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-USER_DIR := user
-USER_CFLAGS += $(CFLAGS) -std=gnu99 -I$(USER_DIR)/include
-
-include $(USER_DIR)/parlib/Makefrag
-include $(USER_DIR)/pthread/Makefrag
-
diff --git a/user/c3po/Makefile b/user/c3po/Makefile
new file mode 100644 (file)
index 0000000..84868ea
--- /dev/null
@@ -0,0 +1,55 @@
+TARGET_ARCH ?= i686
+CFLAGS = -O2 -static -fomit-frame-pointer -DOPTIMIZE=2 -DNO_TIMING
+LIBNAME = c3po
+SUBDIRS = util stack coro threads #aio 
+V ?= @
+
+GCCPREFIX ?= $(TARGET_ARCH)-ros-
+CC := $(GCCPREFIX)gcc
+GCC_ROOT := $(shell which $(CC) | xargs dirname)/../
+
+SRCDIR := 
+OBJDIR := $(SRCDIR)obj
+INCDIR = $(SRCDIR)include
+UTILDIR = $(SRCDIR)util
+STACKDIR = $(SRCDIR)stack
+CORODIR = $(SRCDIR)coro
+THREADSDIR = $(SRCDIR)threads
+AIODIR = $(SRCDIR)aio
+
+INCS = -I$(INCDIR) -I$(UTILDIR) -I$(THREADSDIR) -I$(STACKDIR) -I$(CORODIR) -I.
+FINALLIB = $(OBJDIR)/lib$(LIBNAME).a
+
+uc = $(shell echo $(1) | tr a-z A-Z)
+lc = $(shell echo $(1) | tr A-Z a-z)
+libname = $(OBJDIR)/$(1)/lib$(1).a
+cleanname = $(1)-clean
+makefragname = $(1)/Makefrag
+filename = $(notdir $(1))
+dirname = $(dir $(1))
+
+CLEANS := $(foreach x, $(SUBDIRS), $(call cleanname,$(x)))
+MAKEFRAGS := $(foreach x, $(SUBDIRS), $(call makefragname,$(x)))
+LIBUCNAME := $(call uc, $(LIBNAME))
+
+all: $(FINALLIB)
+
+include $(MAKEFRAGS)
+ALLOBJS = $(foreach x, $(SUBDIRS), $(wildcard $(OBJDIR)/$(x)/*.o))
+ALLLIBS = $(foreach x, $(SUBDIRS), $(call libname,$(x)))
+
+$(FINALLIB): $(ALLLIBS)
+       @echo + ar [$(LIBUCNAME)] $@
+       @mkdir -p $(@D)
+       $(V)$(AR) rc $@ $(ALLOBJS)
+
+install: $(FINALLIB)
+       cp $(FINALLIB) $(GCC_ROOT)/$(TARGET_ARCH)-ros/lib/
+       mkdir -p $(GCC_ROOT)/$(TARGET_ARCH)-ros/include/$(LIBNAME)
+       cp -R $(INCDIR)/* $(GCC_ROOT)/$(TARGET_ARCH)-ros/include/$(LIBNAME)
+
+clean: $(CLEANS)
+       @echo + clean [$(LIBUCNAME)]
+       $(V)rm -rf $(FINALLIB)
+       $(V)rm -rf $(OBJDIR)
+       
diff --git a/user/c3po/aio/Makefrag b/user/c3po/aio/Makefrag
new file mode 100644 (file)
index 0000000..435347f
--- /dev/null
@@ -0,0 +1,25 @@
+AIO_NAME    := aio
+AIO_UCNAME  := $(call uc, $(AIO_NAME))
+AIO_CFLAGS  := $(CFLAGS)
+AIO_HEADERS := $(wildcard $(AIODIR)/*.h)
+AIO_CFILES  := $(wildcard $(AIODIR)/*.c)
+AIO_OBJDIR  := $(OBJDIR)/$(AIO_NAME)
+AIO_OBJS    := $(patsubst %.c, %.o, $(AIO_CFILES))
+AIO_OBJS    := $(foreach x, $(AIO_OBJS), $(AIO_OBJDIR)/$(call filename,$(x)))
+
+LIBAIO = $(AIO_OBJDIR)/lib$(AIO_NAME).a
+
+$(AIO_NAME)-clean:
+       @echo + clean [$(LIBUCNAME) $(AIO_UCNAME)]
+       $(V)rm -rf $(AIO_OBJS) $(LIBAIO)
+       $(V)rm -rf $(AIO_OBJDIR)
+
+$(LIBAIO): $(AIO_OBJS)
+       @echo + ar [$(LIBUCNAME) $(AIO_UCNAME)] $@
+       $(V)$(AR) rc $@ $^
+
+$(AIO_OBJDIR)/%.o: $(AIODIR)/%.c $(AIO_HEADERS)
+       @echo + cc [$(LIBUCNAME) $(AIO_UCNAME)] $<
+       @mkdir -p $(@D)
+       $(V)$(CC) $(AIO_CFLAGS) $(INCS) -o $@ -c $<
+
diff --git a/user/c3po/aio/blocking_io.c b/user/c3po/aio/blocking_io.c
new file mode 100644 (file)
index 0000000..ae2bb69
--- /dev/null
@@ -0,0 +1,1582 @@
+/**
+ * blocking IO routines
+ *
+ * These routines make use of asynchronous IO and the signal/wait
+ * calls of a threading package to provide a blocking IO abstraction
+ * on top of underlying non-blocking IO calls.
+ *
+ * The semantics of these calls mirror those of the standard IO
+ * libraries.
+ **/
+
+#include "threadlib.h"
+#include "io_internal.h"
+#include "blocking_io.h"
+#include "util.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <string.h>
+#include <malloc.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+// for socketcall stuff
+#include <linux/net.h>
+
+#define USE_NODELAY 0
+
+#ifndef DEBUG_blocking_io_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+#ifndef SYS_pread
+# ifdef __NR_pread
+#  define SYS_pread     __NR_pread
+#  define SYS_pwrite   __NR_pwrite
+# else
+#  define SYS_pread     180
+#  define SYS_pwrite   181
+# endif
+#endif
+
+// oontrol whether or not a user's nonblocking IO calls are actually nonblocking...
+#define ALLOW_USER_NONBLOCKING_IO 1
+
+#define DO_EXTRA_YIELDS 0
+
+
+// Timing counters
+static cap_timer_t poll_timer;
+static cap_timer_t extra_poll_timer;
+
+
+//////////////////////////////////////////////////////////////////////
+// Prototypes for internal functions
+//////////////////////////////////////////////////////////////////////
+
+// function pointers for IO routines
+
+void (*sockio_init)(void);
+int (*sockio_add_request)(iorequest_t* req);
+void (*sockio_poll)();
+
+void (*diskio_init)(void);
+int (*diskio_add_request)(iorequest_t* req);
+void (*diskio_poll)();
+
+
+
+
+//////////////////////////////////////////////////////////////////////
+// manage FD data structures
+//////////////////////////////////////////////////////////////////////
+
+// fdstructs are allocated in banks
+#define FD_BANK_SHIFT 15
+#define FDS_PER_BANK (1<<FD_BANK_SHIFT)  // 2^15
+#define FD_BANK_MASK (FDS_PER_BANK - 1)  // 2^15 - 1
+
+// allow up to 1 million  
+#define MAX_FD (1024*1024)
+#define MAX_BANK ((MAX_FD >> FD_BANK_SHIFT) + 1)
+fdstruct_t *fdstruct_list[MAX_BANK];
+
+int next_new_fd = 0;
+
+latch_t fdstruct_latch;
+
+fdstruct_t* get_fdstruct_no_dup_resolve(int fd);
+
+/**
+ * find/allocate the fdstruct_t for the given fd
+ * this will return the root fdstruct_t if the fd is a dup'ed one
+ *
+ * GAURANTEES: never return NULL
+ **/
+fdstruct_t* get_fdstruct(int fd)
+{
+  fdstruct_t *res = get_fdstruct_no_dup_resolve(fd);
+  if (res->root_fds != NULL)
+    return res->root_fds;
+  return res;
+}
+
+/**
+ * find/allocate a fdstruct_t, don't resolve to the root fd in a 
+ * dup'ed list
+ */
+fdstruct_t* get_fdstruct_no_dup_resolve(int fd)
+{
+  fdstruct_t *bank = fdstruct_list[fd >> FD_BANK_SHIFT];
+  fdstruct_t *fds;
+
+  // acquire lock
+  thread_latch( fdstruct_latch );
+
+  // allocate the bank, if necessary
+  if(bank == NULL) {
+    //bank = (fdstruct_t*) malloc(FDS_PER_BANK * sizeof(fdstruct_t));
+    bank = (fdstruct_t*) calloc(FDS_PER_BANK, sizeof(fdstruct_t));
+    assert(bank);
+    fdstruct_list[fd >> FD_BANK_SHIFT] = bank;
+  }
+
+  // do some lazy initialization of unused fdstruct_t's.  Lazy is MUCH
+  // better here, since we may never need to touch many of the pages,
+  // and hence don't need to take the unnecessary page faults.  Since
+  // fds are allocated sequentially, this should generally be quite
+  // fast.
+  //
+  // FIXME: the algorithm below is really bad if there is a big gap in
+  // FDs.
+  //
+  // FIXME: for now, assume that newly allocated memory is zeroed.
+  // This is not unreasonable, since newly mapped pages of VM are
+  // mapped copy-on-write to the zero page.  When we roll our own
+  // memory allocator, we'll need to be careful about this.
+  /*
+  if(fd >= next_new_fd) {
+    int firstbank = next_new_fd >> FD_BANK_SHIFT;
+    int lastbank = fd >> FD_BANK_SHIFT;
+    int i, first, last;
+
+    for(i=firstbank; i<=lastbank; i++) {
+      // allocate memory for the bank, if necessary
+      if( fdstruct_list[i] == NULL ) {
+        //fdstruct_list[i] = (fdstruct_t*) malloc(FDS_PER_BANK * sizeof(fdstruct_t));
+        fdstruct_list[i] = (fdstruct_t*) calloc(FDS_PER_BANK, sizeof(fdstruct_t));
+        assert( fdstruct_list[i] );
+      }
+
+      if(i==firstbank) first = next_new_fd & FD_BANK_MASK;
+      else first=0;
+
+      if(i==lastbank) last = fd & FD_BANK_MASK;
+      else last=FDS_PER_BANK-1;
+
+      // zero out all fdstructs up to the highest one.  NOTE that this
+      // also ensures that FD_UNUSED is set
+      bzero(&fdstruct_list[i][first], (last-first+1)*sizeof(fdstruct_t));
+    }
+
+    next_new_fd = fd+1;
+  }
+  */
+  thread_unlatch( fdstruct_latch );
+
+  fds = &(bank[fd & FD_BANK_MASK]);
+  if (fds->state == FD_UNUSED) {
+    fds->state = FD_CLOSED;
+    fds->fd = fd;
+  }
+
+  // NOTE: the init stuff below should now be unnecessary, since zero is the correct value for all defaults.
+    /*
+  if (fds->state == FD_UNUSED)
+    {
+      // We do not have record for this FD.
+      // It can be stdin/stdout or other FD not opened
+      // through our interface.
+      // Check whether this is a valid FD
+      off_t r;
+      r = syscall(SYS_lseek, fd, 0, SEEK_CUR);
+      if (r == -1 && errno == EBADF) {
+        thread_unlatch( fdstruct_latch );
+       assert (fds->fd == fd);
+       return fds;
+      }
+
+      // guess at the file type.  open(), accept(), etc. should re-set
+      // this, since they may know better.
+      //
+      // FIXME: this may not work for pipes, FIFOs, ttys, etc.
+      // FIXME: better solution is to override all functions that create new FDs
+      if (r == -1 && errno == ESPIPE)
+       fds->type = FD_SOCKET;
+      else
+       fds->type = FD_FILE;
+
+      fds->fd = fd;
+      fds->state = FD_UNKNOWN;
+      fds->off = (r>=0 ? r : 0);
+
+      debug("Added FD %d as %s\n", fd, (fds->type==FD_FILE ? "FD_FILE" : "FD_SOCKET"));
+    }
+    */
+
+  assert (fds->fd == fd);
+  return fds;
+}
+
+static inline void zero_fdstruct(fdstruct_t *fds)
+{
+  int fd = fds->fd;
+  bzero(fds, sizeof(fdstruct_t));
+  fds->fd = fd;
+  fds->state = FD_CLOSED;
+}
+
+// internal function to close a fd
+// this involves handling of dup'ed fds
+static void close_fd(int fd)
+{
+  fdstruct_t *fds = get_fdstruct_no_dup_resolve(fd);
+  debug("fd=%d\n", fd);
+  
+  if(fds->state != FD_CLOSED) {
+    cpu_tick_t lifetime;
+    GET_REAL_CPU_TICKS( lifetime );
+    lifetime -= fds->time_opened; 
+    if( fds->type == FD_SOCKET )
+      thread_stats_close_socket( lifetime );
+    else
+      thread_stats_close_file( lifetime );
+  }
+
+
+  // zero or one fds in the dup list
+  if (fds->next_fds == NULL  ||  fds->next_fds == fds)
+  {
+    zero_fdstruct( fds );
+  } 
+
+  // this is the messiest case
+  // we're closing the root fd, while there are dup'ed fds
+  // we migrate the file state to the next dup'ed fd and make that fd the root fd
+  //
+  // FIXME: this could be done cleaner if we maintain a separate struct for each open file besides
+  // each fd and put all info (e.g. offset) there
+  // - zf
+  else if (fds->root_fds == fds) {
+
+    fdstruct_t *new_root_fds = fds->next_fds;
+
+    assert (new_root_fds != fds);
+    debug("closing old root fd: %d, new root fd: %d\n", fds->fd, new_root_fds->fd);
+
+
+    // copy info to the new root
+    {
+      int saved_fd = new_root_fds->fd;
+      *new_root_fds = *fds;
+      new_root_fds->fd = saved_fd;
+      new_root_fds->root_fds = new_root_fds;
+    }
+
+    // close the old root fds
+    zero_fdstruct( fds );
+
+    // set new root fd in all dup'ed fd structs
+    {
+      fdstruct_t *next = new_root_fds->next_fds;
+      while (next != new_root_fds) {
+        next->root_fds = new_root_fds;
+        next = next->next_fds;
+      }
+    }
+
+  } 
+
+
+  // there are other dup'ed fds open for this file
+  // we should just close the current fd, not the root fd that holds info about this open file
+  else {
+    fdstruct_t *pre_fds = fds;
+    fdstruct_t *cur_fds = pre_fds->next_fds;
+    while (cur_fds != fds) {
+      pre_fds = cur_fds;
+      cur_fds = cur_fds->next_fds;
+    }
+
+    debug("closing dup'ed fd: %d, root fd: %d\n", fd, fds->fd);
+    
+    // delete cur_fds from the dup list
+    pre_fds->next_fds = cur_fds->next_fds;
+
+    // close cur_fds
+    zero_fdstruct( fds );
+  }
+}
+
+
+
+// internal function to dup a fd
+static void dup_fd(int oldfd, int newfd)
+{
+  fdstruct_t *oldfds, *newfds;
+  debug("oldfd=%d, newfd=%d\n", oldfd, newfd);
+
+  // Clear out the fdstruct_t.  newfd should have already been closed by dup or fcntl.
+  close_fd( newfd );
+
+  oldfds = get_fdstruct_no_dup_resolve(oldfd);
+  newfds = get_fdstruct_no_dup_resolve(newfd);
+
+
+  // the dup list is currently empty
+  if( oldfds->root_fds == NULL )
+    oldfds->root_fds = oldfds->next_fds = oldfds;
+
+  newfds->state = FD_OPEN;
+  GET_REAL_CPU_TICKS( newfds->time_opened );
+  if( oldfds->type == FD_SOCKET )
+    thread_stats_open_socket();
+  else
+    thread_stats_open_file();
+  
+  // add newfds to the linked list of all dup'ed fds
+  newfds->root_fds = oldfds->root_fds;
+  newfds->next_fds = oldfds->next_fds;
+  oldfds->next_fds = newfds;
+}
+
+
+/**
+ * Add a request to the fd's wait list
+ **/
+inline void add_waiter(iorequest_t *req)
+{
+  thread_latch( req->fds->reqlatch );
+  ll_add_existing_to_tail(&req->fds->reqlist, (linked_list_entry_t*)req);
+  thread_unlatch( req->fds->reqlatch );
+}
+
+/**
+ * remove a specific waiter from the request list
+ **/
+inline void remove_waiter(iorequest_t *req) 
+{
+  thread_latch( req->fds->reqlatch );
+  ll_remove_entry(&req->fds->reqlist, (linked_list_entry_t*)req);
+  thread_unlatch( req->fds->reqlatch );
+}
+
+/**
+ * retrieve the head of the waiter list
+ **/
+inline iorequest_t* remove_first_waiter(fdstruct_t *fds) 
+{
+  iorequest_t *req;
+  thread_latch( fds->reqlatch );
+  req = (iorequest_t*) ll_remove_head(&fds->reqlist);
+  thread_unlatch( fds->reqlatch );
+  return req;
+}
+
+/**
+ * return the number of waiters
+ **/
+inline iorequest_t* view_first_waiter(fdstruct_t *fds)
+{
+  iorequest_t *req;
+  thread_latch( fds->reqlatch );
+  req = (iorequest_t*) ll_view_head(&fds->reqlist);
+  thread_unlatch( fds->reqlatch );
+  return req;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// perform IO requests
+//////////////////////////////////////////////////////////////////////
+
+// internal stats provided by nio.c.  Replicated here to remove
+// compile errors in benchmarks when not using nio.
+int __cap_outstanding_disk_requests;
+int __epoll_wait_count, __epoll_wait_return_count;
+int __io_getevents_count, __io_getevents_return_count;
+
+static ssize_t do_rw(iotype type, int fd, void *buf, size_t count, off_t offset)
+{
+  iorequest_t req;
+  int res;
+  
+  tdebug("type=%d  fd=%d\n", type, fd);
+
+  if(fd < 0) { errno = EBADFD; return -1; }
+  
+  // get the fd structure
+  req.fds = get_fdstruct(fd);
+
+  if ( !cap_override_rw || (ALLOW_USER_NONBLOCKING_IO && req.fds->nonblocking)) {
+    // bypass all our wrapping when user wants true nonblocking i/o
+    tdebug("non-blocking i/o\n");
+    switch (type) {
+    case READ:   res = syscall(SYS_read, fd, buf, count); break;
+    case WRITE:  res = syscall(SYS_write, fd, buf, count); break;
+    case PREAD:  res = syscall(SYS_pread, fd, buf, count, offset); break;
+    case PWRITE: res = syscall(SYS_pwrite, fd, buf, count, offset); break;
+    default: res = -1; assert(0);
+    }
+    // weird hack - for some reason read() retuns -1, but sets errno=0 sometimes.  ;-/
+    if( res == -1 && errno == 0 )
+      errno = EAGAIN;
+#ifdef DO_EXTRA_YIELDS
+    if( cap_override_rw ) thread_yield();
+#endif
+    CAP_CLEAR_SYSCALL();
+    return res;
+  }
+  
+  req.type = type;
+  req.args.rw.buf = buf;
+  req.args.rw.count = count;
+  req.args.rw.off = offset;
+  req.thread = thread_self();
+
+  // add to the request list
+  add_waiter(&req);
+  
+  // do a request, based on fd type.  The add_request() call should
+  // suspend the thread.
+  if(req.fds->type == FD_SOCKET) {
+    IOSTAT_START(sockio);
+    res = sockio_add_request(&req);
+    IOSTAT_DONE(sockio, res<0);
+    if( res >= 0 ) {
+      if( type == READ || type == PREAD )    sockio_stats.bytes_read += res;
+      else                                   sockio_stats.bytes_written += res;
+    }
+  } else {
+    IOSTAT_START(diskio);
+    res = diskio_add_request(&req);
+    IOSTAT_DONE(diskio, res<0);
+    if( res >= 0) {
+      if( type == READ || type == PREAD )    diskio_stats.bytes_read += res;
+      else                                   diskio_stats.bytes_written += res;
+    }
+  }
+
+  // attempt to remove the request, just to be sure...
+  remove_waiter(&req);
+
+  tdebug("io done. rv=%d\n", res);
+  CAP_CLEAR_SYSCALL();
+  return res;
+}
+
+inline static int set_nonblocking(int fd)
+{
+  int flags = 1;
+  if (ioctl(fd, FIONBIO, &flags) &&
+      ((flags = syscall(SYS_fcntl, fd, F_GETFL, 0)) < 0 ||
+       syscall(SYS_fcntl, fd, F_SETFL, flags | O_NONBLOCK) < 0)) {
+    int myerr = errno;
+    syscall(SYS_close, fd);
+    errno = myerr;
+    fatal("can't set fd %d to nonblocking IO!!\n", fd);
+    return -1;
+  }  
+  return 0;
+}
+
+inline static int set_blocking(int fd)
+{
+  int flags = 0;
+  if (ioctl(fd, FIONBIO, &flags) &&
+      ((flags = syscall(SYS_fcntl, fd, F_GETFL, 0)) < 0 ||
+       syscall(SYS_fcntl, fd, F_SETFL, flags & ~O_NONBLOCK) < 0)) {
+    int myerr = errno;
+    syscall(SYS_close, fd);
+    errno = myerr;
+    fatal("can't set fd %d to blocking IO!!\n", fd);
+    return -1;
+  }  
+  return 0;
+}
+
+#ifdef USE_NODELAY
+inline static int set_tcp_nodelay(int fd)
+{
+  int enable = 1;
+  if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)) < 0) {
+    int myerr = errno;
+    syscall(SYS_close, fd);
+    errno = myerr;
+    if( errno != ENOTSUP && errno != ENOPROTOOPT ) {
+      perror("setsockopt(..., TCP_NODELAY, ...)");
+      //fatal("can't set fd %d to TCP_NODELAY!!\n", fd);
+      //return -1;
+    }
+  }
+  return 0;
+}
+#endif
+
+
+//////////////////////////////////////////////////////////////////////
+// support for making user-land poll requests async
+//
+// extra polling support
+// these entries are timed polls the user program calls
+//
+// See the definition of poll() below, for the other half of these routines
+//////////////////////////////////////////////////////////////////////
+
+typedef struct st_poll_entry {
+  thread_t *t;
+  struct pollfd *ufds;
+  int nfds;
+  int res;    // != 0 means the poll is finished
+  int pollcount;  // FIXME: this is for debug only
+  int *pos;       // points to a int that holds the position of this entry in the list
+                  // this is used to help the thread that creates this entry keep track of it
+                  // because the entry could be moved around but other threads
+} poll_entry_t;
+
+#define MAX_EXTRA_POLL_ENTRIES 1024
+static poll_entry_t extra_poll_entries[MAX_EXTRA_POLL_ENTRIES];
+int num_extra_polls = 0;
+
+extern unsigned long long start_usec;
+
+// process all poll requests in extra_poll_entries[]
+inline void extra_poll()
+{
+  int i;
+  for (i = 0; i < num_extra_polls; i++) {
+    poll_entry_t *e = &extra_poll_entries[i];
+    int rv = 0;
+    if (e->res)   // already finished
+      continue;
+//    debug("polling extra poll %d\n", i);
+    rv = syscall(SYS_poll, e->ufds, e->nfds, 0); 
+    e->pollcount++;
+    if (rv != 0)  // something happens
+      {
+       e->res = rv;
+       thread_resume(e->t);
+       tdebug("poll done after %d tries, rv=%d, ufds[0].revents=%x.\n", e->pollcount, rv, e->ufds[0].revents);
+       // we do not delete the entry now
+       // because the thread issueing the request
+       // needs to gather the return value
+       // however the entry will not be polled again
+       // because its res value is not 0 now
+      }
+  }
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// External routines
+//////////////////////////////////////////////////////////////////////
+
+// from glibc source: include/libc-symbols.h
+# define strong_alias(name, aliasname) _strong_alias(name, aliasname)
+# define _strong_alias(name, aliasname) \
+  extern __typeof (name) aliasname __attribute__ ((alias (#name)));
+
+inline ssize_t read(int fd, void *buf, size_t count)
+{
+  CAP_SET_SYSCALL();
+  tdebug("fd=%d  buf=%p  count=%d\n",fd, buf, count);
+  return do_rw(READ, fd, buf, count, (off_t)-1);
+}
+strong_alias (read, __read);
+
+inline ssize_t write(int fd, const void *buf, size_t count)
+{
+  CAP_SET_SYSCALL();
+  tdebug("fd=%d  buf=%p  count=%d\n",fd, buf, count);
+  return do_rw(WRITE, fd, (void*) buf, count, (off_t)-1);
+}
+strong_alias (write, __write);
+
+inline ssize_t pread(int fd, void *buf, size_t count, off_t offset)
+{
+  CAP_SET_SYSCALL();
+  tdebug("fd=%d  buf=%p  count=%d  off=%lud\n",fd, buf, count, offset);
+  return do_rw(PREAD, fd, buf, count, offset);
+}
+strong_alias (pread, __pread);
+
+inline ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset)
+{
+  CAP_SET_SYSCALL();
+  tdebug("fd=%d  buf=%p  count=%d  off=%lud\n",fd, buf, count, offset);
+  return do_rw(PWRITE, fd, (void*) buf, count, offset);
+}
+strong_alias (pwrite, __pwrite);
+
+// bogus readv(), writev()
+inline ssize_t readv(int fd, const struct iovec *vector, int count)
+{
+  (void) fd,
+  (void) vector;
+  (void) count;
+  CAP_SET_SYSCALL();
+  tdebug(" \n");
+  errno = ENOSYS;
+  CAP_CLEAR_SYSCALL();
+  return -1;
+}
+strong_alias (readv, __readv);
+
+inline ssize_t writev(int fd, const struct iovec *vector, int count)
+{
+  // foolish implementation
+  int i, rv, total = 0;
+  CAP_SET_SYSCALL();
+  tdebug("fd=%d, count=%d\n", fd, count);
+  for (i=0; i < count; i++) {
+    rv = write(fd, (vector+i)->iov_base, (vector+i)->iov_len);
+    if (rv == -1) {
+      CAP_CLEAR_SYSCALL();
+      return rv;
+    }
+    total += rv;
+  }
+  CAP_CLEAR_SYSCALL();
+  return total;
+}
+strong_alias (writev, __writev);
+
+
+/**
+ * wrapper for open()
+ **/
+int open(const char *pathname, int flags, ...)
+{
+  mode_t mode;
+  int fd;
+  fdstruct_t *fds;
+  va_list ap;
+  
+  if(flags & O_CREAT) {
+    va_start(ap, flags);
+    mode = va_arg(ap, mode_t);
+    va_end(ap);
+  } else {
+    mode = 0744; // this is ignored anyway
+  }
+
+  if( !cap_override_rw ) {
+    return syscall(SYS_open, pathname, flags, mode);
+  }
+
+  tdebug("path=%s\n",pathname);
+
+  flags |= O_NONBLOCK;
+  //flags &= ~O_NONBLOCK;
+
+  CAP_SET_SYSCALL();
+#ifdef DO_EXTRA_YIELDS
+  if( cap_override_rw ) thread_yield();
+#endif
+  fd = syscall(SYS_open,pathname, flags, mode);
+  CAP_CLEAR_SYSCALL();
+  tdebug("fd=%d\n", fd);
+  if(fd < 0) 
+    return fd;
+
+  // set the file back to blocking mode, so the IO routines will wait correctly for completions
+  // FIXME: 1. this should perhaps depend on what sort of disk IO is being used
+  // FIXME: 2. This may be problematic for things like pipes, special
+  // devices, etc. - those should perhaps go through the poll
+  // interface. (?)
+  //  if( set_blocking(fd) != 0 )
+  //  return -1;
+
+
+  fds = get_fdstruct_no_dup_resolve(fd);
+  zero_fdstruct( fds );
+  fds->state = FD_OPEN;
+  fds->type = FD_FILE;
+
+  // FIXME: there is an ugly initialization race here, if we don't do
+  // this check.  This is a pretty hacky solution, though - find a
+  // better one!!
+  if( cap_override_rw )
+    GET_REAL_CPU_TICKS( fds->time_opened );
+  thread_stats_open_file();
+  
+  return fd;
+}
+strong_alias (open, __open);
+
+
+/**
+ * wrapper for creat()
+ **/
+inline int creat(const char *pathname, mode_t mode)
+{
+  CAP_SET_SYSCALL();
+  tdebug("path=%s\n",pathname);
+  return open(pathname, O_CREAT|O_WRONLY|O_TRUNC, mode);
+}
+strong_alias (creat, __creat);
+
+/**
+ * wrapper for close()    
+ **/
+int close(int fd)
+{
+  tdebug("fd=%d\n",fd);
+  if(fd < 0) { errno = EBADFD; return -1; }
+  
+  // FIXME: need to clean up outstanding requests, so we don't have
+  // confusion when the OS re-uses this FD.  For FDs accessed by a
+  // single thread, there will never be other outstanding requets.
+  // For FDs accessed by multiple threads, it probably makes sense to
+  // wait for the other IOs to complete before closing.  The best way
+  // to do this is to add a CLOSE request type, and just queue this
+  // along w/ everything else.
+  // 
+  // It might be good to track this anyway, to inform the programmer,
+  // since outstanding requests here means there is a race in the app
+  // anyway.  The only time this really makes sense is if multiple
+  // threads are accessing a particular file, and an IO error occurrs
+  // that should effect all of them.  In this case, the order doesn't
+  // matter, since all requests that are submitted to the kernel after
+  // the IO error occurs will simply return errors anyway.
+
+
+  close_fd(fd);
+
+  {
+    int ret;
+    CAP_SET_SYSCALL();
+#ifdef DO_EXTRA_YIELDS
+    thread_yield();
+#endif
+    //thread_usleep(100);
+    //markmark
+
+    ret = syscall(SYS_close,fd);
+    CAP_CLEAR_SYSCALL();
+    return ret;
+  }
+
+  
+
+  // FIXME: close always takes a long time - why??
+  //return syscall(SYS_close,fd);
+}
+strong_alias (close, __close);
+
+/**
+ * wrapper for lseek.  Keep track of current file offset, for async disk routines.
+ **/
+inline off_t lseek(int fd, off_t off, int whence) 
+{
+  int res;
+  fdstruct_t *fds;
+
+  tdebug("fd=%d, off=%ld, whence=%d\n",fd, off, whence);
+  if(fd < 0) { errno = EBADFD; return -1; }
+
+  fds = get_fdstruct(fd);
+
+  if (fds->state != FD_OPEN) {
+    errno = EBADF;
+    return -1;
+  }
+
+  if (fds->type != FD_FILE) {
+    errno = ESPIPE;
+    return -1;
+  }
+
+  // FIXME: we _could_ skip the seek if we are using linux aio.  This
+  // causes trouble with directory functions, however, since we do not
+  // catch readdir()/getdents(), and these make use of lseek.
+  res = syscall(SYS_lseek, fd, off, whence);
+  if (res != -1)
+    fds->off = res;
+
+  /*
+    // FIXME: we don't need this, since we actually just do the system call.
+  switch(whence) {
+  case SEEK_SET: fds->off = off; res = fds->off; break;
+  case SEEK_CUR: fds->off += off; res = fds->off; break;
+  case SEEK_END: assert(0); break;   // FIXME
+  default: res = (off_t)-1; errno = EINVAL;
+  }
+  */
+  
+  return res;
+}
+strong_alias (lseek, __lseek);
+
+
+/**
+ * wrapper for fcntl.  we only need this for dup-ed fds. 
+ **/
+int fcntl(int fd, int cmd, ...)
+{
+  int res;
+  va_list ap;
+  tdebug("fd=%d\n",fd);
+  if(fd < 0) { errno = EBADFD; return -1; }
+
+  va_start(ap, cmd);
+
+  switch( cmd ) {
+
+    // set up FD correctly for F_DUPFD
+  case F_DUPFD: {
+    long newfd = va_arg(ap, long);
+    // FIXME: this might block on closing newfd
+    CAP_SET_SYSCALL();
+#ifdef DO_EXTRA_YIELDS
+    thread_yield();
+#endif
+    res = syscall(SYS_fcntl, fd, cmd, newfd);
+    CAP_CLEAR_SYSCALL();
+    debug("F_DUPFD: fd=%d, newfd=%ld, res=%d\n", fd, newfd, res);
+    if(res < 0) return res;
+    dup_fd(fd, res);
+    break;
+  }
+    // struct flock* arg
+  case F_GETLK: case F_SETLK: case F_SETLKW: {
+    struct flock *arg = va_arg(ap, struct flock *);
+    // FIXME: this could block - don't allow them.
+    assert( cmd != F_SETLKW );
+    res = syscall(SYS_fcntl, fd, cmd, arg);
+    break;
+  }
+    // no arg cases, just to be clean
+  case F_GETFD: case F_GETOWN: 
+    //case F_GETSIG: // linux-specific.  should be OK w/ below, so leave it out
+    res = syscall(SYS_fcntl, fd, cmd);
+    break;
+
+  case F_GETFL: {
+    fdstruct_t *fds = get_fdstruct(fd);
+    res = syscall(SYS_fcntl, fd, cmd);
+    if( !fds->nonblocking ) // clear 
+      res &= ~O_NONBLOCK;
+    break;
+  }
+  case F_SETFL: {
+    long arg = va_arg(ap, long);
+    fdstruct_t *fds = get_fdstruct(fd);
+    if (arg & O_NONBLOCK)
+    {
+      fds->nonblocking = 1;
+      debug("setting fd=%d to non-blocking mode\n", fd);
+    } else {
+      fds->nonblocking = 0;
+      debug("setting fd=%d to blocking mode\n", fd);
+    }
+    // we should not let the user set the fd to blocking mode
+    arg |= O_NONBLOCK;  // FIXME: we need to coax the user with F_GETFL too
+    res = syscall(SYS_fcntl, fd, cmd, arg);
+    break;
+  }
+
+    // no arg and long arg can be treated the same, since fcntl() will
+    // ignore the spurious long() arg.
+  default: {
+    long arg = va_arg(ap, long);
+    res = syscall(SYS_fcntl, fd, cmd, arg);
+    break;
+    }
+  }
+  
+  va_end(ap);
+
+  return res;
+}
+strong_alias (fcntl, __fcntl);
+
+
+/**
+ * wrapper for connect.  suspend the thread while the connection takes place.
+ **/
+int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
+{
+  fdstruct_t *fds;
+  int res;
+  tdebug("fd=%d\n",sockfd);
+  if(sockfd < 0) { errno = EBADFD; return -1; }
+
+  // set nonblocking.  NOTE: we do this here rather than in socket(),
+  // on the off chance that someone passes us a valid socket not
+  // obtained via socket() (for example, something that was dup-ed.
+  if( set_nonblocking(sockfd) < 0 )  return -1;
+
+  // start the connect operation.  
+  //
+  // FIXME: the socketcall stuff here is both ugly and linux-specific.
+  // This should be abstracted for portability.
+  (void) serv_addr;
+  (void) addrlen;
+  res = syscall(SYS_socketcall, SYS_CONNECT, &sockfd);
+  if(res == -1 && errno != EINPROGRESS)
+    return res;
+
+  // initialize the fdstruct
+  fds = get_fdstruct(sockfd);
+  fds->type = FD_SOCKET;
+  fds->state = FD_CLOSED;
+
+  // connection is in progress, so block until done
+  if(res==-1) {
+    iorequest_t req;
+    int err;
+    socklen_t len = sizeof(err);
+
+    req.fds = fds;
+    req.type = CONNECT;
+    req.thread = thread_self();
+
+    // block
+    add_waiter(&req);
+    CAP_SET_SYSCALL();
+    IOSTAT_START(sockio);
+    res = sockio_add_request(&req);
+    IOSTAT_DONE(sockio,res<0);
+    CAP_CLEAR_SYSCALL();
+    remove_waiter(&req);
+
+    if( res < 0)
+      return -1; 
+
+    // call getsockopt() to see if the connect() succeeded
+    res = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &len);
+    assert(res == 0);
+    if(err > 0) {
+      errno = err;
+      return -1;
+    }
+    assert(err == 0);
+  }
+
+#ifdef USE_NODELAY
+  if( set_tcp_nodelay(sockfd) < 0 )  return -1;
+#endif
+
+  // successfully created connection
+  fds->state = FD_OPEN;
+  thread_stats_open_socket();
+  GET_REAL_CPU_TICKS( fds->time_opened );
+  return 0;
+}
+strong_alias (connect, __connect);
+
+#define HP_TIMING_NOW(Var)      __asm__ __volatile__ ("rdtsc" : "=A" (Var))
+unsigned long rdtsc;
+
+/**
+ * wrapper for accept.  
+ **/
+int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
+{
+  fdstruct_t *fds;
+  int res;
+  unsigned long args[4];
+  tdebug("fd=%d\n",sockfd);
+
+  args[0] = (unsigned long)sockfd;
+  args[1] = (unsigned long)addr;
+  args[2] = (unsigned long)addrlen;
+  args[3] = (unsigned long)SOCK_NONBLOCK;
+
+  if(sockfd < 0) { errno = EBADFD; return -1; }
+  fds = get_fdstruct(sockfd);
+
+  // set nonblocking.  NOTE: we do this here rather than in socket(),
+  // on the off chance that someone passes us a valid socket not
+  // obtained via socket() (for example, something that was dup-ed.
+  if( fds->state != FD_LISTENING ) {
+    fds->state = FD_LISTENING;
+
+    if (set_nonblocking(sockfd) == -1) {
+      fatal("failed to set accept socket to nonblocking IO\n");
+      return -1;
+    }
+  }
+
+  // try the syscall first 
+  res = -1;
+  res = syscall(SYS_socketcall, SYS_ACCEPT4, args);
+
+  // do the accept request
+  if( res < 0  &&  (errno == EAGAIN || errno == EWOULDBLOCK) ){
+    iorequest_t req;
+
+    // set up a request
+    req.fds = fds;
+    req.type = ACCEPT;
+    req.args.scall.which = SYS_ACCEPT;
+    req.args.scall.argv = &sockfd;
+    req.thread = thread_self();
+
+    // block
+    add_waiter(&req);
+    CAP_SET_SYSCALL();
+    IOSTAT_START(sockio);
+    res = sockio_add_request(&req);
+    IOSTAT_DONE(sockio, res<0);
+    CAP_CLEAR_SYSCALL();
+    remove_waiter(&req);
+  }
+
+  // allocate the fdstruct_t
+  if(res >= 0) {
+    fdstruct_t *newfds = get_fdstruct(res);
+    zero_fdstruct( newfds );
+    newfds->type = FD_SOCKET;
+    newfds->state = FD_OPEN;
+    GET_REAL_CPU_TICKS( newfds->time_opened );
+
+    if (set_nonblocking(res) == -1) return -1;
+#ifdef USE_NODELAY
+    if (set_tcp_nodelay(res) == -1) return -1;
+#endif
+
+    thread_stats_open_socket();
+  } 
+
+  else {
+    perror("error response from accept()");
+  }
+
+  HP_TIMING_NOW(rdtsc);
+  tdebug("rv=%d, rdtsc=%lu\n", res, rdtsc);
+        
+  return res;
+}
+strong_alias (accept, __accept);
+
+/**
+ * wrapper for poll.
+ **/
+int poll(struct pollfd *ufds, nfds_t nfds, int timeout)
+{
+  int rv, sus_rv, i;
+  long utimeout;
+  tdebug("nfds=%d, timeout=%d, ufds[0].fd=%d\n", (int)nfds, timeout, ufds[0].fd);
+  CAP_SET_SYSCALL();
+
+  if (0 && timeout == 0) {
+    // no timeout required
+    // delegate to syscall directly
+    tdebug("poll delegated to syscall.\n"); 
+    return syscall(SYS_poll, ufds, nfds, 0);
+  }
+
+  // FIXME: turn this into a blocking call w/ no timeout, if there is only one fd
+
+#if 1
+  if( nfds == 1 ) {
+    iorequest_t req;
+    int res;
+
+    // set up a request
+    req.fds = get_fdstruct( ufds[0].fd );
+    req.type = POLL1;
+    req.args.poll1.ufds = ufds;
+    req.thread = thread_self();
+
+    // block
+    add_waiter(&req);
+    CAP_SET_SYSCALL();
+    IOSTAT_START(sockio);
+    res = sockio_add_request(&req);
+    IOSTAT_DONE(sockio, res<0);
+    CAP_CLEAR_SYSCALL();
+    remove_waiter(&req);
+    
+    return res;
+  }
+
+  assert( 0 ); // works for apache...  ;-)
+#endif
+
+  utimeout = timeout * 1000;
+
+  assert (num_extra_polls < MAX_EXTRA_POLL_ENTRIES);
+
+  i = num_extra_polls;
+  extra_poll_entries[i].t = thread_self();
+  extra_poll_entries[i].ufds = ufds;
+  extra_poll_entries[i].nfds = nfds;
+  extra_poll_entries[i].res = 0;
+  extra_poll_entries[i].pollcount = 0;
+  extra_poll_entries[i].pos = &i;     // i will be updated if this entry is moved by other threads
+  
+  num_extra_polls++;
+
+  tdebug("poll count=%d, timeout=%ld\n", num_extra_polls, utimeout);
+  
+  sus_rv = thread_suspend_self(utimeout);
+
+  assert(extra_poll_entries[i].ufds == ufds);   // make sure we have the same entry
+
+  // this result is correct no matter whether timeout happens
+  // RACE: sus_rv == TIMEDOUT doesn't mean the poll is not successful
+  // the sequence can be: timeout -> poll gets events -> this thread is scheduled
+  rv = extra_poll_entries[i].res;
+
+  // delete the entry
+  assert(num_extra_polls > 0);   // contains at least our entry
+  memcpy(&extra_poll_entries[i], 
+        &extra_poll_entries[num_extra_polls - 1],
+        sizeof(poll_entry_t)); // copy the last entry over
+  *extra_poll_entries[i].pos = i;   // update its position pointer
+  num_extra_polls--;
+  
+  tdebug("poll finished, count=%d\n", num_extra_polls);
+
+  return rv;
+}
+strong_alias (poll, __poll);
+
+/**
+ * wrapper for select
+ * this is not fully implemented (no blocking select supported
+ */
+int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
+{
+  int ret;
+  CAP_SET_SYSCALL();
+  tdebug("count=%d\n", n);
+                
+  if (timeout->tv_sec == 0 && timeout->tv_usec == 0) {
+    // nonblocking polling
+#ifdef DO_EXTRA_YIELDS
+    thread_yield();
+#endif
+    ret = syscall(SYS_select, n, readfds, writefds, exceptfds, timeout);
+  } else if (readfds == NULL && writefds == NULL && exceptfds == NULL) {
+    // barf if the timeout would wrap around.  4294 = 2^32 / 10^6
+    assert( timeout->tv_sec < 4294 );  
+    thread_usleep((unsigned) timeout->tv_sec * 1000000 + (unsigned)timeout->tv_usec);
+    ret = 0;
+  } else {
+    output("blocking select() not implemented!\n");
+    ret = -1;
+    assert(0);
+  }
+  
+
+  CAP_CLEAR_SYSCALL();
+  return ret;
+}
+strong_alias(select, __select);
+
+// FIXME: do the same thing for pselect()
+
+
+/**
+ * wrapper for dup
+ **/
+int dup(int oldfd)
+{
+  int newfd;
+  CAP_SET_SYSCALL();
+  tdebug("fd=%d\n",oldfd);
+  if(oldfd < 0) { errno = EBADFD; return -1; }
+
+#ifdef DO_EXTRA_YIELDS
+  thread_yield();
+#endif
+  newfd = syscall(SYS_dup,oldfd);
+  if(newfd != -1) {
+    dup_fd(oldfd, newfd);
+  }
+  CAP_CLEAR_SYSCALL();
+  return newfd;
+}
+strong_alias (dup, __dup);
+
+
+/**
+ * wrapper for dup2
+ **/
+int dup2(int oldfd, int newfd)
+{
+  int ret;
+  tdebug("oldfd=%d newfd=%d\n",oldfd,newfd);
+
+  if(oldfd < 0) { errno = EBADFD; return -1; }
+  
+  CAP_SET_SYSCALL();
+#ifdef DO_EXTRA_YIELDS
+  thread_yield();
+#endif
+  ret = syscall(SYS_dup2, oldfd, newfd);
+  //res = __dup2(oldfd, newfd);  // alternative?
+  CAP_CLEAR_SYSCALL();
+
+  dup_fd(oldfd, newfd);
+
+  return ret;
+}
+strong_alias (dup2, __dup2);
+
+/**
+ * Sleep
+ **/
+// FIXME: what about signals?
+unsigned int sleep(unsigned int sec) {
+  CAP_SET_SYSCALL();
+  thread_usleep((unsigned long long) sec * 1000000);
+  CAP_CLEAR_SYSCALL();
+  return 0;
+}
+strong_alias (sleep, __sleep);
+
+
+/**
+ * usleep
+ **/
+// FIXME: what about signals?
+int usleep(__useconds_t usec) {
+  tdebug("usec=%ld\n", (long)usec);
+  CAP_SET_SYSCALL();
+  thread_usleep(usec);
+  CAP_CLEAR_SYSCALL();
+  return 0;
+}
+strong_alias (usleep, __usleep);
+
+
+
+/**
+ * wrapper for pipe.
+ **/
+int pipe(int filedes[2])
+{
+  int ret = syscall(SYS_pipe, filedes);
+  if( ret == 0 ) {
+    fdstruct_t *fds;
+    fds = get_fdstruct(filedes[0]);  fds->state = FD_OPEN;    GET_REAL_CPU_TICKS( fds->time_opened );
+    fds = get_fdstruct(filedes[1]);  fds->state = FD_OPEN;    GET_REAL_CPU_TICKS( fds->time_opened );
+    thread_stats_open_socket();
+    thread_stats_open_socket();
+  }
+  return ret;
+}
+strong_alias (pipe, __pipe);
+
+
+int socketpair(int d, int type, int protocol, int sv[2])
+{
+  int ret;
+  (void) type; (void) protocol;
+
+  ret = syscall(SYS_socketcall, SYS_SOCKETPAIR, &d);
+  if( ret == 0 ) {
+    fdstruct_t *fds;
+    fds = get_fdstruct(sv[0]);  fds->state = FD_OPEN;    GET_REAL_CPU_TICKS( fds->time_opened );
+    fds = get_fdstruct(sv[1]);  fds->state = FD_OPEN;    GET_REAL_CPU_TICKS( fds->time_opened );
+    thread_stats_open_socket();
+    thread_stats_open_socket();
+  }
+  return ret;
+}
+strong_alias(socketpair, __socketpair);
+
+
+/*
+// FIXME: do some cleanup of thread lib state?
+int fork()
+{
+  assert(0);
+  return -1;
+}
+strong_alias (fork,__fork);
+*/
+
+// FIXME: do this
+
+
+
+// apache's DNS lookups have problems w/ this, so disable for now
+#if 0
+/**
+ * This is the same for all of the send / recv functions
+ **/
+static inline int sendrecv_aux(int type, int which, int fd, void *args)
+{
+  iorequest_t req;
+  int res;
+  (void) type;
+
+  // set up a request
+  req.fds = get_fdstruct(fd);
+  req.type = type;
+  req.args.scall.which = which;
+  req.args.scall.argv = args;
+  req.thread = thread_self();
+  
+  // block
+  add_waiter(&req);
+  IOSTAT_START(sockio);
+  res = sockio_add_request(&req);
+  IOSTAT_DONE(sockio, res<0);
+  CAP_CLEAR_SYSCALL();
+  remove_waiter(&req);
+  
+  return res;
+}
+
+/**
+ * wrapper for send
+ **/
+int send(int s, const void *msg, size_t len, int flags)
+{
+  (void) msg; (void) len;
+
+  if(flags & MSG_DONTWAIT)
+    return syscall(SYS_socketcall, SYS_SEND, &s);
+
+  flags |= MSG_DONTWAIT; // switch to nonblocking
+  CAP_SET_SYSCALL();
+  return sendrecv_aux(SEND, SYS_SEND, s, &s);
+}
+strong_alias (send,__send);
+
+
+/**
+ * wrapper for sendto
+ **/
+int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen)
+{
+  (void) msg; (void) len; (void) to; (void) tolen;
+
+  if(flags & MSG_DONTWAIT)
+    return syscall(SYS_socketcall, SYS_SENDTO, &s);
+
+  flags |= MSG_DONTWAIT; // switch to nonblocking
+  CAP_SET_SYSCALL();
+  return sendrecv_aux(SEND, SYS_SENDTO, s, &s);
+}
+strong_alias (sendto,__sendto);
+
+/**
+ * wrapper for sendmsg
+ **/
+int sendmsg(int s, const struct msghdr *msg, int flags) 
+{
+  (void) msg;
+
+  if(flags & MSG_DONTWAIT)
+    return syscall(SYS_socketcall, SYS_SENDMSG, &s);
+
+  flags |= MSG_DONTWAIT; // switch to nonblocking
+  CAP_SET_SYSCALL();
+  return sendrecv_aux(SEND, SYS_SENDMSG, s, &s);
+}
+strong_alias (sendmsg,__sendmsg);
+
+
+// FIXME: the recv functions don't properly handle the MSG_WAITALL
+// flag - this needs to be fixed in the IO polling functions!!
+
+/**
+ * wrapper for recv()
+ **/
+int recv(int s, void *buf, size_t len, int flags)
+{
+  (void) buf; (void) len; (void)flags;
+
+  assert( (flags & MSG_WAITALL) == 0 );
+  CAP_SET_SYSCALL();
+  return sendrecv_aux(RECV, SYS_RECV, s, &s);
+}
+strong_alias (recv,__recv);
+
+  
+/**
+ * wrapper for recvfrom()
+ **/
+int recvfrom(int  s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen)
+{
+  (void) buf; (void) len; (void) from; (void) fromlen; (void) flags;
+
+  assert( (flags & MSG_WAITALL) == 0 );
+  CAP_SET_SYSCALL();
+  return sendrecv_aux(RECV, SYS_RECVFROM, s, &s);
+}
+strong_alias (recvfrom,__recvfrom);
+
+
+/**
+ * wrapper for recvmsg()
+ **/
+int recvmsg(int s, struct msghdr *msg, int flags)
+{
+  (void) msg; (void) flags;
+
+  assert( (flags & MSG_WAITALL) == 0 );
+  CAP_SET_SYSCALL();
+  return sendrecv_aux(RECV, SYS_RECVMSG, s, &s);
+}
+strong_alias (recvmsg,__recvmsg);
+
+
+// FIXME: need a real implimentation of this
+int shutdown(int s, int how) {
+  (void) how;
+  close(s);
+  return 0;
+}
+strong_alias (shutdown,__shutdown);
+#endif
+
+
+// FIXME: wrappers still needed:
+//     stat, fstat
+//     readdir (both a system call and a lib func --- ugly)
+//     getdents
+
+//     shutdown ??
+//   
+//
+// look at pth_syscal.c and pth_high.c for others (sigprocmask, fork, etc.)
+
+
+// FIXME.  BUGS:
+//     check pipes, FIFOs, ttys, etc.  Need to use the right lib.
+
+
+
+
+
+
+//////////////////////////////////////////////////////////////////////
+// Initialization and polling wrapper functions
+//////////////////////////////////////////////////////////////////////
+
+
+extern int num_runnable_threads;
+
+/**
+ * perform a poll
+ **/
+// FIXME: have this return a number, to indicate how many things were found (?)
+static void blocking_io_poll(long long usecs)
+{
+  if( usecs > 1e7) {
+    output("blocking_io_poll: %lld  %lld\n", usecs, (long long)1e7);
+    //abort();
+    usecs = 0;
+  }
+    
+  start_timer(&poll_timer);
+  {
+    if( sockio_stats.active >  0  &&  diskio_stats.active == 0 ) 
+      sockio_poll( usecs );
+    else if ( sockio_stats.active == 0  &&  diskio_stats.active > 0)
+      diskio_poll( usecs );
+    else {
+      // don't allow either to block
+      if( diskio_stats.active > 0 )
+        diskio_poll( (long long) 0 );
+        //diskio_poll( usecs );
+      if( sockio_stats.active > 0 )
+        sockio_poll( usecs );
+    }
+  }
+  stop_timer(&poll_timer);
+
+  // extra poll never blocks anyway...
+  start_timer(&extra_poll_timer);
+  extra_poll();
+  //tdebug("num=%d\n", num_runnable_threads);
+  stop_timer(&extra_poll_timer);
+}
+
+
+#define SET_IO(type,name) \
+do {\
+  if( !type##_##name##_is_available ) {\
+    warning("%s '%s' is not available on this system\n",__STRING(type),__STRING(name)); \
+    exit(1); \
+  } \
+  type##_init = type##_##name##_init; \
+  type##_poll = type##_##name##_poll; \
+  type##_add_request = type##_##name##_add_request; \
+  output("CAPRICCIO_%s=%s\n", \
+         (strcmp(__STRING(type),"sockio") == 0 ? "SOCKIO" : "DISKIO"), \
+         __STRING(name)); \
+} while( 0 )
+
+static void pick_io_scheme()
+{
+  char *env;
+
+  // sockio
+  env = getenv("CAPRICCIO_SOCKIO");
+  if( env == NULL )// default
+    if( sockio_epoll_is_available )
+      SET_IO(sockio, epoll ); 
+    else
+      SET_IO(sockio, poll ); 
+  else if( !strcasecmp(env,"poll") )
+    SET_IO(sockio, poll );
+  else if( !strcasecmp(env,"epoll") )
+    SET_IO(sockio, epoll );
+  else 
+    fatal("Invalid value for CAPRICCIO_SOCKIO: '%s'\n",env);
+
+  // diskio
+  env = getenv("CAPRICCIO_DISKIO");
+  if( env == NULL ) // default
+    if( diskio_aio_is_available )
+      SET_IO(diskio, aio ); 
+    else
+      SET_IO(diskio, blocking ); // default
+  else if( !strcasecmp(env,"immediate") )
+    SET_IO(diskio, immediate );
+  else if( !strcasecmp(env,"blocking") )
+    SET_IO(diskio, blocking );
+  else if( !strcasecmp(env,"kthread") )
+    SET_IO(diskio, kthread );
+  else if( !strcasecmp(env,"aio") )
+    SET_IO(diskio, aio );
+  else 
+    fatal("Invalid value for CAPRICCIO_DISKIO: '%s'\n",env);
+
+}
+
+
+
+/**
+ * init the IO routines
+ **/
+static void blocking_io_init() __attribute__((constructor));
+static void blocking_io_init() 
+{
+  static int init_done = 0;
+  if( init_done ) return;
+  init_done = 1;
+
+  thread_latch_init( fdstruct_latch );
+
+  // pick the version of the disk and socket IO libs
+  pick_io_scheme();
+
+  sockio_init();
+  diskio_init();
+
+  set_io_polling_func( blocking_io_poll );
+
+  init_timer(&poll_timer);
+  register_timer("poll", &poll_timer);
+  init_timer(&extra_poll_timer);
+  register_timer("extra_poll", &extra_poll_timer);
+
+}
+// This is implemented in NIO now.
+int __cap_outstanding_disk_requests = 0;
diff --git a/user/c3po/aio/blocking_io.h b/user/c3po/aio/blocking_io.h
new file mode 100644 (file)
index 0000000..20b35c8
--- /dev/null
@@ -0,0 +1,38 @@
+
+
+#ifndef BLOCKING_IO_H
+#define BLOCKING_IO_H
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <sys/syscall.h>
+//#ifdef HAVE_SYS_SOCKETCALL_H
+//#include <sys/socketcall.h>
+//#endif
+
+
+// replacements for standard system calls
+extern int open(const char *pathname, int flags, ...);
+extern int creat(const char *pathname, mode_t mode);
+extern int close(int fd);
+
+extern ssize_t read(int fd, void *buf, size_t count);
+extern ssize_t write(int fd, const void *buf, size_t count);
+extern ssize_t pread(int fd, void *buf, size_t count, off_t offset);
+extern ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
+
+extern off_t lseek(int fd, off_t off, int whence);
+extern int fcntl(int fd, int cmd, ...);
+extern int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
+extern int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
+extern int dup(int oldfd);
+extern int dup2(int oldfd, int newfd);
+
+
+// FIXME: should add disk r/w calls that specify the offset
+
+
+
+#endif // BLOCKING_IO_H
diff --git a/user/c3po/aio/check_syscall b/user/c3po/aio/check_syscall
new file mode 100755 (executable)
index 0000000..d5640bc
--- /dev/null
@@ -0,0 +1,85 @@
+#!/usr/bin/perl
+######################################################################
+#
+#  Do a sanity check on a source code file, to make sure it makes
+#  system calls via syscall(), rather directly.  This prevents stupid
+#  mistakes due to looping in the io routines
+#
+#  USAGE: check_syscall FILENAME
+#
+#  add special cases that are _not_ bugs to the @special_cases array
+#
+######################################################################
+use strict;
+
+
+# NOTE: keep this in sync w/ blocking_io.h
+my @syscalls = qw(open creat close read write
+                  lseek fcntl connect accept dup dup2);
+
+# files to skip entirely
+my @skipfiles = qw(aiotest.c pthread_diskio.c);
+
+# special cases, for various files
+my %special_cases =
+(
+ 'blocking_io.c' => 
+ [
+  ' SYSCALL\(.*\)\s*$',          # the definition of our version of the system call
+  'open\(pathname, O_CREAT',   # creat() calls open()
+ ],
+);
+
+
+my $syscall_re = "\\b\(" . join("|", @syscalls) . "\)\\b";
+my $skipfiles_re = "\(" . join("|", @skipfiles) . "\)\$";
+my $exitcode = 0;
+
+foreach my $file (@ARGV) {
+    next if($file =~ m!$skipfiles_re!);
+
+    #open(FILE,"<$file") || die "can't read from file '$file'";
+
+    my $syscall;
+    my $lineno=0;
+ LINE: while (my $line=<FILE>) {
+        $lineno++;
+
+        # strip comments
+        $line =~ s!//.*$!!;
+        next if $line =~ /^ \* /;
+
+        # strip strings
+        $line =~ s!\\"!!g;
+        $line =~ s!\\'!!g;
+        $line =~ s!".*?"!!g;
+        $line =~ s!'.*?'!!g;
+
+        # check for syscalls
+        ($syscall) = ($line =~ m!$syscall_re!);
+        next if $syscall eq "";
+        
+        # special cases
+        foreach my $f (keys %special_cases) {
+            my $l = $special_cases{$f};
+            if ($file =~ /$f$/) {
+                foreach my $re (@$l) {
+                    my $temp = $re;
+                    $temp =~ s!SYSCALL!$syscall!g;
+                    next LINE if($line =~ m!$temp!);
+                }
+            }
+        }
+
+        # warn
+        print STDERR "$file:$lineno  BAD SYSCALL: $syscall() - Line follows:\n";
+        chomp($line); 
+        print $line, "\n";
+        $exitcode = 1;
+    }
+
+}
+
+
+exit $exitcode;
+
diff --git a/user/c3po/aio/diskio_aio.c b/user/c3po/aio/diskio_aio.c
new file mode 100644 (file)
index 0000000..f8bfc9a
--- /dev/null
@@ -0,0 +1,291 @@
+/**
+ * blocking IO routines
+ *
+ * These routines make use of asynchronous IO and the signal/wait
+ * calls of a threading package to provide a blocking IO abstraction
+ * on top of underlying non-blocking IO calls.
+ *
+ * The semantics of these calls mirror those of the standard IO
+ * libraries.
+ **/
+
+#include "threadlib.h"
+#include "io_internal.h"
+#include "util.h"
+
+
+#ifndef DEBUG_diskio_aio_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+
+#ifndef HAVE_AIO
+
+// dummy functions to allow correct linking
+int diskio_aio_is_available = 0;
+void diskio_aio_init() {};
+void diskio_aio_poll(long long usecs) { (void)usecs; }
+int diskio_aio_add_request(iorequest_t* req) { (void)req; return -1; }
+
+#else // HAVE_AIO
+
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libaio.h>
+#include <time.h>
+
+
+#define AIO_QUEUE_SIZE 10000
+
+int diskio_aio_is_available = 1;
+static void reinit();
+
+//////////////////////////////////////////////////////////////////////
+// Internal data
+//////////////////////////////////////////////////////////////////////
+
+
+// ioctx
+static io_context_t ioctx;
+
+static int num_outstanding = 0;
+
+int diskio_aio_add_request(iorequest_t* req)
+{
+  int ret;
+  struct iocb cb;
+  struct iocb *cbs[1];
+  debug_print_request("new request",req);
+
+  //diocb.req = req;
+  switch( req->type ) {
+  case READ:   
+  case PREAD:   
+    io_prep_pread(&cb, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->fds->off);  
+    break;
+  case WRITE:  
+  case PWRITE:  
+    io_prep_pwrite(&cb, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->fds->off); 
+    break;
+  default: assert(0);
+  }  
+
+  // set the data, for later
+  cb.data = req;
+
+  // Submit the request
+  cbs[0] = &cb;
+
+  ret = io_submit(ioctx, 1, cbs);
+  if( ret < 0 ) {
+    warning("reinitializing AIO!!  io_submit returned %d: %s\n", ret, strerror(0-ret));
+    reinit();
+    ret = io_submit(ioctx, 1, cbs);
+  }
+  if (ret < 0) {
+    errno = 0 - ret;
+    warning("reinit didn't help!!  io_submit returned %d: %s\n", ret, strerror(0-ret));
+    return -1;
+  } else if(ret != 1) {
+    warning("io_submit returned wrong number of requests!!\n");
+    errno = EINVAL;
+    return -1;
+  }
+
+#if 0
+  // debug code.  This forces an immediate reply, to avoid any
+  // scheduling / polling overhead in the threading layer.
+  // 
+  // For a single-threaded looping read() test, scheduling seems to
+  // add about 16% additional overhead.
+  if( 0 ) {
+    int res;
+    struct io_event myevents[1];    
+    struct timespec t;
+    t.tv_sec = 0;
+    t.tv_nsec = 0;
+
+    while ( (res = io_getevents(ioctx, 0, 1, myevents, NULL)) <= 0) {
+      ; 
+    }
+
+    {
+      struct iocb *cb = (struct iocb *)myevents[0].obj;
+      iorequest_t *rreq = (iorequest_t *)cb->data;
+
+      int len = myevents[0].res;
+
+      (void) rreq;
+      assert( rreq = req );
+
+      // set return values
+      req->ret = len;
+      req->err = myevents[0].res2;   // FIXME: Is this correct? 
+    }
+
+    return req->ret;
+  }
+#endif
+
+  debug("%s: submit a request(type=%d).\n", thread_name(thread_self()), req->type);
+
+  // suspend the current thread
+  num_outstanding++;
+  req->ret = 5551212;
+  req->err = 5551212;
+  thread_suspend_self(0);
+
+  debug_print_request("request done",req);
+
+  // set errno, and return
+  errno = req->err;
+  return req->ret;
+}
+
+
+// Process at most how many events at a time
+#define EVENT_BATCH_FACTOR 10
+static struct io_event events[EVENT_BATCH_FACTOR];
+
+// Read completion event from Linux-aio and resume corresponding 
+// threads.
+//
+// NOTE: this routine is NOT thread-safe!! 
+void diskio_aio_poll(long long usecs)
+{
+  int i, complete;
+  struct timespec t;
+
+  if( usecs > 1e7) {
+    output("diskio_aio_poll: %lld  %lld\n", usecs, (long long)1e7);
+    //abort();
+    usecs = 0;
+  }
+
+  // FIXME: not sure how AIO handles -1 timeout, so just give a big one
+  t.tv_sec = 0;
+  if( usecs < 0 ) {
+    t.tv_nsec = 1e8; // max of 100 ms
+  } else { 
+    t.tv_nsec = usecs*1000;
+  }
+
+  // just to be safe
+  if( t.tv_nsec > 1e8 ) t.tv_nsec = 1e8;
+
+
+  // short-circuit, if there are no outstanding requests
+  if( num_outstanding <= 0 )
+    return;
+
+  if ( (complete = io_getevents(ioctx, 0, EVENT_BATCH_FACTOR, events, &t)) <= 0) {
+    // No event for now, just return
+    return;
+  }
+
+  num_outstanding -= complete;
+  debug("%d disk events ready, %d still left\n", complete, num_outstanding); 
+
+  for (i = 0; i < complete; i++ )
+    {
+      struct iocb *cb = (struct iocb *)events[i].obj;
+      iorequest_t *req = (iorequest_t *) cb->data;
+
+      debug("events[%d]:\n", i);
+      debug("   req         %p\n", req);
+      debug("   data        %p\n", (void*) events[i].data);
+      debug("   obj         %p\n", (void*) events[i].obj);
+      debug("   res         %ld\n", events[i].res);
+      debug("   obj         %ld\n", events[i].res2);
+      debug("\n");
+      debug("iocb:\n");
+      debug("   data        %p\n", cb->data);
+      debug("   opcode      %d\n", cb->aio_lio_opcode);
+      debug("   filedes     %d\n", cb->aio_fildes);
+      debug("\n");
+      
+
+      // set return values
+      if( (long)events[i].res >= 0 ) {
+        req->ret = events[i].res;
+        req->err = 0;
+      } else {
+        req->ret = -1;
+        req->err = -1 * events[i].res;
+      }
+      debug("request: ret=%d err=%d\n", req->ret, req->err);
+      
+      // update file position, if necessary
+      if((req->type==READ || req->type==WRITE) && req->ret > 0) {
+        req->fds->off += req->ret;
+      }
+
+      thread_resume(req->thread);
+    }
+
+  // FIXME: adding io_destroy(ioctx) here prevents the SIGSEGV
+
+  debug("processed %d completion events.\n", complete);
+}
+
+/**
+ * Initailize the aio layer, possibly for a second time.  This is
+ * useful b/c there seem to be cases in which the AIO layer gets
+ * hosed, for no good reason.
+ **/
+static void reinit()
+{
+  static int init_done = 0;
+  int ret;
+
+  // if the IO layer was previously initialized, clean up
+  if( init_done ) {
+    // FIXME: we should kill off any pending requests here, too!!
+    if( num_outstanding > 0 )
+      warning("Reinitializing AIO with %d requests still outstanding!!\n",num_outstanding);
+    num_outstanding = 0;
+
+    ret = io_destroy(ioctx);
+    output("io_destroy() = %d\n", ret);
+  }
+
+  // Init Linux-AIO
+  ioctx = NULL;
+  if (io_setup(AIO_QUEUE_SIZE, &ioctx) != 0)
+    fatal("Failed to initialize Linux-AIO.  Do you have the right kernel?\n");
+
+  init_done = 1;
+}
+
+void diskio_aio_init() 
+{
+  static int init_done = 0;
+
+  if(init_done) return;
+  init_done = 1;
+
+  // Init Linux-AIO
+  reinit();
+
+  // FIXME: adding io_destroy(ioctx) here prevents the SIGSEGV
+
+  // FIXME: doing an atexit() func to call io_destroy() doesn't help.
+}
+
+#endif // HAVE_AIO
+
+
+
+
+
+
+
+
+
diff --git a/user/c3po/aio/diskio_blocking.c b/user/c3po/aio/diskio_blocking.c
new file mode 100644 (file)
index 0000000..78231d7
--- /dev/null
@@ -0,0 +1,121 @@
+
+/**
+ * Kernel threads version of async disk IO.  This is intended for use
+ * on development machines that don't have an aio patched kernel
+ * installed.
+ **/
+
+#include "io_internal.h"
+#include "util.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <syscall.h>
+#include <sys/syscall.h>
+
+#ifndef DEBUG_diskio_blocking_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+#ifndef SYS_pread
+# ifdef __NR_pread
+#  define SYS_pread     __NR_pread
+#  define SYS_pwrite   __NR_pwrite
+# else
+#  define SYS_pread     180
+#  define SYS_pwrite   181
+# endif
+#endif
+
+int diskio_blocking_is_available = 1;
+
+
+typedef struct {
+  linked_list_entry_t e;
+  iorequest_t *req;
+} rlist_entry_t;
+
+linked_list_t reqlist;
+
+latch_t diskio_latch;
+
+int diskio_blocking_add_request(iorequest_t* req)
+{
+  rlist_entry_t e;
+  debug_print_request("",req);
+
+  e.req = req;
+
+  thread_latch( diskio_latch );
+  ll_add_existing_to_tail(&reqlist, &e.e);
+  thread_unlatch( diskio_latch );
+
+  // suspend the current thread
+  thread_suspend_self(0);
+
+  // set errno, and return
+  errno = req->err;
+  return req->ret;
+}
+
+
+void diskio_blocking_poll(long long usecs)
+{
+  rlist_entry_t *e;
+  iorequest_t *req;
+  (void) usecs;  // we always block
+
+  while( 1 ) {
+    thread_latch( diskio_latch );
+    e = (rlist_entry_t*) ll_remove_head( &reqlist );
+    thread_unlatch( diskio_latch );
+
+    if(e == NULL) 
+      break;
+    req = e->req;
+
+    switch( req->type ) {
+    case READ:
+      debug("doing read(%d,%p,%d)\n", req->fds->fd, req->args.rw.buf, req->args.rw.count);
+      req->ret = syscall(SYS_read, req->fds->fd, req->args.rw.buf, req->args.rw.count);
+      debug("read done.\n");
+      break;
+    case WRITE:
+      debug("doing write(%d,%p,%d)\n", req->fds->fd, req->args.rw.buf, req->args.rw.count);
+      req->ret = syscall(SYS_write, req->fds->fd, req->args.rw.buf, req->args.rw.count);
+      debug("write done.\n");
+      break;
+    case PREAD:
+      debug("doing pread(%d,%p,%d,%d)\n", req->fds->fd, req->args.rw.buf, req->args.rw.count, (int)req->args.rw.off);
+      req->ret = syscall(SYS_pread, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->args.rw.off);
+      debug("pread done.\n");
+      break;
+    case PWRITE:
+      debug("doing pwrite(%d,%p,%d,%d)\n", req->fds->fd, req->args.rw.buf, req->args.rw.count, (int)req->args.rw.off);
+      req->ret = syscall(SYS_pwrite, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->args.rw.off);
+      debug("pwrite done.\n");
+      break;
+    default:
+      assert(0);
+    }
+
+    
+    
+    debug("resuming thread: %s\n", thread_name(req->thread));
+    thread_resume(req->thread);
+  }
+}
+
+
+void diskio_blocking_init() 
+{
+  ll_init(&reqlist, "diskio_kthread_reqlist", NULL);
+  thread_latch_init( diskio_latch );
+
+  
+}
+
diff --git a/user/c3po/aio/diskio_immediate.c b/user/c3po/aio/diskio_immediate.c
new file mode 100644 (file)
index 0000000..ab2247d
--- /dev/null
@@ -0,0 +1,80 @@
+
+/**
+ * Kernel threads version of async disk IO.  This is intended for use
+ * on development machines that don't have an aio patched kernel
+ * installed.
+ **/
+
+#include "io_internal.h"
+#include "util.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <sys/syscall.h>
+
+#ifndef DEBUG_diskio_immediate_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+
+#ifndef SYS_pread
+# ifdef __NR_pread
+#  define SYS_pread     __NR_pread
+#  define SYS_pwrite   __NR_pwrite
+# else
+#  define SYS_pread     180
+#  define SYS_pwrite   181
+# endif
+#endif
+
+int diskio_immediate_is_available = 1;
+
+
+int diskio_immediate_add_request(iorequest_t* req)
+{
+  debug_print_request("",req);
+
+  switch( req->type ) {
+  case READ:
+    debug("doing read(%d,%p,%d)\n", req->fds->fd, req->args.rw.buf, req->args.rw.count);
+    req->ret = syscall(SYS_read, req->fds->fd, req->args.rw.buf, req->args.rw.count);
+    debug("read done.\n");
+    break;
+  case WRITE:
+    debug("doing write(%d,%p,%d)\n", req->fds->fd, req->args.rw.buf, req->args.rw.count);
+    req->ret = syscall(SYS_write, req->fds->fd, req->args.rw.buf, req->args.rw.count);
+    debug("write done.\n");
+    break;
+  case PREAD:
+    debug("doing pread(%d,%p,%d,%d)\n", req->fds->fd, req->args.rw.buf, req->args.rw.count, (int)req->args.rw.off);
+    req->ret = syscall(SYS_pread, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->args.rw.off);
+    debug("pread done.\n");
+    break;
+  case PWRITE:
+    debug("doing pwrite(%d,%p,%d,%d)\n", req->fds->fd, req->args.rw.buf, req->args.rw.count, (int)req->args.rw.off);
+    req->ret = syscall(SYS_pwrite, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->args.rw.off);
+    debug("pwrite done.\n");
+    break;
+  default:
+    assert(0);
+  }
+
+  req->err = errno;
+  return req->ret;
+}
+
+
+void diskio_immediate_poll(long long usecs)
+{
+  (void) usecs;
+}
+
+
+void diskio_immediate_init() 
+{
+}
+
diff --git a/user/c3po/aio/diskio_kthread.c b/user/c3po/aio/diskio_kthread.c
new file mode 100644 (file)
index 0000000..bf5bd19
--- /dev/null
@@ -0,0 +1,221 @@
+
+/**
+ * Kernel threads version of async disk IO.  This is intended for use
+ * on development machines that don't have an aio patched kernel
+ * installed.
+ **/
+
+#include "io_internal.h"
+#include "util.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <syscall.h>
+#include <sys/syscall.h>
+#include <sched.h>
+
+#ifndef DEBUG_diskio_kthread_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+
+#ifndef SYS_pread
+# ifdef __NR_pread
+#  define SYS_pread     __NR_pread
+#  define SYS_pwrite   __NR_pwrite
+# else
+#  define SYS_pread     180
+#  define SYS_pwrite   181
+# endif
+#endif
+
+
+// FIXME: need more flexibility here.  These both behave very badly, in terms of latency.  ;-(
+
+#define NUM_WORKERS 30
+#define WORKER_SLEEP_MICROSECONDS 100
+//#define NUM_WORKERS 1
+//#define WORKER_SLEEP_MICROSECONDS 1
+
+occ_list_t *requests, *main_rspare;
+occ_list_t *finished, *main_fspare;
+static occ_list_t mainstructs[4];
+
+int diskio_kthread_is_available = 1;
+
+// in the main capriccio kernel thread
+int diskio_kthread_add_request(iorequest_t* req)
+{
+  occ_list_entry_t e;
+  debug_print_request("",req);
+
+  e.data = req;
+
+  // NOTE: we only use one spare for the main thread, since it is still running single-threaded.
+  occ_enqueue(&requests, &main_rspare, &e);
+
+  // suspend the current thread
+  thread_suspend_self(0);
+
+  // set errno, and return
+  errno = req->err;
+  return req->ret;
+}
+
+// in the main capriccio kernel thread
+void diskio_kthread_poll(long long usecs)
+{
+  occ_list_entry_t *e;
+  iorequest_t *req;
+  (void) usecs; // we never block here
+
+  while( 1 ) {
+    debug("main: about to dequeue\n");
+    e = occ_dequeue(&finished, &main_fspare);
+    debug("main: dequeued e=%p\n",e);
+
+    // the IO is now done, so just wake the thread
+    if(e == NULL) 
+      break;
+    req = (iorequest_t*) e->data;
+
+    debug_print_request("removed from finished queue",req);
+    tdebug("resuming thread: %s\n", thread_name(req->thread));
+    thread_resume(req->thread);
+  }
+}
+
+typedef struct {
+  occ_list_t *fspare;
+  occ_list_t *rspare;
+  long long wake_time;
+  int id;
+} worker_args_t;
+
+// do the actual IO - in seperate kernel threads
+static int diskio_worker_thread(void *clone_args)
+{
+  worker_args_t *args = (worker_args_t*) clone_args;
+  occ_list_entry_t *e;
+  iorequest_t *req;
+
+  while( 1 ) {
+
+    // get a pending request
+    debug("%d: about to dequeue\n", args->id);
+    e = occ_dequeue(&requests, &args->rspare);
+    debug("%d: got e=%p\n",args->id,e);
+
+    // no requests, sleep a bit
+    if(e == NULL) {
+      // FIXME: better to use signals?
+      struct timespec ts;
+      long long now = (current_usecs() % WORKER_SLEEP_MICROSECONDS);
+      
+      if( args->wake_time > now )
+        ts.tv_nsec = (args->wake_time - now);
+      else 
+        ts.tv_nsec = WORKER_SLEEP_MICROSECONDS - (args->wake_time - now);
+      ts.tv_sec = 0;
+      ts.tv_nsec = 1;
+
+      //syscall(SYS_nanosleep, &ts, NULL);
+      syscall(SYS_sched_yield);
+
+      continue;
+    }
+
+    req = (iorequest_t*) e->data;
+
+    debug_print_request("removed from request queue", req);
+
+    switch( req->type ) {
+    case READ:
+      req->ret = syscall(SYS_read, req->fds->fd, req->args.rw.buf, req->args.rw.count);
+      break;
+    case WRITE:
+      req->ret = syscall(SYS_write, req->fds->fd, req->args.rw.buf, req->args.rw.count);
+      break;
+    case PREAD:
+      req->ret = syscall(SYS_pread, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->args.rw.off);
+      break;
+    case PWRITE:
+      req->ret = syscall(SYS_pwrite, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->args.rw.off);
+      break;
+    default:
+      assert(0);
+    }
+
+    // put the request on the finished list
+    debug_print_request("adding to the finished queue", req);
+    occ_enqueue(&finished, &args->fspare, e);
+  }
+
+  return 0;
+}
+
+
+static worker_args_t arglist[NUM_WORKERS];
+static occ_list_t rsparelist[NUM_WORKERS];
+static occ_list_t fsparelist[NUM_WORKERS];
+
+// NOTE: 1024 causes corruption.
+//#define STACKSIZE 4096  // seems to be OK
+#define STACKSIZE 4096*4
+static char stacklist[NUM_WORKERS][STACKSIZE];
+
+// FIXME: remove CLONE_SIGHAND & possibly CLONE_THREAD once we use signal handling to rendesvous
+#ifndef CLONE_THREAD
+#define CLONE_THREAD 0
+#endif
+#ifndef CLONE_PARENT
+#define CLONE_PARENT 0
+#endif
+#define CLONE_THREAD_OPTS (\
+   CLONE_FS | \
+   CLONE_FILES | \
+   CLONE_PTRACE | \
+   CLONE_VM | \
+   CLONE_THREAD | \
+   CLONE_PARENT | \
+   CLONE_SIGHAND | \
+   0)
+
+//   CLONE_SIGHAND | 
+
+
+void diskio_kthread_init() 
+{
+  int i, ret;
+
+  // create request lists
+  debug("initializing\n");
+  requests = &mainstructs[0];  init_occ_list(requests);
+  finished = &mainstructs[1];  init_occ_list(finished);
+  debug("  done w/ lists\n");
+  main_rspare = &mainstructs[2];  // no init needed - just a copy to swap in & out
+  main_fspare = &mainstructs[3];  
+  
+  // create worker threads
+  // FIXME: kind of lame to have a fixed number
+  for( i=0; i<NUM_WORKERS; i++ ) {
+    arglist[i].rspare = &rsparelist[ i ];  // no init needed - just a copy to swap in & out
+    arglist[i].fspare = &fsparelist[ i ];
+    arglist[i].id = i;
+    arglist[i].wake_time = (WORKER_SLEEP_MICROSECONDS/NUM_WORKERS)*i;
+
+    ret = clone(diskio_worker_thread, stacklist[i]+STACKSIZE-4, CLONE_THREAD_OPTS, &arglist[i]);
+
+    if(ret == -1) {
+      perror("clone"); 
+      exit(1);
+    }
+  }
+  debug("  done w/ workers\n");
+
+}
+
diff --git a/user/c3po/aio/io_internal.h b/user/c3po/aio/io_internal.h
new file mode 100644 (file)
index 0000000..9696f28
--- /dev/null
@@ -0,0 +1,172 @@
+
+#ifndef IO_INTERNAL_H
+#define IO_INTERNAL_H
+
+#include "threadlib.h"
+#include "util.h"
+#include <sys/types.h>
+
+
+
+// types of FDs
+
+
+
+// track information about a file descriptor
+typedef struct fdstruct_st {
+  int fd;          // file descriptor number
+
+  // next 2 fields are for dup() handling
+  struct fdstruct_st *root_fds;   // "master" fd that contains all other info if this fd is a dup()'ed one
+                                  // if this is different from fd, then all other fields in the struct are irrelavant
+  struct fdstruct_st *next_fds;   // all dup'ed fd's form a circular linked list, this is the next one. 
+                                  // should be == NULL if not dup'ed
+  // group bitfields together
+  enum { // type of fd
+    FD_SOCKET=0,      // default 
+    FD_FILE           // regular disk file - use aio
+  } type:1;
+
+  enum { // valid states of an FD
+    FD_UNUSED=0,   // FD has not been seen before
+    FD_UNKNOWN,    // don't know the state of this FD yet
+    FD_CLOSED,     
+    FD_OPEN, 
+    FD_LISTENING,  // a non-blocking server socket, ready for accept()
+  } state:3;
+
+  unsigned int nonblocking:1;   // is the underlying file set to O_NONBLOCK?
+
+  cpu_tick_t time_opened;       // The time the fd was opened.  used internally for stats gathering
+    
+  off_t off;       // file offset
+
+  union {  // for use by underlying IO routines.
+    struct {                      // for sockio_epoll
+      __uint32_t events;
+    } epoll;
+  } u;
+
+  latch_t reqlatch;
+  linked_list_t reqlist;
+} fdstruct_t;
+
+// types of IO requests
+typedef enum {
+  READ, 
+  WRITE, 
+  PREAD,   // only for files.
+  PWRITE,  // only for files.
+  CONNECT, // only for sockets.  underlying socket IO should just check for writability
+  ACCEPT,  // only for sockets.  underlying socket IO should just check for readability
+  POLL1,   // only for sockets.  only support single fds
+  SEND,    // only for sockets.  for UDP sending operations
+  RECV,    // only for sockets.  for UDP receiving operations
+} iotype;
+
+// an outstanding IO request
+typedef struct iorequest_st {
+  // "inherit" from linked_list_entry_t
+  linked_list_entry_t lle;
+
+  // request info
+  fdstruct_t *fds;
+  iotype type;
+  thread_t *thread;
+
+  // IO-specific request info
+  union {
+    struct {
+      __uint32_t events;
+    } epoll;
+  } u;
+
+  // args
+  union {
+
+    // for read, write, pread, pwrite
+    struct {
+      void *buf;
+      size_t count;
+      off_t off;
+    } rw;
+
+    // for poll
+    struct {
+      struct pollfd *ufds;
+    } poll1;
+
+    // for things that use SYS_socketcall
+    struct {
+      int which;      // which socket call?
+      void *argv;
+    } scall;
+
+  } args;
+
+  // return vals
+  ssize_t ret;
+  int err;
+} iorequest_t;
+
+#define debug_print_request(msg,req) \
+do {\
+  switch(req->type) { \
+  case READ: \
+    tdebug("%s:  read(%d,%p,%d) = %d (errno=%d)\n", \
+           msg, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->ret, req->err); \
+    break; \
+  case WRITE: \
+    tdebug("%s:  write(%d,%p,%d) = %d (errno=%d)\n", \
+           msg, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->ret, req->err); \
+    break; \
+  case PREAD: \
+    tdebug("%s:  pread(%d,%p,%d,%lud) = %d (errno=%d)\n", \
+           msg, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->args.rw.off, req->ret, req->err); \
+    break; \
+  case PWRITE: \
+    tdebug("%s:  pwrite(%d,%p,%d,%lud) = %d (errno=%d)\n", \
+           msg, req->fds->fd, req->args.rw.buf, req->args.rw.count, req->args.rw.off, req->ret, req->err); \
+    break; \
+  case CONNECT: \
+    tdebug("%s:  connect(%d, ...) = %d (errno=%d)\n", msg, req->fds->fd, req->ret, req->err); \
+    break; \
+  case ACCEPT:  \
+    tdebug("%s:  accept(%d, ...) = %d (errno=%d)\n", msg, req->fds->fd, req->ret, req->err); \
+    break; \
+  case POLL1:  \
+    tdebug("%s:  poll(%d, ...) = %d (errno=%d)\n", msg, req->fds->fd, req->ret, req->err); \
+    break; \
+  default: \
+    tdebug("%s:  unknown request type: %d\n", msg, req->type); \
+  } \
+} while( 0 )
+
+
+// internal utility functions
+fdstruct_t* get_fdstruct(int fd);
+inline void add_waiter(iorequest_t *req);
+inline void remove_waiter(iorequest_t *req);
+inline iorequest_t* remove_first_waiter(fdstruct_t *fds);
+inline iorequest_t* view_first_waiter(fdstruct_t *fds);
+
+
+// 
+// scheduler functions
+#define DECLARE_IO(type,name) \
+  extern int  type##_##name##_is_available; \
+  extern void type##_##name##_init(void); \
+  extern int  type##_##name##_add_request(iorequest_t *req); \
+  extern void type##_##name##_poll(long long timeout); 
+
+DECLARE_IO(sockio,poll);
+DECLARE_IO(sockio,epoll);
+
+DECLARE_IO(diskio,immediate);
+DECLARE_IO(diskio,blocking);
+DECLARE_IO(diskio,kthread);
+DECLARE_IO(diskio,aio);
+
+
+#endif //IO_INTERNAL_H
+
diff --git a/user/c3po/aio/sockio_epoll.c b/user/c3po/aio/sockio_epoll.c
new file mode 100644 (file)
index 0000000..a6977c5
--- /dev/null
@@ -0,0 +1,328 @@
+/**
+ * blocking IO routines
+ *
+ * These routines make use of asynchronous IO and the signal/wait
+ * calls of a threading package to provide a blocking IO abstraction
+ * on top of underlying non-blocking IO calls.
+ *
+ * The semantics of these calls mirror those of the standard IO
+ * libraries.
+ **/
+
+#include "threadlib.h"
+#include "io_internal.h"
+#include "util.h"
+#include <sys/poll.h>
+
+#ifndef DEBUG_sockio_epoll_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+
+
+#ifndef HAVE_SYS_EPOLL
+
+// epoll isn't available, so just make dummy functions for linking
+int sockio_epoll_is_available = 0;
+int sockio_epoll_add_request(iorequest_t *req) { (void) req; return -1; }
+void sockio_epoll_init() {}
+void sockio_epoll_poll(long long usec) {(void) usec;}
+
+#else // HAVE_SYS_EPOLL
+
+
+#include <unistd.h>
+#include <syscall.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/epoll.h>
+
+// for socketcall stuff
+#include <linux/net.h>
+
+
+
+
+//////////////////////////////////////////////////////////////////////
+// Internal data
+//////////////////////////////////////////////////////////////////////
+
+
+latch_t sockio_epoll_latch;
+
+//#define EPOLL_INITIAL_FDS 10000
+#define EPOLL_INITIAL_FDS 100
+static int epollfd = -1;
+
+//////////////////////////////////////////////////////////////////////
+// IO management routines
+//////////////////////////////////////////////////////////////////////
+
+int sockio_epoll_is_available = 1;
+
+// cheesy hack, to keep an extra flag in w/ the epoll events
+#define SEP_REGISTERED_FD 0x8000
+#if (SEP_REGISTERED_FD & (EPOLLIN|EPOLLOUT|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET))
+#error conflict with SEP_REGISTERED_FD - redefine!!
+#endif
+
+
+/**
+ * Process the list of requests.  This correctly handles the case
+ * where new items are added to the list durring processing.  
+ **/
+void process_request_list(fdstruct_t *fds) 
+{
+  iorequest_t *req, *head;
+  int res;
+
+  thread_latch( sockio_epoll_latch );
+  head = req = view_first_waiter(fds);
+  thread_unlatch( sockio_epoll_latch );
+
+  while( req != NULL ) {
+      
+    // this request isn't satisfiable w/ the current flags, so quit
+    if( (req->u.epoll.events & fds->u.epoll.events) == 0 )
+      break;
+    
+    // do the IO
+    do {
+      switch( req->type ) {
+      case READ:    res = syscall(SYS_read,  fds->fd, req->args.rw.buf, req->args.rw.count); break;
+      case WRITE:   res = syscall(SYS_write, fds->fd, req->args.rw.buf, req->args.rw.count); break;
+      case CONNECT: res = 0; break; // system call already done in blocking_io.c
+      case POLL1:   res = 1; req->args.poll1.ufds[0].revents = fds->u.epoll.events; break;
+      case ACCEPT: case SEND: case RECV:
+        res = syscall(SYS_socketcall, req->args.scall.which, req->args.scall.argv); break;
+      default: assert(0); res=-1;
+      }
+    } while(res==-1 && errno==EINTR);
+    
+    // if the op would have blocked, clear the saved epoll flags, and try wait again
+    if(res==-1 && errno==EAGAIN) {
+      fds->u.epoll.events = (fds->u.epoll.events & ~req->u.epoll.events);
+      break;
+    }
+    
+    // we got an error completion.  Set the error flags for this FD, and wake all waiters
+    if(res == -1) {
+      thread_latch( sockio_epoll_latch );
+      while( (req=remove_first_waiter(fds)) != NULL ) {
+        req->ret = -1;
+        req->err = errno;
+        if( req->thread != thread_self() )
+          thread_resume( req->thread );
+      }
+      thread_unlatch( sockio_epoll_latch );
+      break;
+    }
+    
+    // The IO completed successfully. save results, and fetch the next request
+    req->ret = res;
+    req->err = 0;
+    thread_latch( sockio_epoll_latch );
+    remove_first_waiter(fds);
+    if( req->thread != thread_self() )
+      thread_resume( req->thread );
+    req = view_first_waiter(fds);
+    thread_unlatch( sockio_epoll_latch );
+  }
+
+
+}
+
+
+int sockio_epoll_add_request(iorequest_t *req) 
+{
+  fdstruct_t *fds = req->fds;
+
+  debug_print_request("",req);
+
+
+  // set the events for this request
+  switch( req->type ) {
+  case READ:    case RECV:  case ACCEPT:  req->u.epoll.events = EPOLLIN|EPOLLPRI|EPOLLERR|EPOLLHUP; break;
+  case WRITE:   case SEND:  case CONNECT: req->u.epoll.events = EPOLLOUT|EPOLLERR|EPOLLHUP; break;
+  case POLL1: req->u.epoll.events = req->args.poll1.ufds[0].events; break;
+  default: assert(0);
+  }
+  
+  // FIXME: races!!  need to avoid race b/w user-side and poll-side updates of flags 
+  thread_latch( sockio_epoll_latch );
+
+  // return w/ an IO error if we have EVER seen an error on this socket.
+  // FIXME: this might be the wrong thing to do?
+  if(fds->u.epoll.events & (EPOLLERR|EPOLLHUP)) {
+    errno = EIO;
+    // FIXME: remove from the epoll set?
+
+    thread_unlatch( sockio_epoll_latch );
+    return -1;
+  }
+
+  // add the fd to the epoll set, if necessary
+  if( !(fds->u.epoll.events & SEP_REGISTERED_FD) ) {
+    struct epoll_event ev;
+    int res;
+    errno = 0;
+    ev.events = EPOLLIN | EPOLLOUT | EPOLLPRI | EPOLLERR | EPOLLHUP | EPOLLET;
+    ev.data.ptr = fds;
+    res = epoll_ctl(epollfd, EPOLL_CTL_ADD, fds->fd, &ev);
+    if( res != 0 ) {
+      output("attempted to register fd=%d  res=%d   errno=%d\n",fds->fd, res, errno);
+      // registration really failed, so just use blocking IO 
+      if(  errno != EEXIST && errno != 0 ) {
+        int res;
+        fdstruct_t* fds = req->fds;
+
+        // FIXME: this is an ugly hack, and shouldn't be necessary if
+        // the system call overriding works, and we've tracked the FDs
+        // correctly.  Having the assertion allows this to work, but
+        // only when optimizations are turned off.
+        assert( 0 );
+
+        switch( req->type ) {
+        case READ:    res = syscall(SYS_read,  fds->fd, req->args.rw.buf, req->args.rw.count); break;
+        case WRITE:   res = syscall(SYS_write, fds->fd, req->args.rw.buf, req->args.rw.count); break;
+        case CONNECT: res = 0; break; // system call already done in blocking_io.c
+        case POLL1:   res = 1; req->args.poll1.ufds[0].revents = fds->u.epoll.events; break;
+        case ACCEPT: case SEND: case RECV:
+          res = syscall(SYS_socketcall, req->args.scall.which, req->args.scall.argv); break;
+        default: assert(0); res=-1;
+        }
+        thread_unlatch( sockio_epoll_latch );
+        return res;
+      }
+    }
+    assert( res == 0 || errno == EEXIST || errno == 0 );
+    fds->u.epoll.events = SEP_REGISTERED_FD|EPOLLOUT|EPOLLPRI|EPOLLIN;
+
+    /*
+    if( 0 ) 
+    { 
+      int ret;
+      struct pollfd ufds[1];
+
+      ufds[0].events = POLLIN | POLLOUT | POLLPRI | POLLERR | POLLHUP;
+      ret = syscall(SYS_poll, ufds, 1, 0);
+      fds->u.epoll.events = ufds[0].revents | SEP_REGISTERED_FD;
+    }
+    */
+  }
+
+  // some special hackery, to find out the exact state of the FD in
+  // the case of POLL1 and nonblocking sockets.  The problem is that
+  // w/ edge-triggered epoll semantics, a high bit doesn't mean the FD
+  // is really ready for IO - it just means that it it WAS ready.  We
+  // don't know for certain until the next actual read or write.  This
+  // means that we might give bad responses back to poll() requests. 
+  //
+  // FIXME: the current solution to this is to use a seperate epoll fd
+  // that is level-triggered, and use extra system calls to fetch the
+  // precice current status of the FD before we sleep.
+  if( req->type == POLL1 ) {
+    int ret;
+    ret = syscall(SYS_poll, req->args.poll1.ufds, 1, 0);
+    if( ret == 1 ) {
+      thread_unlatch( sockio_epoll_latch );
+      return 1;
+    } else {
+      fds->u.epoll.events &= ~(req->args.poll1.ufds[0].events);
+    }
+  }
+
+  thread_unlatch( sockio_epoll_latch );
+
+
+
+  // if this is the first request, just try the IO
+  // FIXME: race w/ view_first_waiter....  fix by latching fdstruct.
+  if( view_first_waiter(fds) == req ) {
+    process_request_list(fds);
+    if( view_first_waiter(fds) == req )
+      thread_suspend_self(0);
+  } else {
+    thread_suspend_self(0);
+  }
+
+  // remove ourselves from the list of waiters, and unlock
+  thread_latch( sockio_epoll_latch );
+  remove_waiter( req );
+  thread_unlatch( sockio_epoll_latch );
+
+  // set errno, and return
+  errno = req->err;
+  return req->ret;
+}
+
+
+
+#define EPOLL_BATCH_SIZE 1000
+static struct epoll_event evlist[EPOLL_BATCH_SIZE];
+
+void sockio_epoll_poll(long long usecs) 
+{
+  int nfds, i;
+  struct epoll_event *ev;
+  fdstruct_t *fds;
+  int timeout;
+  tdebug("start\n");
+
+  if( 1 || usecs > 1e7) {
+    //output("sockio_epoll_poll: %lld\n", usecs);
+    //abort();
+    usecs = 0;
+  }
+
+  // translate from microseconds to milliseconds
+  if( usecs < 0 )
+    //timeout = -1;
+    timeout = 100;  // max is 100 ms
+  else 
+    timeout = usecs/1000;
+  
+  // just to be safe
+  if( timeout > 100 ) timeout = 100;
+
+
+  do { 
+    nfds=epoll_wait(epollfd, evlist, EPOLL_BATCH_SIZE, timeout);
+  } while( nfds < 0  &&  (errno == EAGAIN || errno == ESPIPE || errno == EINPROGRESS || errno==EINTR) );
+  if(nfds < 0) 
+    fatal("epoll_wait() failed - %s\n", strerror(errno));
+
+
+  for (i=0, ev=evlist; i<nfds; i++, ev++) {
+    // save new events
+    fds = (fdstruct_t*) ev->data.ptr;
+    
+    thread_latch( sockio_epoll_latch );
+    fds->u.epoll.events |= ev->events;
+    thread_unlatch( sockio_epoll_latch );
+
+    process_request_list( fds );
+  }
+}
+
+
+void sockio_epoll_init() 
+{
+  // init the lock
+  thread_latch_init( sockio_epoll_latch );
+
+  // init the epoll infrastructure
+  epollfd = epoll_create(EPOLL_INITIAL_FDS);
+  if(epollfd < 0) {
+    fatal("epoll initialization failed - %s\n", strerror(errno));
+  }
+}
+
+
+
+#endif // HAVE_SYS_EPOLL
diff --git a/user/c3po/aio/sockio_poll.c b/user/c3po/aio/sockio_poll.c
new file mode 100644 (file)
index 0000000..ad1a1fe
--- /dev/null
@@ -0,0 +1,251 @@
+/**
+ * blocking IO routines
+ *
+ * These routines make use of asynchronous IO and the signal/wait
+ * calls of a threading package to provide a blocking IO abstraction
+ * on top of underlying non-blocking IO calls.
+ *
+ * The semantics of these calls mirror those of the standard IO
+ * libraries.
+ **/
+
+#include "threadlib.h"
+#include "io_internal.h"
+#include "util.h"
+
+#include <unistd.h>
+#include <syscall.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/poll.h>
+#include <sys/syscall.h>
+
+// for socketcall stuff
+#include <linux/net.h>
+
+
+#ifndef DEBUG_sockio_poll_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// Internal data
+//////////////////////////////////////////////////////////////////////
+
+#define MAX_OUTSTANDING_REQUESTS 20000
+static struct pollfd ufds[ MAX_OUTSTANDING_REQUESTS ];
+static int num_outstanding = 0;
+
+latch_t sockio_poll_latch = LATCH_INITIALIZER_UNLOCKED;
+
+
+int sockio_poll_is_available = 1;
+
+//////////////////////////////////////////////////////////////////////
+// IO management routines
+//////////////////////////////////////////////////////////////////////
+
+int sockio_poll_add_request(iorequest_t *req) 
+{
+  fdstruct_t *fds = req->fds;
+  debug_print_request("",req);
+
+  // get lock
+  thread_latch( sockio_poll_latch );
+
+  // the request was already processed by the polling thread, between
+  // the call and when we got the latch
+  // 
+  // FIXME: handle this!!
+  
+  if( view_first_waiter(fds) != req ) {
+    // this is an additional request, so return an error if it isn't accept();
+    debug("additional request for fd=%d\n",fds->fd);
+  } else {
+    // add the first reqeuest
+
+    // check that we don't have too many outstanding requests
+    if(num_outstanding >= MAX_OUTSTANDING_REQUESTS) {
+      errno = ENOMEM;
+      thread_unlatch( sockio_poll_latch );
+      return -1;
+    }
+
+    // try the IO first
+#if 1
+    { 
+      int ret;
+      do {
+        switch (req->type) {
+        case READ:    ret = syscall(SYS_read, fds->fd, req->args.rw.buf, req->args.rw.count); break;
+        case WRITE:   ret = syscall(SYS_write, fds->fd, req->args.rw.buf, req->args.rw.count); break;
+        case POLL1:   ret = syscall(SYS_poll, req->args.poll1.ufds, 1, 0); break;
+        case CONNECT: ret=0; break;  // system call already done in blocking_io.c 
+        case ACCEPT: case SEND: case RECV:
+          ret = syscall(SYS_socketcall, req->args.scall.which, req->args.scall.argv); break;
+        default: assert(0); ret=0;
+        }
+      } while(ret==-1 && errno==EINTR);
+      
+      // the request completed - just return to the user
+      if( req->type == POLL1  &&  ret == 0 )
+        ; // make sure we don't return too soon
+      else if( ret >= 0 || (errno!=EAGAIN && errno!=EWOULDBLOCK)) {
+        thread_unlatch( sockio_poll_latch );
+        return ret;
+      }
+    }
+#endif
+
+    // this is the first request for this FD
+    debug("adding first request for fd=%d\n",fds->fd);
+    ufds[num_outstanding].fd     = fds->fd;
+    switch( req->type ) {
+    case READ: case RECV: case ACCEPT:     ufds[num_outstanding].events = POLLIN|POLLPRI; break;
+    case WRITE: case SEND: case CONNECT:   ufds[num_outstanding].events = POLLOUT; break;
+    case POLL1:  ufds[num_outstanding].events = req->args.poll1.ufds[0].events; break;
+    default: assert(0);
+    }
+    num_outstanding++;
+  }
+
+  // release lock
+  thread_unlatch( sockio_poll_latch );
+
+  // suspend the current thread
+  thread_suspend_self(0);
+
+  // set errno and return 
+  errno = req->err;
+  return req->ret;
+}
+
+
+
+void sockio_poll_poll(long long usecs) 
+{
+  int ret, i;
+  fdstruct_t *fds;
+  iorequest_t *req;
+  int timeout = (usecs == -1 ? -1 : usecs / 1000);  // translate from microseconds to milliseconds
+
+  // get the lock
+  thread_latch( sockio_poll_latch );
+
+  assert(num_outstanding >= 0);
+  if( num_outstanding == 0 ) {
+    thread_unlatch( sockio_poll_latch );
+    return;
+  }
+
+  // NOTE: why a 100us timeout here?  this is preventing all runnable threads from proceeding - zf
+  // removing it
+  
+  //while( (ret=syscall(SYS_poll, ufds, num_outstanding, timeout)) < 0  &&  errno == EINTR ) ;
+  (void) timeout;
+  while( (ret=syscall(SYS_poll, ufds, num_outstanding, 0)) < 0  &&  errno == EINTR ) ;
+  if(ret < 0) {
+    perror("FATAL ERROR.  poll() failed. ");
+    exit(1);
+  }
+  
+  if(ret == 0) {
+    // release the lock
+    thread_unlatch( sockio_poll_latch );
+    return;
+  }
+
+  tdebug("%d fds are ready for IO\n", ret);
+
+  // process the returned events
+  for(i=num_outstanding-1; i>=0; i--) {
+
+    // skip past fds w/ no events
+    if( ufds[i].revents == 0 )
+      continue;
+
+    // find the fdstruct & get the first request
+    fds = get_fdstruct(ufds[i].fd);
+    req = view_first_waiter(fds);
+
+    // do the IO for as many waiters as possible
+    //
+    // FIXME: do this with just one syscall by using readv/writev!!!
+    // FIXME: it's VERY VERY BAD to hold sockio_poll_latch across these
+    // syscalls.  To fix, sockio_poll_add_request needs to drop waiting
+    // requests into a list somewhere, so only the list needs to be locked
+    while( req != NULL ) {
+      do {
+        switch (req->type) {
+        case READ:    ret = syscall(SYS_read, fds->fd, req->args.rw.buf, req->args.rw.count); break;
+        case WRITE:   ret = syscall(SYS_write, fds->fd, req->args.rw.buf, req->args.rw.count); break;
+        case POLL1:   ret = 1; req->args.poll1.ufds[0].revents = ufds[i].revents; break;
+        case CONNECT: ret=0; break;  // system call already done in blocking_io.c 
+        case ACCEPT: case SEND: case RECV:
+          ret = syscall(SYS_socketcall, req->args.scall.which, req->args.scall.argv); break;
+        default: assert(0);
+        }
+      } while(ret==-1 && errno==EINTR);
+
+      // the request would have blocked.  Keep the fd in the poll set, and try again later
+      if( ret == -1  &&  (errno==EAGAIN || errno==EWOULDBLOCK)) 
+        req = NULL;
+
+      // there was some other error - return this error to all waiters
+      else if( ret == -1 ) {
+        while( (req=remove_first_waiter(fds)) != NULL ) {
+          req->ret = -1;
+          req->err = errno;
+          thread_resume( req->thread );
+        }
+      }
+
+      // the call succeeded
+      else {
+        remove_first_waiter(fds);
+        req->ret = ret;
+        req->err = 0;
+        thread_resume( req->thread );
+
+        // a read or write succeeded, but we didn't get the full count.
+        if( (req->type == READ || req->type == WRITE) && (size_t) ret < req->args.rw.count )
+          req = NULL;
+
+        // for everything else, we get the next request
+        else 
+          req = view_first_waiter(fds);
+      }
+    }
+
+    // update the poll flags for the fd
+    req = view_first_waiter(fds);
+    if( req != NULL ) {
+      // add flags for the next poll() call
+      debug("more waiters for %d - will poll again", fds->fd);
+      switch( req->type ) {
+      case READ: case RECV: case ACCEPT:     ufds[i].events = POLLIN|POLLPRI; break;
+      case WRITE: case SEND: case CONNECT:   ufds[i].events = POLLOUT; break;
+      case POLL1:  ufds[i].events = req->args.poll1.ufds[0].events; break;
+      default: assert(0);
+      }
+    } else {
+      // plug the hole in the request list
+      num_outstanding--;
+      ufds[i] = ufds[num_outstanding];
+    }
+  }
+
+  // release the lock
+  thread_unlatch( sockio_poll_latch );
+}
+
+
+
+void sockio_poll_init() 
+{
+}
+
diff --git a/user/c3po/coro/Makefrag b/user/c3po/coro/Makefrag
new file mode 100644 (file)
index 0000000..02bbff2
--- /dev/null
@@ -0,0 +1,25 @@
+CORO_NAME    := coro
+CORO_UCNAME  := $(call uc, $(CORO_NAME))
+CORO_CFLAGS  := $(CFLAGS)
+CORO_HEADERS := $(wildcard $(CORODIR)/*.h)
+CORO_CFILES  := $(wildcard $(CORODIR)/*.c)
+CORO_OBJDIR  := $(OBJDIR)/$(CORO_NAME)
+CORO_OBJS    := $(patsubst %.c, %.o, $(CORO_CFILES))
+CORO_OBJS    := $(foreach x, $(CORO_OBJS), $(CORO_OBJDIR)/$(call filename,$(x)))
+
+LIBCORO = $(CORO_OBJDIR)/lib$(CORO_NAME).a
+
+$(CORO_NAME)-clean:
+       @echo + clean [$(LIBUCNAME) $(CORO_UCNAME)]
+       $(V)rm -rf $(CORO_OBJS) $(LIBCORO)
+       $(V)rm -rf $(CORO_OBJDIR)
+
+$(LIBCORO): $(CORO_OBJS)
+       @echo + ar [$(LIBUCNAME) $(CORO_UCNAME)] $@
+       $(V)$(AR) rc $@ $^
+
+$(CORO_OBJDIR)/%.o: $(CORODIR)/%.c $(CORO_HEADERS)
+       @echo + cc [$(LIBUCNAME) $(CORO_UCNAME)] $<
+       @mkdir -p $(@D)
+       $(V)$(CC) $(CORO_CFLAGS) $(INCS) -o $@ -c $<
+
diff --git a/user/c3po/coro/coro.c b/user/c3po/coro/coro.c
new file mode 100644 (file)
index 0000000..ef2ea2b
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+    Coroutine implementation for x86/linux, gcc-2.7.2
+
+    Copyright 1999 by E. Toernig <froese@gmx.de>
+*/
+
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/mman.h>
+#include <execinfo.h>
+#include "coro.h"
+
+#if !defined(i386)
+#error For x86-CPUs only
+#endif
+
+#if !defined(__GNUC__)
+#warning May break without gcc.  Be careful.
+#endif
+
+/* asm-name for identifier x */
+#if 1
+#define _(x) #x
+#else
+#define _(x) "_"#x
+#endif
+
+struct coroutine co_main[1] = { { NULL, NULL, NULL, NULL, NULL, 0 } };
+struct coroutine *co_current = co_main;
+
+
+#define fatal(msg) \
+    for (;;)                                           \
+    {                                                  \
+         int fee1dead = write(2, "coro: " msg "\r\n", sizeof(msg)+7);  \
+         *(unsigned int *)0 = fee1dead; \
+    }
+
+
+
+/*
+    Create new coroutine.
+    'func' is the entry point
+    'stack' is the start of the coroutines stack.  if 0, one is allocated.
+    'size' is the size of the stack
+*/
+
+
+static void wrap(void *data) __attribute__((noreturn,regparm(1)));
+
+static void __attribute__((noreturn,regparm(1))) 
+wrap(void *data) /* arg in %eax! */
+{
+    co_current->resumeto = co_current->caller;
+
+    for (;;)
+       data = co_resume(co_current->func(data));
+}
+
+struct coroutine *
+co_create(void *func, void *stack, int size)
+{
+    struct coroutine *co;
+    int to_free = 0;
+
+    if (size < 128)
+       return 0;
+
+    if (stack == 0)
+    {
+       size += 4096-1;
+       size &= ~(4096-1);
+       stack = mmap(0, size, PROT_READ|PROT_WRITE,
+                             MAP_PRIVATE|MAP_ANON, -1, 0);
+       if (stack == (void*)-1)
+           return 0;
+
+       to_free = size;
+    }
+    co = stack + size;
+    co = (struct coroutine*)((unsigned long)co & ~3);
+    co -= 1;
+
+    co->sp = co;
+    co->caller = 0;
+    co->resumeto = 0;
+    co->user = 0;
+    co->func = func;
+    co->to_free = to_free;
+
+    co->sp = ((void**)co->sp)-1; *(void**)co->sp = wrap;  // return addr (here: start addr)
+    co->sp = ((void**)co->sp)-1; *(void**)co->sp = 0;     // ebp
+    co->sp = ((void**)co->sp)-1; *(void**)co->sp = 0;     // ebx
+    co->sp = ((void**)co->sp)-1; *(void**)co->sp = 0;     // esi
+    co->sp = ((void**)co->sp)-1; *(void**)co->sp = 0;     // edi
+    return co;
+}
+
+
+
+/*
+    delete a coroutine.
+*/
+
+void
+co_delete(struct coroutine *co)
+{
+    if (co == co_current)
+       fatal("coroutine deletes itself");
+
+    if (co->to_free)
+       munmap((void *)co + sizeof(*co) - co->to_free, co->to_free);
+}
+
+
+
+/*
+    delete self and switch to 'new_co' passing 'data'
+*/
+
+static void *helper_args[2];
+
+static void
+del_helper(void **args)
+{
+    for (;;)
+    {
+       if (args != helper_args)
+           fatal("resume to deleted coroutine");
+       co_delete(co_current->caller);
+       args = co_call(args[0], args[1]);
+    }
+}
+
+void
+co_exit_to(struct coroutine *new_co, void *data)
+{
+    static struct coroutine *helper = 0;
+    static char stk[512]; // enough for a kernel call and a signal handler
+
+    // FIXME: multi-cpu race on use of helper!!  Solve by creating an array of helpers for each CPU
+    helper_args[0] = new_co;
+    helper_args[1] = data;
+
+    if (helper == 0)
+       helper = co_create(del_helper, stk, sizeof(stk));
+
+    // we must leave this coroutine.  so call the helper.
+    co_call(helper, helper_args);
+    fatal("stale coroutine called");
+}
+
+void
+co_exit(void *data)
+{
+    co_exit_to(co_current->resumeto, data);
+}
+
+
+
+/*
+    Call other coroutine.
+    'new_co' is the coroutine to switch to
+    'data' is passed to the new coroutine
+*/
+
+//void *co_call(struct coroutine *new_co, void *data) { magic }
+asm(   ".text"                         );
+asm(   ".align 16"                     );
+asm(   ".globl "_(co_call)             );
+asm(   ".type "_(co_call)",@function"  );
+asm(   _(co_call)":"                   );
+
+asm(   "pushl %ebp"                    );      // save reg-vars/framepointer
+asm(   "pushl %ebx"                    );
+asm(   "pushl %esi"                    );
+asm(   "pushl %edi"                    );
+
+asm(   "movl "_(co_current)",%eax"     );      // get old co
+asm(   "movl %esp,(%eax)"              );      // save sp of old co
+
+asm(   "movl 20(%esp),%ebx"            );      // get new co (arg1)
+asm(   "movl %ebx,"_(co_current)       );      // set as current
+asm(   "movl %eax,4(%ebx)"             );      // save caller
+asm(   "movl 24(%esp),%eax"            );      // get data (arg2)
+asm(   "movl (%ebx),%esp"              );      // restore sp of new co
+
+asm(   "popl %edi"                     );      // restore reg-vars/frameptr
+asm(   "popl %esi"                     );
+asm(   "popl %ebx"                     );
+asm(   "popl %ebp"                     );
+asm(   "ret"                           );      // return to new coro
+
+asm(   "1:"                            );
+asm(   ".size "_(co_call)",1b-"_(co_call));
+
+
+void *_co_esp, *_co_ebp, **_co_array;   // temp save place for co_backtrace
+static int _co_size, _co_rv;
+
+// we do this by temporarily switch to the coroutine and call backtrace()
+int co_backtrace(struct coroutine *cc, void **array, int size)
+{
+  (void) _co_esp; // avoid unused var warning
+  (void) _co_ebp;
+
+  if (cc == co_current)
+    return backtrace(array, size);
+      
+  // save arguments to global variable so we do not need to use the stack
+  // when calling backtrace()
+  _co_array = array;
+  _co_size = size;
+
+  asm(  "movl %ebp, "_(_co_ebp)      );   // save %ebp
+  asm(  "movl %esp, "_(_co_esp)      );   // save %esp
+  asm(  "movl (%%eax), %%esp" 
+                 : /* no output */ 
+                 : "a" (cc)         );   // set %esp to the co-routine
+  asm(  "movl %esp, %ebp"            );
+  asm(  "add  $12, %ebp"              );   // this is where co_call() saved the previous $ebp!
+  
+  _co_rv = backtrace(_co_array, _co_size);
+
+  asm(  "movl "_(_co_esp)", %esp"  );   // restore %esp
+  asm(  "movl "_(_co_ebp)", %ebp"  );   // restore %ebp
+
+  return _co_rv;
+}
+
+
+void *
+co_resume(void *data)
+{
+    data = co_call(co_current->resumeto, data);
+    co_current->resumeto = co_current->caller;
+    return data;
+}
+
+
diff --git a/user/c3po/coro/coro.h b/user/c3po/coro/coro.h
new file mode 100644 (file)
index 0000000..1c23597
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef CORO_H
+#define CORO_H
+
+struct coroutine
+{
+    void *sp;                  /* saved stack pointer while coro is inactive */
+    struct coroutine *caller;  /* PUBLIC who has called this coroutine */
+    struct coroutine *resumeto;        /* PUBLIC who to resume to */
+    void *user;                        /* PUBLIC user data.  for whatever you want. */
+
+    void *(*func)(void *);     /* coroutines main function */
+    int to_free;               /* how much memory to free on co_delete */
+};
+
+extern struct coroutine *co_current;   /* currently active coroutine */
+extern struct coroutine co_main[];     /* automagically generated main coro. */
+
+struct coroutine *co_create(void *func, void *stack, int stacksize);
+void *co_call(struct coroutine *co, void *data);
+void *co_resume(void *data);
+void co_delete(struct coroutine *co);
+void co_exit_to(struct coroutine *co, void *data) __attribute__((noreturn));
+void co_exit(void *data) __attribute__((noreturn));
+
+/* Store up to SIZE return addresses of the stack state of coroutine CC and return 
+ * the number of values stored - zf */
+int co_backtrace(struct coroutine *cc, void **array, int size);
+
+#endif
diff --git a/user/c3po/include/README b/user/c3po/include/README
new file mode 100644 (file)
index 0000000..75683e1
--- /dev/null
@@ -0,0 +1,3 @@
+
+include files for use by applications.  These override standard system headers when necessary, to ensure that our threads lib is used correctly.
+
diff --git a/user/c3po/include/bits/pthreadtypes.h b/user/c3po/include/bits/pthreadtypes.h
new file mode 100644 (file)
index 0000000..21bc82c
--- /dev/null
@@ -0,0 +1,2 @@
+
+#include <pthread.h>
diff --git a/user/c3po/include/capriccio.h b/user/c3po/include/capriccio.h
new file mode 100644 (file)
index 0000000..3ca3ce4
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef _CAPRICCIO_H
+#define _CAPRICCIO_H
+
+/* various capriccio utility for user program */
+
+#ifndef cpu_tick_t
+typedef unsigned long long cpu_tick_t;
+#endif
+
+extern void *current_thread;
+extern unsigned long long start_usec;
+extern cpu_tick_t ticks_per_microsecond;
+
+void output(char *fmt, ...) __attribute__ ((format (printf,1,2)));
+unsigned thread_tid(void *t);
+
+#ifndef GET_CPU_TICKS
+#define GET_CPU_TICKS(Var)      __asm__ __volatile__ ("rdtsc" : "=A" (Var))
+#endif
+
+static inline long long current_usecs(void)
+{
+  register cpu_tick_t ret;
+  GET_CPU_TICKS( ret );
+  return (ret / ticks_per_microsecond);
+}
+
+#define cap_output(args...) \
+do {\
+  output("%3d %9lld : %s() - ", (int)thread_tid(current_thread), (long long)(current_usecs() - start_usec), __FUNCTION__  ); \
+  output(args); \
+} while( 0 )
+
+
+#endif
diff --git a/user/c3po/include/fptrcheck.h b/user/c3po/include/fptrcheck.h
new file mode 100644 (file)
index 0000000..02993de
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef FPTR_CHECK_H
+#define FPTR_CHECK_H
+
+// -----------------------------------------------------------------------
+
+// fptrcheck.h
+//
+// This module implements a sanity check for the graph analysis used by
+// the stack module.  It provides instrumentation for function pointer
+// call sites and function entry points in order to determine whether
+// our graph was missing edges.
+//
+// These checks are enabled or disabled by the analyzegraph program.
+
+// -----------------------------------------------------------------------
+
+extern int fptr_caller;
+
+#define FPTR_CALL(cur) \
+    do { \
+        if (fptr_caller != -1) { \
+            fptr_report_unvalidated_overwrite(cur); \
+        } \
+        fptr_caller = (cur); \
+    } while (0)
+
+#define FPTR_CHECK(cur, a) \
+    do { \
+        if (fptr_caller != -1) { \
+            int *p = (a); \
+            while (*p != 0) { \
+                if (*p == fptr_caller) { \
+                    fptr_caller = -1; \
+                    break; \
+                } \
+                p++; \
+            } \
+            if (fptr_caller != -1) { \
+                fptr_report_unexpected_call(cur); \
+                fptr_caller = -1; \
+            } \
+        } \
+    } while (0)
+
+#define FPTR_DONE(cur) \
+    do { \
+        if (fptr_caller != -1) { \
+            fptr_report_unvalidated_return(cur); \
+            fptr_caller = -1; \
+        } \
+    } while (0)
+
+#endif // FPTR_CHECK_H
diff --git a/user/c3po/include/pthread.h b/user/c3po/include/pthread.h
new file mode 100644 (file)
index 0000000..e82ada5
--- /dev/null
@@ -0,0 +1,443 @@
+#ifndef _CAPRICCIO_PTHREAD_H_
+#define _CAPRICCIO_PTHREAD_H_
+
+/*
+**
+** BOOTSTRAPPING
+**
+*/
+
+/*
+ * Prevent system includes from implicitly including
+ * possibly existing vendor Pthread headers
+ */
+#define PTHREAD
+#define PTHREAD_H
+#define _PTHREAD_H
+#define _PTHREAD_H_
+#define PTHREAD_INCLUDED
+#define _PTHREAD_INCLUDED
+#define SYS_PTHREAD_H
+#define _SYS_PTHREAD_H
+#define _SYS_PTHREAD_H_
+#define SYS_PTHREAD_INCLUDED
+#define _SYS_PTHREAD_INCLUDED
+#define BITS_PTHREADTYPES_H
+#define _BITS_PTHREADTYPES_H
+#define _BITS_PTHREADTYPES_H_
+#define _BITS_SIGTHREAD_H
+
+
+/*
+ * Protect namespace, because possibly existing vendor Pthread stuff
+ * would certainly conflict with our defintions of pthread*_t.
+ */
+#define pthread_t              __vendor_pthread_t
+#define pthread_attr_t         __vendor_pthread_attr_t
+#define pthread_key_t          __vendor_pthread_key_t
+#define pthread_once_t         __vendor_pthread_once_t
+#define pthread_mutex_t        __vendor_pthread_mutex_t
+#define pthread_mutexattr_t    __vendor_pthread_mutexattr_t
+#define pthread_cond_t         __vendor_pthread_cond_t
+#define pthread_condattr_t     __vendor_pthread_condattr_t
+#define pthread_rwlock_t       __vendor_pthread_rwlock_t
+#define pthread_rwlockattr_t   __vendor_pthread_rwlockattr_t
+#define sched_param            __vendor_sched_param
+
+/*
+ * Allow structs containing pthread*_t in vendor headers
+ * to have some type definitions
+ */
+#if 0
+typedef int __vendor_pthread_t;
+typedef int __vendor_pthread_attr_t;
+typedef int __vendor_pthread_key_t;
+typedef int __vendor_pthread_once_t;
+typedef int __vendor_pthread_mutex_t;
+typedef int __vendor_pthread_mutexattr_t;
+typedef int __vendor_pthread_cond_t;
+typedef int __vendor_pthread_condattr_t;
+typedef int __vendor_pthread_rwlock_t;
+typedef int __vendor_pthread_rwlockattr_t;
+typedef int __vendor_sched_param;
+#endif
+
+/*
+ * Include essential vendor headers
+ */
+
+#include <limits.h>
+
+//#include <features.h>
+
+//#include <sched.h>
+//#include <time.h>
+
+#include <bits/posix_opt.h>
+
+// has a number of things that we need to undef, and redef.
+
+
+#define __need_sigset_t
+#include <signal.h>
+//#include <bits/pthreadtypes.h>
+//#include <bits/initspin.h>
+
+#include <sys/types.h>     /* for ssize_t         */
+//#include <sys/time.h>      /* for struct timeval  */
+//#include <sys/socket.h>    /* for sockaddr        */
+//#include <sys/signal.h>    /* for sigset_t        */
+//#include <time.h>          /* for struct timespec */
+//#include <unistd.h>        /* for off_t           */
+
+/*
+ * Unprotect namespace, so we can define our own variants now
+ */
+#undef pthread_t
+#undef pthread_attr_t
+#undef pthread_key_t
+#undef pthread_once_t
+#undef pthread_mutex_t
+#undef pthread_mutexattr_t
+#undef pthread_cond_t
+#undef pthread_condattr_t
+#undef pthread_rwlock_t
+#undef pthread_rwlockattr_t
+#undef sched_param
+
+/*
+ * Cleanup more Pthread namespace from vendor values
+ */
+#undef  _POSIX_THREADS
+#undef  _POSIX_THREAD_ATTR_STACKADDR
+#undef  _POSIX_THREAD_ATTR_STACKSIZE
+#undef  _POSIX_THREAD_PRIORITY_SCHEDULING
+#undef  _POSIX_THREAD_PRIO_INHERIT
+#undef  _POSIX_THREAD_PRIO_PROTECT
+#undef  _POSIX_THREAD_PROCESS_SHARED
+#undef  _POSIX_THREAD_SAFE_FUNCTIONS
+#undef  PTHREAD_DESTRUCTOR_ITERATIONS
+#undef  PTHREAD_KEYS_MAX
+#undef  PTHREAD_STACK_MIN
+#undef  PTHREAD_THREADS_MAX
+#undef  PTHREAD_CREATE_DETACHED
+#undef  PTHREAD_CREATE_JOINABLE
+#undef  PTHREAD_SCOPE_SYSTEM
+#undef  PTHREAD_SCOPE_PROCESS
+#undef  PTHREAD_INHERIT_SCHED
+#undef  PTHREAD_EXPLICIT_SCHED
+#undef  PTHREAD_CANCEL_ENABLE
+#undef  PTHREAD_CANCEL_DISABLE
+#undef  PTHREAD_CANCEL_ASYNCHRONOUS
+#undef  PTHREAD_CANCEL_DEFERRED
+#undef  PTHREAD_CANCELED
+#undef  PTHREAD_PROCESS_PRIVATE
+#undef  PTHREAD_PROCESS_SHARED
+#undef  PTHREAD_ONCE_INIT
+#undef  PTHREAD_MUTEX_DEFAULT
+#undef  PTHREAD_MUTEX_RECURSIVE
+#undef  PTHREAD_MUTEX_NORMAL
+#undef  PTHREAD_MUTEX_ERRORCHECK
+#undef  PTHREAD_MUTEX_INITIALIZER
+#undef  PTHREAD_COND_INITIALIZER
+#undef  PTHREAD_RWLOCK_INITIALIZER
+
+/*
+**
+** API DEFINITION
+**
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Extra structure definitions
+ */
+struct timeval;
+struct timespec;
+
+/*
+ * GNU Pth indentification
+ */
+#define _POSIX_THREAD_IS_CAPRICCIO 0x100
+
+/*
+ * Compile time symbolic feature macros for portability
+ * specification to applications using pthreads
+ */
+#define _POSIX_THREADS
+#define _POSIX_THREAD_ATTR_STACKADDR
+#define _POSIX_THREAD_ATTR_STACKSIZE
+#undef  _POSIX_THREAD_PRIORITY_SCHEDULING
+#undef  _POSIX_THREAD_PRIO_INHERIT
+#undef  _POSIX_THREAD_PRIO_PROTECT
+#undef  _POSIX_THREAD_PROCESS_SHARED
+#undef  _POSIX_THREAD_SAFE_FUNCTIONS
+
+/*
+ * System call mapping support type
+ * (soft variant can be overridden)
+ */
+//#define _POSIX_THREAD_SYSCALL_HARD @PTH_SYSCALL_HARD@
+//#ifndef _POSIX_THREAD_SYSCALL_SOFT
+//#define _POSIX_THREAD_SYSCALL_SOFT @PTH_SYSCALL_SOFT@
+//#endif
+
+/*
+ * Run-time invariant values
+ */
+#define PTHREAD_DESTRUCTOR_ITERATIONS  4
+#define PTHREAD_KEYS_MAX               256
+#define PTHREAD_STACK_MIN              8192
+#define PTHREAD_THREADS_MAX            10000 /* actually yet no restriction */
+
+/*
+ * Flags for threads and thread attributes.
+ */
+// People are using 0 for JOINABLE!
+#define PTHREAD_CREATE_DETACHED     0x01
+#define PTHREAD_CREATE_JOINABLE     0x0
+//#define PTHREAD_CREATE_DETACHED     0x01
+//#define PTHREAD_CREATE_JOINABLE     0x02
+#define PTHREAD_SCOPE_SYSTEM        0x03
+#define PTHREAD_SCOPE_PROCESS       0x04
+#define PTHREAD_INHERIT_SCHED       0x05
+#define PTHREAD_EXPLICIT_SCHED      0x06
+
+/*
+ * Values for cancellation
+ */
+#define PTHREAD_CANCEL_ENABLE        0x01
+#define PTHREAD_CANCEL_DISABLE       0x02
+#define PTHREAD_CANCEL_ASYNCHRONOUS  0x01
+#define PTHREAD_CANCEL_DEFERRED      0x02
+#define PTHREAD_CANCELED             ((void *)-1)
+
+/*
+ * Flags for mutex priority attributes
+ */
+#define PTHREAD_PRIO_INHERIT        0x00
+#define PTHREAD_PRIO_NONE           0x01
+#define PTHREAD_PRIO_PROTECT        0x02
+
+/*
+ * Flags for read/write lock attributes
+ */
+#define PTHREAD_PROCESS_PRIVATE     0x00
+#define PTHREAD_PROCESS_SHARED      0x01
+
+/*
+ * Forward structure definitions.
+ * These are mostly opaque to the application.
+ */
+struct pthread_st;
+struct pthread_attr_st;
+struct pthread_cond_st;
+struct pthread_mutex_st;
+struct pthread_rwlock_st;
+struct sched_param;
+
+/*
+ * Primitive system data type definitions required by P1003.1c
+ */
+typedef struct  pthread_st              *pthread_t;
+typedef struct  pthread_attr_st         *pthread_attr_t;
+typedef int                              pthread_key_t;
+typedef int                              pthread_once_t;
+typedef int                              pthread_mutexattr_t;
+typedef struct  pthread_mutex_st        *pthread_mutex_t;
+typedef int                              pthread_condattr_t;
+typedef struct  pthread_cond_st         *pthread_cond_t;
+typedef int                              pthread_rwlockattr_t;
+typedef struct  pthread_rwlock_st       *pthread_rwlock_t;
+
+/*
+ * Once support.
+ */
+#define PTHREAD_ONCE_INIT 0
+
+/*
+ * Mutex static initialization values.
+ */
+enum pthread_mutextype {
+    PTHREAD_MUTEX_DEFAULT = 1,
+    PTHREAD_MUTEX_RECURSIVE,
+    PTHREAD_MUTEX_NORMAL,
+    PTHREAD_MUTEX_ERRORCHECK
+};
+
+enum pthread_sched {
+       SCHED_OTHER = 0,
+       SCHED_FIFO = 1,
+       SCHED_RR = 2
+};
+
+/*
+ * Mutex/CondVar/RWLock static initialization values.
+ */
+#define __PTHREAD_UNINITIALIZED 23
+
+#define PTHREAD_MUTEX_INITIALIZER   (pthread_mutex_t)(__PTHREAD_UNINITIALIZED)
+#define PTHREAD_COND_INITIALIZER    (pthread_cond_t)(__PTHREAD_UNINITIALIZED)
+#define PTHREAD_RWLOCK_INITIALIZER  (pthread_rwlock_t)(__PTHREAD_UNINITIALIZED)
+
+/*
+ * IEEE (``POSIX'') Std 1003.1 Second Edition 1996-07-12
+ */
+
+/* thread attribute routines */
+extern int       pthread_attr_init(pthread_attr_t *);
+extern int       pthread_attr_destroy(pthread_attr_t *);
+extern int       pthread_attr_setinheritsched(pthread_attr_t *, int);
+extern int       pthread_attr_getinheritsched(const pthread_attr_t *, int *);
+extern int       pthread_attr_setschedparam(pthread_attr_t *, struct sched_param *);
+extern int       pthread_attr_getschedparam(const pthread_attr_t *, struct sched_param *);
+extern int       pthread_attr_setschedpolicy(pthread_attr_t *, int);
+extern int       pthread_attr_getschedpolicy(const pthread_attr_t *, int *);
+extern int       pthread_attr_setscope(pthread_attr_t *, int);
+extern int       pthread_attr_getscope(const pthread_attr_t *, int *);
+extern int       pthread_attr_setstacksize(pthread_attr_t *, size_t);
+extern int       pthread_attr_getstacksize(const pthread_attr_t *, size_t *);
+extern int       pthread_attr_setstackaddr(pthread_attr_t *, void *);
+extern int       pthread_attr_getstackaddr(const pthread_attr_t *, void **);
+extern int       pthread_attr_setdetachstate(pthread_attr_t *, int);
+extern int       pthread_attr_getdetachstate(const pthread_attr_t *, int *);
+extern int       pthread_attr_setguardsize(pthread_attr_t *, int);
+extern int       pthread_attr_getguardsize(const pthread_attr_t *, int *);
+extern int       pthread_attr_setname_np(pthread_attr_t *, char *);
+extern int       pthread_attr_getname_np(const pthread_attr_t *, char **);
+extern int       pthread_attr_setprio_np(pthread_attr_t *, int);
+extern int       pthread_attr_getprio_np(const pthread_attr_t *, int *);
+
+/* thread routines */
+extern int       pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *);
+extern int       __pthread_detach(pthread_t);
+extern int       pthread_detach(pthread_t);
+/* #define          pthread_detach(t) __pthread_detach(t) */
+extern pthread_t pthread_self(void);
+extern int       pthread_equal(pthread_t, pthread_t);
+extern int       pthread_yield_np(void);
+extern int       pthread_yield(void);
+extern void      pthread_exit(void *);
+extern int       pthread_join(pthread_t, void **);
+extern int       pthread_once(pthread_once_t *, void (*)(void));
+extern int       pthread_sigmask(int, const sigset_t *, sigset_t *);
+extern int       pthread_kill(pthread_t, int);
+
+/* concurrency routines */
+extern int       pthread_getconcurrency(void);
+extern int       pthread_setconcurrency(int);
+
+/* context routines */
+extern int       pthread_key_create(pthread_key_t *, void (*)(void *));
+extern int       pthread_key_delete(pthread_key_t);
+extern int       pthread_setspecific(pthread_key_t, const void *);
+extern void     *pthread_getspecific(pthread_key_t);
+
+/* cancel routines */
+extern int       pthread_cancel(pthread_t);
+extern void      pthread_testcancel(void);
+extern int       pthread_setcancelstate(int, int *);
+extern int       pthread_setcanceltype(int, int *);
+
+/* scheduler routines */
+extern int       pthread_setschedparam(pthread_t, int, const struct sched_param *);
+extern int       pthread_getschedparam(pthread_t, int *, struct sched_param *);
+
+/* cleanup routines */
+extern void      pthread_cleanup_push(void (*)(void *), void *);
+extern void      pthread_cleanup_pop(int);
+extern int       pthread_atfork(void (*)(void), void (*)(void), void (*)(void));
+
+/* mutex attribute routines */
+extern int       pthread_mutexattr_init(pthread_mutexattr_t *);
+extern int       pthread_mutexattr_destroy(pthread_mutexattr_t *);
+extern int       pthread_mutexattr_setprioceiling(pthread_mutexattr_t *, int);
+extern int       pthread_mutexattr_getprioceiling(pthread_mutexattr_t *, int *);
+extern int       pthread_mutexattr_setprotocol(pthread_mutexattr_t *, int);
+extern int       pthread_mutexattr_getprotocol(pthread_mutexattr_t *, int *);
+extern int       pthread_mutexattr_setpshared(pthread_mutexattr_t *, int);
+extern int       pthread_mutexattr_getpshared(pthread_mutexattr_t *, int *);
+extern int       pthread_mutexattr_settype(pthread_mutexattr_t *, int);
+extern int       pthread_mutexattr_gettype(pthread_mutexattr_t *, int *);
+
+/* mutex routines */
+extern int       pthread_mutex_init(pthread_mutex_t *, const pthread_mutexattr_t *);
+extern int       pthread_mutex_destroy(pthread_mutex_t *);
+extern int       pthread_mutex_setprioceiling(pthread_mutex_t *, int, int *);
+extern int       pthread_mutex_getprioceiling(pthread_mutex_t *, int *);
+extern int       pthread_mutex_lock(pthread_mutex_t *);
+extern int       pthread_mutex_trylock(pthread_mutex_t *);
+extern int       pthread_mutex_unlock(pthread_mutex_t *);
+
+/* rwlock attribute routines */
+extern int       pthread_rwlockattr_init(pthread_rwlockattr_t *);
+extern int       pthread_rwlockattr_destroy(pthread_rwlockattr_t *);
+extern int       pthread_rwlockattr_setpshared(pthread_rwlockattr_t *, int);
+extern int       pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *, int *);
+
+/* rwlock routines */
+extern int       pthread_rwlock_init(pthread_rwlock_t *, const pthread_rwlockattr_t *);
+extern int       pthread_rwlock_destroy(pthread_rwlock_t *);
+extern int       pthread_rwlock_rdlock(pthread_rwlock_t *);
+extern int       pthread_rwlock_tryrdlock(pthread_rwlock_t *);
+extern int       pthread_rwlock_wrlock(pthread_rwlock_t *);
+extern int       pthread_rwlock_trywrlock(pthread_rwlock_t *);
+extern int       pthread_rwlock_unlock(pthread_rwlock_t *);
+
+/* condition attribute routines */
+extern int       pthread_condattr_init(pthread_condattr_t *);
+extern int       pthread_condattr_destroy(pthread_condattr_t *);
+extern int       pthread_condattr_setpshared(pthread_condattr_t *, int);
+extern int       pthread_condattr_getpshared(pthread_condattr_t *, int *);
+
+/* condition routines */
+extern int       pthread_cond_init(pthread_cond_t *, const pthread_condattr_t *);
+extern int       pthread_cond_destroy(pthread_cond_t *);
+extern int       pthread_cond_broadcast(pthread_cond_t *);
+extern int       pthread_cond_signal(pthread_cond_t *);
+extern int       pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *);
+extern int       pthread_cond_timedwait(pthread_cond_t *, pthread_mutex_t *, const struct timespec *);
+
+/*
+ * Extensions created by POSIX 1003.1j
+ */
+extern int       pthread_abort(pthread_t);
+
+/*
+ * Replacement Functions (threading aware)
+ */
+
+/*
+  zf: this is a good list of replacements we need to implement
+
+extern pid_t              __pthread_fork(void);
+extern unsigned int       __pthread_sleep(unsigned int);
+extern int                __pthread_sigwait(const sigset_t *, int *);
+extern pid_t              __pthread_waitpid(pid_t, int *, int);
+extern int                __pthread_connect(int, struct sockaddr *, socklen_t);
+extern int                __pthread_accept(int, struct sockaddr *, socklen_t *);
+extern int                __pthread_select(int, fd_set *, fd_set *, fd_set *, struct timeval *);
+extern int                __pthread_poll(struct pollfd *, nfds_t, int);
+extern ssize_t            __pthread_read(int, void *, size_t);
+extern ssize_t            __pthread_write(int, const void *, size_t);
+extern ssize_t            __pthread_readv(int, const struct iovec *, int);
+extern ssize_t            __pthread_writev(int, const struct iovec *, int);
+extern ssize_t            __pthread_recv(int, void *, size_t, int);
+extern ssize_t            __pthread_send(int, const void *, size_t, int);
+extern ssize_t            __pthread_recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *);
+extern ssize_t            __pthread_sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t);
+extern ssize_t            __pthread_pread(int, void *, size_t, off_t);
+extern ssize_t            __pthread_pwrite(int, const void *, size_t, off_t);
+
+*/
+
+/*
+ * More "special" POSIX stuff
+ */
+
+  //#define sched_yield  pthread_yield_np
+
+#endif
diff --git a/user/c3po/include/semaphore.h b/user/c3po/include/semaphore.h
new file mode 100644 (file)
index 0000000..bbdd951
--- /dev/null
@@ -0,0 +1,8 @@
+
+#ifndef _SEMAPHORE_H
+#define _SEMAPHORE_H    1
+
+// apache needs this to compile
+# error semaphores are not supported
+
+#endif  /* semaphore.h */
diff --git a/user/c3po/include/stackbounds.h b/user/c3po/include/stackbounds.h
new file mode 100644 (file)
index 0000000..642a41e
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef STACKBOUNDS_H
+#define STACKBOUNDS_H
+
+#pragma stacksize("strlen", 128)
+#pragma stacksize("strcpy", 128)
+#pragma stacksize("strcmp", 128)
+#pragma stacksize("strcasecmp", 128)
+#pragma stacksize("strchr", 128)
+#pragma stacksize("strrchr", 128)
+
+#pragma stacksize("tolower", 128)
+
+#pragma stacksize("memset", 128)
+#pragma stacksize("memcpy", 128)
+#pragma stacksize("memcmp", 128)
+#pragma stacksize("memchr", 128)
+
+#pragma stacksize("__errno_location", 128)
+
+#pragma stacksize("__ctype_b_loc", 128)
+#pragma stacksize("__xstat", 128)
+
+#pragma stacksize("read", 256)
+#pragma stacksize("write", 256)
+#pragma stacksize("readv", 256)
+#pragma stacksize("writev", 256)
+#pragma stacksize("open", 256)
+#pragma stacksize("close", 256)
+#pragma stacksize("accept", 256)
+#pragma stacksize("poll", 256)
+#pragma stacksize("fcntl", 256)
+
+#pragma stacksize("pthread_mutex_lock", 128)
+#pragma stacksize("pthread_mutex_unlock", 128)
+#pragma stacksize("pthread_cond_wait", 128)
+#pragma stacksize("pthread_cond_signal", 128)
+
+#pragma stacksize("pthread_yield", 128)
+
+#endif // STACKBOUNDS_H
diff --git a/user/c3po/include/stacklink.h b/user/c3po/include/stacklink.h
new file mode 100644 (file)
index 0000000..aba1cf4
--- /dev/null
@@ -0,0 +1,228 @@
+#ifndef STACKLINK_H
+#define STACKLINK_H
+
+// -----------------------------------------------------------------------
+
+// stacklink.h
+//
+// This module defines the primary interface for stack chunk allocation
+// and linking.  It is used by capriccio's thread system to allocate
+// initial stack chunks, and it is used by the build system to link and
+// unlink stack chunks where appropriate.
+
+// -----------------------------------------------------------------------
+
+// Compile-time settings: comment or uncomment as you please.
+
+// Collect statistics at each call site.
+//#define STACK_CALL_STATS
+
+// Collect statistics about stack depth.
+//#define STATS_USAGE_STATS
+
+// Check for stack overflow when checking for free space on a chunk.
+//#define STACK_PARANOID
+
+// Print statistics and debug info.
+//#define STACK_VERBOSE
+
+// -----------------------------------------------------------------------
+
+// Global variables.
+
+// Current stack fingerprint.
+extern int stack_fingerprint;
+
+// Counters for tracking how often checks are executed.
+extern int stack_extern;
+extern int stack_check_link;
+extern int stack_check_nolink;
+extern int stack_nocheck;
+
+// Counters for tracking stack use.
+extern int stack_waste_ext;
+extern int stack_waste_int;
+extern int stack_allocchunks[];
+
+// The current state of the stack and the free lists for various chunk sizes.
+// Indices to the free list array indicate sizes (kb log2 scale).
+extern void *stack_bottom;
+extern void *stack_freechunks[];
+
+// -----------------------------------------------------------------------
+
+// Function prototypes.  The macros below should only call these functions.
+
+// Ensure that stack_freechunks[bucket] in non-null.
+extern void stack_alloc_chunk(int bucket);
+
+// Get a chunk from the specified bucket or return a chunk when finished.
+extern void *stack_get_chunk(int bucket);
+extern void stack_return_chunk(int bucket, void *chunk);
+
+// Error and stats reporting.
+extern void stack_report_call_stats(void);
+extern void stack_report_usage_stats(void);
+extern void stack_report_link(void *chunk, int used, int node, int succ);
+extern void stack_report_unlink(void *chunk);
+extern void stack_report_overflow(void);
+extern void stack_report_unreachable(int id, char *name);
+
+// -----------------------------------------------------------------------
+
+// Implement settings.
+
+#ifdef STACK_CALL_STATS
+# define STATS_CALL_INC(stat) { stat++; }
+#else // ndef STACK_CALL_STATS
+# define STATS_CALL_INC(stat)
+#endif // STACK_CALL_STATS
+
+#ifdef STATS_USAGE_STATS
+# define STATS_USAGE_INC(stat, amt) { (stat) += (amt); }
+# define STATS_USAGE_DEC(stat, amt) { (stat) -= (amt); }
+#else // ndef STATS_USAGE_STATS
+# define STATS_USAGE_INC(stat, amt)
+# define STATS_USAGE_DEC(stat, amt)
+#endif // STATS_USAGE_STATS
+
+#ifdef STACK_PARANOID
+# define CHECK_OVERFLOW() \
+    { if (savesp < stack_bottom) stack_report_overflow(); }
+#else // ndef STACK_PARANOID
+# define CHECK_OVERFLOW()
+#endif // STACK_PARANOID
+
+#ifdef STACK_VERBOSE
+# define REPORT_LINK(chunk, used, node, succ) \
+    stack_report_link(chunk, used, node, succ)
+# define REPORT_UNLINK(chunk) stack_report_unlink(chunk)
+#else // ndef STACK_VERBOSE
+# define REPORT_LINK(chunk, used, node, succ)
+# define REPORT_UNLINK(chunk)
+#endif // STACK_VERBOSE
+
+// -----------------------------------------------------------------------
+
+// Magic macro code follows.  Note that I got rid of the do { ... } while (0)
+// construct because we may not be turning on optimizations that would
+// eliminate the corresponding conditionals.  We don't need to use these
+// macros in any scenarios where do/while would prevent a syntax error.
+
+// -----------------------------------------------------------------------
+
+// Private macros: don't call these directly outside of this file or stack.c.
+
+// Get a chunk from the specified free list (bucket).
+#define GET_CHUNK(bucket, chunk) \
+{ \
+    if (stack_freechunks[bucket] == 0) { \
+        stack_alloc_chunk(bucket); \
+    } \
+    (chunk) = stack_freechunks[bucket]; \
+    stack_freechunks[bucket] = *(((void**) (chunk)) - 1); \
+    STATS_USAGE_INC(stack_allocchunks[bucket], 1); \
+}
+
+// Return a chunk to the free list.
+#define RETURN_CHUNK(bucket, chunk) \
+{ \
+    STATS_USAGE_DEC(stack_allocchunks[bucket], 1); \
+    *(((void**) (chunk)) - 1) = stack_freechunks[bucket]; \
+    stack_freechunks[bucket] = (chunk); \
+}
+
+// Unconditionally link a stack chunk of the specified size.
+// Note that stack_size == (1 << (bucket + 10)).  I specify them
+// separately to reduce overhead when optimizations are disabled.
+#define STACK_LINK_NOSTATS(stack_size, bucket) \
+{ \
+    GET_CHUNK(bucket, savechunk); \
+    savebottom = stack_bottom; \
+    stack_bottom = savechunk - (stack_size); \
+    asm volatile("movl %%esp,%0" : "=g" (savesp) :); \
+    savesp = (void*) ((unsigned long) savechunk - (unsigned long) savesp); \
+    asm volatile("addl %0,%%esp" : : "g" (savesp) : "esp"); \
+}
+
+// Unconditionally unlink a stack chunk.
+#define STACK_UNLINK_NOSTATS(bucket) \
+{ \
+    asm volatile("subl %0,%%esp" : : "g" (savesp) : "esp"); \
+    stack_bottom = savebottom; \
+    RETURN_CHUNK(bucket, savechunk); \
+}
+
+// Unconditionally link a stack chunk of the specified size, as above.
+// Record some stats using the new stack chunk while we're at it.
+#define STACK_LINK(stack_size, bucket, node, succ) \
+{ \
+    STACK_LINK_NOSTATS(stack_size, bucket); \
+    STATS_USAGE_INC(stack_waste_int, ((void*) &savebottom) - stack_bottom); \
+    REPORT_LINK(savechunk, \
+                (((unsigned long) savechunk) - \
+                 ((unsigned long) savesp) - \
+                 ((unsigned long) savebottom)), \
+                (node), (succ)); \
+}
+
+// Unconditionally unlink a stack chunk, with stats.
+#define STACK_UNLINK(bucket) \
+{ \
+    REPORT_UNLINK(savechunk); \
+    STATS_USAGE_DEC(stack_waste_int, ((void*) &savebottom) - stack_bottom); \
+    STACK_UNLINK_NOSTATS(bucket); \
+}
+
+// -----------------------------------------------------------------------
+
+// Public macros: this is your interface.
+
+// Unconditionally link a stack chunk, presumably for external function stacks.
+#define STACK_EXTERN_LINK(stack_size, bucket, node, succ) \
+{ \
+    STATS_CALL_INC(stack_extern); \
+    STACK_LINK(stack_size, bucket, node, succ); \
+}
+
+// Unconditionally unlink an external function stack chunk.
+#define STACK_EXTERN_UNLINK(bucket) \
+{ \
+    STACK_UNLINK(bucket); \
+}
+
+// Link a new stack chunk if necessary.
+#define STACK_CHECK_LINK(max_path, stack_size, bucket, node, succ) \
+{ \
+    asm volatile("movl %%esp,%0" : "=g" (savesp) :); \
+    CHECK_OVERFLOW(); \
+    if ((unsigned long) (savesp - stack_bottom) <= (unsigned long) (max_path)){\
+        STATS_CALL_INC(stack_check_link); \
+        STACK_LINK(stack_size, bucket, node, succ); \
+    } else { \
+        STATS_CALL_INC(stack_check_nolink); \
+        savesp = 0; \
+    } \
+}
+
+// Unlink a stack chunk if the above macro linked one.
+#define STACK_CHECK_UNLINK(bucket) \
+{ \
+    if (savesp != 0) { \
+        STACK_UNLINK(bucket); \
+    } \
+}
+
+// Increment counters for call sites with no stack linking.
+#define STACK_NOCHECK(node, succ) \
+{ \
+    STATS_CALL_INC(stack_nocheck); \
+}
+
+// Report an error for a function presumed to be unreachable.
+#define STACK_UNREACHABLE(id, name) \
+{ \
+    stack_report_unreachable(id, name); \
+}
+
+#endif // STACKLINK_H
diff --git a/user/c3po/stack/Makefrag b/user/c3po/stack/Makefrag
new file mode 100644 (file)
index 0000000..cbb7456
--- /dev/null
@@ -0,0 +1,25 @@
+STACK_NAME    := stack
+STACK_UCNAME  := $(call uc, $(STACK_NAME))
+STACK_CFLAGS  := $(CFLAGS)
+STACK_HEADERS := $(wildcard $(STACKDIR)/*.h)
+STACK_CFILES  := $(wildcard $(STACKDIR)/*.c)
+STACK_OBJDIR  := $(OBJDIR)/$(STACK_NAME)
+STACK_OBJS    := $(patsubst %.c, %.o, $(STACK_CFILES))
+STACK_OBJS    := $(foreach x, $(STACK_OBJS), $(STACK_OBJDIR)/$(call filename,$(x)))
+
+LIBSTACK = $(STACK_OBJDIR)/lib$(STACK_NAME).a
+
+$(STACK_NAME)-clean:
+       @echo + clean [$(LIBUCNAME) $(STACK_UCNAME)]
+       $(V)rm -rf $(STACK_OBJS) $(LIBSTACK)
+       $(V)rm -rf $(STACK_OBJDIR)
+
+$(LIBSTACK): $(STACK_OBJS)
+       @echo + ar [$(LIBUCNAME) $(STACK_UCNAME)] $@
+       $(V)$(AR) rc $@ $^
+
+$(STACK_OBJDIR)/%.o: $(STACKDIR)/%.c $(STACK_HEADERS)
+       @echo + cc [$(LIBUCNAME) $(STACK_UCNAME)] $<
+       @mkdir -p $(@D)
+       $(V)$(CC) $(STACK_CFLAGS) $(INCS) -o $@ -c $<
+
diff --git a/user/c3po/stack/fptr.c b/user/c3po/stack/fptr.c
new file mode 100644 (file)
index 0000000..d037058
--- /dev/null
@@ -0,0 +1,21 @@
+#include "util.h"
+
+int fptr_caller = -1;
+
+void
+fptr_report_unexpected_call(int cur)
+{
+  output("unexpected call %d -> %d\n", fptr_caller, cur);
+}
+
+void
+fptr_report_unvalidated_return(int cur)
+{
+  output("unvalidated fptr_caller %d (return to %d)\n", fptr_caller, cur);
+}
+
+void
+fptr_report_unvalidated_overwrite(int cur)
+{
+  output("unvalidated fptr_caller %d (overwrite in %d)\n", fptr_caller, cur);
+}
diff --git a/user/c3po/stack/stack.c b/user/c3po/stack/stack.c
new file mode 100644 (file)
index 0000000..f8671a3
--- /dev/null
@@ -0,0 +1,154 @@
+#include "stacklink.h"
+#include "util.h"
+
+#ifndef DEBUG_stack_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+#define MAX_BUCKETS 32
+
+int stack_fingerprint = 0;
+
+int stack_extern = 0;
+int stack_check_link = 0;
+int stack_check_nolink = 0;
+int stack_nocheck = 0;
+
+int stack_waste_ext = 0;
+int stack_waste_int = 0;
+int stack_allocchunks[MAX_BUCKETS] =
+{
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+// disable stack checking for initial thread
+void *stack_bottom = NULL;
+void *stack_freechunks[MAX_BUCKETS] =
+{
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+  0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+void stack_alloc_many_chunks(int bucket)
+{
+  int bytes = 1 << 16; // 64 KB
+  char *p = (char*) malloc(bytes);
+
+  if (p != NULL) {
+    int chunkbytes = 1 << (bucket + 10);
+    char *base = p;
+
+    // get pointer to end of memory area and align
+    p += bytes;
+    p = (char*) (((int) p) & ~(chunkbytes - 1));
+
+    // we should get at least one chunk
+    assert((p - base) >= chunkbytes);
+
+    while ((p - base) >= chunkbytes) {
+      *(((void**) p) - 1) = stack_freechunks[bucket];
+      stack_freechunks[bucket] = p;
+
+      debug("allocated %p\n", p);
+
+      p -= chunkbytes;
+    }
+  } else {
+    output("malloc error\n");
+    abort();
+  }
+}
+
+void stack_alloc_one_chunk(int bucket)
+{
+  int bytes = 1 << (bucket + 10);
+  char *p = (char*) malloc(bytes);
+
+  if (p != NULL) {
+    p += bytes;
+
+    *(((void**) p) - 1) = stack_freechunks[bucket];
+    stack_freechunks[bucket] = p;
+
+    debug("allocated %p\n", p);
+  } else {
+    output("malloc error\n");
+    abort();
+  }
+}
+
+void stack_alloc_chunk(int bucket)
+{
+  assert(bucket >= 0);
+  if (bucket < 5) {
+    stack_alloc_many_chunks(bucket);
+  } else {
+    stack_alloc_one_chunk(bucket);
+  }
+}
+
+void *stack_get_chunk(int bucket)
+{
+  void *chunk;
+  GET_CHUNK(bucket, chunk);
+  return chunk;
+}
+
+void stack_return_chunk(int bucket, void *chunk)
+{
+  // FIXME: this needs to follow any chained pages that still remain, in the case of pthread_exit()
+
+  RETURN_CHUNK(bucket, chunk);
+}
+
+void stack_report_call_stats(void)
+{
+  output("links:      %d check/links    %d check/nolinks\n"
+         "            %d externals    %d nochecks\n",
+         stack_check_link, stack_check_nolink,
+         stack_extern, stack_nocheck);
+}
+
+void stack_report_usage_stats(void)
+{
+  int total = 0;
+  int size;
+  int i;
+
+  for (i = 0, size = 1; i < MAX_BUCKETS; i++, size <<= 1) {
+    total += (stack_allocchunks[i] * size);
+  }
+
+  output("stack:      %d KB allocated    %d KB internal waste\n",
+         total, stack_waste_int / 1024);
+}
+
+void stack_report_link(void *chunk, int used, int node, int succ)
+{
+  output("linking stack %p (used %d node %d succ %d )\n",
+         chunk, used, node, succ);
+}
+
+void stack_report_unlink(void *chunk)
+{
+  output("unlinking stack %p\n", chunk);
+}
+
+void stack_report_overflow(void)
+{
+  abort();
+}
+
+void stack_report_unreachable(int id, char *name)
+{
+  output("error: reached unreachable node (%d, %s)\n", id, name);
+  abort();
+}
diff --git a/user/c3po/threads/Makefrag b/user/c3po/threads/Makefrag
new file mode 100644 (file)
index 0000000..886de6c
--- /dev/null
@@ -0,0 +1,25 @@
+THREADS_NAME    := threads
+THREADS_UCNAME  := $(call uc, $(THREADS_NAME))
+THREADS_CFLAGS  := $(CFLAGS)
+THREADS_HEADERS := $(wildcard $(THREADSDIR)/*.h)
+THREADS_CFILES  := $(wildcard $(THREADSDIR)/*.c)
+THREADS_OBJDIR  := $(OBJDIR)/$(THREADS_NAME)
+THREADS_OBJS    := $(patsubst %.c, %.o, $(THREADS_CFILES))
+THREADS_OBJS    := $(foreach x, $(THREADS_OBJS), $(THREADS_OBJDIR)/$(call filename,$(x)))
+
+LIBTHREADS = $(THREADS_OBJDIR)/lib$(THREADS_NAME).a
+
+$(THREADS_NAME)-clean:
+       @echo + clean [$(LIBUCNAME) $(THREADS_UCNAME)]
+       $(V)rm -rf $(THREADS_OBJS) $(LIBTHREADS)
+       $(V)rm -rf $(THREADS_OBJDIR)
+
+$(LIBTHREADS): $(THREADS_OBJS)
+       @echo + ar [$(LIBUCNAME) $(THREADS_UCNAME)] $@
+       $(V)$(AR) rc $@ $^
+
+$(THREADS_OBJDIR)/%.o: $(THREADSDIR)/%.c $(THREADS_HEADERS)
+       @echo + cc [$(LIBUCNAME) $(THREADS_UCNAME)] $<
+       @mkdir -p $(@D)
+       $(V)$(CC) $(THREADS_CFLAGS) $(INCS) -o $@ -c $<
+
diff --git a/user/c3po/threads/blocking_graph.c b/user/c3po/threads/blocking_graph.c
new file mode 100644 (file)
index 0000000..24f4771
--- /dev/null
@@ -0,0 +1,513 @@
+/**
+ *
+ *  Fucntions to gather/manage statistics about nodes in the call graph
+ *
+ **/
+
+#include "threadlib.h"
+#include "threadlib_internal.h"
+#include "blocking_graph.h"
+#include "util.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <execinfo.h>
+
+
+#ifndef DEBUG_blocking_graph_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+// Set this to 1 to print thread stats
+#define PRINT_STAT 0
+
+
+// external flags, to turn off stats gathering
+int bg_save_stats = 1;
+int bg_stats_epoch = 0;
+
+
+// track the nodes - used for dynamic graph building 
+// (BG_NODE_TYPE_YIELD and BG_NODE_TYPE_NAMED)
+static PLHashTable *nodehash = NULL;
+
+
+// function prototypes
+PLHashNumber HashPtr(const void *key);
+
+
+// used to inform the BG routines how they got there...  Set by the IO routines
+const char *cap_current_syscall;  
+
+//////////////////////////////////////////////////////////////////////
+// node list handling
+//////////////////////////////////////////////////////////////////////
+
+// dummy node, for threads that don't have a real node yet
+bg_node_t *bg_dummy_node=NULL;
+bg_node_t *bg_exit_node=NULL;
+
+// FIXME: this stuff is horribly non-thread safe!!  There are races
+// both on bg_maxnode, and (due to realloc) bg_nodelist!!.  Changes to
+// both should be infrequent, so optomistic concurrency control is
+// ultimately the way to go.
+
+// list of nodes
+int bg_num_nodes = 0;
+static int bg_nodelist_size = 0;
+bg_node_t **bg_nodelist = NULL;
+
+#define BG_NODELIST_INC 100
+
+
+static inline bg_node_t* bg_newnode(bg_node_type_t type)
+{
+  bg_node_t* node;
+
+  // allocate the node
+  node = malloc( sizeof(bg_node_t) );
+  assert(node);
+  bzero(node, sizeof(bg_node_t));
+
+  node->type = type;
+  node->node_num = bg_num_nodes++;
+  node->latch = LATCH_INITIALIZER_UNLOCKED;
+
+  // set the system call
+  node->system_call = (cap_current_syscall ? cap_current_syscall : "unknown");
+
+  node->edgehash = PL_NewHashTable(20, HashPtr, PL_CompareValues, PL_CompareValues, NULL, NULL);
+  assert(node->edgehash);
+
+  // allocate list space if necessary
+  if(node->node_num >= bg_nodelist_size) {
+    bg_nodelist = realloc(bg_nodelist, (bg_nodelist_size+BG_NODELIST_INC)*sizeof(bg_node_t*));
+    assert(bg_nodelist);
+    bzero(&bg_nodelist[bg_nodelist_size], BG_NODELIST_INC*sizeof(bg_node_t*));
+    bg_nodelist_size += BG_NODELIST_INC;
+  }
+
+  bg_nodelist[ node->node_num ] = node;
+
+  return node;
+}
+
+
+
+
+//////////////////////////////////////////////////////////////////////
+// blocking graph node management
+//////////////////////////////////////////////////////////////////////
+
+static latch_t nodehash_latch = LATCH_INITIALIZER_UNLOCKED;
+
+/**
+ * add a named node
+ **/
+void bg_named_set_node(char *file, char *func, int line)
+{
+  bg_node_t *this_node;
+  static bg_node_t key_node;
+
+  // we skipped a blocking point without actually yielding.  Insert a
+  // yield now.  This both changes how the edges in the graph look,
+  // and ensures that we have enough yields.
+  // 
+  // FIXME: not sure if this is really a good idea!!
+  if( current_thread->curr_stats.node != NULL )
+    thread_yield();
+
+
+  // lock the node hash
+  thread_latch( nodehash_latch );
+
+  key_node.type = BG_NODE_TYPE_NAMED;
+  key_node.u.named.file = file;
+  key_node.u.named.func = func;
+  key_node.u.named.line = line;
+
+  // get the node structure for the current node
+  this_node = PL_HashTableLookup(nodehash, &key_node);
+  if( this_node == NULL ) {
+    this_node = bg_newnode(BG_NODE_TYPE_NAMED);
+
+    this_node->u.named.file = file;
+    this_node->u.named.func = func;
+    this_node->u.named.line = line;
+
+    PL_HashTableAdd(nodehash, this_node, this_node);
+  }
+
+  // unlock the node hash
+  thread_unlatch( nodehash_latch );
+
+  current_thread->curr_stats.node = this_node;
+}
+
+
+/**
+ * Add a cil-specified node
+ **/
+// FIXME: implement this in earnest
+void bg_cil_set_node(int num)
+{
+  // we skipped a blocking point without actually yielding.  Insert a
+  // yield now.  This both changes how the edges in the graph look,
+  // and ensures that we have enough yields.
+  // 
+  // FIXME: not sure if this is really a good idea!!
+  if( current_thread->curr_stats.node != NULL )
+    thread_yield();
+
+  // this should have already been initialized
+  current_thread->curr_stats.node = bg_nodelist[ num ];
+}
+
+
+
+/**
+ * add a yield point node
+ **/
+#define MAX_STACK_FRAMES 200
+void bg_backtrace_set_node()
+{
+  bg_node_t *this_node;
+  static void* stack[MAX_STACK_FRAMES];
+  static bg_node_t key_node;
+
+  // lock
+  thread_latch( nodehash_latch );
+  
+  // fill in the key node
+  key_node.type = BG_NODE_TYPE_YIELD;
+  key_node.u.yield.stack = stack;
+  key_node.u.yield.numframes = backtrace(stack, MAX_STACK_FRAMES);
+  
+  // look up this stack trace
+  this_node = (bg_node_t*) PL_HashTableLookup(nodehash, &key_node);
+  if(this_node == NULL) {
+    this_node = bg_newnode(BG_NODE_TYPE_YIELD);
+
+    assert( cap_current_syscall ); // so we can find the places that don't set this...
+    
+    this_node->u.yield.numframes = key_node.u.yield.numframes;
+    this_node->u.yield.stack = malloc(sizeof(void*) * key_node.u.yield.numframes); 
+    assert(this_node->u.yield.stack);
+    memcpy(this_node->u.yield.stack, stack, sizeof(void*) * key_node.u.yield.numframes);
+    
+    PL_HashTableAdd(nodehash, this_node, this_node);
+  }
+  
+  // unlock
+  thread_unlatch( nodehash_latch );
+
+  current_thread->curr_stats.node = this_node;
+}
+
+
+/**
+ * update the blocking graph node for the current thread.  This also
+ * updates the stats for the node, and the edge just taken.
+ **/
+
+// keep totals and show averages
+//#define ADD_STATS(sum,new) (sum += new)
+//#define AVG_STATS(sum,count) (count > 0 ? (sum/count) : 0)
+
+// keep a decaying history
+// FIXME: cheesy hack here to do fixed-point arithmatic.  needs to be un-done on output!!
+#define ADD_STATS(avg,new) ( avg = (avg==0) ? ((new)<<2) : ((avg*7 + ((new)<<2)) >> 3) )
+#define AVG_STATS(avg,count) (avg>>2)
+
+
+void bg_update_stats()
+{
+  bg_node_t *this_node = current_thread->curr_stats.node;
+  bg_node_t *prev_node = current_thread->prev_stats.node;
+  thread_stats_t *prev_stats = &current_thread->prev_stats;
+  thread_stats_t *curr_stats = &current_thread->curr_stats;
+  bg_edge_t *edge;
+
+  // short circuit, if there's nothing to do.  
+  //
+  // FIXME: need to do epochs or something, so the first set of stats
+  // aren't bogus when we turn things on again.
+
+  // update stats for the current thread
+  bg_set_current_stats( curr_stats );
+
+  // if the prev stats are not from the current epoch, don't add the
+  // info in, as it isn't valid
+  if( prev_stats->epoch != bg_stats_epoch )
+    return;
+  
+
+  // update aggregate stats for the previous node
+  {
+    // NOTE: don't bother latching since the numbers are only updated
+    // here, and it's not that big of a deal if others see slightly
+    // inconsistent data
+
+    prev_node->stats.count++;
+
+    // update the stack usage.  NOTE: the stack grows down, so we subtract the new from the old
+    ADD_STATS(prev_node->stats.stack, prev_stats->stack - curr_stats->stack);
+    
+    // update the total stack numbers as well
+    ADD_STATS(total_stack_in_use, prev_stats->stack - curr_stats->stack);
+    
+    // update the cpu ticks, memory, etc.
+    ADD_STATS(prev_node->stats.cpu_ticks, curr_stats->cpu_ticks - prev_stats->cpu_ticks);
+#ifdef USE_PERFCTR
+    ADD_STATS(prev_node->stats.real_ticks, curr_stats->real_ticks - prev_stats->real_ticks);
+#endif // USE_PERFCTR
+    ADD_STATS(prev_node->stats.files, curr_stats->files - prev_stats->files);
+    ADD_STATS(prev_node->stats.sockets, curr_stats->sockets - prev_stats->sockets);
+    ADD_STATS(prev_node->stats.heap, curr_stats->heap - prev_stats->heap);
+  }
+
+  // get the edge structure from prev_node -> this_node
+  {
+    // get or add the edge structure
+    thread_latch( prev_node->latch );
+    edge = PL_HashTableLookup(prev_node->edgehash, this_node);
+    if( edge == NULL ) {
+      edge = malloc( sizeof(bg_edge_t) );
+      assert(edge);
+      bzero(edge, sizeof(bg_edge_t));
+      edge->src  = prev_node;
+      edge->dest = this_node;
+      edge->latch = LATCH_INITIALIZER_UNLOCKED;
+      PL_HashTableAdd(prev_node->edgehash, this_node, edge);
+    }
+    thread_unlatch( prev_node->latch );
+    // update the edge stats.  NOTE: as above, no latch needed
+    {
+      edge->stats.count++;
+      assert( edge->stats.count > 0 );
+      
+      // update the stack usage.  NOTE: the stack grows down, so we subtract the new from the old
+      ADD_STATS(edge->stats.stack, prev_stats->stack - curr_stats->stack);
+      
+      // update the total stack numbers as well
+      ADD_STATS(total_stack_in_use, prev_stats->stack - curr_stats->stack);
+      
+      // update the cpu ticks, memory, etc.
+      ADD_STATS(edge->stats.cpu_ticks, curr_stats->cpu_ticks - prev_stats->cpu_ticks);
+#ifdef USE_PERFCTR
+      ADD_STATS(edge->stats.real_ticks, curr_stats->real_ticks - prev_stats->real_ticks);
+#endif
+      ADD_STATS(edge->stats.files, curr_stats->files - prev_stats->files);
+      ADD_STATS(edge->stats.sockets, curr_stats->sockets - prev_stats->sockets);
+      ADD_STATS(edge->stats.heap, curr_stats->heap - prev_stats->heap);
+    }
+  }
+
+
+  // update some global stats
+  update_edge_totals( curr_stats->cpu_ticks - prev_stats->cpu_ticks );
+
+}
+
+
+
+
+//////////////////////////////////////////////////////////////////////
+// printing functions
+//////////////////////////////////////////////////////////////////////
+
+
+PRIntn edge_enumerator(PLHashEntry *e, PRIntn i, void *arg)
+{
+  bg_edge_t *edge = (bg_edge_t*) e->value;
+  bg_node_t *s = edge->src;
+  bg_node_t *d = edge->dest;
+  (void) arg, (void) i;
+
+  // header for this edge
+  switch( edge->src->type ) {
+  case BG_NODE_TYPE_NAMED:
+    output("  %s:%s():%d -> %s:%s():%d", 
+           s->u.named.file, s->u.named.func, s->u.named.line, 
+           d->u.named.file, d->u.named.func, d->u.named.line);
+    break;
+  default: 
+    output("  %3d -> %3d", s->node_num, d->node_num);
+  }
+
+  output("     [ label = \"");
+  output(" num %6d  \\l", edge->stats.count);
+  output(" cpu  %6lld   \\l",  AVG_STATS(edge->stats.cpu_ticks,   edge->stats.count));
+#ifdef USE_PERFCTR
+  output(" rcpu %6lld   \\l",  AVG_STATS(edge->stats.real_ticks,  edge->stats.count));
+#endif
+  output(" stak %ld     \\l",  AVG_STATS(edge->stats.stack,       edge->stats.count));
+  output(" heap %ld     \\l",  AVG_STATS(edge->stats.heap,        edge->stats.count)); 
+  output(" sock %d     \\l",   AVG_STATS(edge->stats.sockets,     edge->stats.count)); 
+  output(" file %d     \\l",   AVG_STATS(edge->stats.files,       edge->stats.count)); 
+  //output("       // mutexes/run  %d\n", edge->stats.mutexes / edge->stats.count); 
+  output("\" ");
+  if( edge->stats.stack < 0 )   // FIXME: better heuristic here
+    output(" color=green");
+  output(" ]\n");
+  
+  return 0;
+}
+
+
+void dump_blocking_graph(void)
+{
+  int i;
+
+  output("// blocking graph dump - create graph with dot -Tgif -oOUTFILE INFILE\n");
+  output("digraph foo {\n");
+  output("  ratio=compress\n");
+  output("  margin=\"0,0\"\n");
+  output("  nodesep=0.1\n");
+  output("  ranksep=0.001\n");
+  output("  rankdir=LR\n");
+  output("  ordering=out\n");
+  output("  node [shape=ellipse style=filled fillcolor=\"#e0e0e0\" color=black]\n");
+  output("  node [label=\"\\N\" fontsize=10 height=.1 width=.1]\n");
+  output("  edge [fontsize=7 arrowsize=.8]\n");
+  output("  \n");
+
+  // output the nodes in order, so the graph will be reasonable looking
+  output("  // NODES\n");
+  for(i=0; i<bg_num_nodes; i++) {
+    bg_node_t *node = bg_nodelist[i];
+    if(node->num_here)
+      output("  %3d     [ label=\"\\N:%s - %d\" fontcolor=\"red\" ]\n", 
+             node->node_num, node->system_call, node->num_here);
+    else
+      output("  %3d     [ label=\"\\N:%s\" ]\n", 
+             node->node_num, node->system_call);
+  }
+  output("  \n");
+
+  // now output the edge info
+  output("  // EDGES\n");
+  for(i=0; i<bg_num_nodes; i++) {
+    PL_HashTableEnumerateEntries( bg_nodelist[i]->edgehash, edge_enumerator, NULL);
+  }
+
+  output("}\n\n");
+}
+
+
+
+//////////////////////////////////////////////////////////////////////
+// Utilities for node hash
+//////////////////////////////////////////////////////////////////////
+
+PLHashNumber HashPtr(const void *key)
+{
+  return (((unsigned long) key)>>2) ^ 0x57954317;
+}
+
+
+PLHashNumber HashNode(const void *key)
+{
+  bg_node_t *node = (bg_node_t*) key;
+  switch( node->type ) {
+  case BG_NODE_TYPE_NAMED: 
+    return (((unsigned long) node->u.named.file)>>2) ^ 
+      //(((unsigned long) node->u.named.func)>>2) ^ 
+      (((unsigned long) node->u.named.line)>>2) ^ 
+      0x57954317;
+  case BG_NODE_TYPE_YIELD: {
+    unsigned long ret = 0x57954317;
+    void **s = node->u.yield.stack;
+    int numframes = node->u.yield.numframes;
+    
+    while(numframes > 1) {
+      ret ^= (unsigned long) *s;
+      s++;
+      numframes--;
+    }
+    
+    return ret;
+  }
+
+  case BG_NODE_TYPE_CIL:
+    return node->node_num;
+  default:
+    assert(0);
+  }
+  
+  assert(0);
+  return 0; // never reached
+}
+
+PRIntn CompareKeyNodes(const void *v1, const void *v2)
+{
+  bg_node_t *n1 = (bg_node_t*) v1;
+  bg_node_t *n2 = (bg_node_t*) v2;
+  assert(n1->type == n2->type);
+
+  switch( n1->type ) {
+  case BG_NODE_TYPE_NAMED: 
+    if( n1->u.named.line != n2->u.named.line ) return 0;
+    //if( n1->u.named.func != n2->u.named.func ) return 0;
+    if( n1->u.named.file != n2->u.named.file ) return 0;
+    return 1;
+  case BG_NODE_TYPE_YIELD: {
+    void **s1, **s2;
+    int numframes;
+    
+    // check numframes
+    if( n1->u.yield.numframes != n2->u.yield.numframes ) return 0;
+
+    for(numframes = n1->u.yield.numframes, s1 = n1->u.yield.stack, s2 = n2->u.yield.stack;
+        numframes > 1;
+        s1++, s2++, numframes--) 
+      if(*s1 != *s2) return 0;
+
+    return 1;
+  }
+  case BG_NODE_TYPE_CIL: 
+    return n1->node_num == n2->node_num;
+  default:
+    assert(0);
+  }
+
+  // never reached
+  assert(0);
+  return 0;
+}
+
+PRIntn CompareValueNodes(const void *v1, const void *v2)
+{
+  bg_node_t *n1 = (bg_node_t*) v1;
+  bg_node_t *n2 = (bg_node_t*) v2;
+
+  assert(n1->type == n2->type);
+  return n1->node_num == n2->node_num;
+}
+
+
+
+//////////////////////////////////////////////////////////////////////
+// Initialization
+//////////////////////////////////////////////////////////////////////
+
+void init_blocking_graph(void)
+{
+
+  // allocate the node hash
+  nodehash  = PL_NewHashTable(100, HashNode, CompareKeyNodes, CompareValueNodes, NULL, NULL);
+  assert(nodehash);
+
+  // add the dummy node to the list
+  cap_current_syscall = "thread_create";
+  bg_dummy_node = bg_newnode( BG_NODE_TYPE_DUMMY );
+
+  cap_current_syscall = "thread_exit";
+  bg_exit_node = bg_newnode( BG_NODE_TYPE_DUMMY );
+}
+
diff --git a/user/c3po/threads/blocking_graph.h b/user/c3po/threads/blocking_graph.h
new file mode 100644 (file)
index 0000000..9d07e9c
--- /dev/null
@@ -0,0 +1,121 @@
+
+#ifndef BLOCKING_GRAPH_H
+#define BLOCKING_GRAPH_H
+
+#include "util.h"
+#include "threadlib.h"
+
+typedef enum {
+  BG_NODE_TYPE_DUMMY = 0,
+  BG_NODE_TYPE_NAMED = 1, 
+  BG_NODE_TYPE_YIELD = 2,
+  BG_NODE_TYPE_CIL   = 3,
+} bg_node_type_t;
+
+
+// nodes in the blocking graph
+// FIXME: use ifdefs to trim the size down?
+typedef struct bg_node_st {
+  int node_num;            // unique ID for this node
+  const char *system_call;       // system call from which the blocking point was reached
+  int num_here;            // number of threads currently in this node
+  latch_t latch;           // latch for this node's data
+  PLHashTable *edgehash;   // list of outgoing edges
+
+
+  // different info for different types of nodes
+  bg_node_type_t type;
+  union {
+    struct {
+      char *file;
+      char *func;
+      int line;
+    } named;
+    struct {
+      int numframes;
+      void *stack;
+    } yield;
+  } u;
+
+  // info used by the various schedulers
+  union {
+    // nothing for global_rr.
+
+    struct { // sched_graph_rr
+      pointer_list_t *runlist;
+    } g_rr;
+
+    struct { // sched_graph_priority
+      pointer_list_t *runlist;
+      long long score;
+      int listpos;
+    } g_pri;
+
+  } sched;
+
+  // aggregate stats for this node, across recently visited edges
+  thread_stats_t stats;
+
+} bg_node_t;
+
+
+// edges in the blocking graph
+typedef struct bg_edge_st {
+  bg_node_t *src;
+  bg_node_t *dest;
+  latch_t latch;
+
+  // the stats
+  thread_stats_t stats;          // cumulative stats for the edge.  These must
+                                 // be divided by count, to get the per-iteration number.
+
+} bg_edge_t;
+
+
+// vars to access the blocking graph node list.  
+// FIXME: these should be wrapped w/ accessor functions
+extern bg_node_t *bg_dummy_node;
+extern bg_node_t *bg_exit_node;
+extern int bg_num_nodes;
+extern bg_node_t **bg_nodelist;
+
+// flags to control stats gathering
+extern int bg_save_stats;
+extern int bg_stats_epoch;
+
+
+
+// stats routines - for testing
+void init_blocking_graph(void);
+void dump_blocking_graph(void);
+
+
+
+#define bg_auto_set_node() bg_named_set_node(__FILE__, __FUNCTION__, __LINE__)
+void bg_named_set_node(char *file, char *function, int line);
+
+void bg_backtrace_set_node();
+
+void bg_cil_set_node(int num);
+
+void bg_update_stats();
+
+
+static inline void bg_set_current_stats(thread_stats_t *stats) 
+{
+  char foo;
+
+  stats->stack = (long) &foo;  // we're just interested in relative values, so this is fine 
+  GET_CPU_TICKS( stats->cpu_ticks );  
+  stats->cpu_ticks -= ticks_diff;  // FIXME: subtract out time to do debug output
+#ifdef USE_PERFCTR
+  GET_REAL_CPU_TICKS( stats->real_ticks );
+  stats->real_ticks -= ticks_rdiff;  // FIXME: subtract out time to do debug output
+#endif
+  stats->epoch = bg_stats_epoch;
+  
+}
+
+
+#endif // BLOCKING_GRAPH_H
+
diff --git a/user/c3po/threads/events.c b/user/c3po/threads/events.c
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/user/c3po/threads/mutex.c b/user/c3po/threads/mutex.c
new file mode 100644 (file)
index 0000000..5ddb893
--- /dev/null
@@ -0,0 +1,420 @@
+
+/**
+ * Simple mutexes, rwlocks and condition variables
+ **/
+
+#include "threadlib_internal.h"
+#include "threadlib.h"
+
+#include "util.h"
+#include <error.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#ifndef DEBUG_mutex_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+
+static void queue_init(queue_t *q, int n);
+static void queue_free(queue_t *q) __attribute__ ((unused));
+static void enqueue(queue_t *q, void *v);
+static void *dequeue(queue_t *q);
+static int queue_isempty(queue_t *q);
+static int queue_remove(queue_t *q, void *v);
+
+inline int thread_mutex_init(mutex_t *m, char *name) 
+{
+  m->state = UNLOCKED;
+  m->name = name;
+  m->count = 0;
+  m->owner = NULL;
+  queue_init(&m->wait_queue, 0);   // This will be allocated when a thread actually waits on the mutex
+  thread_latch_init( m->latch );
+  return TRUE;
+}
+
+
+static inline int _thread_mutex_lock(mutex_t *m, int istry)
+{
+  if (m == NULL)
+    return_errno(FALSE, EINVAL);
+
+  thread_latch( m->latch );
+
+  // If unlocked, just lock it
+  if (m->state == UNLOCKED)
+    {
+      // Need to have a spin lock on the mutex if we have multiple kernel threads
+      debug("thread_mutex_lock: locking mutex directly\n");
+      m->state = LOCKED;
+      m->owner = current_thread;
+      m->count = 1;
+      thread_unlatch( m->latch );
+      return TRUE;
+    }
+    
+  assert(m->count >= 1);
+
+  // already locked by caller
+  if (m->owner == current_thread)
+    {
+      // recursive lock
+      m->count++;
+      debug("thread_mutex_lock: recursive locking\n");
+      thread_unlatch( m->latch );
+      return TRUE;
+    }
+
+  // Locked by someone else.  
+  if (istry)
+    return_errno_unlatch(FALSE, EBUSY, m->latch);
+
+  // add current thread to waiting queue
+  assert(current_thread);
+  enqueue(&m->wait_queue, current_thread);
+
+  // suspend the current thread
+  thread_unlatch( m->latch );
+  CAP_SET_SYSCALL();
+  thread_suspend_self(0);
+  CAP_CLEAR_SYSCALL();
+
+  // wake up.  Verify that we now own the lock
+  thread_latch( m->latch );
+  assert(m->state == LOCKED);
+  assert(m->owner == current_thread);
+  assert(m->count == 1);
+  thread_unlatch( m->latch );
+
+  return TRUE;
+}
+
+inline int thread_mutex_lock(mutex_t *m)
+{
+  return _thread_mutex_lock(m, FALSE);
+}
+
+inline int thread_mutex_trylock(mutex_t *m)
+{
+  return _thread_mutex_lock(m, TRUE);
+}
+
+inline int thread_mutex_unlock(mutex_t *m)
+{
+  thread_t *t;
+
+  if (m == NULL)
+    return_errno(FALSE, EINVAL);
+
+  thread_latch(m->latch);
+  if (m->state != LOCKED) 
+    return_errno_unlatch(FALSE, EDEADLK, m->latch);
+  if (m->owner != current_thread)
+    return_errno_unlatch(FALSE, EPERM, m->latch);
+
+  m->count--;
+
+  // still locked by the current thread
+  if (m->count > 0)
+    return_errno_unlatch(TRUE, 0, m->latch);
+
+  // get the first waiter
+  t = (thread_t *)dequeue(&m->wait_queue);
+
+  // no threads waiting
+  if(t == NULL) {
+    m->state = UNLOCKED;
+    m->count = 0;
+    m->owner = NULL;
+  }
+
+  // resume the waiter
+  else {
+    m->owner = t;
+    m->count = 1;
+    thread_resume(t);
+  }
+
+  thread_unlatch(m->latch);
+  return TRUE;
+}
+
+
+/*
+**  Read-Write Locks
+*/
+
+int thread_rwlock_init(rwlock_t *rwlock)
+{
+    if (rwlock == NULL)
+        return_errno(FALSE, EINVAL);
+    rwlock->rw_state = THREAD_RWLOCK_INITIALIZED;
+    rwlock->rw_readers = 0;
+    thread_mutex_init(&rwlock->rw_mutex_rd, NULL);
+    thread_mutex_init(&rwlock->rw_mutex_rw, NULL);
+    return TRUE;
+}
+
+static int _thread_rwlock_lock(rwlock_t *rwlock, int op, int tryonly)
+{
+    /* consistency checks */
+    if (rwlock == NULL)
+        return_errno(FALSE, EINVAL);
+    if (!(rwlock->rw_state & THREAD_RWLOCK_INITIALIZED))
+        return_errno(FALSE, EDEADLK);
+
+    /* lock lock */
+    if (op == RWLOCK_RW) {
+        /* read-write lock is simple */
+      if (!_thread_mutex_lock(&(rwlock->rw_mutex_rw), tryonly))
+       return FALSE;
+      rwlock->rw_mode = RWLOCK_RW;
+    }
+    else {
+        /* read-only lock is more complicated to get right */
+        if (!_thread_mutex_lock(&(rwlock->rw_mutex_rd), tryonly))
+            return FALSE;
+        rwlock->rw_readers++;
+        if (rwlock->rw_readers == 1) {
+            if (!_thread_mutex_lock(&(rwlock->rw_mutex_rw), tryonly)) {
+                rwlock->rw_readers--;
+                thread_mutex_unlock(&(rwlock->rw_mutex_rd));
+                return FALSE;
+            }
+        }
+        rwlock->rw_mode = RWLOCK_RD;
+        thread_mutex_unlock(&(rwlock->rw_mutex_rd));
+    }
+    return TRUE;
+}
+
+int thread_rwlock_lock(rwlock_t *l, int op)
+{
+  return _thread_rwlock_lock(l, op, FALSE);
+}
+
+int thread_rwlock_trylock(rwlock_t *l, int op)
+{
+  return _thread_rwlock_lock(l, op, TRUE);
+}
+
+int thread_rwlock_unlock(rwlock_t *rwlock)
+{
+    /* consistency checks */
+    if (rwlock == NULL)
+        return_errno(FALSE, EINVAL);
+    if (!(rwlock->rw_state & THREAD_RWLOCK_INITIALIZED))
+        return_errno(FALSE, EDEADLK);
+
+    /* unlock lock */
+    if (rwlock->rw_mode == RWLOCK_RW) {
+        /* read-write unlock is simple */
+        if (!thread_mutex_unlock(&(rwlock->rw_mutex_rw)))
+            return FALSE;
+    }
+    else {
+        /* read-only unlock is more complicated to get right */
+        if (!_thread_mutex_lock(&(rwlock->rw_mutex_rd), FALSE))
+            return FALSE;
+        rwlock->rw_readers--;
+        if (rwlock->rw_readers == 0) {
+            if (!thread_mutex_unlock(&(rwlock->rw_mutex_rw))) {
+                rwlock->rw_readers++;
+                thread_mutex_unlock(&(rwlock->rw_mutex_rd));
+                return FALSE;
+            }
+        }
+        rwlock->rw_mode = RWLOCK_RD;
+        thread_mutex_unlock(&(rwlock->rw_mutex_rd));
+    }
+    return TRUE;
+}
+
+/*
+**  Condition Variables
+*/
+
+int thread_cond_init(cond_t *cond)
+{
+    if (cond == NULL)
+        return_errno(FALSE, EINVAL);
+    cond->cn_state   = THREAD_COND_INITIALIZED;
+    cond->cn_waiters = 0;
+    queue_init(&cond->wait_queue, 0);
+    return TRUE;
+}
+
+int thread_cond_timedwait(cond_t *cond, mutex_t *mutex, const struct timespec *abstime)
+{
+  unsigned long timeout = 0;
+  int sus_rv = 0;
+    /* consistency checks */
+    if (cond == NULL || mutex == NULL)
+        return_errno(FALSE, EINVAL);
+    if (!(cond->cn_state & THREAD_COND_INITIALIZED))
+        return_errno(FALSE, EDEADLK);
+
+    /* add us to the number of waiters */
+    cond->cn_waiters++;
+
+    /* unlock mutex (caller had to lock it first) */
+    thread_mutex_unlock(mutex);
+
+    /* wait until the condition is signaled */
+    assert (current_thread);
+    enqueue(&cond->wait_queue, current_thread);
+
+    if (abstime) {
+      struct timeval tv;
+      gettimeofday(&tv, NULL);
+      timeout = (abstime->tv_sec - tv.tv_sec) * 1000000 +
+       (abstime->tv_nsec / 1000 - tv.tv_usec);
+    }
+
+    CAP_SET_SYSCALL();
+    sus_rv = thread_suspend_self(timeout);
+    CAP_CLEAR_SYSCALL();
+    
+    /* relock mutex */
+    thread_mutex_lock(mutex);
+
+       // FIXME: this is wrong, we should retry after INTERRUPTED
+    if (sus_rv == TIMEDOUT || sus_rv == INTERRUPTED) {
+      /* we timed out */
+      /* remove us from the number of waiters */
+      /* our thread could possibly be already removed by thread_cond_signal() */
+      if (queue_remove(&cond->wait_queue, current_thread))
+       cond->cn_waiters--;
+
+      return_errno(FALSE, ETIMEDOUT);
+    } else
+      return TRUE;
+}
+
+int thread_cond_wait(cond_t *cond, mutex_t *mutex)
+{
+  return thread_cond_timedwait(cond, mutex, NULL);
+}
+
+
+static int _thread_cond_signal(cond_t *cond, int broadcast)
+{
+    /* consistency checks */
+    if (cond == NULL)
+        return_errno(FALSE, EINVAL);
+    if (!(cond->cn_state & THREAD_COND_INITIALIZED))
+        return_errno(FALSE, EDEADLK);
+
+    // do something only if there is at least one waiters (POSIX semantics)
+    if (cond->cn_waiters > 0) {
+      // signal the condition
+      do {
+       thread_t *t = dequeue(&cond->wait_queue);
+       assert (t != NULL);
+       thread_resume(t);   // t could also be a timed out thread, but it doesn't matter
+       cond->cn_waiters--;
+      } while (broadcast && !queue_isempty(&cond->wait_queue));
+      
+      // and give other threads a chance to grab the CPU 
+      CAP_SET_SYSCALL();
+      thread_yield();
+      CAP_CLEAR_SYSCALL();
+    }
+
+    /* return to caller */
+    return TRUE;
+}
+
+int thread_cond_signal(cond_t *cond) {
+  return _thread_cond_signal(cond, FALSE);
+}
+
+int thread_cond_broadcast(cond_t *cond) {
+  return _thread_cond_signal(cond, TRUE);
+}
+
+// Simple queue implementation
+#define WRAP_LEN(head, tail, n) (head) <= (tail) ? ((tail) - (head)) \
+                                : ((n) + (tail) - (head))
+#define WRAP_DEC(x, n) ((x) == 0 ? (n) - 1 : (x) - 1)
+#define WRAP_INC(x, n) ((x) == ((n) - 1) ? 0 : (x) + 1)
+
+static void queue_init(queue_t *q, int n) {
+  q->size = n + 1;
+  q->data = (void **)malloc((n + 1) * sizeof(void *));
+  assert(q->data);
+  q->head = 0;
+  q->tail = 0;
+}
+
+static void queue_free(queue_t *q) {
+  free(q->data);
+}
+
+static void enqueue(queue_t *q, void *v) {
+  int cur_size = WRAP_LEN(q->head, q->tail, q->size);
+  if (cur_size == q->size - 1) {
+
+    /* Enlarge the queue */
+    int newsize;
+    if (q->size == 0)
+      newsize = 2;
+    else 
+      newsize = q->size + q->size;
+
+    q->data = realloc(q->data, newsize * sizeof(void *));
+    assert(q->data);
+    if (q->head > q->tail) {   /* do we need to move data? */
+      memmove(q->data + newsize - (q->size - q->head), q->data + q->head, (q->size - q->head)
+             * sizeof(void *));  
+      q->head = newsize - (q->size - q->head);
+      assert(*(q->data + newsize - 1) == *(q->data + q->size - 1) ); // the old last element should now be at the end of the larger block
+    }
+    q->size = newsize;
+  }
+  *(q->data + q->tail) = v;
+  q->tail = (q->tail == q->size - 1) ? 0 : q->tail + 1;
+}
+
+static void *dequeue(queue_t *q) {
+  void *r;
+
+  // the queue is empty
+  if(q->head == q->tail) 
+    return NULL;
+
+  r = *(q->data + q->head);
+  q->head = (q->head == q->size - 1) ? 0 : q->head + 1;
+  return r;
+}
+
+// remove the first occurance of a certain value from the queue
+// return TRUE if the value is found
+// FIXME: this is O(N)!
+static int queue_remove(queue_t *q, void *v) {
+  int i = q->head;
+  int rv = FALSE;
+
+  while (i != q->tail) {
+    if (q->data[i] == v) {
+      q->tail = WRAP_DEC(q->tail, q->size);
+      q->data[i] = q->data[q->tail];
+      rv = TRUE;
+      break;
+    }
+    i = WRAP_INC(i, q->size);
+  }
+
+  return rv;
+}
+
+static int queue_isempty(queue_t *q) {
+  return q->head == q->tail;
+}
+
+#undef WRAP_LEN
+
diff --git a/user/c3po/threads/pthread.c b/user/c3po/threads/pthread.c
new file mode 100644 (file)
index 0000000..62bfaa4
--- /dev/null
@@ -0,0 +1,1009 @@
+
+// pthread.h must be the first file included
+#define _PTHREAD_PRIVATE
+#include <pthread.h>
+#include "threadlib.h"
+#include "threadlib_internal.h"
+#undef _PTHREAD_PRIVATE
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include "util.h"
+
+#ifndef DEBUG_pthread_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+// comment out, to enable debugging in this file
+//#define debug(...)
+
+#ifdef OK
+#undef OK
+#endif
+#define OK 0
+
+// Add code to ensure initialization code here
+#define pthread_initialize()
+
+/*
+**  THREAD ATTRIBUTE ROUTINES
+*/
+
+int pthread_attr_init(pthread_attr_t *attr)
+{
+  thread_attr_t na;
+  if (attr == NULL)
+    return_errno(EINVAL, EINVAL);
+  if ((na = thread_attr_new()) == NULL)
+    return errno;
+  (*attr) = (pthread_attr_t)na;
+  return OK;
+}
+
+int pthread_attr_destroy(pthread_attr_t *attr)
+{
+  thread_attr_t na;
+  if (attr == NULL || *attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    na = (thread_attr_t)(*attr);
+    thread_attr_destroy(na);
+    *attr = NULL;
+    return OK;
+}
+
+int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched)
+{
+    (void) inheritsched;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched)
+{
+    if (attr == NULL || inheritsched == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *schedparam)
+{
+    (void) schedparam;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *schedparam)
+{
+    if (attr == NULL || schedparam == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_setschedpolicy(pthread_attr_t *attr, int schedpolicy)
+{
+    (void) schedpolicy;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *schedpolicy)
+{
+    if (attr == NULL || schedpolicy == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_setscope(pthread_attr_t *attr, int scope)
+{
+    (void) scope;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_getscope(const pthread_attr_t *attr, int *scope)
+{
+    if (attr == NULL || scope == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)
+{
+    (void) stacksize;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    // FIXME: now doing nothing
+
+    return OK;
+}
+
+int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize)
+{
+    if (attr == NULL || stacksize == NULL)
+        return_errno(EINVAL, EINVAL);
+
+    // FIXME: returning a faked one
+    *stacksize = 256 * 1024;
+    return OK;
+}
+
+// FIXME: implemente this
+int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr)
+{
+    (void) stackaddr;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr)
+{
+    if (attr == NULL || stackaddr == NULL)
+        return_errno(EINVAL, EINVAL);
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
+{
+    int s;
+
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (detachstate == PTHREAD_CREATE_DETACHED)
+        s = THREAD_CREATE_DETACHED;
+    else  if (detachstate == PTHREAD_CREATE_JOINABLE)
+        s = THREAD_CREATE_JOINABLE;
+    else
+        return_errno(EINVAL, EINVAL);
+    if (!thread_attr_set((thread_attr_t)(*attr), THREAD_ATTR_JOINABLE, s))
+        return errno;
+    return OK;
+}
+
+int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate)
+{
+    int s;
+
+    if (attr == NULL || detachstate == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (!thread_attr_get((thread_attr_t)(*attr), THREAD_ATTR_JOINABLE, &s))
+        return errno;
+    if (s == THREAD_CREATE_JOINABLE)
+        *detachstate = PTHREAD_CREATE_JOINABLE;
+    else
+        *detachstate = PTHREAD_CREATE_DETACHED;
+    return OK;
+}
+
+int pthread_attr_setguardsize(pthread_attr_t *attr, int stacksize)
+{
+    if (attr == NULL || stacksize < 0)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_getguardsize(const pthread_attr_t *attr, int *stacksize)
+{
+    if (attr == NULL || stacksize == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_attr_setname_np(pthread_attr_t *attr, char *name)
+{
+    if (attr == NULL || name == NULL)
+        return_errno(EINVAL, EINVAL);
+    notimplemented(pthread_attr_setname_np);
+    //    if (!thread_attr_set((thread_attr_t)(*attr), THREAD_ATTR_NAME, name))
+    //        return errno;
+    return OK;
+}
+
+int pthread_attr_getname_np(const pthread_attr_t *attr, char **name)
+{
+    if (attr == NULL || name == NULL)
+        return_errno(EINVAL, EINVAL);
+    //    if (!thread_attr_get((thread_attr_t)(*attr), THREAD_ATTR_NAME, name))
+    //        return errno;
+    notimplemented(pthread_attr_getname_np);
+    *name = "FAKE_NAME";
+    return OK;
+}
+
+int pthread_attr_setprio_np(pthread_attr_t *attr, int prio)
+{
+  //    if (attr == NULL || (prio < THREAD_PRIO_MIN || prio > THREAD_PRIO_MAX))
+  //      return_errno(EINVAL, EINVAL);
+    if (!thread_attr_set((thread_attr_t)(*attr), THREAD_ATTR_PRIO, prio))
+        return errno;
+    return OK;
+}
+
+int pthread_attr_getprio_np(const pthread_attr_t *attr, int *prio)
+{
+    if (attr == NULL || prio == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (!thread_attr_get((thread_attr_t)(*attr), THREAD_ATTR_PRIO, prio))
+        return errno;
+    return OK;
+}
+
+
+
+/*
+**  THREAD ROUTINES
+*/
+
+int pthread_create(
+    pthread_t *thread, const pthread_attr_t *attr,
+    void *(*start_routine)(void *), void *arg)
+{
+    pthread_initialize();
+    if (thread == NULL || start_routine == NULL)
+        return_errno(EINVAL, EINVAL);
+    //    if (thread_ctrl(THREAD_CTRL_GETTHREADS) >= PTHREAD_THREADS_MAX)
+    //    return_errno(EAGAIN, EAGAIN);
+    if (attr == NULL)
+        *thread = (pthread_t)thread_spawn(NULL, start_routine, arg);
+    else
+        *thread = (pthread_t)thread_spawn_with_attr(NULL, start_routine, arg, (thread_attr_t)(*attr));
+    if (*thread == NULL) {
+        errno = ENOMEM;
+        return -1;
+    }
+    return OK;
+}
+
+int __pthread_detach(pthread_t thread)
+{
+    thread_attr_t na;
+
+    if (thread == NULL)
+        return_errno(EINVAL, EINVAL);
+    if ((na = thread_attr_of((thread_t *)thread)) == NULL)
+        return errno;
+    if (!thread_attr_set(na, THREAD_ATTR_JOINABLE, FALSE))
+        return errno;
+    thread_attr_destroy(na);
+    return OK;
+}
+
+int pthread_detach(pthread_t thread)
+{
+    return __pthread_detach(thread);
+}
+
+pthread_t pthread_self(void)
+{
+    pthread_initialize();
+    return (pthread_t)thread_self();
+}
+
+int pthread_equal(pthread_t t1, pthread_t t2)
+{
+    return (t1 == t2);
+}
+
+int pthread_yield_np(void)
+{
+    pthread_initialize();
+    thread_yield();
+    return OK;
+}
+
+int pthread_yield(void)
+{
+    pthread_initialize();
+    thread_yield();
+    return OK;
+}
+
+void pthread_exit(void *value_ptr)
+{
+    pthread_initialize();
+    thread_exit(value_ptr);
+    return;
+}
+
+int pthread_join(pthread_t thread, void **value_ptr)
+{
+    pthread_initialize();
+    if (!thread_join((thread_t *)thread, value_ptr))
+        return errno;
+    //    if (value_ptr != NULL)
+    //        if (*value_ptr == THREAD_CANCELED)
+    //            *value_ptr = PTHREAD_CANCELED;
+    return OK;
+}
+
+int pthread_once(
+    pthread_once_t *once_control, void (*init_routine)(void))
+{
+    pthread_initialize();
+    if (once_control == NULL || init_routine == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*once_control != 1)
+        init_routine();
+    *once_control = 1;
+    return OK;
+}
+strong_alias(pthread_once,__pthread_once);
+
+int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
+{
+    return sigprocmask(how, set, oset);                // this is our implementation
+}
+
+int pthread_kill(pthread_t thread, int sig)
+{
+    pthread_initialize();
+       
+//    return thread_kill((thread_t*)thread, sig);
+}
+
+/*
+**  CONCURRENCY ROUTINES
+**  
+**  We just have to provide the interface, because SUSv2 says:
+**  "The pthread_setconcurrency() function allows an application to
+**  inform the threads implementation of its desired concurrency
+**  level, new_level. The actual level of concurrency provided by the
+**  implementation as a result of this function call is unspecified."
+*/
+
+static int pthread_concurrency = 0;
+
+int pthread_getconcurrency(void)
+{
+    return pthread_concurrency;
+}
+
+int pthread_setconcurrency(int new_level)
+{
+    if (new_level < 0)
+        return_errno(EINVAL, EINVAL);
+    pthread_concurrency = new_level;
+    return OK;
+}
+
+/*
+**  CONTEXT ROUTINES
+*/
+
+int pthread_key_create(pthread_key_t *key, void (*destructor)(void *))
+{
+    pthread_initialize();
+    if (!thread_key_create((thread_key_t *)key, destructor))
+        return errno;
+    return OK;
+}
+
+int pthread_key_delete(pthread_key_t key)
+{
+    if (!thread_key_delete((thread_key_t)key))
+        return errno;
+    return OK;
+}
+
+int pthread_setspecific(pthread_key_t key, const void *value)
+{
+    if (!thread_key_setdata((thread_key_t)key, value))
+        return errno;
+    return OK;
+}
+
+void *pthread_getspecific(pthread_key_t key)
+{
+    return thread_key_getdata((thread_key_t)key);
+}
+
+/*
+**  CANCEL ROUTINES
+*/
+
+int pthread_cancel(pthread_t thread)
+{
+    (void) thread;
+  //    if (!thread_cancel((thread_t)thread))
+  //      return errno;
+  notimplemented(pthread_cancel);
+    return OK;
+}
+
+void pthread_testcancel(void)
+{
+  //    thread_cancel_point();
+  notimplemented(pthread_testcancel);
+    return;
+}
+
+int pthread_setcancelstate(int state, int *oldstate)
+{
+    (void) state;
+    (void) oldstate;
+  /*
+    int s, os;
+
+    if (oldstate != NULL) {
+        thread_cancel_state(0, &os);
+        if (os & THREAD_CANCEL_ENABLE)
+            *oldstate = PTHREAD_CANCEL_ENABLE;
+        else
+            *oldstate = PTHREAD_CANCEL_DISABLE;
+    }
+    if (state != 0) {
+        thread_cancel_state(0, &s);
+        if (state == PTHREAD_CANCEL_ENABLE) {
+            s |= THREAD_CANCEL_ENABLE;
+            s &= ~(THREAD_CANCEL_DISABLE);
+        }
+        else {
+            s |= THREAD_CANCEL_DISABLE;
+            s &= ~(THREAD_CANCEL_ENABLE);
+        }
+        thread_cancel_state(s, NULL);
+    }
+  */
+  notimplemented(pthread_setcancelstate);
+    return OK;
+}
+
+int pthread_setcanceltype(int type, int *oldtype)
+{
+    (void) type;
+    (void) oldtype;
+  /*
+    int t, ot;
+
+    if (oldtype != NULL) {
+        thread_cancel_state(0, &ot);
+        if (ot & THREAD_CANCEL_DEFERRED)
+            *oldtype = PTHREAD_CANCEL_DEFERRED;
+        else
+            *oldtype = PTHREAD_CANCEL_ASYNCHRONOUS;
+    }
+    if (type != 0) {
+        thread_cancel_state(0, &t);
+        if (type == PTHREAD_CANCEL_DEFERRED) {
+            t |= THREAD_CANCEL_DEFERRED;
+            t &= ~(THREAD_CANCEL_ASYNCHRONOUS);
+        }
+        else {
+            t |= THREAD_CANCEL_ASYNCHRONOUS;
+            t &= ~(THREAD_CANCEL_DEFERRED);
+        }
+        thread_cancel_state(t, NULL);
+    }
+  */
+  notimplemented(pthread_setcanceltype);
+    return OK;
+}
+
+/*
+**  SCHEDULER ROUTINES
+*/
+
+int pthread_setschedparam(pthread_t pthread, int policy, const struct sched_param *param)
+{
+    (void) pthread;
+    (void) policy;
+    (void) param;
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_getschedparam(pthread_t pthread, int *policy, struct sched_param *param)
+{
+    (void) pthread;
+    (void) policy;
+    (void) param;
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+/*
+**  CLEANUP ROUTINES
+*/
+
+void pthread_cleanup_push(void (*routine)(void *), void *arg)
+{
+    (void) routine;
+    (void) arg;
+    pthread_initialize();
+    //    thread_cleanup_push(routine, arg);
+    notimplemented(pthread_cleanup_push);
+    return;
+}
+
+void pthread_cleanup_pop(int execute)
+{
+    (void) execute;
+  //    thread_cleanup_pop(execute);
+  notimplemented(pthread_cleanup_pop);
+    return;
+}
+
+/*
+**  AT-FORK SUPPORT
+*/
+int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
+{
+    (void) prepare;
+    (void) parent;
+    (void) child;
+    return_errno(ENOSYS, ENOSYS);
+}
+
+/*
+**  MUTEX ATTRIBUTE ROUTINES
+*/
+
+int pthread_mutexattr_init(pthread_mutexattr_t *attr)
+{
+    pthread_initialize();
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* nothing to do for us */
+    return OK;
+}
+
+int pthread_mutexattr_destroy(pthread_mutexattr_t *attr)
+{
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* nothing to do for us */
+    return OK;
+}
+
+int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *attr, int prioceiling)
+{
+    (void) prioceiling;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_mutexattr_getprioceiling(pthread_mutexattr_t *attr, int *prioceiling)
+{
+    (void) prioceiling;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol)
+{
+    (void) protocol;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_mutexattr_getprotocol(pthread_mutexattr_t *attr, int *protocol)
+{
+    (void) protocol;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared)
+{
+    (void) pshared;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared)
+{
+    (void) pshared;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type)
+{
+    (void) type;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_mutexattr_gettype(pthread_mutexattr_t *attr, int *type)
+{
+    (void) type;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+/*
+**  MUTEX ROUTINES
+*/
+
+int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
+{
+    mutex_t *m;
+    (void) attr;
+
+    pthread_initialize();
+    if (mutex == NULL)
+        return_errno(EINVAL, EINVAL);
+    if ((m = (mutex_t *)malloc(sizeof(mutex_t))) == NULL)
+        return errno;
+    if (!thread_mutex_init(m, "pthread_mutex"))
+        return errno;
+    (*mutex) = (pthread_mutex_t)m;
+    return OK;
+}
+
+int pthread_mutex_destroy(pthread_mutex_t *mutex)
+{
+    if (mutex == NULL)
+        return_errno(EINVAL, EINVAL);
+    free(*mutex);
+    *mutex = NULL;
+    return OK;
+}
+
+int pthread_mutex_setprioceiling(pthread_mutex_t *mutex, int prioceiling, int *old_ceiling)
+{
+    (void) prioceiling;
+    (void) old_ceiling;
+    if (mutex == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*mutex == PTHREAD_MUTEX_INITIALIZER)
+        if (pthread_mutex_init(mutex, NULL) != OK)
+            return errno;
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_mutex_getprioceiling(pthread_mutex_t *mutex, int *prioceiling)
+{
+    (void) prioceiling;
+    if (mutex == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*mutex == PTHREAD_MUTEX_INITIALIZER)
+        if (pthread_mutex_init(mutex, NULL) != OK)
+            return errno;
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_mutex_lock(pthread_mutex_t *mutex)
+{
+    if (mutex == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*mutex == PTHREAD_MUTEX_INITIALIZER)
+        if (pthread_mutex_init(mutex, NULL) != OK)
+            return errno;
+    if (!thread_mutex_lock((mutex_t *)(*mutex)))
+        return errno;
+    return OK;
+}
+
+int pthread_mutex_trylock(pthread_mutex_t *mutex)
+{
+    if (mutex == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*mutex == PTHREAD_MUTEX_INITIALIZER)
+        if (pthread_mutex_init(mutex, NULL) != OK)
+            return errno;
+    if (!thread_mutex_trylock((mutex_t *)(*mutex)))
+        return errno;
+    return OK;
+}
+
+int pthread_mutex_unlock(pthread_mutex_t *mutex)
+{
+    if (mutex == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*mutex == PTHREAD_MUTEX_INITIALIZER)
+        if (pthread_mutex_init(mutex, NULL) != OK)
+            return errno;
+    if (!thread_mutex_unlock((mutex_t *)(*mutex)))
+        return errno;
+    return OK;
+}
+
+/*
+**  LOCK ATTRIBUTE ROUTINES
+*/
+
+int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
+{
+    pthread_initialize();
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* nothing to do for us */
+    return OK;
+}
+
+int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr)
+{
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* nothing to do for us */
+    return OK;
+}
+
+int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared)
+{
+    (void) pshared;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared)
+{
+    (void) pshared;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+/*
+**  LOCK ROUTINES
+*/
+
+int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
+{
+    rwlock_t *rw;
+    (void) attr;
+
+    pthread_initialize();
+    if (rwlock == NULL)
+        return_errno(EINVAL, EINVAL);
+    if ((rw = (rwlock_t *)malloc(sizeof(rwlock_t))) == NULL)
+        return errno;
+    if (!thread_rwlock_init(rw))
+        return errno;
+    (*rwlock) = (pthread_rwlock_t)rw;
+    return OK;
+}
+
+int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
+{
+    if (rwlock == NULL)
+        return_errno(EINVAL, EINVAL);
+    free(*rwlock);
+    *rwlock = NULL;
+    return OK;
+}
+
+int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
+{
+    if (rwlock == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
+        if (pthread_rwlock_init(rwlock, NULL) != OK)
+            return errno;
+    if (!thread_rwlock_lock((rwlock_t *)(*rwlock), RWLOCK_RD))
+        return errno;
+    return OK;
+}
+
+int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
+{
+    if (rwlock == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
+        if (pthread_rwlock_init(rwlock, NULL) != OK)
+            return errno;
+    if (!thread_rwlock_trylock((rwlock_t *)(*rwlock), RWLOCK_RD))
+        return errno;
+    return OK;
+}
+
+int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
+{
+    if (rwlock == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
+        if (pthread_rwlock_init(rwlock, NULL) != OK)
+            return errno;
+    if (!thread_rwlock_lock((rwlock_t *)(*rwlock), RWLOCK_RW))
+        return errno;
+    return OK;
+}
+
+int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
+{
+    if (rwlock == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
+        if (pthread_rwlock_init(rwlock, NULL) != OK)
+            return errno;
+    if (!thread_rwlock_trylock((rwlock_t *)(*rwlock), RWLOCK_RW))
+        return errno;
+    return OK;
+}
+
+int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
+{
+    if (rwlock == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*rwlock == PTHREAD_RWLOCK_INITIALIZER)
+        if (pthread_rwlock_init(rwlock, NULL) != OK)
+            return errno;
+    if (!thread_rwlock_unlock((rwlock_t *)(*rwlock)))
+        return errno;
+    return OK;
+}
+
+/*
+**  CONDITION ATTRIBUTE ROUTINES
+*/
+
+int pthread_condattr_init(pthread_condattr_t *attr)
+{
+    pthread_initialize();
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* nothing to do for us */
+    return OK;
+}
+
+int pthread_condattr_destroy(pthread_condattr_t *attr)
+{
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* nothing to do for us */
+    return OK;
+}
+
+int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared)
+{
+    (void) pshared;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+int pthread_condattr_getpshared(pthread_condattr_t *attr, int *pshared)
+{
+    (void) pshared;
+    if (attr == NULL)
+        return_errno(EINVAL, EINVAL);
+    /* not supported */
+    return_errno(ENOSYS, ENOSYS);
+}
+
+/*
+**  CONDITION ROUTINES
+*/
+
+int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)
+{
+    cond_t *cn;
+    (void) attr;
+
+    pthread_initialize();
+    if (cond == NULL)
+        return_errno(EINVAL, EINVAL);
+    if ((cn = (cond_t *)malloc(sizeof(cond_t))) == NULL)
+        return errno;
+    if (!thread_cond_init(cn))
+        return errno;
+    (*cond) = (pthread_cond_t)cn;
+    return OK;
+}
+
+int pthread_cond_destroy(pthread_cond_t *cond)
+{
+    if (cond == NULL)
+        return_errno(EINVAL, EINVAL);
+    free(*cond);
+    *cond = NULL;
+    return OK;
+}
+
+int pthread_cond_broadcast(pthread_cond_t *cond)
+{
+    if (cond == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*cond == PTHREAD_COND_INITIALIZER)
+        if (pthread_cond_init(cond, NULL) != OK)
+            return errno;
+    if (!thread_cond_broadcast((cond_t *)(*cond)))
+        return errno;
+    return OK;
+}
+
+int pthread_cond_signal(pthread_cond_t *cond)
+{
+    if (cond == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*cond == PTHREAD_COND_INITIALIZER)
+        if (pthread_cond_init(cond, NULL) != OK)
+            return errno;
+    if (!thread_cond_signal((cond_t *)(*cond)))
+        return errno;
+    return OK;
+}
+
+int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
+{
+    if (cond == NULL || mutex == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*cond == PTHREAD_COND_INITIALIZER)
+        if (pthread_cond_init(cond, NULL) != OK)
+            return errno;
+    if (*mutex == PTHREAD_MUTEX_INITIALIZER)
+        if (pthread_mutex_init(mutex, NULL) != OK)
+            return errno;
+    if (!thread_cond_wait((cond_t *)(*cond), (mutex_t *)(*mutex)))
+        return errno;
+    return OK;
+}
+
+int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
+                           const struct timespec *abstime)
+{
+    if (cond == NULL || mutex == NULL)
+        return_errno(EINVAL, EINVAL);
+    if (*cond == PTHREAD_COND_INITIALIZER)
+        if (pthread_cond_init(cond, NULL) != OK)
+            return errno;
+    if (*mutex == PTHREAD_MUTEX_INITIALIZER)
+        if (pthread_mutex_init(mutex, NULL) != OK)
+            return errno;
+    if (!thread_cond_timedwait((cond_t *)(*cond), (mutex_t *)(*mutex), abstime))
+      return errno;
+    return OK;
+}
+
+/*
+**  POSIX 1003.1j
+*/
+
+/*
+int pthread_abort(pthread_t thread)
+{
+    if (!thread_abort((thread_t)thread))
+        return errno;
+    return OK;
+}
+
+
+*/
+
+//////////////////////////////////////////////////
+// Set the emacs indentation offset
+// Local Variables: ***
+// c-basic-offset:4 ***
+// End: ***
+//////////////////////////////////////////////////
diff --git a/user/c3po/threads/pthreadtest.c b/user/c3po/threads/pthreadtest.c
new file mode 100644 (file)
index 0000000..35fd7ca
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+**  test_pthread.c: Pth test program (pthread API)
+*/
+
+#ifdef GLOBAL
+#include <pthread.h>
+#define pthread_yield_np sched_yield
+#else
+#include "pthread.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#define die(str) \
+    do { \
+        fprintf(stdout, "**die: %s: errno=%d\n", str, errno); \
+        exit(1); \
+    } while (0)
+
+static void *child(void *_arg)
+{
+    char *name = (char *)_arg;
+    int i;
+
+    fprintf(stdout, "child: startup %s\n", name);
+    for (i = 0; i < 100; i++) {
+        if (i++ % 10 == 0)
+            pthread_yield_np();
+        fprintf(stdout, "child: %s counts i=%d\n", name, i);
+    }
+    fprintf(stdout, "child: shutdown %s\n", name);
+    return _arg;
+}
+
+int main(int argc, char *argv[])
+{
+    pthread_attr_t thread_attr;
+    pthread_t thread[4];
+    char *rc;
+    (void) argc;
+    (void) argv;
+
+    fprintf(stdout, "main: init\n");
+
+    fprintf(stdout, "main: initializing attribute object\n");
+    if (pthread_attr_init(&thread_attr) != 0)
+        die("pthread_attr_init");
+    if (pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_JOINABLE) != 0)
+        die("pthread_attr_setdetachstate");
+
+    fprintf(stdout, "main: create thread 1\n");
+    if (pthread_create(&thread[0], &thread_attr, child, (void *)"foo") != 0)
+        die("pthread_create");
+    fprintf(stdout, "main: create thread 2\n");
+    if (pthread_create(&thread[1], &thread_attr, child, (void *)"bar") != 0)
+        die("pthread_create");
+    fprintf(stdout, "main: create thread 3\n");
+    if (pthread_create(&thread[2], &thread_attr, child, (void *)"baz") != 0)
+        die("pthread_create");
+    fprintf(stdout, "main: create thread 4\n");
+    if (pthread_create(&thread[3], &thread_attr, child, (void *)"quux") != 0)
+        die("pthread_create");
+
+    fprintf(stdout, "main: destroying attribute object\n");
+    if (pthread_attr_destroy(&thread_attr) != 0)
+        die("pthread_attr_destroy");
+
+    pthread_yield_np();
+
+    fprintf(stdout, "main: joining...\n");
+    if (pthread_join(thread[0], (void **)&rc) != 0)
+        die("pthread_join");
+    fprintf(stdout, "main: joined thread: %s\n", rc);
+    if (pthread_join(thread[1], (void **)&rc) != 0)
+        die("pthread_join");
+    fprintf(stdout, "main: joined thread: %s\n", rc);
+    if (pthread_join(thread[2], (void **)&rc) != 0)
+        die("pthread_join");
+    fprintf(stdout, "main: joined thread: %s\n", rc);
+    if (pthread_join(thread[3], (void **)&rc) != 0)
+        die("pthread_join");
+    fprintf(stdout, "main: joined thread: %s\n", rc);
+
+    fprintf(stdout, "main: exit\n");
+    return 0;
+}
+
diff --git a/user/c3po/threads/readproc.c b/user/c3po/threads/readproc.c
new file mode 100644 (file)
index 0000000..5bf7d6c
--- /dev/null
@@ -0,0 +1,245 @@
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ros/syscall.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "util.h"
+#include "readproc.h"
+
+
+
+
+/**
+ * read data from a file in /proc, caching the FD if that seems to work.
+ **/
+int read_proc_file(int *fdp, char *name, char *buf, int len)
+{
+  register int ret;
+  register int fd = *fdp;
+
+  // open the proc file
+  if( fd < 0 ) {
+    fd = syscall(SYS_open,name,O_RDONLY); 
+    if(fd < 0) {
+      warning("error opening %s: %s\n",name,strerror(errno));
+      *fdp = -2;
+      return -1;
+    }
+    *fdp = fd;
+  }
+
+  // read
+  // FIXME: pread should be better, but it always returns 0 bytes on my home system.  Why?
+#define USE_PREAD 0
+#if USE_PREAD
+  ret = syscall(SYS_pread, fd, buf, len, 0);
+#else
+  /*  if( syscall(SYS_lseek, fd, 0, SEEK_SET) < 0 ) {
+    warning("error w/ lseek: %s\n", strerror(errno));
+    close(fd);
+    *fdp = -2; 
+    return -1;
+    }  */
+  ret = syscall(SYS_read, fd, buf, len);
+  // FIXME: fd caching seems to flake out someitmes - why??
+  syscall(SYS_close, fd);
+  *fdp = -1;
+#endif
+
+  if( ret < 50 ) {
+    warning("too little data in %s (%d bytes)\n",name,ret);
+    if( fd >= 0 ) syscall(SYS_close, fd);
+    *fdp = -1;
+    return -1;
+  }
+
+  return ret;
+}
+
+
+/**
+ * Read stats from /proc/self/stat.
+ *
+ * FIXME: the FD caching may not work correctly for cloned children -
+ * check on this later.
+ **/
+void refresh_process_stats(proc_self_stat_t *stat)
+{
+  static int fd = -1;
+  char buf[256];
+  int ret;
+  //int len;
+
+  ret = read_proc_file(&fd, "/proc/self/stat", buf, sizeof(buf));
+  if( ret < 0 ) return;
+  if( ret == sizeof(buf) ) {
+    warning("too much data in /proc/self/stat - can't get valid stats\n"); 
+    if( fd >= 0) close(fd);
+    fd = -2;
+    return;
+  }
+  
+  // parse.  We could do this faster ourselves, by not keeping all of
+  // this data.  The speed shouldn't really matter, though, since this
+  // routine should only be called a few times / second.
+  ret = sscanf(buf, "%d (%[^)]) %c %d %d %d %d %d %lu %lu \
+%lu %lu %lu %lu %lu %ld %ld %ld %ld %ld %ld %llu %lu %ld %lu %lu %lu %lu %lu \
+%lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %lu",
+
+               &stat->pid, 
+               &stat->comm[0],
+               &stat->state,
+               &stat->ppid,
+               &stat->pgrp,
+               &stat->session,
+               &stat->tty_nr,
+               &stat->tty_pgrp,
+
+               &stat->flags,
+               &stat->min_flt,
+               &stat->cmin_flt,
+               &stat->maj_flt,
+               &stat->cmaj_flt,
+               &stat->tms_utime,
+               &stat->tms_stime,
+               &stat->tms_cutime,
+               &stat->tms_cstime,
+
+               &stat->priority,
+               &stat->nice,
+               &stat->removed, 
+               &stat->it_real_value,
+               &stat->start_time, // just unsigned long in 2.4
+  
+               &stat->vsize,
+               &stat->rss,
+               &stat->rss_rlim_cur,
+               &stat->start_code,
+               &stat->end_code,
+               &stat->start_stack,
+               &stat->esp,
+               &stat->eip,
+
+               &stat->pending_sig, // obsolete - use /proc/self/status
+               &stat->blocked_sig, // obsolete - use /proc/self/status
+               &stat->sigign,      // obsolete - use /proc/self/status
+               &stat->sigcatch,    // obsolete - use /proc/self/status
+               &stat->wchan,
+
+               &stat->nswap,
+               &stat->cnswap,
+               &stat->exit_signal,
+               &stat->cpu_num,
+               &stat->rt_priority, // new in 2.5
+               &stat->policy      // new in 2.5
+               );
+
+  warning("stats: %d (%s)... %ld %ld   %ld  ret=%d\n", 
+          stat->pid, 
+          stat->comm,
+          stat->vsize,
+          stat->rss,
+          stat->maj_flt,
+          ret);
+  
+  if( ret != 39 && ret != 41 ) {
+    warning("error parsing /proc/self/stat - got %d items\n", ret);
+    //bzero(stat, sizeof(proc_self_stat_t));
+    if( fd >= 0) close(fd);
+    fd = -1;
+    return;
+  }
+}
+
+
+
+
+void refresh_global_stats(proc_global_stats_t *stat)
+{
+  static int proc_stat_fd = -1;
+  char buf[1024], *p, *end;
+  int ret;
+
+  ret = read_proc_file(&proc_stat_fd, "/proc/stat", buf, sizeof(buf)-1);
+  if( ret < 0 ) return;
+  buf[ret] = '\0';
+
+
+  // FIXME: parse CPU info (?)
+
+  // parse pageing activity
+  p = strstr(buf,"page ");
+  if( p == NULL ) {warning("couldn't find page data in /proc/stat!\n"); return;}
+
+  p += 5; // strlen("page ");
+  stat->pages_in = strtol(p, &end, 0);
+  if( p == end ) {warning("bad pagein data\n"); return;}
+  
+  p = end;
+  stat->pages_out = strtol(p, &end, 0);
+  if( p == end ) {warning("bad pagein data\n"); return;}
+
+  
+  // parse swap file activity
+  p = strstr(buf,"swap ");
+  if( p == NULL ) {warning("couldn't find swap data in /proc/stat!\n"); return;}
+
+  p += 5; // strlen("swap ");
+  stat->pages_swapin = strtol(p, &end, 0);
+  if( p == end ) {warning("bad pswapin data\n"); return;}
+  
+  p = end;
+  stat->pages_swapout = strtol(p, &end, 0);
+  if( p == end ) {warning("bad pswapout data\n"); return;}
+
+
+  warning("gstats:   %d %d   %d %d\n",
+          stat->pages_in, stat->pages_out,
+          stat->pages_swapin, stat->pages_swapout);
+}
+
+
+#ifdef READPROC_DEFINE_MAIN
+
+
+void printstats(proc_self_stat_t *stat)
+{
+  printf("%s:  vsize=%lu  rss=%ld  min_flt=%lu  maj_flt=%lu\n", 
+         stat->comm, stat->vsize/1024/1024, stat->rss*4/1024,
+         stat->min_flt, stat->maj_flt);
+}
+
+
+int main(int argc, char **argv)
+{
+  proc_self_stat_t selfstats;
+  (void) argc; (void) argv;
+
+  refresh_process_stats(&selfstats); printstats(&selfstats);
+
+  malloc(1024*1024);
+  refresh_process_stats(&selfstats);
+  printstats(&selfstats);
+
+  {
+    char *p = malloc(1024*1024);
+    int i;
+
+    refresh_process_stats(&selfstats); printstats(&selfstats);
+    for( i=0; i<1024; i++ )
+      p[i*1024] = 4;
+    refresh_process_stats(&selfstats); printstats(&selfstats);
+  }
+
+  malloc(1024*1024);
+  refresh_process_stats(&selfstats); printstats(&selfstats);
+
+}
+
+
+#endif
diff --git a/user/c3po/threads/readproc.h b/user/c3po/threads/readproc.h
new file mode 100644 (file)
index 0000000..f94a83a
--- /dev/null
@@ -0,0 +1,67 @@
+\
+#ifndef READPROC_H
+#define READPROC_H
+
+typedef struct {
+  int pid;
+  char comm[256]; // FIXME: a really long command name would mess things up
+  char state;
+  int ppid;
+  int pgrp;
+  int session;
+  int tty_nr;
+  int tty_pgrp;
+
+  unsigned long flags;
+  unsigned long min_flt;
+  unsigned long cmin_flt;
+  unsigned long maj_flt;
+  unsigned long cmaj_flt;
+  unsigned long tms_utime;
+  unsigned long tms_stime;
+  long tms_cutime;
+  long tms_cstime;
+
+  long priority;
+  long nice;
+  long removed; 
+  long it_real_value;
+  unsigned long long start_time; // just unsigned long in 2.4
+  
+  unsigned long vsize;
+  long rss;
+  unsigned long rss_rlim_cur;
+  unsigned long start_code;
+  unsigned long end_code;
+  unsigned long start_stack;
+  unsigned long esp;
+  unsigned long eip;
+
+  unsigned long pending_sig; // obsolete - use /proc/self/status
+  unsigned long blocked_sig; // obsolete - use /proc/self/status
+  unsigned long sigign;      // obsolete - use /proc/self/status
+  unsigned long sigcatch;    // obsolete - use /proc/self/status
+  unsigned long wchan;
+
+  unsigned long nswap;
+  unsigned long cnswap;
+  int exit_signal;
+  int cpu_num;
+
+  unsigned long rt_priority; // new in 2.5
+  unsigned long policy;      // new in 2.5
+
+} proc_self_stat_t;
+
+
+typedef struct {
+  int pages_in;
+  int pages_out;
+  int pages_swapin;
+  int pages_swapout;
+} proc_global_stats_t;
+
+void refresh_process_stats(proc_self_stat_t *stat);
+void refresh_global_stats(proc_global_stats_t *stat);
+
+#endif // READPROC_H
diff --git a/user/c3po/threads/resource_stats.c b/user/c3po/threads/resource_stats.c
new file mode 100644 (file)
index 0000000..7ecb983
--- /dev/null
@@ -0,0 +1,482 @@
+
+
+#include "threadlib_internal.h"
+#include "readproc.h"
+
+#include <math.h>
+#include <malloc.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/resource.h>
+#include <ros/syscall.h>
+
+#ifndef DEBUG_resource_stats_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+// statistics
+int total_sockets_in_use = 0;
+int total_socket_opens = 0;
+int total_socket_closes = 0;
+long long total_socket_lifetime = 0;
+
+int total_files_in_use = 0;
+int total_file_opens = 0;
+int total_file_closes = 0;
+long long total_file_lifetime = 0;
+
+long long total_heap_in_use = 0;
+long long total_stack_in_use = 0;
+
+cpu_tick_t total_edge_cycles = 0;
+int total_edges_taken = 0;
+static cpu_tick_t prev_edge_cycles = 0;
+static int prev_edges_taken = 0;
+
+
+#define IOSTAT_INITIALIZER {0,0,0,0,0,0}
+
+iostats_t sockio_stats      = IOSTAT_INITIALIZER;
+iostats_t diskio_stats      = IOSTAT_INITIALIZER;
+
+
+
+long long max_memory;
+int max_fds;
+proc_self_stat_t proc_stats, prev_proc_stats;
+proc_global_stats_t global_stats, prev_global_stats;
+
+
+typeof(__free_hook) orig_free_hook;
+typeof(__malloc_hook) orig_malloc_hook;
+typeof(__realloc_hook) orig_realloc_hook;
+
+
+static void *capriccio_malloc_hook(size_t size, const void *callsite)
+{
+  void *ret;
+  (void) callsite;
+  
+  // FIXME: race
+  __malloc_hook = orig_malloc_hook;
+  ret = malloc(size);
+  __malloc_hook = (typeof(__malloc_hook))capriccio_malloc_hook;
+
+  if( !ret ) return NULL;
+
+  thread_stats_add_heap( malloc_usable_size(ret) );
+  
+  return ret;
+}
+
+
+static void capriccio_free_hook(void *ptr, const void *callsite)
+{
+  (void) callsite;
+  if( !ptr ) return;
+
+  // FIXME: subtracts for cases in which free() fails.  ;-/ It also
+  // behaves incorrectly if things are freed that were malloc-ed
+  // before this lib is initialized (because of bad ordering of
+  // initializers, say.)  
+  //
+  // We'll need our own (thread-safe) memory allocator anyway when we
+  // go to multiple CPUs, so just ignore this for now.
+  thread_stats_add_heap( 0 - malloc_usable_size(ptr) );
+
+  // FIXME: race
+  __free_hook = orig_free_hook;
+  free(ptr);
+  __free_hook = capriccio_free_hook;
+}
+
+
+static void* capriccio_realloc_hook(void *ptr, size_t size, const void *callsite)
+{
+  (void) callsite;
+  if( ptr )
+    thread_stats_add_heap( 0 - malloc_usable_size(ptr) );
+
+  // FIXME: race
+  __realloc_hook = orig_realloc_hook;
+  ptr = realloc(ptr,size);
+  __realloc_hook = capriccio_realloc_hook;
+
+  if( !ptr ) return NULL;
+
+  thread_stats_add_heap( malloc_usable_size(ptr) );
+  
+  return ptr;
+}
+
+
+
+
+
+inline void thread_stats_add_heap(long size) 
+{
+  if( current_thread )
+    current_thread->curr_stats.heap += size;
+
+  total_heap_in_use += size;
+  
+  // FIXME: bugs in accounting make this not always true!!
+  //assert( total_heap_in_use >= 0 );
+  if( total_heap_in_use < 0 )
+    total_heap_in_use = 0;
+}
+
+
+/**
+ * Update some global stats about system performance.  
+ **/
+
+// FIXME: temporarily make these global, for debugging & testing
+int socket_overload = 0; 
+int vm_overload = 0;
+
+
+// FIXME: MAJOR KLUDGE - make these public, so we can print them out from knot
+double open_rate;
+double close_rate;
+double avg_socket_lifetime;
+
+static void check_socket_overload( cpu_tick_t now )
+{
+  static cpu_tick_t then = 0;
+  static int prev_socket_opens = 0;
+  static int prev_socket_closes = 0;
+  static cpu_tick_t prev_socket_lifetime=0;
+
+  // calcualte rates
+  open_rate = (double) (total_socket_opens - prev_socket_opens) * ticks_per_second / (now - then);
+  close_rate = (double) (total_socket_closes - prev_socket_closes) * ticks_per_second / (now - then);
+  avg_socket_lifetime = (total_socket_closes <= prev_socket_closes) ? 0 : 
+    (double)(total_socket_lifetime - prev_socket_lifetime) / 
+    (total_socket_closes - prev_socket_closes) / 
+    ticks_per_millisecond; 
+
+  // FIXME: this stuff doesn't seem to work well at present!
+  //
+  // we are overloaded if the incoming queue is increasing too fast
+  //if( open_rate > 1500  &&  open_rate > 1.05 * close_rate )
+  if( avg_socket_lifetime > 300 )
+    socket_overload = 1;
+  else 
+    socket_overload = 0;
+
+
+  // update history  
+  then = now;
+  prev_socket_opens = total_socket_opens;
+  prev_socket_closes = total_socket_closes;
+  prev_socket_lifetime = total_socket_lifetime;
+}
+
+
+static void check_vm_overload( cpu_tick_t now )
+{
+  static cpu_tick_t then = 0;
+
+  if( then == 0 ) {
+    then = now;
+    return;
+  }
+
+  // rotate the stats
+  if( 0 ) {
+    prev_proc_stats = proc_stats;
+    refresh_process_stats( &proc_stats );
+  }
+
+  prev_global_stats = global_stats;
+  refresh_global_stats( &global_stats );
+
+  // no more than 1 every 50 ms
+  // FIXME: tune this, based on disk activity?
+  //if(  (proc_stats.maj_flt - prev_proc_stats.maj_flt) > (now-then)/ticks_per_millisecond/50 ) 
+  //if( proc_stats.maj_flt > prev_proc_stats.maj_flt )
+  if( global_stats.pages_swapout > prev_global_stats.pages_swapout ||
+      global_stats.pages_swapin > prev_global_stats.pages_swapin )
+    vm_overload = 1;
+  else 
+    vm_overload = 0;
+
+}
+
+
+void check_overload( cpu_tick_t now )
+{
+  check_vm_overload( now );
+  check_socket_overload( now );
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// keep histograms of timing info
+//////////////////////////////////////////////////////////////////////
+
+#define HISTOGRAM_BUCKETS 60
+
+typedef struct {
+  int buckets[ HISTOGRAM_BUCKETS ];
+  int num;
+  double sum;
+  int overflow;
+  double overflow_sum;
+} histogram_t;
+
+histogram_t file_lifetime_hist;
+histogram_t socket_lifetime_hist;
+
+
+static inline void update_lifetime_histogram( histogram_t *hist, cpu_tick_t lifetime )
+{
+  register long millis = (long) (lifetime / ticks_per_millisecond);
+  register int idx = 0;
+
+  // the buckets get increasingly broader as we go up in time
+  while( millis > 10 )
+    idx += 10, millis /= 10;
+
+  if( idx + millis < HISTOGRAM_BUCKETS )
+    hist->buckets[ idx + millis ]++;
+  else 
+    hist->overflow++, hist->overflow_sum++;
+
+  hist->num++;
+  hist->sum += millis;
+}
+
+static void histogram_stats( histogram_t *hist, double *mean, double *dev )
+{
+  int i, j, multiplier;
+  double sum, m;
+
+  if(hist->num == 0)
+    *mean = *dev = 0;
+  
+  *mean = m = hist->sum / hist->num;
+
+  sum = (0.5 - m) * (0.5 - m) * hist->buckets[0];
+  multiplier = 1;
+  for(i=0; i<HISTOGRAM_BUCKETS/10; i++) {
+    for(j=1; j<10; j++) {
+      sum += hist->buckets[10*i + j] * ((0.5 + j) * multiplier - m) * ((0.5 + j) * multiplier - m);
+    }
+    multiplier *= 10;
+  }
+  sum += (hist->overflow_sum/hist->overflow - m) * (hist->overflow_sum/hist->overflow - m);
+
+  *dev = sqrt( sum / hist->num );
+}
+
+
+
+
+
+void thread_stats_open_socket() 
+{
+  if( current_thread )
+    current_thread->curr_stats.sockets++;
+  total_sockets_in_use++;
+  total_socket_opens++;
+}
+
+void thread_stats_close_socket(cpu_tick_t lifetime) 
+{
+  if( current_thread )
+    current_thread->curr_stats.sockets--;
+  total_sockets_in_use--;
+  total_socket_closes++;
+  total_socket_lifetime += lifetime;
+
+  update_lifetime_histogram( &socket_lifetime_hist, lifetime );
+}
+
+
+void thread_stats_open_file() 
+{
+  if( current_thread )
+    current_thread->curr_stats.files++;
+  total_files_in_use++;
+  total_file_opens++;
+}
+
+void thread_stats_close_file(cpu_tick_t lifetime) 
+{
+  if( current_thread )
+    current_thread->curr_stats.files--;
+  total_files_in_use--;
+  total_file_lifetime += lifetime;
+  total_file_closes++;
+
+  update_lifetime_histogram( &file_lifetime_hist, lifetime );
+}
+
+
+/**
+ * Check to see if running this thread would violate the current
+ * admission control policy.
+ *
+ * Return value: 1 to admit, 0 to reject.
+ *
+ * FIXME: this should really return a preference value, for how "good"
+ * this node is, based on current resource usage.  
+ **/
+int check_admission_control(bg_node_t *node)
+{
+  // FIXME: so far, just assume memory preassure comes from the heap
+  if( vm_overload  &&  node->stats.heap > 0 ) {
+    debug("rejecting node '%d' for vm usage\n",node->node_num);
+    return 0;
+  }
+
+  if( socket_overload  &&  node->stats.sockets > 0 ) {
+    debug("rejecting node '%d' for socket usage\n",node->node_num);
+    return 0;
+  }  
+  
+  return 1;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// output function
+//////////////////////////////////////////////////////////////////////
+
+// FIXME: it is probably faster to 
+
+void print_resource_stats(void)
+{
+  static double then = 0;
+  static iostats_t prev_sockio_stats, prev_diskio_stats;
+  double now = current_usecs();
+  double elapsed = (now - then) / 1e6;
+
+  output("resources:  %d file    %d sock      %lld KB heap     %lld KB stack\n", 
+         total_files_in_use, total_sockets_in_use, (total_heap_in_use / 1024), (total_stack_in_use / 1024));
+  output("limits:     %d max fds (%.0f%% used)     %lld KB max memory (%.0f%% used)\n", 
+         max_fds, (float)100*(total_files_in_use+total_sockets_in_use) / max_fds,
+         max_memory/1024, (float)100*(total_heap_in_use + total_stack_in_use) / max_memory);
+
+  // show edge timings
+  if( prev_edges_taken != total_edges_taken ) {
+    output("timings:  %d edges    %lld ticks avg.  (%lld usec)\n",
+           total_edges_taken - prev_edges_taken, 
+           (total_edge_cycles - prev_edge_cycles) / (total_edges_taken - prev_edges_taken), 
+           (total_edge_cycles - prev_edge_cycles) / (total_edges_taken - prev_edges_taken) / ticks_per_microsecond);
+    prev_edge_cycles = total_edge_cycles;
+    prev_edges_taken = total_edges_taken;
+  } else {
+    output("timings:  0 edges    0 ticks avg.  (0 usec)\n");
+  }
+
+
+  // show throughput
+  if( then > 0 ) {
+    double mean, dev;
+
+    output("sockios per sec:   %.0f req   %.0f comp   %.1f Mb read   %.1f Mb written  %.0f err\n",
+           (sockio_stats.requests - prev_sockio_stats.requests) / elapsed,
+           (sockio_stats.completions - prev_sockio_stats.completions) / elapsed,
+           8*(sockio_stats.bytes_read - prev_sockio_stats.bytes_read) / elapsed / (1024*1024),
+           8*(sockio_stats.bytes_written - prev_sockio_stats.bytes_written) / elapsed / (1024*1024),
+           (sockio_stats.errors - prev_sockio_stats.errors) / elapsed);
+
+    output("diskios per sec:   %.0f req   %.0f comp   %.1f MB read   %.1f MB written  %.0f err\n",
+           (diskio_stats.requests - prev_diskio_stats.requests) / elapsed,
+           (diskio_stats.completions - prev_diskio_stats.completions) / elapsed,
+           (diskio_stats.bytes_read - prev_diskio_stats.bytes_read) / elapsed / (1024*1024),
+           (diskio_stats.bytes_written - prev_diskio_stats.bytes_written) / elapsed / (1024*1024),
+           (diskio_stats.errors - prev_diskio_stats.errors) / elapsed);
+
+    histogram_stats( &socket_lifetime_hist, &mean, &dev );
+    output("socket lifetime:   %.1f ms avg   %.1f stddev\n", mean, dev);
+
+    histogram_stats( &file_lifetime_hist, &mean, &dev );
+    output("file lifetime:     %.1f ms avg   %.1f stddev\n", mean, dev);
+  }
+
+  then = now;
+  prev_sockio_stats = sockio_stats;
+  prev_diskio_stats = diskio_stats;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// initialization
+//////////////////////////////////////////////////////////////////////
+
+//static void resource_stats_init() __attribute__((constructor));
+static void resource_stats_init()
+{
+  // clear out IO histograms
+  bzero( &socket_lifetime_hist, sizeof(socket_lifetime_hist) );
+  bzero( &file_lifetime_hist, sizeof(file_lifetime_hist) );
+
+
+  // install malloc hooks
+  orig_free_hook = __free_hook;
+  orig_malloc_hook = __malloc_hook;
+  orig_realloc_hook = __realloc_hook;
+  __free_hook = capriccio_free_hook;
+  __malloc_hook = capriccio_malloc_hook;
+  __realloc_hook = capriccio_realloc_hook;
+
+
+  // heap
+
+  // this just returns the size of virtual memory - not very useful.  ;-(
+  {
+    struct rlimit r;
+    int ret;
+    ret = getrlimit(RLIMIT_RSS, &r);       assert( ret == 0 );
+    max_memory = r.rlim_cur;
+    r.rlim_cur = r.rlim_max;
+    if( setrlimit(RLIMIT_RSS, &r) == 0 )
+      max_memory = r.rlim_cur;
+  }
+
+  // read memory info from /proc/meminfo.  
+  // PORT: for similar things for non-Linux platforms, look at meminfo (http://meminfo.seva.net/)
+  {
+    char buf[1024];
+    int fd, ret;
+    char *p;
+
+    fd = syscall(SYS_open, "/proc/meminfo", O_RDONLY);    assert( fd >= 0);
+    ret = syscall(SYS_read, fd, buf, sizeof(buf)-1);      assert( ret > 0);
+    buf[ret] = '\0';
+    
+    p = strstr(buf,"MemTotal:");   assert( p );
+    p += strlen("MemTotal:");
+    while( *p == ' ' ) p++;
+    
+    max_memory = atoi( p ) * 1024;
+  }
+
+
+  // fds
+  {
+    struct rlimit r;
+    int ret;
+    ret = getrlimit(RLIMIT_NOFILE, &r);       assert( ret == 0 );
+    max_fds = r.rlim_cur;
+    r.rlim_cur = r.rlim_max;
+    if( setrlimit(RLIMIT_NOFILE, &r) == 0 )
+      max_fds = r.rlim_cur;
+  }
+
+  // baseline process stats
+  refresh_process_stats(&proc_stats);
+  prev_proc_stats = proc_stats;
+}
+
+
diff --git a/user/c3po/threads/resource_stats.h b/user/c3po/threads/resource_stats.h
new file mode 100644 (file)
index 0000000..81457c1
--- /dev/null
@@ -0,0 +1,58 @@
+
+
+#ifndef RESOURCE_STATS_H
+#define RESOURCE_STATS_H
+
+#include "clock.h"
+
+struct bg_node_st;
+// track the thread's resource
+typedef struct thread_resources_st {
+  int epoch;                // for validating versions when taking diffs
+  struct bg_node_st *node;  // the most recently seen graph node
+
+  unsigned int count;       // number of times the edge has been seen
+
+  long stack;               // total stack space
+  int files;                // number of files
+  int sockets;              // number of sockets
+  cpu_tick_t cpu_ticks;      // cpu time
+  cpu_tick_t real_ticks;     // real time, including time swapped out - only used if USE_PERFCTR is defined
+  long heap;                // bytes of heap used by the thread 
+  //int mutexes;              // locks held
+
+  // FIXME: performance counter stuff.
+  // icache misses
+  // dcache misses
+
+} thread_stats_t;
+
+
+extern inline void thread_stats_open_socket();
+extern inline void thread_stats_close_socket(cpu_tick_t lifetime);
+extern inline void thread_stats_open_file();
+extern inline void thread_stats_close_file(cpu_tick_t lifetime);
+
+extern inline int check_admission_control(struct bg_node_st *node);
+
+
+#define OVERLOAD_CHECK_INTERVAL (200*ticks_per_millisecond)
+extern inline void check_overload( cpu_tick_t now );
+
+
+/**
+ * track global edge timings
+ **/
+extern cpu_tick_t total_edge_cycles;
+extern int total_edges_taken;
+static inline void update_edge_totals(cpu_tick_t ticks)
+{
+  total_edge_cycles += ticks;
+  total_edges_taken++;
+}
+
+void print_resource_stats(void);
+
+
+
+#endif
diff --git a/user/c3po/threads/sched_global_rr.c b/user/c3po/threads/sched_global_rr.c
new file mode 100644 (file)
index 0000000..8464080
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ *
+ *  Basic Round Robin scheduling with single global queue
+ *
+ **/
+
+#include "threadlib_internal.h"
+#include "util.h"
+
+#ifndef DEBUG_sched_global_rr_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+
+static pointer_list_t *runlist = NULL;
+
+
+//////////////////////////////////////////////////////////////////////
+// generic stuff
+//////////////////////////////////////////////////////////////////////
+
+
+void sched_global_generic_init(void)
+{
+  runlist = new_pointer_list("sched_global_rr_runlist");
+}
+
+thread_t* sched_global_generic_next_thread(void) 
+{
+  thread_t *next = pl_remove_head(runlist);
+  if( next == (thread_t*) -1 ) 
+    return NULL;
+  else 
+    return next;
+}
+
+
+
+//////////////////////////////////////////////////////////////////////
+// Round robin scheduler
+//////////////////////////////////////////////////////////////////////
+
+strong_alias( sched_global_generic_init, sched_global_rr_init );
+strong_alias( sched_global_generic_next_thread, sched_global_rr_next_thread );
+
+void sched_global_rr_add_thread(thread_t *t)
+{
+  pl_add_tail(runlist, t);
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// LIFO scheduler
+//////////////////////////////////////////////////////////////////////
+
+strong_alias( sched_global_generic_init, sched_global_lifo_init );
+strong_alias( sched_global_generic_next_thread, sched_global_lifo_next_thread );
+
+void sched_global_lifo_add_thread(thread_t *t)
+{
+  pl_add_head(runlist, t);
+}
+
diff --git a/user/c3po/threads/sched_graph_priority.c b/user/c3po/threads/sched_graph_priority.c
new file mode 100644 (file)
index 0000000..723839b
--- /dev/null
@@ -0,0 +1,295 @@
+/**
+ *
+ *  Basic scheduler with different queues for each graph node
+ *
+ **/
+#include "threadlib_internal.h"
+#include "util.h"
+
+#ifndef DEBUG_graph_priority_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+
+// number of threads we'll run before checking priorities again
+static int threads_in_epoch;
+
+// flag, regarding whether or not we should include admission control
+int do_admission_control = 0;
+
+
+// sorted array of nodes.
+// FIXME: either auto-size this, or use w/ static node list from cil
+#define GP_NODELIST_SIZE 1024
+bg_node_t *gp_nodelist[ GP_NODELIST_SIZE ];
+int gp_nodelist_num = 1;
+
+// our current position in the node list
+static int gp_nodelist_pos = 1;
+
+
+void show_node_priorities() __attribute__((unused));
+
+
+//////////////////////////////////////////////////////////////////////
+// internal functions, to recalculate priorities, etc.
+//////////////////////////////////////////////////////////////////////
+
+
+static PRIntn edge_enumerator(PLHashEntry *e, PRIntn i, void *arg)
+{
+  bg_edge_t *edge = (bg_edge_t*) e->value;
+  thread_stats_t *stats = (thread_stats_t*) arg;
+  (void) i;
+
+  thread_latch( edge->latch );
+
+  stats->count += edge->stats.count;
+  stats->cpu_ticks += edge->stats.cpu_ticks;
+  stats->stack += edge->stats.stack;
+  stats->files += edge->stats.files;
+  stats->sockets += edge->stats.sockets;
+  stats->heap += edge->stats.heap;
+  //stats->mutexes += edge->stats.mutexes;
+
+  // FIXME: is this the right place for this?  Perhaps use exponential history instead
+  bzero(&edge->stats, sizeof(edge->stats));
+
+  thread_unlatch( edge->latch );
+
+  return 0;
+}
+
+
+static void calculate_node_score(bg_node_t *node)
+{
+  thread_stats_t stats;
+
+  // zero out the summary info
+  bzero(&stats, sizeof(thread_stats_t));
+
+  thread_latch( node->latch );
+
+  // collect totals from all edges
+  PL_HashTableEnumerateEntries( node->edgehash, edge_enumerator, &stats);
+
+  // just return, if no edges were taken
+  // FIXME: keep a count in the node, so we don't have to visit edges in order to find this out
+  if(stats.count <= 0) {
+    // allow the previous score to decay
+    node->sched.g_pri.score = ( node->sched.g_pri.score >> 2 );
+    thread_unlatch( node->latch );
+    return;
+  }
+
+  // divide to get per-iteration numbers
+  stats.stack /= stats.count;
+  stats.heap /= stats.count;
+  stats.cpu_ticks /= stats.count;
+  stats.files /= stats.count;
+  stats.sockets /= stats.count;
+
+  // do the weighting
+  // 
+  // FIXME: this should depend in part on which resources are scarce.
+  // For example, things that allocate memory or stack should be more
+  // heavily penalized when memory is tight.
+
+  //node->sched.g_pri.score = stats.stack * 4 + stats.cpu_ticks /100 + stats.fds * 100;
+  node->sched.g_pri.score = (stats.stack + stats.heap) + stats.files * 10000 + stats.sockets+1000;
+
+  thread_unlatch( node->latch );
+
+#ifdef DEBUG_sched_graph_priority_c
+  show_node_priorities();
+#endif
+
+  //debug("--- node %d:   %d  (%ld s, %lld T, %d fd)\n",
+  //      node->node_num, node->sched.graph_priority.score, stats.stack, stats.cpu_ticks, stats.count);
+
+}
+
+
+
+// adjust priorities for all nodes
+static void adjust_priorities(void)
+{
+  int i;
+
+  for( i=1; i<gp_nodelist_num; i++ ) {
+    calculate_node_score( gp_nodelist[i] );
+  }
+
+}
+
+
+// sort the node list, based on priority.  We use a bublesort, which
+// should be reasonable as long as the order doesn't change too often.
+static void sort_nodelist(void)
+{
+  int i,j;
+  bg_node_t *node;
+
+  // walk the list, and find things that are out of order
+  for( i=1; i<gp_nodelist_num-1; i++ ) {
+    // bubble up if out of order
+    if( gp_nodelist[i+1]->sched.g_pri.score < gp_nodelist[i]->sched.g_pri.score ) {
+      node = gp_nodelist[i+1];
+
+      j = i;
+      do {
+        gp_nodelist[j+1] = gp_nodelist[j];
+        gp_nodelist[j+1]->sched.g_pri.listpos = j+1;
+        
+        j--;
+      } while( j > 0  &&  node->sched.g_pri.score < gp_nodelist[j]->sched.g_pri.score );
+
+      gp_nodelist[j+1] = node;
+      gp_nodelist[j+1]->sched.g_pri.listpos = j+1;
+    }
+  }
+  
+
+  // sanity check
+#if 0
+  assert( !gp_nodelist[0] );
+  assert( !gp_nodelist[ gp_nodelist_num ] );
+  for( i=1; i<gp_nodelist_num-1; i++ ) {
+    assert(gp_nodelist[i]->node_num != gp_nodelist[i+1]->node_num);
+    assert(gp_nodelist[i]->sched.g_pri.score <= gp_nodelist[i+1]->sched.g_pri.score);
+  }
+#endif
+
+}
+
+
+
+
+//////////////////////////////////////////////////////////////////////
+// externally-visible functions
+//////////////////////////////////////////////////////////////////////
+
+
+// The algorithm for picking the next thread is as follows:  
+//   - find a thread that is likely to generally release resources by walking the list of nodes
+//   - skip nodes that allocate something that is too close to maxed out.
+
+// FIXME: current algorithm will lead to starvation if the highest
+// priority stage has one or more threads that just yield, and don't
+// block.  ;-(
+
+thread_t* sched_graph_priority_next_thread(void) 
+{
+  thread_t* t=NULL;
+
+  // some debug output
+#ifdef DEBUG_sched_graph_priority_c
+  {
+    static unsigned long long then = 0;
+    unsigned long long now = current_usecs();
+    if( now - then > 1e6 ) {
+      show_node_priorities();
+      then = now;
+    }
+  }    
+#endif
+
+  threads_in_epoch--;
+
+  // fix up the node priorities if it's been a while
+  if( threads_in_epoch <= 0 ) {
+    adjust_priorities();
+    sort_nodelist();
+    gp_nodelist_pos = 1;
+
+    // FIXME: choose this based on (a) the number of runnable threads
+    // and (b) the volatility of the priorities.  If things haven't
+    // changed recently, assume that we're OK for a bit longer than if
+    // they are still changing.
+    threads_in_epoch = 1;
+  }
+
+  
+  // look for a good node
+  while( gp_nodelist_pos < gp_nodelist_num ) {
+    bg_node_t *node = gp_nodelist[ gp_nodelist_pos ];
+
+    // skip nodes that would force allocation of scarce resources
+    //if( do_admission_control )
+    //  ;// FIXME: add this
+
+    // get the next thread from the current node
+    t = pl_remove_head( node->sched.g_pri.runlist );
+    if( valid_thread(t) ) 
+      break;
+    else 
+      gp_nodelist_pos++;
+  } 
+
+  // sanity check.  As long as we aren't doing admission control, we should always find a good t
+  //if( !do_admission_control )
+  //  assert( valid_thread(t) );
+
+
+  return t;
+}
+
+
+void sched_graph_priority_init(void)
+{
+
+  // pick something relatively small, so we can adjust this soon
+  threads_in_epoch = 1;
+
+  bzero(gp_nodelist, sizeof(gp_nodelist));
+}
+
+
+void sched_graph_priority_add_thread(thread_t *t)
+{
+  bg_node_t *node = t->curr_stats.node;
+
+  // add nodes that we have't seen before to the end of our list
+  if( node->sched.g_pri.listpos <= 0 ) {
+    node->sched.g_pri.listpos = gp_nodelist_num;
+    gp_nodelist[ gp_nodelist_num ] = node;
+    gp_nodelist_num++;
+    assert( gp_nodelist_num < GP_NODELIST_SIZE );  // make sure we aren't out of space
+
+    // allocate the runlist, if necessary
+    assert( node->sched.g_pri.runlist == NULL );
+    node->sched.g_pri.runlist = new_pointer_list("node_runlist");
+    node->sched.g_pri.score = 0;
+  }
+
+  // adjust our position in the node list, if this node has higher
+  // priority.  A position of 0 indicates that the node hasn't been
+  // seen yet.
+  // 
+  // fixme: think about this more --- this could lead to starvation.
+  // Also, is it really necessary?
+  //
+  //else if( node->sched.g_pri.listpos < gp_nodelist_pos ) {
+  //  gp_nodelist_pos = node->sched.g_pri.listpos;
+  //}
+
+  // add the node to the runlist
+  pl_add_tail(node->sched.g_pri.runlist, t);
+}
+
+
+void show_node_priorities()
+{
+  int i;
+
+  output("Node priority list:\n");
+  for( i=1; i<gp_nodelist_num; i++ ) {
+    output("  %15lld  node %d\n", gp_nodelist[i]->sched.g_pri.score, gp_nodelist[i]->node_num);
+  }
+  output("\n\n");
+}
+
+
diff --git a/user/c3po/threads/sched_graph_rr.c b/user/c3po/threads/sched_graph_rr.c
new file mode 100644 (file)
index 0000000..15bac0e
--- /dev/null
@@ -0,0 +1,226 @@
+/**
+ *
+ *  Basic scheduler with different queues for each graph node
+ *
+ **/
+#include "threadlib_internal.h"
+#include "util.h"
+
+#ifndef DEBUG_graph_rr_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+// global scheduler latch
+static latch_t scheduler_latch = LATCH_INITIALIZER_UNLOCKED;
+
+
+//////////////////////////////////////////////////////////////////////
+// generic functions, used by many variations of the stage scheduler 
+//////////////////////////////////////////////////////////////////////
+void sched_graph_generic_init(void)
+{
+}
+
+
+void sched_graph_generic_add_thread(thread_t *t)
+{
+  bg_node_t *node = t->curr_stats.node;
+
+  thread_latch( scheduler_latch );
+
+  // allocate the runlist, if necessary
+  if( node->sched.g_rr.runlist == NULL ) {
+    node->sched.g_rr.runlist = new_pointer_list("node_runlist");
+  }
+
+  // add the node to the runlist
+  pl_add_tail(node->sched.g_rr.runlist, t);
+
+  thread_unlatch( scheduler_latch );
+}
+
+
+
+//////////////////////////////////////////////////////////////////////
+// RR among stages - moving up in stage number
+//////////////////////////////////////////////////////////////////////
+
+strong_alias( sched_graph_generic_init, sched_graph_rr_init );
+strong_alias( sched_graph_generic_add_thread, sched_graph_rr_add_thread );
+
+// do RR among stages
+thread_t* sched_graph_rr_next_thread(void) 
+{
+  static int curr_stage = 0;
+  int i;
+  thread_t *t = NULL;
+
+  thread_latch( scheduler_latch );
+
+  // find the next node that has a runnable thread
+  i = curr_stage;
+  do {
+    //if( bg_nodelist[i]->sched.g_rr.runlist ) 
+    if( bg_nodelist[i]->sched.g_rr.runlist  &&  check_admission_control(bg_nodelist[i]) )
+      t = pl_remove_head( bg_nodelist[i]->sched.g_rr.runlist );
+    i = (i + 1) % bg_num_nodes;
+  } while( !valid_thread(t)  &&  i != curr_stage );
+
+  curr_stage = i;
+
+  thread_unlatch( scheduler_latch );
+
+  return t;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// RR among stages - moving down in stage number
+//////////////////////////////////////////////////////////////////////
+
+strong_alias( sched_graph_generic_init, sched_graph_rr_down_init );
+strong_alias( sched_graph_generic_add_thread, sched_graph_rr_down_add_thread );
+
+// do RR among stages
+thread_t* sched_graph_rr_down_next_thread(void) 
+{
+  static int curr_stage = 0;
+  int i;
+  thread_t *t = NULL;
+
+  thread_latch( scheduler_latch );
+
+  // find the next node that has a runnable thread
+  i = curr_stage;
+  do {
+    i = (i + bg_num_nodes - 1) % bg_num_nodes;
+    if( bg_nodelist[i]  &&  bg_nodelist[i]->sched.g_rr.runlist )
+      t = pl_remove_head( bg_nodelist[i]->sched.g_rr.runlist );
+  } while( !valid_thread(t) && i != curr_stage );
+  curr_stage = i;
+
+  // this happens if the original curr_stage was the only one w/ runnable thraeds
+  if( !valid_thread(t)  &&  bg_nodelist[i]  &&  bg_nodelist[i]->sched.g_rr.runlist )
+    t = pl_remove_head( bg_nodelist[i]->sched.g_rr.runlist );
+
+  thread_unlatch( scheduler_latch );
+
+  return t;
+}
+
+
+
+
+
+//////////////////////////////////////////////////////////////////////
+// A batching scheduler.
+//////////////////////////////////////////////////////////////////////
+
+strong_alias( sched_graph_generic_init, sched_graph_batch_init );
+strong_alias( sched_graph_generic_add_thread, sched_graph_batch_add_thread );
+
+// process everything from the current stage, before moving on to the next
+thread_t* sched_graph_batch_next_thread(void) 
+{
+  static int curr_stage = 0;
+  static int left_in_stage = 0;
+  int start_stage, i;
+  thread_t *t = NULL;
+
+  thread_latch( scheduler_latch );
+
+  // update the stage, if we have reached the max
+  if( left_in_stage <= 0 ) {
+    start_stage = curr_stage;
+    do {
+      curr_stage++;  curr_stage = curr_stage%bg_num_nodes;
+    } while( curr_stage!=start_stage && bg_nodelist[curr_stage]->sched.g_rr.runlist == NULL );
+    
+    // allow at most 1.5x the current number of threads
+    left_in_stage = pl_size( bg_nodelist[curr_stage]->sched.g_rr.runlist ) * 3 / 2;
+  }
+
+  // check the current stage for runnable threads
+  if( bg_nodelist[curr_stage]  &&  bg_nodelist[curr_stage]->sched.g_rr.runlist )
+    t = pl_remove_head( bg_nodelist[curr_stage]->sched.g_rr.runlist );
+  if( valid_thread(t) ) {
+    thread_unlatch( scheduler_latch );
+    left_in_stage--;
+    return t;
+  }
+
+  // find the next node that has a runnable thread
+  i = curr_stage;
+  do {
+    i = (i + 1) % bg_num_nodes;
+    if( bg_nodelist[i]  &&  bg_nodelist[i]->sched.g_rr.runlist )
+      t = pl_remove_head( bg_nodelist[i]->sched.g_rr.runlist );
+  } while( !valid_thread(t) && i != curr_stage );
+
+  if( i != curr_stage ) {
+    curr_stage = i;
+    left_in_stage = pl_size( bg_nodelist[curr_stage]->sched.g_rr.runlist ) * 3 / 2;
+  }
+
+  thread_unlatch( scheduler_latch );
+
+  return t;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// pick threads from highest number first, but with admission control 
+//////////////////////////////////////////////////////////////////////
+
+strong_alias( sched_graph_generic_init, sched_graph_highnum_init );
+strong_alias( sched_graph_generic_add_thread, sched_graph_highnum_add_thread );
+
+// find the first available thread from the highest numbered node
+thread_t* sched_graph_highnum_next_thread(void) 
+{
+  static int iterations = 100;
+  static int rr_stage = 0;
+  thread_t *t=NULL;
+  int i;
+
+  thread_latch( scheduler_latch );
+
+  // every so often, give other stages a chance to run.
+  if( iterations <= 0 ) {
+    iterations = 100;
+    i = rr_stage;
+    do {
+      if( bg_nodelist[i]->sched.g_rr.runlist &&
+          check_admission_control(bg_nodelist[i]) ) {
+        t = pl_remove_head( bg_nodelist[i]->sched.g_rr.runlist );
+      }
+      if( valid_thread(t) ) break;
+      i = (i+1) % bg_num_nodes;
+    } while( i != rr_stage );
+    rr_stage = i;
+  } 
+  
+  // otherwise, do the normal algorithm
+  else {
+    i = bg_num_nodes-1;
+    do {
+      if( bg_nodelist[i]->sched.g_rr.runlist  &&
+          check_admission_control(bg_nodelist[i]) ) {
+        t = pl_remove_head( bg_nodelist[i]->sched.g_rr.runlist );
+      }
+      i--;
+    } while( i >= 0  &&  !valid_thread(t) );
+
+    iterations--;
+  }
+
+  thread_unlatch( scheduler_latch );
+
+  return t;
+}
+
+
+
diff --git a/user/c3po/threads/semaphore.h b/user/c3po/threads/semaphore.h
new file mode 100644 (file)
index 0000000..e5fcf42
--- /dev/null
@@ -0,0 +1,79 @@
+/* Dummy stub for unimplemented thread semaphere */
+
+
+#ifndef _SEMAPHORE_H
+#define _SEMAPHORE_H    1
+
+#include <pthread.h>
+
+#include <features.h>
+#include <sys/types.h>
+#ifdef __USE_XOPEN2K
+# define __need_timespec
+# include <time.h>
+#endif
+
+#ifndef _PTHREAD_DESCR_DEFINED
+/* Thread descriptors.  Needed for `sem_t' definition.  */
+typedef struct _pthread_descr_struct *_pthread_descr;
+# define _PTHREAD_DESCR_DEFINED
+#endif
+
+/* System specific semaphore definition.  */
+typedef struct
+{
+//  struct _pthread_fastlock __sem_lock;
+//  int __sem_value;
+//  _pthread_descr __sem_waiting;
+} sem_t;
+
+
+
+/* Value returned if `sem_open' failed.  */
+#define SEM_FAILED     ((sem_t *) 0)
+
+/* Maximum value the semaphore can have.  */
+#define SEM_VALUE_MAX  ((int) ((~0u) >> 1))
+
+
+__BEGIN_DECLS
+
+/* Initialize semaphore object SEM to VALUE.  If PSHARED then share it
+   with other processes.  */
+extern int sem_init (sem_t *__sem, int __pshared, unsigned int __value) __THROW;
+
+/* Free resources associated with semaphore object SEM.  */
+extern int sem_destroy (sem_t *__sem) __THROW;
+
+/* Open a named semaphore NAME with open flaot OFLAG.  */
+extern sem_t *sem_open (__const char *__name, int __oflag, ...) __THROW;
+
+/* Close descriptor for named semaphore SEM.  */
+extern int sem_close (sem_t *__sem) __THROW;
+
+/* Remove named semaphore NAME.  */
+extern int sem_unlink (__const char *__name) __THROW;
+
+/* Wait for SEM being posted.  */
+extern int sem_wait (sem_t *__sem) __THROW;
+
+#ifdef __USE_XOPEN2K
+/* Similar to `sem_wait' but wait only until ABSTIME.  */
+extern int sem_timedwait (sem_t *__restrict __sem,
+                         __const struct timespec *__restrict __abstime)
+     __THROW;
+#endif
+
+/* Test whether SEM is posted.  */
+extern int sem_trywait (sem_t *__sem) __THROW;
+
+/* Post SEM.  */
+extern int sem_post (sem_t *__sem) __THROW;
+
+/* Get current value of SEM and store it in *SVAL.  */
+extern int sem_getvalue (sem_t *__restrict __sem, int *__restrict __sval)
+     __THROW;
+
+__END_DECLS
+
+#endif  /* semaphore.h */
diff --git a/user/c3po/threads/threadlib.c b/user/c3po/threads/threadlib.c
new file mode 100644 (file)
index 0000000..dafe4ec
--- /dev/null
@@ -0,0 +1,1458 @@
+#include "coro.h"
+#include "threadlib_internal.h"
+#include "util.h"
+#include "config.h"
+#include "blocking_graph.h"
+#include "stacklink.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <execinfo.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <ros/syscall.h>
+#include <sys/syscall.h>
+
+// comment out, to enable debugging in this file
+#ifndef DEBUG_threadlib_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+// FIXME: this doesn't work properly yet in all cases at program exit.
+// The performance does seem slightly better, however.
+//#define NO_SCHEDULER_THREAD 1
+
+
+// sanity check THREAD_KEY_MAX and size of key_data_count
+//#if THREAD_KEY_MAX >> (sizeof(thread_t.key_data_count)-1) != 1
+//#error not enough space in thread_t.key_data_count
+//#endif
+
+
+// The PID of the main process.  This is useful to prevent errors when
+// forked children call exit_func().
+//
+// FIXME: unfortunately, this still doesn't seem to work, as AIO gets
+// hosed if a forked child exits before the main thread.  This may be
+// a bug w/ AIO, however.
+static pid_t capriccio_main_pid;
+
+// flag to indicate whether or not to override syscalls.  We don't
+// override durring initialization, in order to avoid loops w/
+// libraries such as perfctr that must do IO.  We also don't override
+// syscalls after SIG_ABRT, so we can get proper core dumps.
+int cap_override_rw = 1;
+
+// flag so the signal stuff knows that the current thread is running in the scheduler
+int in_scheduler = 0;
+
+void **start_node_addrs = NULL;
+int *start_node_stacks = NULL;
+
+#ifdef USE_NIO
+#define SCHEDULER_STACK_SIZE 1024*128
+#else
+#define SCHEDULER_STACK_SIZE 1024
+#endif
+
+static thread_t* main_thread=NULL;
+#ifndef NO_SCHEDULER_THREAD
+thread_t* scheduler_thread=NULL;
+#endif
+thread_t* current_thread=NULL;
+static int current_thread_exited = 0;
+
+// a list of all threads, used by sig_handler()
+pointer_list_t *threadlist = NULL;
+
+static int num_daemon_threads = 0;
+static int num_suspended_threads = 0;
+int num_runnable_threads = 0;
+static int num_zombie_threads = 0;
+#if OPTIMIZE < 1
+#define sanity_check_threadcounts() {\
+   assert(num_daemon_threads >= 0); \
+   assert(num_suspended_threads >= 0); \
+   assert(num_runnable_threads >= 0); \
+   assert(num_zombie_threads >= 0); \
+   assert(num_runnable_threads + num_suspended_threads + num_zombie_threads == pl_size(threadlist)); \
+}
+#define sanity_check_io_stats() {\
+   assert(sockio_stats.requests == sockio_stats.active + sockio_stats.completions + sockio_stats.errors); \
+   assert(diskio_stats.requests == diskio_stats.active + diskio_stats.completions + diskio_stats.errors); \
+}
+#else
+#define sanity_check_threadcounts()
+#define sanity_check_io_stats()
+#endif
+
+// modular scheduling functions
+static void (*sched_init)(void); 
+static void (*sched_add_thread)(thread_t *t); 
+static thread_t* (*sched_next_thread)(void); 
+
+// flags regarding the state of main_thread
+int exit_whole_program = 0;
+static int exit_func_done = 0;
+static int main_exited = 0;
+
+
+// sleep queue, points to thread_t that's sleeping
+static pointer_list_t *sleepq = NULL;
+static unsigned long long last_check_time = 0;       // when is the sleep time calculated from
+static unsigned long long max_sleep_time=0;          // length of the whole sleep queue, in microseconds
+static unsigned long long first_wake_usecs=0;        // wall clock time of the wake time of the first sleeping thread
+
+inline static void free_thread( thread_t *t );
+inline static void sleepq_check();
+inline static void sleepq_add_thread(thread_t *t, unsigned long long timeout);
+inline static void sleepq_remove_thread(thread_t *t);
+
+
+/**
+ * set the IO polling function.  Used by the aio routines.  Shouldn't
+ * be used elsewhere.
+ **/
+// FIXME: it's ugly to break the namespaces up like this, but it is
+// still nice to be able to test the threads package independant of
+// the IO overriding stuff.
+static void (*io_polling_func)(long long usecs);
+
+void set_io_polling_func(void (*func)(long long))
+{
+  assert( !io_polling_func );
+  io_polling_func = func;
+}
+
+unsigned long long start_usec;
+
+static cap_timer_t scheduler_timer;
+static cap_timer_t main_timer;
+static cap_timer_t app_timer;
+
+/**
+ * Main scheduling loop
+ **/
+static void* do_scheduler(void *arg)
+{
+  static cpu_tick_t next_poll=0, next_overload_check=0, next_info_dump=0, next_graph_stats=0, now=0;
+  static int pollcount=1000;
+  static int init_done = 0;
+
+  (void) arg;  // suppress GCC "unused parameter" warning
+
+  in_scheduler = 1;
+
+  // make sure we start out by saving edge stats for a while
+  if( !init_done ) {
+    init_done = 1;
+    if (conf_no_statcollect) 
+      bg_save_stats = 0;
+    else
+      bg_save_stats = 1;
+    
+    GET_REAL_CPU_TICKS( now );
+    next_graph_stats = now + 1 * ticks_per_second;
+    
+    start_timer(&scheduler_timer);
+  }
+
+  while( 1 ) {
+
+    //current_thread = scheduler_thread;
+    sanity_check_threadcounts();
+    sanity_check_io_stats();
+
+    // wake up threads that have timeouts
+    sleepq_check(0);   
+    sanity_check_threadcounts();
+
+    // break out if there are only daemon threads
+    if(unlikely (num_suspended_threads == 0  &&  num_runnable_threads == num_daemon_threads)) {
+      // dump the blocking graph
+      if( exit_func_done && conf_dump_blocking_graph ) {
+        tdebug("dumping blocking graph from do_scheduler()\n");
+        dump_blocking_graph(); 
+      }
+        
+      // go back to mainthread, which should now be in exit_func()
+      current_thread = main_thread;
+      in_scheduler = 0;
+      co_call(main_thread->coro, NULL);
+      in_scheduler = 1;
+
+      if( unlikely(current_thread_exited) ) {     // free memory from deleted threads
+        current_thread_exited=0;
+        if (current_thread != main_thread) // main_thread is needed for whole program exit
+          free_thread( current_thread );
+      }
+        
+      return NULL;
+    }
+
+
+    // cheesy way of handling things with timing requirements
+    {
+      GET_REAL_CPU_TICKS( now );
+        
+      // toggle stats collection
+      if( conf_no_statcollect == 0 && next_graph_stats < now ) {
+        bg_save_stats = 1 - bg_save_stats;
+
+        if( bg_save_stats ) { 
+          // record stats for 100 ms
+          next_graph_stats = now + 100 * ticks_per_millisecond;
+            
+          // update the stats epoch, to allow proper handling of the first data items
+          bg_stats_epoch++;
+        }            
+        else {
+          // avoid stats for 2000 ms
+          next_graph_stats = now + 2000 * ticks_per_millisecond;
+        }
+        //output(" *********************** graph stats %s\n", bg_save_stats ? "ON" : "OFF" );
+      }
+        
+      // resource utalization
+      //if( unlikely (next_overload_check < now) ) {
+      //  check_overload( now );
+      //  next_overload_check = now + OVERLOAD_CHECK_INTERVAL;
+      //}
+
+      // poll
+      if( likely( (int)io_polling_func) ) {
+        if( num_runnable_threads==0  ||  --pollcount <= 0  ||  next_poll < now ) {
+          //if( num_runnable_threads==0 ) {
+          // poll
+          long long timeout = 0;
+
+          if( num_runnable_threads==0 ) {
+            if (first_wake_usecs == 0) {
+              timeout = -1;
+            } else {
+              // there are threads in the sleep queue
+              // so poll for i/o till at most that time
+              unsigned long long now;
+              now = current_usecs();
+             tdebug ("first_wake: %lld, now: %lld\n", first_wake_usecs, now);
+              if (first_wake_usecs > now)
+                timeout = first_wake_usecs - now;
+            }
+          }
+
+          stop_timer(&scheduler_timer);
+          //if( timeout != -1 )  output("timeout is not zero\n");
+          io_polling_func( timeout ); // allow blocking
+          start_timer(&scheduler_timer);
+          sanity_check_threadcounts();
+
+
+#ifndef USE_NIO
+          // sleep for a bit, if there was nothing to do
+          // FIXME: let the IO functions block instead??
+          if( num_runnable_threads == 0 ) {
+            syscall(SYS_yield);
+          }
+#endif
+
+          // vary the poll rate depending on the workload
+#if 0
+          if( num_runnable_threads < 5 ) {
+            next_poll = now + (10*ticks_per_millisecond);
+            pollcount = 1000;
+          } else if( num_runnable_threads < 10 ) {
+            next_poll = now + (50*ticks_per_millisecond);
+            pollcount = 2000;
+          } else {
+            next_poll = now + (100*ticks_per_millisecond);
+            pollcount = 3000;
+          }
+#else
+          next_poll = now + (ticks_per_millisecond << 13);
+         pollcount = 10000;
+
+#endif
+        }
+      }
+
+      // debug stats
+      if( 0 && next_info_dump < now ) {
+        dump_debug_info();
+        next_info_dump = now + 5 * ticks_per_second;
+      }
+
+    }
+
+    // get the head of the run list
+    current_thread = sched_next_thread();
+
+    // scheduler gave an invlid even though there are runnable
+    // threads.  This indicates that every runnable thead is likely to
+    // require use of an overloaded resource. 
+    if( !valid_thread(current_thread) ) {
+      pollcount = 0;
+      continue;
+    }
+
+    // barf, if the returned thread is still on the sleep queue
+    assert( current_thread->sleep == -1 );
+
+    tdebug("running TID %d (%s)\n", current_thread->tid, current_thread->name ? current_thread->name : "no name");
+
+    sanity_check_threadcounts();
+
+
+    // call thread
+    stop_timer(&scheduler_timer);
+    start_timer(&app_timer);
+    in_scheduler = 0;
+    co_call(current_thread->coro, NULL);
+    in_scheduler = 1;
+    stop_timer(&app_timer);
+    start_timer(&scheduler_timer);
+
+    if( unlikely(current_thread_exited) ) {     // free memory from deleted threads
+      current_thread_exited=0;
+      if (current_thread != main_thread) // main_thread is needed for whole program exit
+        free_thread( current_thread );
+    }
+
+#ifdef NO_SCHEDULER_THREAD
+    return NULL;
+#endif
+  }
+
+  return NULL;
+}
+
+
+
+static int get_stack_size_kb_log2(void *func)
+{
+  int result = conf_new_stack_kb_log2;
+  if (start_node_addrs != NULL) {
+    int i = 0;
+    while (start_node_addrs[i] != NULL && start_node_addrs[i] != func) {
+      i++;
+    }
+    if (start_node_addrs[i] == func) {
+      result = start_node_stacks[i];
+    } else {
+      fatal("Couldn't find stack size for thread entry point %p\n", func);
+    }
+  }
+  return result;
+}
+
+
+/**
+ * Wrapper function for new threads.  This allows us to clean up
+ * correctly if a thread exits without calling thread_exit().
+ **/
+static void* new_thread_wrapper(void *arg)
+{
+  void *ret;
+  (void) arg;
+
+  // set up initial stats
+  current_thread->curr_stats.files = 0;
+  current_thread->curr_stats.sockets = 0;
+  current_thread->curr_stats.heap = 0;
+  bg_set_current_stats( &current_thread->curr_stats );
+  current_thread->prev_stats = current_thread->curr_stats;
+
+  // set up stack limit for new thread
+  stack_bottom = current_thread->stack_bottom;
+  stack_fingerprint = current_thread->stack_fingerprint;
+
+  // start the thread
+  tdebug("Initial arg = %p\n", current_thread->initial_arg);
+  ret = current_thread->initial_func(current_thread->initial_arg);
+  
+  // call thread_exit() to do the cleanup
+  thread_exit(ret);
+  
+  return NULL;
+}
+
+static thread_t* new_thread(char *name, void* (*func)(void *), void *arg, thread_attr_t attr)
+{
+  static unsigned max_tid = 1;
+  thread_t *t = malloc( sizeof(thread_t) );
+  int stack_size_kb_log2 = get_stack_size_kb_log2(func);
+  void *stack = stack_get_chunk( stack_size_kb_log2 );
+  int stack_size = 1 << (stack_size_kb_log2 + 10);
+
+  if( !t || !stack ) {
+    if (t) free(t);
+    if (stack) stack_return_chunk(stack_size_kb_log2, stack);
+    return NULL;
+  }
+
+  bzero(t, sizeof(thread_t));
+
+  t->coro = co_create(new_thread_wrapper, stack - stack_size, stack_size);
+  t->stack = stack;
+  t->stack_size_kb_log2 = stack_size_kb_log2;
+  t->stack_bottom = stack - stack_size;
+  t->stack_fingerprint = 0;
+  t->name = (name ? name : "noname"); 
+  t->initial_func = func;
+  t->initial_arg = arg;
+  t->joinable = 1;
+  t->tid = max_tid++;
+  t->sleep = -1;
+
+  if( attr ) {
+    t->joinable = attr->joinable;
+    t->daemon = attr->daemon;
+    if(t->daemon)
+      num_daemon_threads++;
+  }
+
+  // FIXME: somehow track the parent thread, for stats creation?
+
+  // make sure the thread has a valid node before we add it to the scheduling list
+  bg_dummy_node->num_here++;
+  t->curr_stats.node = bg_dummy_node;
+
+  pl_add_tail(threadlist, t);
+
+  num_runnable_threads++;
+  sched_add_thread(t);
+  sanity_check_threadcounts();
+
+  return t;
+}
+
+
+/**
+ * Free the memory associated with the given thread.
+ **/
+inline static void free_thread( thread_t *t )
+{
+  static int iter = -1;
+  iter++;
+  pl_remove_pointer(threadlist, t);
+
+  assert(t->state == ZOMBIE);
+  t->state = GHOST;  // just for good measure
+  num_zombie_threads--;
+
+  if( t != main_thread ) {
+    co_delete( t->coro );
+    stack_return_chunk( t->stack_size_kb_log2, t->stack );
+    free( t );
+  }
+}
+
+/*
+
+void exit(int code) {
+       fprintf (stderr, "exit called!");
+       exit_whole_program = 1;
+    syscall(SYS_exit, code);
+faint: goto faint;
+}
+*/
+
+#ifndef NO_ATEXIT
+/**
+ * give control back to the scheduler after main() exits.  This allows
+ * remaining threads to continue running.
+ * FIXME: we don't know whether user explicit calls exit() or main() normally returns
+ * in the previous case, we should exit immediately, while in the later, we should 
+ * join other threads.
+ * Overriding exit() does not work because normal returning from
+ * main() also calls exit().
+ **/
+static void exit_func(void)
+{
+  // don't do anything if we're in a forked child process
+  if( getpid() != capriccio_main_pid )
+    return;
+
+  exit_func_done = 1;
+  main_exited = 1;
+  if( !exit_whole_program )
+       // this will block until all other threads finish
+    thread_exit(NULL);
+
+  // dump the blocking graph before we exit
+  if( conf_dump_blocking_graph ) {
+    tdebug("dumping blocking graph from exit_func()\n");
+    dump_blocking_graph(); 
+  }
+
+  // FIXME: make sure to kill cloned children
+
+  if( conf_dump_timing_info ) {
+    if( main_timer.running )   stop_timer(&main_timer);
+    if( scheduler_timer.running )   stop_timer(&scheduler_timer);
+    if( app_timer.running )   stop_timer(&app_timer);
+    print_timers();
+  }
+}
+#endif
+
+static char *THREAD_STATES[] = {"RUNNABLE", "SUSPENDED", "ZOMBIE", "GHOST"};
+
+
+
+// dump status to stderr 
+void dump_debug_info()
+{
+  output("\n\n-- Capriccio Status Dump --\n");
+  output("Current thread %d  (%s)\n", 
+         current_thread ? (int)thread_tid(current_thread) : -1,
+         (current_thread && current_thread->name) ? current_thread->name : "noname");
+  output("threads:    %d runnable    %d suspended    %d daemon\n", 
+         num_runnable_threads, num_suspended_threads, num_daemon_threads);
+
+  print_resource_stats();
+
+  stack_report_usage_stats();
+  stack_report_call_stats();
+
+  {
+    int i;
+    output("thread locations:");
+    for(i=0; i<bg_num_nodes; i++) {
+      bg_node_t *node = bg_nodelist[i];
+      if(node->num_here)
+        output("  %d:%d", node->node_num, node->num_here);
+    }
+    output("\n");
+  }
+
+  output("\n\n");
+}
+void dump_thread_state()
+{
+  void *bt[100];
+  int count, i;
+  linked_list_entry_t *e;
+
+  output("\n-- State of all threads --\n");
+
+  e = ll_view_head(threadlist);
+  while (e) {
+    char sleepstr[80];
+    thread_t *t = (thread_t *) pl_get_pointer(e);
+
+    output("Thread %2d (%s)  %s    %d file   %d sock    %ld KB heap   ? KB stack    %s%s%s\n", 
+           thread_tid(t), t->name, THREAD_STATES[t->state], 
+           t->curr_stats.files, 
+           t->curr_stats.sockets, 
+           t->curr_stats.heap / 1024,
+           //(long)-1, // FIXME: add total stack numbers after incorporating Jeremy's code
+           t->joinable ? "joinable  " : "",
+           t->daemon ? "daemon  " : "",
+           t->sleep > 0 ? (sprintf(sleepstr,"sleep=%lld  ",t->sleep), sleepstr) : "");
+
+    if( conf_show_thread_stacks ) {
+      count = co_backtrace(t->coro, bt, 100);
+      if (count == 100)
+        output("WARN: only output first 100 stack frames.\n");
+      
+#if (0)
+      {
+        void **frame;
+        frame = bt;
+        while( count-- )
+          output("    %p\n",*(frame++));
+      }
+#else
+      {
+        // NOTE: backtrace_symbols_fd causes a loop w/ our IO functions.
+        char **p = backtrace_symbols(bt, count);
+        for (i = 0; i < count; i++)
+          output("   %s\n", *(p+i));
+        free(p);
+      }
+#endif
+      
+      output("\n");
+    }
+      
+    e = ll_view_next(threadlist, e);
+  }
+
+  return;
+}
+
+
+
+
+/**
+ * decide on the scheduler to use, based on the CAPRICCIO_SCHEDULER
+ * environment variable.  This function should only be called once,
+ * durring the initialization of the thread runtime.
+ **/
+#define SET_SCHEDULER(s) do {\
+  sched_init = sched_##s##_init; \
+  sched_next_thread = sched_##s##_next_thread; \
+  sched_add_thread = sched_##s##_add_thread; \
+  if( !conf_no_init_messages ) \
+    output("CAPRICCIO_SCHEDULER=%s\n",__STRING(s)); \
+} while(0)
+
+static void pick_scheduler()
+{
+  char *sched = getenv("CAPRICCIO_SCHEDULER");
+
+  if(sched == NULL) 
+    SET_SCHEDULER( global_rr ); // defaults
+  else if( !strcasecmp(sched,"global_rr") )
+    SET_SCHEDULER( global_rr );
+  else if( !strcasecmp(sched,"global_lifo") )
+    SET_SCHEDULER( global_lifo );
+  else if( !strcasecmp(sched,"graph_rr") )
+    SET_SCHEDULER( graph_rr );
+  else if( !strcasecmp(sched,"graph_rr_down") )
+    SET_SCHEDULER( graph_rr_down );
+  else if( !strcasecmp(sched,"graph_batch") )
+    SET_SCHEDULER( graph_batch );
+  else if( !strcasecmp(sched,"graph_highnum") )
+    SET_SCHEDULER( graph_highnum );
+  else if( !strcasecmp(sched,"graph_priority") )
+    SET_SCHEDULER( graph_priority );
+  else
+    fatal("Invalid value for CAPRICCIO_SCHEDULER: '%s'\n",sched);
+
+}
+
+/**
+ * perform necessary management to yield the current thread
+ * if suspended == TRUE && timeout != 0 -> the thread is added 
+ * to the sleep queue and later waken up when the clock times out
+ * returns FALSE if time-out actually happens, TRUE if waken up
+ * by other threads, INTERRUPTED if interrupted by a signal
+ **/
+static int thread_yield_internal(int suspended, unsigned long long timeout)
+{
+// now we use a per-thread errno stored in thread_t
+   int savederrno;
+  int rv = OK;
+
+  tdebug("current_thread=%p\n",current_thread);
+
+  savederrno = errno;
+
+  // decide what to do with the thread
+  if( !suspended ) // just add it to the runlist
+    sched_add_thread( current_thread );
+  else if( timeout ) // add to the sleep list
+    sleepq_add_thread( current_thread, timeout);
+
+  {
+#ifdef SHOW_EDGE_TIMES
+    cpu_tick_t start, end, rstart, rend;
+    GET_CPU_TICKS(start);
+    GET_REAL_CPU_TICKS(rstart);
+#endif
+
+    // figure out the current node in the graph
+    if( !conf_no_stacktrace )
+      bg_backtrace_set_node();
+    // FIXME: fake out what cil would do...  current_thread->curr_stats.node = bg_dummy_node;
+
+    // we should already have been told the node by CIL or directly by the programmer
+    assert( current_thread->curr_stats.node != NULL );
+    
+    // update node counts
+    current_thread->prev_stats.node->num_here--;
+    current_thread->curr_stats.node->num_here++;
+    
+    // update the blocking graph info
+    if( bg_save_stats )
+      bg_update_stats();
+  
+#ifdef SHOW_EDGE_TIMES
+    GET_CPU_TICKS(end);
+    GET_REAL_CPU_TICKS(rend);
+    {
+      thread_stats_t *curr = &current_thread->curr_stats;
+      thread_stats_t *prev = &current_thread->prev_stats;
+      output(" %3d -> %-3d     %7lld ticks  (%lld ms)   %7lld rticks (%lld ms)    ", 
+             prev->node->node_num,  curr->node->node_num, 
+             curr->cpu_ticks - prev->cpu_ticks,
+             (curr->cpu_ticks - prev->cpu_ticks) / ticks_per_millisecond,
+# ifdef USE_PERFCTR
+             curr->real_ticks - prev->real_ticks,
+             (curr->real_ticks - prev->real_ticks) / ticks_per_millisecond
+# else
+             curr->cpu_ticks - prev->cpu_ticks,
+             (curr->cpu_ticks - prev->cpu_ticks) / ticks_per_millisecond
+# endif
+             );
+
+      output("update bg node %d:   %lld   (%lld ms)   real: %lld (%lld ms)\n", 
+             current_thread->curr_stats.node->node_num, 
+             (end-start), (end-start)/ticks_per_millisecond, 
+             (rend-rstart), (rend-rstart)/ticks_per_millisecond);
+    }
+#endif
+  }
+
+  // squirrel away the stack limit for next time
+  current_thread->stack_bottom = stack_bottom;
+  current_thread->stack_fingerprint = stack_fingerprint;
+
+  // switch to the scheduler thread
+#ifdef NO_SCHEDULER_THREAD
+  do_scheduler(NULL);
+#else
+  co_call(scheduler_thread->coro, NULL);
+#endif
+  
+  // set up stack limit for new thread
+  stack_bottom = current_thread->stack_bottom;
+  stack_fingerprint = current_thread->stack_fingerprint;
+
+  // rotate the stats
+  if( bg_save_stats ) {
+    current_thread->prev_stats = current_thread->curr_stats;
+    
+    // update thread time, to skip time asleep
+    GET_CPU_TICKS( current_thread->prev_stats.cpu_ticks );
+    current_thread->prev_stats.cpu_ticks -= ticks_diff;  // FIXME: subtract out time to do debug output
+#ifdef USE_PERFCTR
+    GET_REAL_CPU_TICKS( current_thread->prev_stats.real_ticks );
+    current_thread->prev_stats.real_ticks -= ticks_rdiff;  // FIXME: subtract out time to do debug output
+#endif    
+  } else {
+    current_thread->prev_stats.node = current_thread->curr_stats.node;
+  }
+  
+  // check whether time-out happens
+  if (suspended && timeout && current_thread->timeout) {
+    rv = TIMEDOUT;
+    current_thread->timeout = 0;
+  }
+
+  // check for and process pending signals
+  if ( likely(!current_thread->sig_waiting) ) {
+       //if (sig_process_pending())
+               rv = INTERRUPTED;
+  } else {
+       // if sig_waiting is 1, sigwait() itself will handle the remaining      
+       rv = INTERRUPTED;
+  }
+  
+  errno = savederrno;
+  return rv;
+}
+
+
+//////////////////////////////////////////////////////////////////////
+// 
+//  External functions
+// 
+//////////////////////////////////////////////////////////////////////
+
+/**
+ * This will be called automatically, either by routines here, or by
+ * the AIO routines
+ **/
+static void thread_init()  __attribute__ ((constructor));
+static void thread_init() 
+{
+  static int init_done = 0;
+
+  capriccio_main_pid = getpid();
+
+  // read config info from the environemtn
+  read_config();
+
+  //assert(0);
+  if(init_done) 
+    return;
+  init_done = 1;
+
+  // make sure the clock init is already done, so we don't wind up w/
+  // a dependancy loop b/w perfctr and the rest of the code.
+  init_cycle_clock();
+  init_debug();
+
+  // start main timer
+  init_timer(&main_timer);
+  register_timer("total", &main_timer);
+  start_timer(&main_timer);
+
+  init_timer(&scheduler_timer);
+  register_timer("sheduler", &scheduler_timer);
+
+  init_timer(&app_timer);
+  register_timer("app", &app_timer);
+
+  // init scheduler function pointers
+  pick_scheduler();
+
+  // init the scheduler code
+  sched_init();
+
+  // create the main thread
+  main_thread = malloc(sizeof(thread_t));  
+  assert(main_thread);
+  bzero(main_thread, sizeof(thread_t));
+  main_thread->name = "main_thread";
+  main_thread->coro = co_main;
+  main_thread->initial_arg = NULL;
+  main_thread->initial_func = NULL;
+  main_thread->tid = 0;   // fixed value
+  main_thread->sleep = -1;
+  current_thread = main_thread;
+
+  // create the scheduler thread
+#ifndef NO_SCHEDULER_THREAD
+  scheduler_thread = (thread_t*) malloc( sizeof(thread_t) ); 
+  assert(scheduler_thread);
+  bzero(scheduler_thread, sizeof(thread_t));
+  scheduler_thread->name = "scheduler";
+  scheduler_thread->coro = co_create(do_scheduler, 0, SCHEDULER_STACK_SIZE);
+  scheduler_thread->tid = -1;
+#endif
+
+  // don't exit when main exits - wait for threads to die
+#ifndef NO_ATEXIT
+  atexit(exit_func);
+#endif
+
+  // intialize blocking graph functions
+  init_blocking_graph();
+
+  // set stats for the main thread
+  {
+    bg_dummy_node->num_here++;
+    current_thread->curr_stats.node = bg_dummy_node;
+    current_thread->curr_stats.files = 0;
+    current_thread->curr_stats.sockets = 0;
+    current_thread->curr_stats.heap = 0;
+    bg_set_current_stats( &current_thread->curr_stats );
+
+    current_thread->prev_stats = current_thread->curr_stats;
+  }
+
+  // create thread list
+  threadlist = new_pointer_list("thread_list");
+  // add main thread to the list
+  pl_add_tail(threadlist, main_thread);
+  num_runnable_threads++;
+  
+  // create sleep queue
+  sleepq = new_pointer_list("sleep_queue");
+  max_sleep_time = 0;
+  last_check_time = 0;
+  first_wake_usecs = 0;
+  start_usec = current_usecs();
+  
+  // make sure the scheduler runs.  NOTE: this is actually very
+  // important, as it prevents a degenerate case in which the main
+  // thread exits before the scheduler is ever called.  This will
+  // actually cause a core dump, b/c the current_thead_exited flag
+  // will be set, and incorrectly flag the first user thread for
+  // deletion, rather than the main thread.
+  thread_yield_internal(FALSE, 0);
+
+  // things are all set up, so now turn on the syscall overrides
+  cap_override_rw = 1;
+}
+
+
+inline thread_t *thread_spawn_with_attr(char *name, void* (*func)(void *), 
+                                 void *arg, thread_attr_t attr)
+{
+  return new_thread(name, func, arg, attr);
+}
+
+inline thread_t *thread_spawn(char *name, void* (*func)(void *), void *arg)
+{
+  return new_thread(name, func, arg, NULL);
+}
+
+
+void thread_yield()
+{
+  CAP_SET_SYSCALL();
+  thread_yield_internal( FALSE, 0 );
+  CAP_CLEAR_SYSCALL();
+}
+
+void thread_exit(void *ret)
+{
+  thread_t *t = current_thread;
+
+  sanity_check_threadcounts();
+  tdebug("current=%s\n", current_thread?current_thread->name : "NULL");
+
+  if (current_thread == main_thread && main_exited == 0) {
+       // the case when the user calls thread_exit() in main thread is complicated
+       // we cannot simply terminate the main thread, because we need that stack to terminate the
+       // whole program normally.  so we call exit() to make the c runtime help us get the stack
+       // context where we can just return to terminate the whole program
+       // this will call exit_func() and in turn call thread_exit() again
+    main_exited = 1;
+       exit (0);               
+  }
+
+  // note the thread exit in the blocking graph
+  t->curr_stats.node = bg_exit_node;
+  current_thread->prev_stats.node->num_here--;
+  current_thread->curr_stats.node->num_here++;
+  if( bg_save_stats ) {
+    bg_update_stats();
+  }
+    
+  // update thread counts
+  num_runnable_threads--;
+  if( t->daemon ) num_daemon_threads--;
+
+  t->state = ZOMBIE;
+  num_zombie_threads++;
+
+  // deallocate the TCB
+  // keep the thread, if the thread is Joinable, and we want the return value for something
+  if ( !( t->joinable ) ) {
+    // tell the scheduler thread to delete the current one
+    current_thread_exited = 1;
+  } else {
+    t->ret = ret;
+    if (t->join_thread)
+      thread_resume(t->join_thread);
+  }
+
+  sanity_check_threadcounts();
+
+  // squirrel away the stack limit--not that we'll need it again
+  current_thread->stack_bottom = stack_bottom;
+  current_thread->stack_fingerprint = stack_fingerprint;
+
+  // give control back to the scheduler
+#ifdef NO_SCHEDULER_THREAD
+  do_scheduler(NULL);
+#else
+  co_call(scheduler_thread->coro, NULL);
+#endif
+}
+
+int thread_join(thread_t *t, void **ret)
+{
+  if (t == NULL)
+    return_errno(FALSE, EINVAL);
+  if ( !( t->joinable ) )
+    return_errno(FALSE, EINVAL);
+
+  assert(t->state != GHOST);
+
+  // A thread can be joined only once
+  if (t->join_thread)   
+    return_errno(FALSE, EACCES);   
+  t->join_thread = current_thread;
+
+  // Wait for the thread to complete
+  tdebug( "**** thread state: %d\n" ,t->state);
+  if (t->state != ZOMBIE) {
+       CAP_SET_SYSCALL();
+    thread_suspend_self(0);
+    CAP_CLEAR_SYSCALL();
+  }
+
+  // clean up the dead thread
+  if (ret != NULL) 
+    *ret = t->ret;
+  free_thread( t );
+
+  return TRUE;
+}
+
+// timeout == 0 means infinite time
+int thread_suspend_self(unsigned long long timeout)
+{
+  num_suspended_threads++;
+  num_runnable_threads--;
+  sanity_check_threadcounts();
+  current_thread->state = SUSPENDED;
+  return thread_yield_internal(TRUE, timeout);
+}
+
+// only resume the thread internally
+// don't touch the timeout flag and the sleep queue
+static void _thread_resume(thread_t *t)
+{
+  tdebug("t=%p\n",t);
+  if (t->state != SUSPENDED)
+    return;
+  num_suspended_threads--;
+  num_runnable_threads++;
+  sanity_check_threadcounts();
+  assert(t->state == SUSPENDED);
+  t->state = RUNNABLE;
+
+  assert( t->sleep == -1 );
+  sched_add_thread(t);
+}
+
+void thread_resume(thread_t *t)
+{
+  // clear timer
+  if (t->sleep != -1)
+    sleepq_remove_thread(t);
+
+  // make the thread runnable
+  _thread_resume(t);
+}
+
+void thread_set_daemon(thread_t *t)
+{
+  if( t->daemon )
+    return;
+  
+  t->daemon = 1;
+  num_daemon_threads++;
+}
+
+inline char* thread_name(thread_t *t)
+{
+  return t->name;
+}
+
+void thread_exit_program(int exitcode)
+{
+  exit_whole_program = 1;
+  raise( SIGINT );
+  syscall(SYS_proc_destroy, exitcode);
+}
+
+
+// Thread attribute handling
+thread_attr_t thread_attr_of(thread_t *t) {
+  thread_attr_t attr = (thread_attr_t)malloc(sizeof(struct _thread_attr));
+  attr->thread = t;
+  return attr;
+}
+
+thread_attr_t thread_attr_new()
+{
+  thread_attr_t attr = (thread_attr_t)malloc(sizeof(struct _thread_attr));
+  attr->thread = NULL;
+  thread_attr_init(attr);
+  return attr;
+}
+
+int thread_attr_init(thread_attr_t attr)
+{
+  if (attr == NULL)
+    return_errno(FALSE, EINVAL);
+  if (attr->thread)
+    return_errno(FALSE, EPERM);
+  attr->joinable = TRUE;
+  return TRUE;
+}
+
+int thread_attr_set(thread_attr_t attr, int field, ...)
+{
+  va_list ap;
+  int rc = TRUE;
+  if(attr == NULL) 
+    return EINVAL;
+
+  va_start(ap, field);
+  switch (field) {
+  case THREAD_ATTR_JOINABLE: {
+    int val = va_arg(ap, int);
+    if(attr->thread == NULL) {
+      if( val == THREAD_CREATE_JOINABLE ) 
+        attr->joinable = TRUE;
+      else
+        attr->joinable = FALSE;
+    } else {
+      if( val == THREAD_CREATE_JOINABLE ) 
+        attr->thread->joinable = 1;
+      else
+        attr->thread->joinable = 0;
+    }
+    break;
+  }
+  default:
+    notimplemented(thread_attr_set);
+  }
+  va_end(ap);
+  return rc;
+}
+
+int thread_attr_get(thread_attr_t attr, int field, ...) 
+{
+  va_list ap;
+  int rc = TRUE;
+  va_start(ap, field);
+  switch (field) {
+  case THREAD_ATTR_JOINABLE: {
+    int *val = va_arg(ap, int *);
+    int joinable = (attr->thread == NULL) ? attr->joinable : attr->thread->joinable;
+    *val = joinable ? THREAD_CREATE_JOINABLE : THREAD_CREATE_DETACHED;
+  }
+  default:
+    notimplemented(thread_attr_get);
+  }
+  va_end(ap);
+  return rc;
+}
+
+int thread_attr_destroy(thread_attr_t attr)
+{
+  free(attr);
+  return TRUE;
+}
+
+// Thread-specific storage
+struct thread_keytab_st {
+    int used;
+    void (*destructor)(void *);
+};
+
+static struct thread_keytab_st thread_keytab[THREAD_KEY_MAX];
+
+int thread_key_create(thread_key_t *key, void (*func)(void *))
+{
+    for ((*key) = 0; (*key) < THREAD_KEY_MAX; (*key)++) {
+        if (thread_keytab[(*key)].used == FALSE) {
+            thread_keytab[(*key)].used = TRUE;
+            thread_keytab[(*key)].destructor = func;
+            return TRUE;
+        }
+    }
+    return_errno(FALSE, EAGAIN);
+}
+
+int thread_key_delete(thread_key_t key)
+{
+    if (key >= THREAD_KEY_MAX)
+        return_errno(FALSE, EINVAL);
+    if (!thread_keytab[key].used)
+        return_errno(FALSE, EINVAL);
+    thread_keytab[key].used = FALSE;
+    return TRUE;
+}
+
+int thread_key_setdata(thread_key_t key, const void *value)
+{
+    if (key >= THREAD_KEY_MAX)
+        return_errno(FALSE, EINVAL);
+    if (!thread_keytab[key].used)
+        return_errno(FALSE, EINVAL);
+    if (current_thread->key_data_value == NULL) {
+        current_thread->key_data_value = (const void **)calloc(1, sizeof(void *)*THREAD_KEY_MAX);
+        if (current_thread->key_data_value == NULL)
+            return_errno(FALSE, ENOMEM);
+    }
+    if (current_thread->key_data_value[key] == NULL) {
+        if (value != NULL)
+            current_thread->key_data_count++;
+    }
+    else {
+        if (value == NULL)
+            current_thread->key_data_count--;
+    }
+    current_thread->key_data_value[key] = value;
+    return TRUE;
+}
+
+void *thread_key_getdata(thread_key_t key)
+{
+    if (key >= THREAD_KEY_MAX)
+        return_errno(NULL, EINVAL);
+    if (!thread_keytab[key].used)
+        return_errno(NULL, EINVAL);
+    if (current_thread->key_data_value == NULL)
+        return NULL;
+    return (void *)current_thread->key_data_value[key];
+}
+
+void thread_key_destroydata(thread_t *t)
+{
+    void *data;
+    int key;
+    int itr;
+    void (*destructor)(void *);
+
+    if (t == NULL)
+        return;
+    if (t->key_data_value == NULL)
+        return;
+    /* POSIX thread iteration scheme */
+    for (itr = 0; itr < THREAD_DESTRUCTOR_ITERATIONS; itr++) {
+        for (key = 0; key < THREAD_KEY_MAX; key++) {
+            if (t->key_data_count > 0) {
+                destructor = NULL;
+                data = NULL;
+                if (thread_keytab[key].used) {
+                    if (t->key_data_value[key] != NULL) {
+                        data = (void *)t->key_data_value[key];
+                        t->key_data_value[key] = NULL;
+                        t->key_data_count--;
+                        destructor = thread_keytab[key].destructor;
+                    }
+                }
+                if (destructor != NULL)
+                    destructor(data);
+            }
+            if (t->key_data_count == 0)
+                break;
+        }
+        if (t->key_data_count == 0)
+            break;
+    }
+    free(t->key_data_value);
+    t->key_data_value = NULL;
+    return;
+}
+
+// for finding the location of the current errno variable
+/*
+int __global_errno = 0;
+int *__errno_location (void)
+{
+       if (likely((int)current_thread))
+               return &current_thread->__errno;
+       else
+               return &__global_errno;
+}
+*/
+
+unsigned thread_tid(thread_t *t)
+{
+  return t ? t->tid : 0xffffffff;
+}
+
+#if 1
+#define sleepq_sanity_check() \
+       assert ((max_sleep_time > 0 && sleepq->num_entries > 0) \
+               || (max_sleep_time == 0 && sleepq->num_entries == 0) )
+#else
+#define sleepq_sanity_check() \
+do { \
+  assert ((max_sleep_time > 0 && sleepq->num_entries > 0) \
+       | (max_sleep_time == 0 && sleepq->num_entries == 0) ); \
+ { \
+  linked_list_entry_t *e; \
+  unsigned long long _total = 0; \
+  e = ll_view_head(sleepq);\
+  while (e) {\
+    thread_t *tt = (thread_t *)pl_get_pointer(e);\
+    assert( tt->sleep >= 0 );\
+    _total += tt->sleep;\
+    e = ll_view_next(sleepq, e);\
+  }\
+  assert( _total == max_sleep_time );\
+ }\
+} while( 0 );
+#endif
+
+
+int print_sleep_queue(void) __attribute__((unused));
+int print_sleep_queue(void)
+{
+  linked_list_entry_t *e; 
+  unsigned long long _total = 0; 
+  e = ll_view_head(sleepq);
+
+  while (e) {
+    thread_t *tt = (thread_t *)pl_get_pointer(e);
+    _total += tt->sleep;
+    output(" %s:  %lld   (%lld)\n", tt->name ? tt->name : "null", tt->sleep, _total );
+    e = ll_view_next(sleepq, e);
+  }
+  return 1;
+}
+
+// check sleep queue to wake up all timed-out threads
+// sync == TRUE -> synchronize last_check_time
+static void sleepq_check(int sync)
+{
+  unsigned long long now;
+  long long interval;
+  linked_list_entry_t *e;
+
+  if (!sync && max_sleep_time == 0) {  // shortcut to return
+    first_wake_usecs = 0;      // FIXME: don't write to it every time
+    return;
+  }
+
+  sleepq_sanity_check();
+
+  now = current_usecs();
+  if( now > last_check_time ) 
+    interval = now-last_check_time;
+  else 
+    interval = 0;
+  last_check_time = now;
+
+
+  // adjust max_sleep_time
+  if (max_sleep_time < (unsigned long long)interval)
+    max_sleep_time = 0;
+  else
+    max_sleep_time -= interval;
+  
+  while (interval > 0 && (e = ll_view_head(sleepq))) {
+    thread_t *t = (thread_t *)pl_get_pointer(e);
+
+    if (t->sleep > interval) {
+      t->sleep -= interval;
+      first_wake_usecs = now + t->sleep;
+      break;
+    }
+
+    interval -= t->sleep;
+    t->sleep = -1;
+    t->timeout = 1;
+
+    //output("  %10llu: thread %d timeout\n", current_usecs(), t->tid);
+    
+    _thread_resume(t);    // this doesn't deal with sleep queue
+    ll_free_entry(sleepq, ll_remove_head(sleepq));
+  }
+
+  if (ll_size(sleepq) == 0) {
+     // the sleepq is empty again
+     first_wake_usecs = 0;
+  }
+
+  sleepq_sanity_check();
+}
+
+
+// set a timer on a thread that will wake the thread up after timeout
+// microseconds.  this is used to implement thread_suspend_self(timeout)
+static void sleepq_add_thread(thread_t *t, unsigned long long timeout)
+{
+  linked_list_entry_t *e;
+  long long total_time;
+  sleepq_check(1); // make sure: last_check_time == now
+
+  assert(t->sleep == -1);
+  sleepq_sanity_check();
+
+  if (timeout >= max_sleep_time) {
+    // set first_wake_usecs if this is the first item
+    if( pl_view_head(sleepq) == NULL )
+      first_wake_usecs = current_usecs() + timeout;
+
+    // just append the thread to the end of sleep queue
+    pl_add_tail(sleepq, t);
+    t->sleep = timeout - max_sleep_time;
+    assert( t->sleep >= 0 );
+    max_sleep_time = timeout;
+    sleepq_sanity_check();
+    return;
+  }
+
+  // let's find a place in the queue to insert the thread
+  // we go backwards
+  e = ll_view_tail(sleepq);
+  total_time = max_sleep_time;
+  while (e) {
+    thread_t *tt = (thread_t *)pl_get_pointer(e);
+    assert(tt->sleep >= 0);
+    total_time -= tt->sleep;
+
+    assert (total_time >= 0); // can be == 0 if we are adding the head item
+    if ((unsigned long long)total_time <= timeout) {
+      // insert t into the queue
+      linked_list_entry_t *newp = ll_insert_before(sleepq, e);
+      pl_set_pointer(newp, t);
+      t->sleep = timeout - total_time;
+      assert( t->sleep > 0 );
+
+      // set first_wake_usecs if this is the first item
+      if( total_time == 0 )
+        first_wake_usecs = current_usecs() + timeout;
+
+      // update the sleep time of the thread right after t
+      tt->sleep -= t->sleep;
+      assert( tt->sleep > 0 );
+      break;
+    }
+    
+    e = ll_view_prev(sleepq, e);
+  }
+
+  assert (e != NULL);   // we're sure to find such an e
+  sleepq_sanity_check();
+  
+  return;
+}
+
+// remove the timer associated with the thread
+inline static void sleepq_remove_thread(thread_t *t)
+{
+  linked_list_entry_t *e;
+
+  assert(t->sleep >= 0);  // the thread must be in the sleep queue
+  sleepq_sanity_check();
+  
+  // let's find the thread in the queue
+  e = ll_view_head(sleepq);
+  while (e) {
+    thread_t *tt = (thread_t *)pl_get_pointer(e);
+    if (tt == t) {
+      linked_list_entry_t *nexte = ll_view_next(sleepq, e);
+      if (nexte) {
+       // e is not the last thread in the queue
+       // we need to lengthen the time the next thread will sleep
+       thread_t *nextt = (thread_t *)pl_get_pointer(nexte);
+       nextt->sleep += t->sleep;
+      } else {
+       // e is the last thread, so we need to adjust max_sleep_time
+       max_sleep_time -= t->sleep;
+      }
+      // remove t
+      ll_remove_entry(sleepq, e);
+      ll_free_entry(sleepq, e);
+      t->sleep = -1;
+      assert (!t->timeout);    // if this fails, someone must has 
+                               // forgot to reset timeout some time ago
+      break;
+    }
+    e = ll_view_next(sleepq, e);
+  }
+
+  assert( t->sleep == -1);
+  assert (e != NULL);   // we must find t in sleep queue
+  sleepq_sanity_check();
+}
+
+
+void thread_usleep(unsigned long long timeout)
+{
+  thread_suspend_self(timeout);
+}
+
+
+// NOTE: dynamic linking craps out w/o these.  There may be a better way.
+//static int capriccio_errno=0;
+//int* __errno_location()
+//{
+//  return &capriccio_errno;
+//}
+
+//extern pid_t __libc_fork();
+//pid_t fork() {
+//  return __libc_fork();
+//}
+//strong_alias(fork,__fork);
+
+
+int sched_yield(void)
+{
+  thread_yield();
+  return 0;
+}
+strong_alias(sched_yield,__sched_yield);
diff --git a/user/c3po/threads/threadlib.h b/user/c3po/threads/threadlib.h
new file mode 100644 (file)
index 0000000..5801190
--- /dev/null
@@ -0,0 +1,225 @@
+
+#ifndef THREADLIB_H
+#define THREADLIB_H
+
+#include "pthread.h"
+#include "resource_stats.h"
+#include <time.h>
+#include <signal.h>
+
+#define likely(x)   __builtin_expect(!!(x),1)
+#define unlikely(x) __builtin_expect(!!(x),0)
+
+struct thread_st;
+typedef struct thread_st thread_t;
+
+typedef enum {
+  UNLOCKED = 0,
+  LOCKED   = 1,
+} lock_state_t;
+
+typedef struct latch_st {
+  thread_t *state;
+} latch_t;
+
+// Queue used for mutex implementation
+typedef struct queue_st {
+  void **data;   /* allocated data */
+  int size;  /* allocated size */
+  int head, tail;   /* valid data in [head, tail) */
+} queue_t;
+
+typedef struct {
+  latch_t latch;
+  lock_state_t state;
+  int count;   // for recursive locking
+  char *name;
+  queue_t wait_queue;
+  thread_t *owner;   // for sanity checking in thread_mutex_unlock()
+} mutex_t;
+
+// the read-write lock structure
+typedef struct rwlock_st { /* not hidden to avoid destructor */
+  int            rw_state;
+  unsigned int   rw_mode;
+  unsigned long  rw_readers;
+  mutex_t    rw_mutex_rd;
+  mutex_t    rw_mutex_rw;
+} rwlock_t;
+
+// the condition variable structure
+typedef struct cond_st { /* not hidden to avoid destructor */
+  unsigned long cn_state;
+  unsigned int  cn_waiters;
+  queue_t wait_queue;
+} cond_t;
+
+
+// All thread attributes
+enum {
+  THREAD_ATTR_JOINABLE = 1,
+  THREAD_ATTR_NAME,  // Not implemented
+  THREAD_ATTR_PRIO   // Not implemented
+};
+
+// Thread joinable attribute
+enum {
+  THREAD_CREATE_DETACHED = 0,
+  THREAD_CREATE_JOINABLE = 1
+};
+
+enum {
+       OK = 0,
+       TIMEDOUT = 1,
+       INTERRUPTED = 2,
+};
+
+typedef struct _thread_attr *thread_attr_t;
+
+void thread_exit(void *ret);
+void thread_exit_program(int exitcode);
+void thread_yield();
+thread_t *thread_spawn(char *name, void* (*func)(void *), void *arg);
+thread_t *thread_spawn_with_attr(char *name, void* (*func)(void *), 
+                           void *arg, thread_attr_t attr);  // added for pthread layer
+// suspend the thread, optionally for a limited period of time (timeout)
+// timeout == 0 -> suspend infinitely unless resumed by another thread
+// return value: OK if resumed by someone else, TIMEDOUT is timedout
+//               INTERRUPTED is interrupted by a signal
+int thread_suspend_self(unsigned long long timeout);
+
+// resume is made idempotent - resuming an already runnable thread does nothing
+void thread_resume(thread_t* t);
+inline char* thread_name(thread_t *t);
+int thread_join(thread_t *t, void **val);
+void thread_set_daemon(thread_t *t);
+
+extern thread_t *current_thread;
+static inline thread_t* thread_self() { return current_thread; }
+
+// Key-based thread specific storage
+typedef int thread_key_t;
+#define THREAD_DESTRUCTOR_ITERATIONS 4
+#define THREAD_KEY_MAX 256  // NOTE: change the size of thread_t.data_count if you change this!
+int thread_key_create(thread_key_t *key, void (*destructor)(void *));
+int thread_key_delete(thread_key_t key);
+int thread_key_setdata(thread_key_t key, const void *value);
+void *thread_key_getdata(thread_key_t key);
+
+void thread_usleep(unsigned long long timeout);
+
+// Mutex - return TRUE on success
+inline int thread_mutex_init(mutex_t *m, char *name);
+inline int thread_mutex_lock(mutex_t *m);
+inline int thread_mutex_trylock(mutex_t *m);    // do not block, return FALSE when mutex held but others
+inline int thread_mutex_unlock(mutex_t *m);
+
+// Rwlocks
+enum rwlock_op {
+  RWLOCK_RD = 1,
+  RWLOCK_RW
+};
+
+int thread_rwlock_init(rwlock_t *l);
+int thread_rwlock_lock(rwlock_t *l, int op);
+int thread_rwlock_trylock(rwlock_t *l, int op);
+int thread_rwlock_unlock(rwlock_t *l);
+
+// Condition variables
+int thread_cond_init(cond_t *c);
+int thread_cond_wait(cond_t *c, mutex_t *m);
+int thread_cond_timedwait(cond_t *c, mutex_t *m, const struct timespec *timeout);
+int thread_cond_signal(cond_t *c);
+int thread_cond_broadcast(cond_t *c);
+
+// attribute
+thread_attr_t thread_attr_of(thread_t *t);
+thread_attr_t thread_attr_new();
+int thread_attr_init(thread_attr_t attr);
+int thread_attr_set(thread_attr_t attr, int field, ...);
+int thread_attr_get(thread_attr_t attr, int field, ...);
+int thread_attr_destroy(thread_attr_t attr);
+
+unsigned thread_tid(thread_t *t);
+
+int thread_kill(thread_t* t, int sig);
+int thread_kill_all(int sig);
+int thread_sigwait(const sigset_t *set, int *sig);
+
+extern inline void thread_stats_add_heap(long size);
+extern inline void thread_stats_add_fds(int num);
+
+
+typedef struct {
+  long active;
+  long long requests;
+  long long completions;
+  long long bytes_read;
+  long long bytes_written;
+  long long errors;
+} iostats_t;
+
+extern iostats_t sockio_stats;
+extern iostats_t diskio_stats;
+
+#define IOSTAT_START(type) {\
+  type##_stats.active++; \
+  type##_stats.requests++; \
+}
+#define IOSTAT_DONE(type, success) {\
+  type##_stats.active--; \
+  if( (success) ) type##_stats.completions++; \
+  else            type##_stats.errors++; \
+}
+
+extern int cap_override_rw;
+
+
+extern const char *cap_current_syscall;  // used to inform the BG routines how they got there...
+#define CAP_SET_SYSCALL()   if(!cap_current_syscall) cap_current_syscall = __FUNCTION__
+#define CAP_CLEAR_SYSCALL() (cap_current_syscall = NULL)
+
+extern void set_io_polling_func( void (*func)(long long) );
+
+// latches
+
+// FIXME: if there is a single kernel thread, it is an error if the latch is already locked 
+// FIXME: need a spinlock, test&set, futex, etc. for multiple kernel threads 
+#define LATCH_UNLOCKED NULL
+#define LATCH_UNKNOWN ((thread_t*)-1)
+#if OPTIMIZE < 2
+#define thread_latch(latch) \
+do {\
+  assert(latch.state == LATCH_UNLOCKED); \
+  latch.state = thread_self() ? thread_self() : LATCH_UNKNOWN; \
+} while(0)
+#else
+#define thread_latch(latch) do { } while(0)
+#endif
+#if OPTIMIZE < 2
+#define thread_unlatch(latch) \
+do { \
+  assert(latch.state != LATCH_UNLOCKED); \
+  latch.state = UNLOCKED; \
+} while(0)
+#else
+#define thread_unlatch(latch) do { (void)(latch);} while(0)
+#endif
+
+#if OPTIMIZE < 2
+#define thread_latch_init(latch) \
+do { \
+  latch.state = LATCH_UNLOCKED; \
+} while(0)
+#else
+#define thread_latch_init(latch) do {(void)(latch);} while(0)
+#endif
+
+#define LATCH_INITIALIZER_UNLOCKED ((latch_t) { LATCH_UNLOCKED })
+#define LATCH_INITIALIZER_LOCKED   ((latch_t) { LATCH_UNKNOWN })
+
+#endif /* THREADLIB_H */
+
+
+
diff --git a/user/c3po/threads/threadlib_internal.h b/user/c3po/threads/threadlib_internal.h
new file mode 100644 (file)
index 0000000..dfba1a7
--- /dev/null
@@ -0,0 +1,154 @@
+
+#ifndef THREADLIB_INTERNAL_H
+#define THREADLIB_INTERNAL_H
+
+#include "pthread.h"
+#include "coro.h"
+#include "threadlib.h"
+#include "util.h"
+#include "blocking_graph.h"
+#include "signal.h"
+#ifdef USE_NIO
+#include <libaio.h>
+#endif
+
+// provide a way to adjust the behavior when unimplemented function is called
+#define notimplemented(x) output("WARNING: " #x " not implemented!\n")
+
+// thread attributes
+// This is a separate struct because pthread has API for users to initizalize
+// pthread_attr_t structs before thread creation
+struct _thread_attr {
+  thread_t *thread;    // != NULL when is bound to a thread
+
+  // Attributes below are valid only when thread == NULL
+  int joinable:1;
+  int daemon:1;
+
+  char *name;
+};
+
+#define THREAD_SIG_MAX 32
+
+struct thread_st {
+  unsigned tid;   // thread id, mainly for readability of debug output
+  struct coroutine *coro;
+  void *stack;
+  void *stack_bottom;
+  int stack_fingerprint;
+//  int __errno;       // thread-local errno
+
+  // misc. short fields, grouped to save space
+  enum {
+    RUNNABLE=0,
+    SUSPENDED,
+    ZOMBIE,        // not yet reaped, for joinable threads
+    GHOST          // already freed, no valid thread should be in this state
+  } state:3;
+
+  unsigned int joinable:1;
+  unsigned int daemon:1;
+  unsigned int key_data_count:8;  // big enough for THREAD_KEY_MAX
+  unsigned int timeout:1;         // whether it is waken up because of timeout
+  unsigned int sig_waiting:1;  // the thread is waiting for a signal (any not blocked in sig_mask). 
+                       // when it arrives, the thread should be waken up and 
+                       // the signal handler should *not* be called
+  short sig_num_received;      // number of signals in sigs_received
+
+#ifdef USE_NIO
+  struct iocb iocb;        // aio control block
+  int ioret;               // i/o ret value, set by polling loop and used by wrapping functions
+  int iocount;            // number of I/O operations done without blocking, used to yield the processor when reached a fixed amount
+#endif
+
+  // startup stuff
+  void* (*initial_func)(void *);
+  void *initial_arg;
+  char *name;
+  int stack_size_kb_log2;
+
+  const void **key_data_value;  // thread specific values
+
+  // stats for the blocking graph.
+  // FIXME: move curr_stats to global var, to save space here.  (only need one anyway)
+  thread_stats_t prev_stats;
+  thread_stats_t curr_stats;
+
+  // per-thread signals
+  // NOTE: external(global) signals are in corresponding global variable
+  sigset_t sig_received;       // per-thread received but unhandled signals
+  sigset_t sig_mask;           // masked signals for this thread
+
+  thread_t *join_thread;   // thread waiting for the completion of the thread
+  void *ret;               // return value, returned to user by thread_join()
+
+  long long sleep;         // relative time for this thread to sleep after the prev one in sleep queue
+                           // -1 means thread is not in the sleep queue
+};
+
+
+extern thread_t *current;
+extern int in_scheduler;
+
+
+// scheduler functions
+#define DECLARE_SCHEDULER(s) \
+  extern void s##_init(void); \
+  extern void s##_add_thread(thread_t *t); \
+  extern thread_t* s##_next_thread(void); 
+
+DECLARE_SCHEDULER(sched_global_rr);
+DECLARE_SCHEDULER(sched_global_lifo);
+DECLARE_SCHEDULER(sched_graph_rr);
+DECLARE_SCHEDULER(sched_graph_rr_down);
+DECLARE_SCHEDULER(sched_graph_batch);
+DECLARE_SCHEDULER(sched_graph_highnum);
+DECLARE_SCHEDULER(sched_graph_priority);
+
+extern void sched_graph_generic_init(void);
+extern void sched_graph_generic_add_thread(thread_t *t);
+
+#define strong_alias(name, aliasname) extern __typeof (name) aliasname __attribute__ ((alias (#name)));
+#define valid_thread(t) (t != NULL  &&  t != (thread_t*)-1)
+#define thread_alive(t) ((t)->state == RUNNABLE || (t)->state == SUSPENDED)
+
+// Internal constants
+
+#define _BIT(n) (1<<(n))
+#define THREAD_RWLOCK_INITIALIZED _BIT(0)
+
+#define THREAD_COND_INITIALIZED _BIT(0)
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+#ifndef TRUE
+#define TRUE (~FALSE)
+#endif
+
+#define return_errno(return_val,errno_val) \
+        do { errno = (errno_val); \
+             debug("return 0x%lx with errno %d(\"%s\")\n", \
+                        (unsigned long)(return_val), (errno), strerror((errno))); \
+             return (return_val); } while (0)
+
+#define return_errno_unlatch(ret,err,latch) \
+do { \
+  thread_unlatch(latch); \
+  errno = (err); \
+  debug("return 0x%lx with errno %d(\"%s\")\n", (unsigned long)(ret), (err), strerror((err))); \
+  return (ret); \
+} while (0)
+
+
+extern void dump_debug_info();
+extern void dump_thread_state();
+
+extern long long total_stack_in_use;
+
+// process all pending signals.  returns 1 is any actually handled, 0 otherwise
+extern inline int sig_process_pending();
+
+extern thread_t *scheduler_thread;
+
+#endif /* THREADLIB_INTERNAL_H */
diff --git a/user/c3po/util/Makefrag b/user/c3po/util/Makefrag
new file mode 100644 (file)
index 0000000..d48814c
--- /dev/null
@@ -0,0 +1,25 @@
+UTIL_NAME    := util
+UTIL_UCNAME  := $(call uc, $(UTIL_NAME))
+UTIL_CFLAGS  := $(CFLAGS)
+UTIL_HEADERS := $(wildcard $(UTILDIR)/*.h)
+UTIL_CFILES  := $(wildcard $(UTILDIR)/*.c)
+UTIL_OBJDIR  := $(OBJDIR)/$(UTIL_NAME)
+UTIL_OBJS    := $(patsubst %.c, %.o, $(UTIL_CFILES))
+UTIL_OBJS    := $(foreach x, $(UTIL_OBJS), $(UTIL_OBJDIR)/$(call filename,$(x)))
+
+LIBUTIL = $(UTIL_OBJDIR)/lib$(UTIL_NAME).a
+
+$(UTIL_NAME)-clean:
+       @echo + clean [$(LIBUCNAME) $(UTIL_UCNAME)]
+       $(V)rm -rf $(UTIL_OBJS) $(LIBUTIL)
+       $(V)rm -rf $(UTIL_OBJDIR)
+
+$(LIBUTIL): $(UTIL_OBJS)
+       @echo + ar [$(LIBUCNAME) $(UTIL_UCNAME)] $@
+       $(V)$(AR) rc $@ $^
+
+$(UTIL_OBJDIR)/%.o: $(UTILDIR)/%.c $(UTIL_HEADERS)
+       @echo + cc [$(LIBUCNAME) $(UTIL_UCNAME)] $<
+       @mkdir -p $(@D)
+       $(V)$(CC) $(UTIL_CFLAGS) $(INCS) -o $@ -c $<
+
diff --git a/user/c3po/util/atomic.h b/user/c3po/util/atomic.h
new file mode 100644 (file)
index 0000000..546e78f
--- /dev/null
@@ -0,0 +1,132 @@
+/**
+ * Assembly language for x86 atomic operations.  These were coopted
+ * from the glibc include files for linux, as noted below.  We have
+ * included them here (unmodified) because the include files only
+ * define them when in kernel mode.
+ **/
+
+#include <unistd.h>
+#include <ros/syscall.h>
+
+// from /usr/include/asm/linux/autoconf.h
+#define CONFIG_SMP 1
+#ifdef CONFIG_SMP
+#define LOCK_PREFIX "lock ; "
+#else
+#define LOCK_PREFIX ""
+#endif
+
+
+// Everything below is from /usr/include/asm/system.h
+
+
+struct __xchg_dummy { unsigned long a[100]; };
+#define __xg(x) ((struct __xchg_dummy *)(x))
+
+
+/*
+ * Note: no "lock" prefix even on SMP: xchg always implies lock anyway
+ * Note 2: xchg has side effect, so that attribute volatile is necessary,
+ *       but generally the primitive is invalid, *ptr is output argument. --ANK
+ */
+static inline unsigned long __xchg(unsigned long x, volatile void * ptr, int size)
+{
+       switch (size) {
+               case 1:
+                       __asm__ __volatile__("xchgb %b0,%1"
+                               :"=q" (x)
+                               :"m" (*__xg(ptr)), "0" (x)
+                               :"memory");
+                       break;
+               case 2:
+                       __asm__ __volatile__("xchgw %w0,%1"
+                               :"=r" (x)
+                               :"m" (*__xg(ptr)), "0" (x)
+                               :"memory");
+                       break;
+               case 4:
+                       __asm__ __volatile__("xchgl %0,%1"
+                               :"=r" (x)
+                               :"m" (*__xg(ptr)), "0" (x)
+                               :"memory");
+                       break;
+       }
+       return x;
+}
+
+#define xchg(ptr,v) ((__typeof__(*(ptr)))__xchg((unsigned long)(v),(ptr),sizeof(*(ptr))))
+#define tas(ptr) (xchg((ptr),1))
+
+static inline void spinlock_lock(int *ptr) 
+{ 
+  while( 1 ) {
+    register int i=1000;
+    while( i && (tas(ptr) == 1) ) 
+      i--; 
+    if( i ) 
+      return;
+    syscall(SYS_yield);
+  }
+}
+#define spinlock_unlock(ptr) { *(ptr)=0; }
+
+
+
+/*
+ * Atomic compare and exchange.  Compare OLD with MEM, if identical,
+ * store NEW in MEM.  Return the initial value in MEM.  Success is
+ * indicated by comparing RETURN with OLD.
+ */
+static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old,
+                                     unsigned long new, int size)
+{
+       unsigned long prev;
+       switch (size) {
+       case 1:
+               __asm__ __volatile__(LOCK_PREFIX "cmpxchgb %b1,%2"
+                                    : "=a"(prev)
+                                    : "q"(new), "m"(*__xg(ptr)), "0"(old)
+                                    : "memory");
+               return prev;
+       case 2:
+               __asm__ __volatile__(LOCK_PREFIX "cmpxchgw %w1,%2"
+                                    : "=a"(prev)
+                                    : "q"(new), "m"(*__xg(ptr)), "0"(old)
+                                    : "memory");
+               return prev;
+       case 4:
+               __asm__ __volatile__(LOCK_PREFIX "cmpxchgl %1,%2"
+                                    : "=a"(prev)
+                                    : "q"(new), "m"(*__xg(ptr)), "0"(old)
+                                    : "memory");
+               return prev;
+       }
+       return old;
+}
+/*
+int atomic_h_mutex=0;
+static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old,
+                                     unsigned long new, int size)
+{
+  unsigned long ret;
+  (void) size;
+  spinlock_lock( &atomic_h_mutex );
+  // assume size == sizeof(long)
+  if( *((unsigned long*)ptr) == old ) {
+    *((unsigned long*)ptr) = new;
+    ret = old;
+  } else {
+    ret = *((unsigned long*)ptr);
+  }
+  spinlock_unlock( &atomic_h_mutex );
+  return ret;
+}
+*/
+
+#define cmpxchg(ptr,o,n)\
+       ((__typeof__(*(ptr)))__cmpxchg((ptr),(unsigned long)(o),\
+                                       (unsigned long)(n),sizeof(*(ptr))))
+
+
+// FIXME: does this work?
+#define SERIALIZATION_BARRIER() { __asm__("cpuid"); }
diff --git a/user/c3po/util/clock.c b/user/c3po/util/clock.c
new file mode 100644 (file)
index 0000000..4e3723e
--- /dev/null
@@ -0,0 +1,220 @@
+/**
+ * routines for getting timing info from the cycle clock
+ **/
+
+//#include <stdio.h>
+#include <sys/time.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "clock.h"
+#include "debug.h"
+
+
+#ifdef USE_PERFCTR
+
+// perfctr specific declarations
+static struct vperfctr *self = NULL;
+struct vperfctr *clock_perfctr = NULL;
+
+static struct perfctr_info info;
+static struct vperfctr_control control;
+
+static void init_perfctr() {
+    unsigned int tsc_on = 1;
+    unsigned int nractrs = 1;
+    unsigned int pmc_map0 = 0;
+    unsigned int evntsel0 = 0;
+
+    self = vperfctr_open();
+    clock_perfctr = self;
+    if( !self ) {
+      char *str = "vperfctr_open() failed!!\n";
+      syscall(SYS_write, 2, str, strlen(str));
+      exit(1);
+    }
+    if( vperfctr_info(clock_perfctr, &info) < 0 ) {
+      char *str = "vperfctr_info() failed!!\n";
+      syscall(SYS_write, 2, str, strlen(str));
+      exit(1);
+    }
+
+    memset(&control, 0, sizeof control);
+
+    /* Attempt to set up control to count clocks via the TSC
+       and retired instructions via PMC0. */
+    switch( info.cpu_type ) {
+      case PERFCTR_X86_GENERIC:
+        nractrs = 0;            /* no PMCs available */
+        break;
+      case PERFCTR_X86_INTEL_P5:
+      case PERFCTR_X86_INTEL_P5MMX:
+      case PERFCTR_X86_CYRIX_MII:
+        /* event 0x16 (INSTRUCTIONS_EXECUTED), count at CPL 3 */
+        evntsel0 = 0x16 | (2 << 6);
+        break;
+      case PERFCTR_X86_INTEL_P6:
+      case PERFCTR_X86_INTEL_PII:
+      case PERFCTR_X86_INTEL_PIII:
+      case PERFCTR_X86_AMD_K7:
+      case PERFCTR_X86_AMD_K8:
+        /* event 0xC0 (INST_RETIRED), count at CPL > 0, Enable */
+        evntsel0 = 0xC0 | (1 << 16) | (1 << 22);
+        break;
+      case PERFCTR_X86_WINCHIP_C6:
+        tsc_on = 0;             /* no working TSC available */
+        evntsel0 = 0x02;        /* X86_INSTRUCTIONS */
+        break;
+      case PERFCTR_X86_WINCHIP_2:
+        tsc_on = 0;             /* no working TSC available */
+        evntsel0 = 0x16;        /* INSTRUCTIONS_EXECUTED */
+        break;
+      case PERFCTR_X86_VIA_C3:
+        pmc_map0 = 1;           /* redirect PMC0 to PERFCTR1 */
+        evntsel0 = 0xC0;        /* INSTRUCTIONS_EXECUTED */  
+        break;
+      case PERFCTR_X86_INTEL_P4:
+      case PERFCTR_X86_INTEL_P4M2:
+        /* PMC0: IQ_COUNTER0 with fast RDPMC */
+        pmc_map0 = 0x0C | (1 << 31);
+        /* IQ_CCCR0: required flags, ESCR 4 (CRU_ESCR0), Enable */
+        evntsel0 = (0x3 << 16) | (4 << 13) | (1 << 12);
+        /* CRU_ESCR0: event 2 (instr_retired), NBOGUSNTAG, CPL>0 */
+        control.cpu_control.p4.escr[0] = (2 << 25) | (1 << 9) | (1 << 2);
+        break;
+    default: {
+          char *str = "cpu type not supported - perfctr init failed!!\n";
+          syscall(SYS_write, 2, str, strlen(str));
+          exit(1);
+        }
+    }
+    control.cpu_control.tsc_on = tsc_on;
+    control.cpu_control.nractrs = nractrs;
+    control.cpu_control.pmc_map[0] = pmc_map0;
+    control.cpu_control.evntsel[0] = evntsel0;
+
+    if (!nractrs) {
+      //output("error: your CPU (%s) doesn't support PMC timing\n", perfctr_info_cpu_name(&info));
+      char *str = "error: your CPU doesn't support PMC timing\n";
+      syscall(SYS_write, 2, str, strlen(str));
+      exit(1);
+    }
+
+    // start the perfctr
+    if( vperfctr_control(clock_perfctr, &control) < 0 ) {
+      char *str = "vperfctr_control failed!!!\n";
+      syscall(SYS_write, 2, str, strlen(str));
+      exit(1);
+    }
+}
+
+#endif
+
+
+
+
+/**
+ * calibrate the cycle clock to wall-clock time. This is rough, but
+ * hopefully good enough for our purposes.
+ **/
+
+cpu_tick_t ticks_per_nanosecond  = 6*10e2;
+cpu_tick_t ticks_per_microsecond = 6*10e5;
+cpu_tick_t ticks_per_millisecond = 6*10e8;
+cpu_tick_t ticks_per_second      = 6*10e11;
+cpu_tick_t real_start_ticks = 0;
+cpu_tick_t virtual_start_ticks = 0;
+
+
+#define __usecs(t) (1e6*(long long)t.tv_sec + t.tv_usec)
+
+static long long timing_loop()
+{
+  struct timeval start_tv, end_tv;
+  long usec;
+  cpu_tick_t start_ticks, end_ticks;
+
+  while( 1 ) {
+    // start the counter right when the clock changes
+    gettimeofday(&start_tv, NULL);
+    usec = start_tv.tv_usec;
+    do {
+      gettimeofday(&start_tv, NULL);
+      GET_REAL_CPU_TICKS( start_ticks );
+    } while( start_tv.tv_usec == usec );
+
+    // now do the timing loop
+    do {
+      gettimeofday(&end_tv, NULL);
+      GET_REAL_CPU_TICKS( end_ticks );
+    } while( __usecs(end_tv) < __usecs(start_tv)+1000 );
+
+    if(__usecs(end_tv) == __usecs(start_tv)+1000)
+      break;
+  }
+  
+  return end_ticks - start_ticks;
+}
+
+
+void init_cycle_clock() __attribute__((constructor));
+void init_cycle_clock(void)
+{
+  static int init_done = 0;
+  int i;
+  long long val = 0;
+
+  if(init_done) return;
+  init_done = 1;
+
+#ifdef USE_PERFCTR
+  init_perfctr();
+#endif
+  
+  // collect some samples
+  for(i=0; i<10; i++) {
+    val += timing_loop();
+  }
+  val = val / 10;
+
+  ticks_per_second      = val * 1e3;
+  ticks_per_millisecond = val * 1e0;
+  ticks_per_microsecond = val / 1e3;
+  ticks_per_nanosecond  = val / 1e6;
+
+  GET_REAL_CPU_TICKS( real_start_ticks );
+  GET_CPU_TICKS( virtual_start_ticks );
+}
+
+
+
+
+/*
+
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "misc.h"
+#include "debug.h"
+
+#ifndef DEBUG_misc_c
+#undef debug
+#define debug(...)
+#undef tdebug
+#define tdebug(...)
+#endif
+
+
+long long current_usecs()
+{
+  struct timeval tv;
+  int rv;
+  rv = gettimeofday(&tv,NULL);
+  assert (rv == 0);
+
+  return ((long long)tv.tv_sec * 1000000) + tv.tv_usec;
+}
+
+*/
diff --git a/user/c3po/util/clock.h b/user/c3po/util/clock.h
new file mode 100644 (file)
index 0000000..3225887
--- /dev/null
@@ -0,0 +1,46 @@
+
+#ifndef CLOCK_H
+#define CLOCK_H
+
+typedef long long cpu_tick_t;
+
+#define GET_REAL_CPU_TICKS(Var)        __asm__ __volatile__ ("rdtsc" : "=A" (Var))
+// interestingly, without __volatile__, it's slower
+//#define GET_REAL_CPU_TICKS(Var)      __asm__ ("rdtsc" : "=A" (Var))
+
+
+#ifndef USE_PERFCTR
+
+# define TIMING_NOW_64(Var) GET_REAL_CPU_TICKS(Var)
+# define GET_CPU_TICKS(Var) GET_REAL_CPU_TICKS(Var)
+
+#else
+
+#include "libperfctr.h"
+extern struct vperfctr *clock_perfctr;
+#define TIMING_NOW_64(var) var = vperfctr_read_tsc(clock_perfctr)
+#define GET_CPU_TICKS(var) var = vperfctr_read_tsc(clock_perfctr)
+
+#endif
+
+
+extern cpu_tick_t ticks_per_second;
+extern cpu_tick_t ticks_per_millisecond;
+extern cpu_tick_t ticks_per_microsecond;
+extern cpu_tick_t ticks_per_nanosecond;
+extern cpu_tick_t real_start_ticks;
+extern cpu_tick_t virtual_start_ticks;
+
+
+static inline long long current_usecs()
+{
+  register cpu_tick_t ret;
+  GET_REAL_CPU_TICKS( ret );
+  return (ret / ticks_per_microsecond);
+}
+
+void init_cycle_clock(void);
+
+#endif
+
+
diff --git a/user/c3po/util/config.c b/user/c3po/util/config.c
new file mode 100644 (file)
index 0000000..3f4915d
--- /dev/null
@@ -0,0 +1,133 @@
+/**
+ * 
+ * Configure capriccio.  This is done via environment variables which are read at startup.
+ *
+ **/
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "debug.h"
+#include "config.h"
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+
+// define variables.  These should match config.h
+int conf_no_init_messages = FALSE;
+int conf_dump_blocking_graph = FALSE;
+int conf_dump_timing_info = FALSE;
+int conf_show_thread_stacks = FALSE;
+int conf_show_thread_details = FALSE;
+int conf_no_debug = FALSE;
+int conf_no_stacktrace = FALSE;
+int conf_no_statcollect = FALSE;
+
+long conf_new_stack_size = 128*1024;
+int conf_new_stack_kb_log2 = 7;
+
+static inline int bool_value(char *str)
+{
+  if(str == NULL) return 0;
+  if( atoi(str) ) return 1;
+  if( !strcasecmp(str,"true") ) return 1;
+  if( !strcasecmp(str,"yes") ) return 1;
+  if( !strcasecmp(str,"y") ) return 1;
+  return 0;
+}
+
+#define get_bool(env,var) {\
+  var = bool_value( getenv(__STRING(env)) ); \
+  if( !conf_no_init_messages ) \
+    output("%s=%s\n",__STRING(env), (var ? "yes" : "no")); \
+}  
+
+
+// for now, just read from env vars.  Add config file option later.
+void read_config(void) __attribute__((constructor));
+void read_config(void) {
+  static int read_config_done = 0;
+  
+  if( read_config_done ) return;
+  read_config_done = 1;
+
+  // BOOLEAN FLAGS
+  get_bool(CAPRICCIO_NO_INIT_MESSAGES, conf_no_init_messages);
+  get_bool(CAPRICCIO_NO_DEBUG, conf_no_debug);
+  get_bool(CAPRICCIO_NO_STACKTRACE, conf_no_stacktrace);
+  get_bool(CAPRICCIO_NO_STATCOLLECT, conf_no_statcollect);
+
+  get_bool(CAPRICCIO_DUMP_BLOCKING_GRAPH, conf_dump_blocking_graph);
+  get_bool(CAPRICCIO_DUMP_TIMING_INFO, conf_dump_timing_info);
+  get_bool(CAPRICCIO_SHOW_THREAD_DETAILS, conf_show_thread_details);
+
+  // NOTE: this is subbordinate to CAPRICCIO_SHOW_THREAD_DETAILS
+  get_bool(CAPRICCIO_SHOW_THREAD_STACKS, conf_show_thread_stacks);
+
+
+  // STACK SIZE
+  {
+    char *str;
+    str = getenv("CAPRICCIO_DEFAULT_STACK_SIZE");
+    if( str != NULL ) {
+      char *p;
+      int mult=0;
+      long val;
+    
+      // read the value
+      val = strtol(str,&p,0);
+      if( p == str )
+        fatal("Bad number format for CAPRICCIO_DEFAULT_STACK_SIZE: '%s'\n", str); 
+    
+      // read the units
+      while( isspace(*p) ) p++;
+      if( *p == '\0' )
+        mult = 1024; // default to KB
+      else if( *p == 'b' || *p == 'B' )
+        mult = 1;        
+      else if( *p == 'k' || *p == 'K' )
+        mult = 1024;
+      else if( *p == 'm' || *p == 'M' )
+        mult = 1024*1024;
+      else 
+        fatal("Bad units for CAPRICCIO_DEFAULT_STACK_SIZE: '%s'\n",str);
+    
+      // pick the next bigger power of 2
+      // FIXME: allow smaller than 1K?
+      // FIXME: allow not power of 2?
+      conf_new_stack_size = 1024;
+      conf_new_stack_kb_log2 = 0;
+      while( conf_new_stack_size < mult * val ) {
+        conf_new_stack_kb_log2++;
+        conf_new_stack_size = conf_new_stack_size << 1;
+      }
+    }
+
+    if( !conf_no_init_messages ) {
+      // show MB, if big enough, and evenly divisible
+      if( conf_new_stack_size > 1024*1024  &&  !(conf_new_stack_size & 0xFFFFF) )
+        output("CAPRICCIO_DEFAULT_STACKSIZE=%ldM\n",conf_new_stack_size/1024/1024);
+
+      // show KB, if big enough, and evenly divisible
+      else if( conf_new_stack_size > 1024  &&  !(conf_new_stack_size & 0x3FF) )
+        output("CAPRICCIO_DEFAULT_STACKSIZE=%ldK\n",conf_new_stack_size/1024);
+
+      // otherwise, show in bytes
+      else
+        output("CAPRICCIO_DEFAULT_STACKSIZE=%ldb\n",conf_new_stack_size);
+    }
+  }
+
+
+  // FIXME: scan environment for unrecognized CAPRICCIO_* env vars,
+  // and warn about likely misspelling.
+
+}
+
+
+
diff --git a/user/c3po/util/config.h b/user/c3po/util/config.h
new file mode 100644 (file)
index 0000000..5cc8e97
--- /dev/null
@@ -0,0 +1,26 @@
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+
+// configuration flags
+extern int conf_no_init_messages;
+extern int conf_dump_blocking_graph;
+extern int conf_dump_timing_info;
+extern int conf_show_thread_stacks;
+extern int conf_show_thread_details;
+extern int conf_no_debug;
+extern int conf_no_stacktrace;
+extern int conf_no_statcollect;
+
+extern long conf_new_stack_size;
+extern int conf_new_stack_kb_log2;
+
+// this is exported so we can force this to be initialized before the
+// threading library.  
+//
+// FIXME: This should really be handled by initializer priorities.
+void read_config(void);
+
+
+#endif // CONFIG_H
diff --git a/user/c3po/util/debug.c b/user/c3po/util/debug.c
new file mode 100644 (file)
index 0000000..c3cd444
--- /dev/null
@@ -0,0 +1,163 @@
+
+
+/**
+ * 
+ *  Simple debug routine
+ *
+ **/
+
+
+#include <ros/syscall.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "debug.h"
+#include "clock.h"
+#include "config.h"
+
+
+#define DBG_NO_TIMING -12312
+
+// NOTE: these are externally visible so the blocking graph stats
+// functions can subtract out debug print times.
+cpu_tick_t ticks_diff = 0;
+cpu_tick_t ticks_rdiff = 0;
+
+static cpu_tick_t vnow_prev = 0;
+static cpu_tick_t vrnow_prev = 0;
+
+void init_debug() __attribute__((constructor));
+void init_debug() {
+  init_cycle_clock();
+  vnow_prev  = virtual_start_ticks;
+  vrnow_prev = real_start_ticks;
+}
+
+static inline void output_aux(int tid, const char *func, const char *fmt, va_list ap)
+{
+  char str[200];
+  int len=0, ret;
+#ifdef USE_PERFCTR
+  cpu_tick_t now,  vnow,  after;
+#endif
+  cpu_tick_t rnow, vrnow, rafter;
+
+  // get times
+  GET_REAL_CPU_TICKS( rnow );
+  vrnow = rnow - ticks_rdiff;
+#ifdef USE_PERFCTR
+  GET_CPU_TICKS( now );
+  vnow = now - ticks_diff;
+#endif
+
+  // add the timing header
+  if( tid != DBG_NO_TIMING ) {
+#ifdef USE_PERFCTR    
+    ret = snprintf(str+len, sizeof(str)-1-len, "%5d %12lld us %12lld us (%+8lld cyc %+8lld cyc): ", 
+                   tid, 
+                   (vnow-virtual_start_ticks)/ticks_per_microsecond, 
+                   (vrnow-real_start_ticks)/ticks_per_microsecond, 
+                   vnow - vnow_prev,  
+                   vrnow - vrnow_prev);
+#else
+    ret = snprintf(str+len, sizeof(str)-1-len, "%5d %12lld us (%+8lld cyc): ", 
+                   tid, 
+                   (vrnow-real_start_ticks)/ticks_per_microsecond, 
+                   vrnow - vrnow_prev);
+#endif
+    assert(ret > 0);
+    len += ret;
+  }
+
+  // add the function name
+  if( func ) {
+    ret = snprintf(str+len, sizeof(str)-1-len, "%s() - ",func);
+    assert(ret > 0);
+    len += ret;
+  }
+
+  // add the message
+  ret = vsnprintf(str+len, sizeof(str)-1-len, fmt, ap);
+  assert(ret > 0);
+  len += ret;
+
+  // output the messag
+  syscall(SYS_write, 2, str, len);
+
+  // update timing info
+  vrnow_prev = vrnow;
+  GET_REAL_CPU_TICKS( rafter );
+  ticks_rdiff += (rafter - rnow);
+#ifdef USE_PERFCTR
+  vnow_prev = vnow;
+  GET_CPU_TICKS( after );
+  ticks_diff  += (after - now);
+#endif
+}
+
+void real_toutput(int tid, const char *func, const char *fmt, ...)
+{
+  va_list ap;
+  if (conf_no_debug)
+    return;
+