sys_waitpid() improvements (XCC)
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 30 Oct 2012 17:10:59 +0000 (10:10 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 30 Oct 2012 17:29:29 +0000 (10:29 -0700)
Cleans up the glibc fork/wait mess by supporting waitpid(-1).  This will
allow shells (busybox/ash) to do some decent job control with
backgrounding tasks.  Previously, we'd wait on our children in order,
instead of waiting on any available child.

This does not handle concurrent wait calls from the parent (even SCPs
could do async calls).  I'm working on that, and will have it in another
commit to serve as an example of the pain caused by concurrent process
management syscalls (whether due to async syscalls by an SCP or parallel
calls from an MCP).

Rebuild glibc.

kern/include/env.h
kern/include/process.h
kern/include/ros/bits/posix_signum.h
kern/include/ros/bits/syscall.h
kern/src/process.c
kern/src/syscall.c
tools/compilers/gcc-glibc/glibc-2.14.1-ros/sysdeps/ros/fork.c
tools/compilers/gcc-glibc/glibc-2.14.1-ros/sysdeps/ros/waitpid.c

index 546e55b..9bba2e2 100644 (file)
@@ -40,7 +40,7 @@ struct proc {
        pid_t ppid;                                     /* parent's pid, not a reference */
        struct proc_list children;      /* protected by the proc lock for now */
        int exitcode;                           /* exit() param or main() return value */
-       struct semaphore state_change;
+       struct cond_var child_wait;     /* signal for dying or o/w waitable child */
        uint32_t state;                         // Status of the process
        struct kref p_kref;             /* Refcnt */
        uint32_t env_flags;
index 7e3a984..d434774 100644 (file)
@@ -72,6 +72,7 @@ void proc_run_s(struct proc *p);
 void __proc_run_m(struct proc *p);
 void proc_restartcore(void);
 void proc_destroy(struct proc *p);
+void proc_signal_parent(struct proc *child);
 void proc_disown_child(struct proc *parent, struct proc *child);
 int proc_change_to_m(struct proc *p);
 void __proc_save_context_s(struct proc *p, struct trapframe *tf);
index d2ae3b0..b90ffa2 100644 (file)
 
 #define __SIGRTMIN     32
 #define __SIGRTMAX     (_NSIG - 1)
+
+/* Posix wait flags (glibc has a copy of these) */
+#define        WNOHANG         1
+#define        WUNTRACED       2
+
 #endif /* ROS_KERNEL */
 
 #endif /* ROS_INC_BITS_POSIX_SIGNUM_H */
index 0e28761..7d6c3ba 100644 (file)
@@ -20,7 +20,7 @@
 #define SYS_change_vcore                       14
 #define SYS_fork                                       15
 #define SYS_exec                                       16
-#define SYS_trywait                                    17
+#define SYS_waitpid                                    17
 #define SYS_mmap                                       18
 #define SYS_munmap                                     19
 #define SYS_mprotect                           20
index 2d6b302..e7f3f13 100644 (file)
@@ -259,7 +259,7 @@ error_t proc_alloc(struct proc **pp, struct proc *parent)
                p->ppid = 0;
        }
        TAILQ_INIT(&p->children);
-       init_sem(&p->state_change, 0);
+       cv_init(&p->child_wait);
        p->state = PROC_CREATED; /* shouldn't go through state machine for init */
        p->env_flags = 0;
        p->env_entry = 0; // cheating.  this really gets set later
@@ -752,10 +752,22 @@ void proc_destroy(struct proc *p)
        close_all_files(&p->open_files, FALSE);
        /* Tell the ksched about our death, and which cores we freed up */
        __sched_proc_destroy(p, pc_arr, nr_cores_revoked);
-       /* Signal our state change.  Assuming we only have one waiter right now. */
-       sleeper = __up_sem(&p->state_change, TRUE);
-       if (sleeper)
-               kthread_runnable(sleeper);
+       /* Tell our parent about our state change (to DYING) */
+       proc_signal_parent(p);
+}
+
+/* Can use this to signal anything that might cause a parent to wait on the
+ * child, such as termination, or (in the future) signals.  Change the state or
+ * whatever before calling. */
+void proc_signal_parent(struct proc *child)
+{
+       struct kthread *sleeper;
+       struct proc *parent = pid2proc(child->ppid);
+       if (!parent)
+               return;
+       cv_signal(&parent->child_wait);
+       /* if the parent was waiting, there's a __launch kthread KMSG out there */
+       proc_decref(parent);
 }
 
 /* Called when a parent is done with its child, and no longer wants to track the
index 6a7d11a..a1b0a40 100644 (file)
@@ -563,50 +563,134 @@ all_out:
        smp_idle();                             /* will reenable interrupts */
 }
 
+/* Helper, will attempt a particular wait on a proc.  Returns the pid of the
+ * process if we waited on it successfully, and the status will be passed back
+ * in ret_status (kernel memory).  Returns 0 if the wait failed.  Not
+ * idempotent.  Only handles DYING. */
+static pid_t try_wait(struct proc *parent, struct proc *child, int *ret_status,
+                      int options)
+{
+       if (child->state == PROC_DYING) {
+               *ret_status = child->exitcode;
+               /* allow the child to be fully cleaned up.  If we're the last ref, this
+                * will trigger a __proc_free() */
+               proc_disown_child(parent, child);
+               return child->pid;
+       }
+       return 0;
+}
+
+/* Waits on a particular child, returns the pid of the child waited on, and
+ * puts the ret status in *ret_status. */
+static pid_t wait_one(struct proc *parent, struct proc *child, int *ret_status,
+                      int options)
+{
+       pid_t retval = 0;       /* 0 means no pids were waited on */
+       if ((retval = try_wait(parent, child, ret_status, options)))
+               return retval;
+       if (options & WNOHANG)
+               return 0;
+       cv_lock(&parent->child_wait);
+       /* Block til there is some activity.  Any child can wake us up, but we
+        * check for the particular child we care about */
+       while (!(retval = try_wait(parent, child, ret_status, options))) {
+               cpu_relax();
+               cv_wait(&parent->child_wait);
+               /* If we're dying, then we don't need to worry about waiting.  We don't
+                * do this yet, but we'll need this outlet when we deal with orphaned
+                * children and having init inherit them. */
+               if (parent->state == PROC_DYING)
+                       break;
+       }
+       cv_unlock(&parent->child_wait);
+       return retval;
+}
+
+/* Helper, like try_wait, but attempts a wait on all children, returning the
+ * specific PID we waited on */
+static pid_t try_wait_any(struct proc *parent, int *ret_status, int options)
+{
+       struct proc *i, *temp;
+       pid_t retval = 0;
+       TAILQ_FOREACH_SAFE(i, &parent->children, sibling_link, temp) {
+               if ((retval = try_wait(parent, i, ret_status, options)))
+                       return retval;
+       }
+       return retval;
+}
+
+/* Waits on any child, returns the pid of the child waited on, and puts the ret
+ * status in *ret_status.  Is basically a waitpid(-1, ... ); */
+static pid_t wait_any(struct proc *parent, int *ret_status, int options)
+{
+       pid_t retval = 0;       /* 0 means no pids were waited on */
+       if (TAILQ_EMPTY(&parent->children))
+               return 0;
+       if ((retval = try_wait_any(parent, ret_status, options)))
+               return retval;
+       if (options & WNOHANG)
+               return 0;
+       cv_lock(&parent->child_wait);
+       /* Block til there is some activity.  Any child can wake us up.  We scan
+        * with a try_wait, but if we have a lot of children, we could try to
+        * optimize this. */
+       while (!(retval = try_wait_any(parent, ret_status, options))) {
+               cpu_relax();
+               cv_wait(&parent->child_wait);
+               /* If we're dying, then we don't need to worry about waiting.  We don't
+                * do this yet, but we'll need this outlet when we deal with orphaned
+                * children and having init inherit them. */
+               if (parent->state == PROC_DYING)
+                       break;
+       }
+       cv_unlock(&parent->child_wait);
+       return retval;
+}
+
 /* Note: we only allow waiting on children (no such thing as threads, for
  * instance).  Right now we only allow waiting on termination (not signals),
  * and we don't have a way for parents to disown their children (such as
- * ignoring SIGCHLD, see man 2 waitpid's Notes). */
-static int sys_trywait(struct proc *parent, pid_t pid, int *status)
-{
-       /* TODO:
-        * - WAIT should handle stop and start via signal too
-        *      - what semantics?  need a wait for every change to state?  etc.
-        * - should have an option for WNOHANG, and a bunch of other things.
-        * - think about what functions we want to work with MCPS
-        *   */
-       struct proc* child = pid2proc(pid);
-       int ret = -1;
-       int ret_status;
-
+ * ignoring SIGCHLD, see man 2 waitpid's Notes).
+ *
+ * We don't bother with stop/start signals here, though we can probably build
+ * it in the helper above.
+ *
+ * Returns the pid of who we waited on, or -1 on error, or 0 if we couldn't
+ * wait (WNOHANG). */
+static pid_t sys_waitpid(struct proc *parent, pid_t pid, int *status,
+                         int options)
+{
+       struct proc *child;
+       pid_t retval = 0;
+       int ret_status = 0;
+
+       /* -1 is the signal for 'any child' */
+       if (pid == -1) {
+               retval = wait_any(parent, &ret_status, options);
+               goto out;
+       }
+       child = pid2proc(pid);
        if (!child) {
                set_errno(ECHILD);      /* ECHILD also used for no proc */
+               retval = -1;
                goto out;
        }
        if (!(parent->pid == child->ppid)) {
                set_errno(ECHILD);
+               retval = -1;
                goto out_decref;
        }
-       /* Block til there is some activity (DYING for now) */
-       if (!(child->state == PROC_DYING)) {
-               sleep_on(&child->state_change);
-               cpu_relax();
-       }
-       assert(child->state == PROC_DYING);
-       ret_status = child->exitcode;
-       /* wait succeeded - need to clean up the proc. */
-       proc_disown_child(parent, child);
-       /* fall through */
-out_success:
-       /* ignoring the retval here - don't care if they have a bad addr. */
-       memcpy_to_user(parent, status, &ret_status, sizeof(ret_status));
-       printd("[PID %d] waited for PID %d (code %d)\n", parent->pid,
-              pid, ret_status);
-       ret = 0;
+       retval = wait_one(parent, child, &ret_status, options);
+       /* fall-through */
 out_decref:
        proc_decref(child);
 out:
-       return ret;
+       /* ignoring / don't care about memcpy's retval here. */
+       if (retval > 0)
+               memcpy_to_user(parent, status, &ret_status, sizeof(ret_status));
+       printd("[PID %d] waited for PID %d, got retval %d (code %d)\n", parent->pid,
+              pid, retval, ret_status);
+       return retval;
 }
 
 /************** Memory Management Syscalls **************/
@@ -1379,7 +1463,7 @@ const static struct sys_table_entry syscall_table[] = {
        [SYS_change_vcore] = {(syscall_t)sys_change_vcore, "change_vcore"},
        [SYS_fork] = {(syscall_t)sys_fork, "fork"},
        [SYS_exec] = {(syscall_t)sys_exec, "exec"},
-       [SYS_trywait] = {(syscall_t)sys_trywait, "trywait"},
+       [SYS_waitpid] = {(syscall_t)sys_waitpid, "waitpid"},
        [SYS_mmap] = {(syscall_t)sys_mmap, "mmap"},
        [SYS_munmap] = {(syscall_t)sys_munmap, "munmap"},
        [SYS_mprotect] = {(syscall_t)sys_mprotect, "mprotect"},
index 265195b..adcdb22 100644 (file)
 
 #include <errno.h>
 #include <unistd.h>
+#include <sys/types.h>
 #include <stdlib.h>
-#include <bits/libc-lock.h>
 #include <ros/syscall.h>
 
-__libc_lock_define(,__fork_lock);
-int* child_list = NULL;
-int  child_list_capacity = 0;
-int  child_list_size = 0;
-
 /* Clone the calling process, creating an exact copy.
    Return -1 for errors, 0 to the new process,
    and the process ID of the new process to the old process.  */
-int
-__fork ()
+pid_t __fork(void)
 {
-  int ret = -1;
-  __libc_lock_lock(__fork_lock);
-
-  if(child_list_size == child_list_capacity)
-  {
-    int newcap = child_list_capacity ? 2*child_list_capacity : 1;
-    int* tmp = realloc(child_list,newcap*sizeof(int));
-    if(!tmp)
-      goto out;
-    child_list_capacity = newcap;
-    child_list = tmp;
-  }
-
-  ret = ros_syscall(SYS_fork, 0, 0, 0, 0, 0, 0);
-  if(ret > 0)
-    child_list[child_list_size++] = ret;
-
-out:
-  __libc_lock_unlock(__fork_lock);
-  return ret;
+       return ros_syscall(SYS_fork, 0, 0, 0, 0, 0, 0);
 }
 libc_hidden_def (__fork)
 weak_alias (__fork, fork)
index b829445..c5de9f8 100644 (file)
@@ -19,8 +19,6 @@
 #include <errno.h>
 #include <sys/wait.h>
 #include <sys/types.h>
-#include <sched.h>
-#include <bits/libc-lock.h>
 #include <ros/syscall.h>
 
 /* Wait for a child matching PID to die.
    return PID and store the dead child's status in STAT_LOC.
    Return (pid_t) -1 for errors.  If the WUNTRACED bit is set in OPTIONS,
    return status for stopped children; otherwise don't.  */
-pid_t
-__libc_waitpid (pid_t pid, int *stat_loc, int options)
+pid_t __libc_waitpid(pid_t pid, int *stat_loc, int options)
 {
-  if ((options & ~(WNOHANG|WUNTRACED)) != 0)
-  {
-    __set_errno (EINVAL);
-    return (pid_t) -1;
-  }
-
-  int s;
-  if(stat_loc == NULL)
-    stat_loc = &s;
-
-  int ret = -1;
-  __libc_lock_define(extern,__fork_lock);
-  __libc_lock_lock(__fork_lock);
-
-  extern volatile int child_list_size;
-  extern int* child_list;
-  while(child_list_size)
-  {
-    for(int i = 0; i < child_list_size; i++)
-    {
-      if(pid == -1 || child_list[i] == pid)
-      {
-        ret = ros_syscall(SYS_trywait, child_list[i], stat_loc, 0, 0, 0, 0);
-        if(ret == 0)
-        {
-          ret = child_list[i];
-          for(int j = i+1, sz = child_list_size; j < sz; j++)
-            child_list[j-1] = child_list[j];
-          child_list_size--;
-          goto out;
-        }
-      }
-    }
-    __libc_lock_unlock(__fork_lock);
-    sched_yield();
-    __libc_lock_lock(__fork_lock);
-  }
-  errno = ECHILD;
-  
-out:
-  __libc_lock_unlock(__fork_lock);
-  return ret;
+       return ros_syscall(SYS_waitpid, pid, stat_loc, options, 0, 0, 0);
 }
 weak_alias (__libc_waitpid, __waitpid)
 libc_hidden_weak (__waitpid)