kernel - Implement td_limit reflection of p_limit
authorMatthew Dillon <dillon@apollo.backplane.com>
Fri, 20 Apr 2018 00:52:53 +0000 (17:52 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Fri, 20 Apr 2018 00:52:53 +0000 (17:52 -0700)
* Reflect proc->p_limit onto thread->td_limit to allow lockless
  rlimits testing.

* Significantly improves performance for rlimits-testing-centric
  operations such as for dup() and dup2().

* Also fixes performance issues across processes when the plimit
  structure is shared across many processes.  In this situation,
  unnecessary locking conflicts developed due to the high level
  of sharing across what would normally be considered distinct
  processes.

Suggested-by: mjg
sys/kern/kern_descrip.c
sys/kern/kern_exit.c
sys/kern/kern_plimit.c
sys/kern/kern_synch.c
sys/sys/resourcevar.h
sys/sys/thread.h

index 402f845..95ab331 100644 (file)
@@ -1,8 +1,8 @@
 /*
- * Copyright (c) 2005 The DragonFly Project.  All rights reserved.
+ * Copyright (c) 2005-2018 The DragonFly Project.  All rights reserved.
  * 
  * This code is derived from software contributed to The DragonFly Project
- * by Jeffrey Hsu.
+ * by Jeffrey Hsu and Matthew Dillon.
  * 
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -177,6 +177,26 @@ fp2filelist(const struct file *fp)
        return &filelist_heads[i];
 }
 
+static __inline
+struct plimit *
+readplimits(struct proc *p)
+{
+       thread_t td = curthread;
+       struct plimit *limit;
+
+       limit = td->td_limit;
+       if (limit != p->p_limit) {
+               spin_lock_shared(&p->p_spin);
+               limit = p->p_limit;
+               atomic_add_int(&limit->p_refcnt, 1);
+               spin_unlock_shared(&p->p_spin);
+               if (td->td_limit)
+                       plimit_free(td->td_limit);
+               td->td_limit = limit;
+       }
+       return limit;
+}
+
 /*
  * System calls on descriptors.
  */
@@ -184,15 +204,13 @@ int
 sys_getdtablesize(struct getdtablesize_args *uap) 
 {
        struct proc *p = curproc;
-       struct plimit *limit = p->p_limit;
+       struct plimit *limit = readplimits(p);
        int dtsize;
 
-       spin_lock(&limit->p_spin);
        if (limit->pl_rlimit[RLIMIT_NOFILE].rlim_cur > INT_MAX)
                dtsize = INT_MAX;
        else
                dtsize = (int)limit->pl_rlimit[RLIMIT_NOFILE].rlim_cur;
-       spin_unlock(&limit->p_spin);
 
        if (dtsize > maxfilesperproc)
                dtsize = maxfilesperproc;
@@ -523,6 +541,7 @@ kern_dup(int flags, int old, int new, int *res)
 {
        struct thread *td = curthread;
        struct proc *p = td->td_proc;
+       struct plimit *limit = readplimits(p);
        struct filedesc *fdp = p->p_fd;
        struct file *fp;
        struct file *delfp;
@@ -541,10 +560,10 @@ kern_dup(int flags, int old, int new, int *res)
         * NOTE: maxfilesperuser is not applicable to dup()
         */
 retry:
-       if (p->p_rlimit[RLIMIT_NOFILE].rlim_cur > INT_MAX)
+       if (limit->pl_rlimit[RLIMIT_NOFILE].rlim_cur > INT_MAX)
                dtsize = INT_MAX;
        else
-               dtsize = (int)p->p_rlimit[RLIMIT_NOFILE].rlim_cur;
+               dtsize = (int)limit->pl_rlimit[RLIMIT_NOFILE].rlim_cur;
        if (dtsize > maxfilesperproc)
                dtsize = maxfilesperproc;
        if (dtsize < minfilesperproc)
@@ -1198,6 +1217,7 @@ fdreserve_locked(struct filedesc *fdp, int fd, int incr)
 int
 fdalloc(struct proc *p, int want, int *result)
 {
+       struct plimit *limit = readplimits(p);
        struct filedesc *fdp = p->p_fd;
        struct uidinfo *uip;
        int fd, rsize, rsum, node, lim;
@@ -1206,12 +1226,10 @@ fdalloc(struct proc *p, int want, int *result)
         * Check dtable size limit
         */
        *result = -1;   /* avoid gcc warnings */
-       spin_lock(&p->p_limit->p_spin);
-       if (p->p_rlimit[RLIMIT_NOFILE].rlim_cur > INT_MAX)
+       if (limit->pl_rlimit[RLIMIT_NOFILE].rlim_cur > INT_MAX)
                lim = INT_MAX;
        else
-               lim = (int)p->p_rlimit[RLIMIT_NOFILE].rlim_cur;
-       spin_unlock(&p->p_limit->p_spin);
+               lim = (int)limit->pl_rlimit[RLIMIT_NOFILE].rlim_cur;
 
        if (lim > maxfilesperproc)
                lim = maxfilesperproc;
@@ -1321,16 +1339,15 @@ found:
 int
 fdavail(struct proc *p, int n)
 {
+       struct plimit *limit = readplimits(p);
        struct filedesc *fdp = p->p_fd;
        struct fdnode *fdnode;
        int i, lim, last;
 
-       spin_lock(&p->p_limit->p_spin);
-       if (p->p_rlimit[RLIMIT_NOFILE].rlim_cur > INT_MAX)
+       if (limit->pl_rlimit[RLIMIT_NOFILE].rlim_cur > INT_MAX)
                lim = INT_MAX;
        else
-               lim = (int)p->p_rlimit[RLIMIT_NOFILE].rlim_cur;
-       spin_unlock(&p->p_limit->p_spin);
+               lim = (int)limit->pl_rlimit[RLIMIT_NOFILE].rlim_cur;
 
        if (lim > maxfilesperproc)
                lim = maxfilesperproc;
index 69703f3..baa1fcb 100644 (file)
@@ -684,12 +684,19 @@ lwp_exit(int masterexit, void *waddr)
        kqueue_terminate(&lp->lwp_kqueue);
 
        /*
-        * Clean up any syscall-cached ucred
+        * Clean up any syscall-cached ucred or rlimit.
         */
        if (td->td_ucred) {
                crfree(td->td_ucred);
                td->td_ucred = NULL;
        }
+       if (td->td_limit) {
+               struct plimit *rlimit;
+
+               rlimit = td->td_limit;
+               td->td_limit = NULL;
+               plimit_free(rlimit);
+        }
 
        /*
         * Nobody actually wakes us when the lock
index c429ef2..8e052ab 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2006,2017 The DragonFly Project.  All rights reserved.
+ * Copyright (c) 2006,2017,2018 The DragonFly Project.  All rights reserved.
  * 
  * This code is derived from software contributed to The DragonFly Project
  * by Matthew Dillon <dillon@backplane.com>
@@ -86,6 +86,26 @@ static MALLOC_DEFINE(M_PLIMIT, "plimit", "resource limits");
 
 static void plimit_copy(struct plimit *olimit, struct plimit *nlimit);
 
+static __inline
+struct plimit *
+readplimits(struct proc *p)
+{
+       thread_t td = curthread;
+       struct plimit *limit;
+
+       limit = td->td_limit;
+       if (limit != p->p_limit) {
+               spin_lock_shared(&p->p_spin);
+               limit = p->p_limit;
+               atomic_add_int(&limit->p_refcnt, 1);
+               spin_unlock_shared(&p->p_spin);
+               if (td->td_limit)
+                       plimit_free(td->td_limit);
+               td->td_limit = limit;
+       }
+       return limit;
+}
+
 /*
  * Initialize proc0's plimit structure.  All later plimit structures
  * are inherited through fork.
@@ -381,8 +401,8 @@ kern_getrlimit(u_int which, struct rlimit *limp)
         if (which >= RLIM_NLIMITS)
                 return (EINVAL);
 
-       limit = p->p_limit;
-        *limp = p->p_rlimit[which];
+       limit = readplimits(p);
+        *limp = limit->pl_rlimit[which];
 
         return (0);
 }
@@ -392,11 +412,14 @@ kern_getrlimit(u_int which, struct rlimit *limp)
  * code for the caller to perform.
  */
 int
-plimit_testcpulimit(struct plimit *limit, u_int64_t ttime)
+plimit_testcpulimit(struct proc *p, u_int64_t ttime)
 {
+       struct plimit *limit;
        struct rlimit *rlim;
        int mode;
 
+       limit = readplimits(p);
+
        /*
         * Initial tests without the spinlock.  This is the fast path.
         * Any 32/64 bit glitches will fall through and retest with
index dec178c..d462d9b 100644 (file)
@@ -300,7 +300,7 @@ schedcpu_resource(struct proc *p, void *data __unused)
                }
        }
 
-       switch(plimit_testcpulimit(p->p_limit, ttime)) {
+       switch(plimit_testcpulimit(p, ttime)) {
        case PLIMIT_TESTCPU_KILL:
                killproc(p, "exceeded maximum CPU limit");
                break;
index 4bd2b7c..d832936 100644 (file)
@@ -78,12 +78,13 @@ struct krate {
  * is moderately large but changes infrequently, it is normally
  * shared copy-on-write after forks.
  *
- * Threaded programs force PLIMITF_EXCLUSIVE to prevent the proc->p_limit
- * pointer from changing out from under threaded access.
+ * Threaded programs cache p_limit in the thread structure, allowing
+ * lockless read access.
  *
  * p_refcnt can change often, don't force cache mastership changes for
  * the rest of the (usually read only) data structure.  Place p_refcnt
- * in its own cache line.
+ * in its own cache line.  The rest of the structure is stable as long
+ * as the caller has a ref.
  */
 struct plimit {
        struct  rlimit pl_rlimit[RLIM_NLIMITS];
@@ -144,7 +145,7 @@ void plimit_init0(struct plimit *);
 struct plimit *plimit_fork(struct proc *);
 u_int64_t plimit_getadjvalue(int i);
 void plimit_lwp_fork(struct proc *);
-int plimit_testcpulimit(struct plimit *, u_int64_t);
+int plimit_testcpulimit(struct proc *, u_int64_t);
 void plimit_modify(struct proc *, int, struct rlimit *);
 void plimit_free(struct plimit *);
 
index e5f2b4f..7e6f1c8 100644 (file)
@@ -255,7 +255,7 @@ struct thread {
     __uint64_t td_sticks;      /* Statclock hits in system mode (uS) */
     __uint64_t td_iticks;      /* Statclock hits processing intr (uS) */
     int                td_locks;       /* lockmgr lock debugging */
-    void       *td_unused01;   /* (future I/O scheduler heuristic) */
+    struct plimit *td_limit;   /* synchronized from proc->p_limit */
     int                td_refs;        /* hold position in gd_tdallq / hold free */
     int                td_nest_count;  /* prevent splz nesting */
     u_int      td_contended;   /* token contention count */
@@ -270,7 +270,7 @@ struct thread {
     struct timeval td_start;   /* start time for a thread/process */
     char       td_comm[MAXCOMLEN+1]; /* typ 16+1 bytes */
     struct thread *td_preempted; /* we preempted this thread */
-    struct ucred *td_ucred;            /* synchronized from p_ucred */
+    struct ucred *td_ucred;    /* synchronized from proc->p_ucred */
     void        *td_vmm;       /* vmm private data */
     lwkt_tokref_t td_toks_have;                /* tokens we own */
     lwkt_tokref_t td_toks_stop;                /* tokens we want */