]> git.neil.brown.name Git - history.git/commitdiff
[PATCH] Futexes IV (Fast Lightweight Userspace Semaphores)
authorRusty Russell <rusty@rustcorp.com.au>
Sun, 10 Mar 2002 03:50:14 +0000 (19:50 -0800)
committerLinus Torvalds <torvalds@home.transmeta.com>
Sun, 10 Mar 2002 03:50:14 +0000 (19:50 -0800)
Fast user-space mutex implementation, allowing user space to do all
of the normal handling, with a minimal fallback to kernel space for
when there is lock contention.

The kernel space implementation does not keep any per-lock data
structures, but instead does a fast hash on the physical page and offset
of the user-space lock when contended.  Thus no build/teardown costs, or
any scalability costs wrt metadata.

Updated syscall numbers for 2.5.6, and changed FUTEX_UP/DOWN definitions
to be more logical for future expansions (eg.  r/w).

14 files changed:
arch/i386/kernel/entry.S
arch/ppc/kernel/misc.S
include/asm-i386/mman.h
include/asm-i386/unistd.h
include/asm-ppc/mman.h
include/asm-ppc/unistd.h
include/linux/futex.h [new file with mode: 0644]
include/linux/hash.h [new file with mode: 0644]
include/linux/mmzone.h
kernel/Makefile
kernel/futex.c [new file with mode: 0644]
mm/filemap.c
mm/mprotect.c
mm/page_alloc.c

index cea5ec00e4f40534252325fd06ccbe8d10791412..92c58397c71be18f0e4687931d69d14cebb766d9 100644 (file)
@@ -717,6 +717,7 @@ ENTRY(sys_call_table)
        .long SYMBOL_NAME(sys_fremovexattr)
        .long SYMBOL_NAME(sys_tkill)
        .long SYMBOL_NAME(sys_sendfile64)
+       .long SYMBOL_NAME(sys_futex)            /* 240 */
 
        .rept NR_syscalls-(.-sys_call_table)/4
                .long SYMBOL_NAME(sys_ni_syscall)
index 0b6e58a1b645e0eb186c18f3eac4b870f8256a1b..ad9f98f6e6a5a4d9c9e12c45fe7042939cc9ce35 100644 (file)
@@ -1289,6 +1289,7 @@ _GLOBAL(sys_call_table)
        .long sys_removexattr
        .long sys_lremovexattr
        .long sys_fremovexattr  /* 220 */
+       .long sys_futex
        .rept NR_syscalls-(.-sys_call_table)/4
                .long sys_ni_syscall
        .endr
index f953c436ce511648e0d8b7b1d951bd62155b2962..85566a0029ca66b98cbf8de039078d3cf37d455a 100644 (file)
@@ -4,6 +4,7 @@
 #define PROT_READ      0x1             /* page can be read */
 #define PROT_WRITE     0x2             /* page can be written */
 #define PROT_EXEC      0x4             /* page can be executed */
+#define PROT_SEM       0x8             /* page may be used for atomic ops */
 #define PROT_NONE      0x0             /* page can not be accessed */
 
 #define MAP_SHARED     0x01            /* Share changes */
index d87ebc3ceff62ea46e64762dbe9a884899bfeb60..84af0bcfdfe3c4125b7cb6e50f890c79be82ab3d 100644 (file)
 #define __NR_fremovexattr      237
 #define __NR_tkill             238
 #define __NR_sendfile64                239
+#define __NR_futex             240
 
 /* user-visible error numbers are in the range -1 - -124: see <asm-i386/errno.h> */
 
index 04206f671b85291991da3a17414954eea638cd2e..eb7aba2be87a3f806177dd6b52dc4cbdbf1a8b56 100644 (file)
@@ -7,6 +7,7 @@
 #define PROT_READ      0x1             /* page can be read */
 #define PROT_WRITE     0x2             /* page can be written */
 #define PROT_EXEC      0x4             /* page can be executed */
+#define PROT_SEM       0x8             /* page may be used for atomic ops */
 #define PROT_NONE      0x0             /* page can not be accessed */
 
 #define MAP_SHARED     0x01            /* Share changes */
index 3e1bd01ed08f72cd15df8c974e0d1baa2c0f6c10..d881d0ed64c794eefa126f16dffcd17c63dc2b2f 100644 (file)
 #define __NR_removexattr       218
 #define __NR_lremovexattr      219
 #define __NR_fremovexattr      220
+#define __NR_futex             221
 
 #define __NR(n)        #n
 
diff --git a/include/linux/futex.h b/include/linux/futex.h
new file mode 100644 (file)
index 0000000..5ebcc87
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef _LINUX_FUTEX_H
+#define _LINUX_FUTEX_H
+
+/* Second argument to futex syscall */
+#define FUTEX_UP (0)
+#define FUTEX_DOWN (1)
+
+#endif
diff --git a/include/linux/hash.h b/include/linux/hash.h
new file mode 100644 (file)
index 0000000..acf17bb
--- /dev/null
@@ -0,0 +1,58 @@
+#ifndef _LINUX_HASH_H
+#define _LINUX_HASH_H
+/* Fast hashing routine for a long.
+   (C) 2002 William Lee Irwin III, IBM */
+
+/*
+ * Knuth recommends primes in approximately golden ratio to the maximum
+ * integer representable by a machine word for multiplicative hashing.
+ * Chuck Lever verified the effectiveness of this technique:
+ * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf
+ *
+ * These primes are chosen to be bit-sparse, that is operations on
+ * them can use shifts and additions instead of multiplications for
+ * machines where multiplications are slow.
+ */
+#if BITS_PER_LONG == 32
+/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */
+#define GOLDEN_RATIO_PRIME 0x9e370001UL
+#elif BITS_PER_LONG == 64
+/*  2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */
+#define GOLDEN_RATIO_PRIME 0x9e37fffffffc0001UL
+#else
+#error Define GOLDEN_RATIO_PRIME for your wordsize.
+#endif
+
+static inline unsigned long hash_long(unsigned long val, unsigned int bits)
+{
+       unsigned long hash = val;
+
+#if BITS_PER_LONG == 64
+       /*  Sigh, gcc can't optimise this alone like it does for 32 bits. */
+       unsigned long n = hash;
+       n <<= 18;
+       hash -= n;
+       n <<= 33;
+       hash -= n;
+       n <<= 3;
+       hash += n;
+       n <<= 3;
+       hash -= n;
+       n <<= 4;
+       hash += n;
+       n <<= 2;
+       hash += n;
+#else
+       /* On some cpus multiply is faster, on others gcc will do shifts */
+       hash *= GOLDEN_RATIO_PRIME;
+#endif
+
+       /* High bits are more random, so use them. */
+       return hash >> (BITS_PER_LONG - bits);
+}
+       
+static inline unsigned long hash_ptr(void *ptr, unsigned int bits)
+{
+       return hash_long((unsigned long)ptr, bits);
+}
+#endif /* _LINUX_HASH_H */
index ff810df6c8ee29d065bbeaf198b2a584fe6b1944..bfe502d98f2a65cb5a3cad399518c1fcdd78349c 100644 (file)
@@ -51,8 +51,7 @@ typedef struct zone_struct {
        /*
         * wait_table           -- the array holding the hash table
         * wait_table_size      -- the size of the hash table array
-        * wait_table_shift     -- wait_table_size
-        *                              == BITS_PER_LONG (1 << wait_table_bits)
+        * wait_table_bits      -- wait_table_size == (1 << wait_table_bits)
         *
         * The purpose of all these is to keep track of the people
         * waiting for a page to become available and make them
@@ -75,7 +74,7 @@ typedef struct zone_struct {
         */
        wait_queue_head_t       * wait_table;
        unsigned long           wait_table_size;
-       unsigned long           wait_table_shift;
+       unsigned long           wait_table_bits;
 
        /*
         * Discontig memory support fields.
index 34fdbca1730c4d27bd969126280d5845547ba4ba..54a895656270871f5b5d36c81661f74131d31e38 100644 (file)
@@ -15,7 +15,7 @@ export-objs = signal.o sys.o kmod.o context.o ksyms.o pm.o exec_domain.o \
 obj-y     = sched.o dma.o fork.o exec_domain.o panic.o printk.o \
            module.o exit.o itimer.o info.o time.o softirq.o resource.o \
            sysctl.o acct.o capability.o ptrace.o timer.o user.o \
-           signal.o sys.o kmod.o context.o 
+           signal.o sys.o kmod.o context.o futex.o
 
 obj-$(CONFIG_UID16) += uid16.o
 obj-$(CONFIG_MODULES) += ksyms.o
diff --git a/kernel/futex.c b/kernel/futex.c
new file mode 100644 (file)
index 0000000..4ef5ff0
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ *  Fast Userspace Mutexes (which I call "Futexes!").
+ *  (C) Rusty Russell, IBM 2002
+ *
+ *  Thanks to Ben LaHaise for yelling "hashed waitqueues" loudly
+ *  enough at me, Linus for the original (flawed) idea, Matthew
+ *  Kirkwood for proof-of-concept implementation.
+ *
+ *  "The futexes are also cursed."
+ *  "But they come in a choice of three flavours!"
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/hash.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/futex.h>
+#include <linux/highmem.h>
+#include <asm/atomic.h>
+
+/* These mutexes are a very simple counter: the winner is the one who
+   decrements from 1 to 0.  The counter starts at 1 when the lock is
+   free.  A value other than 0 or 1 means someone may be sleeping.
+   This is simple enough to work on all architectures, but has the
+   problem that if we never "up" the semaphore it could eventually
+   wrap around. */
+
+/* FIXME: This may be way too small. --RR */
+#define FUTEX_HASHBITS 6
+
+/* We use this instead of a normal wait_queue_t, so we can wake only
+   the relevent ones (hashed queues may be shared) */
+struct futex_q {
+       struct list_head list;
+       struct task_struct *task;
+       /* Page struct and offset within it. */
+       struct page *page;
+       unsigned int offset;
+};
+
+/* The key for the hash is the address + index + offset within page */
+static struct list_head futex_queues[1<<FUTEX_HASHBITS];
+static spinlock_t futex_lock = SPIN_LOCK_UNLOCKED;
+
+static inline struct list_head *hash_futex(struct page *page,
+                                          unsigned long offset)
+{
+       unsigned long h;
+
+       /* struct page is shared, so we can hash on its address */
+       h = (unsigned long)page + offset;
+       return &futex_queues[hash_long(h, FUTEX_HASHBITS)];
+}
+
+static inline void wake_one_waiter(struct list_head *head,
+                                  struct page *page,
+                                  unsigned int offset)
+{
+       struct list_head *i;
+
+       spin_lock(&futex_lock);
+       list_for_each(i, head) {
+               struct futex_q *this = list_entry(i, struct futex_q, list);
+
+               if (this->page == page && this->offset == offset) {
+                       wake_up_process(this->task);
+                       break;
+               }
+       }
+       spin_unlock(&futex_lock);
+}
+
+/* Add at end to avoid starvation */
+static inline void queue_me(struct list_head *head,
+                           struct futex_q *q,
+                           struct page *page,
+                           unsigned int offset)
+{
+       q->task = current;
+       q->page = page;
+       q->offset = offset;
+
+       spin_lock(&futex_lock);
+       list_add_tail(&q->list, head);
+       spin_unlock(&futex_lock);
+}
+
+static inline void unqueue_me(struct futex_q *q)
+{
+       spin_lock(&futex_lock);
+       list_del(&q->list);
+       spin_unlock(&futex_lock);
+}
+
+/* Get kernel address of the user page and pin it. */
+static struct page *pin_page(unsigned long page_start)
+{
+       struct mm_struct *mm = current->mm;
+       struct page *page;
+       int err;
+
+       down_read(&mm->mmap_sem);
+       err = get_user_pages(current, current->mm, page_start,
+                            1 /* one page */,
+                            1 /* writable */,
+                            0 /* don't force */,
+                            &page,
+                            NULL /* don't return vmas */);
+       up_read(&mm->mmap_sem);
+
+       if (err < 0)
+               return ERR_PTR(err);
+       return page;
+}
+
+/* Try to decrement the user count to zero. */
+static int decrement_to_zero(struct page *page, unsigned int offset)
+{
+       atomic_t *count;
+       int ret = 0;
+
+       count = kmap(page) + offset;
+       /* If we take the semaphore from 1 to 0, it's ours.  If it's
+           zero, decrement anyway, to indicate we are waiting.  If
+           it's negative, don't decrement so we don't wrap... */
+       if (atomic_read(count) >= 0 && atomic_dec_and_test(count))
+               ret = 1;
+       kunmap(page);
+       return ret;
+}
+
+/* Simplified from arch/ppc/kernel/semaphore.c: Paul M. is a genius. */
+static int futex_down(struct list_head *head, struct page *page, int offset)
+{
+       int retval = 0;
+       struct futex_q q;
+
+       current->state = TASK_INTERRUPTIBLE;
+       queue_me(head, &q, page, offset);
+
+       while (!decrement_to_zero(page, offset)) {
+               if (signal_pending(current)) {
+                       retval = -EINTR;
+                       break;
+               }
+               schedule();
+               current->state = TASK_INTERRUPTIBLE;
+       }
+       current->state = TASK_RUNNING;
+       unqueue_me(&q);
+       /* If we were signalled, we might have just been woken: we
+          must wake another one.  Otherwise we need to wake someone
+          else (if they are waiting) so they drop the count below 0,
+          and when we "up" in userspace, we know there is a
+          waiter. */
+       wake_one_waiter(head, page, offset);
+       return retval;
+}
+
+static int futex_up(struct list_head *head, struct page *page, int offset)
+{
+       atomic_t *count;
+
+       count = kmap(page) + offset;
+       atomic_set(count, 1);
+       smp_wmb();
+       kunmap(page);
+       wake_one_waiter(head, page, offset);
+       return 0;
+}
+
+asmlinkage int sys_futex(void *uaddr, int op)
+{
+       int ret;
+       unsigned long pos_in_page;
+       struct list_head *head;
+       struct page *page;
+
+       pos_in_page = ((unsigned long)uaddr) % PAGE_SIZE;
+
+       /* Must be "naturally" aligned, and not on page boundary. */
+       if ((pos_in_page % __alignof__(atomic_t)) != 0
+           || pos_in_page + sizeof(atomic_t) > PAGE_SIZE)
+               return -EINVAL;
+
+       /* Simpler if it doesn't vanish underneath us. */
+       page = pin_page((unsigned long)uaddr - pos_in_page);
+       if (IS_ERR(page))
+               return PTR_ERR(page);
+
+       head = hash_futex(page, pos_in_page);
+       switch (op) {
+       case FUTEX_UP:
+               ret = futex_up(head, page, pos_in_page);
+               break;
+       case FUTEX_DOWN:
+               ret = futex_down(head, page, pos_in_page);
+               break;
+       /* Add other lock types here... */
+       default:
+               ret = -EINVAL;
+       }
+       put_page(page);
+
+       return ret;
+}
+
+static int __init init(void)
+{
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(futex_queues); i++)
+               INIT_LIST_HEAD(&futex_queues[i]);
+       return 0;
+}
+__initcall(init);
index 9681f6d3e92cb6c3bd7812b67e92099eb5f8bfd2..bad145db7dfc43920f2d60288d33f633e667a5be 100644 (file)
@@ -25,6 +25,7 @@
 #include <linux/iobuf.h>
 #include <linux/compiler.h>
 #include <linux/fs.h>
+#include <linux/hash.h>
 
 #include <asm/pgalloc.h>
 #include <asm/uaccess.h>
@@ -773,32 +774,8 @@ static int read_cluster_nonblocking(struct file * file, unsigned long offset,
 static inline wait_queue_head_t *page_waitqueue(struct page *page)
 {
        const zone_t *zone = page_zone(page);
-       wait_queue_head_t *wait = zone->wait_table;
-       unsigned long hash = (unsigned long)page;
-
-#if BITS_PER_LONG == 64
-       /*  Sigh, gcc can't optimise this alone like it does for 32 bits. */
-       unsigned long n = hash;
-       n <<= 18;
-       hash -= n;
-       n <<= 33;
-       hash -= n;
-       n <<= 3;
-       hash += n;
-       n <<= 3;
-       hash -= n;
-       n <<= 4;
-       hash += n;
-       n <<= 2;
-       hash += n;
-#else
-       /* On some cpus multiply is faster, on others gcc will do shifts */
-       hash *= GOLDEN_RATIO_PRIME;
-#endif
-
-       hash >>= zone->wait_table_shift;
 
-       return &wait[hash];
+       return &zone->wait_table[hash_ptr(page, zone->wait_table_bits)];
 }
 
 /* 
index ca1713ab4113fa5be6305a278cfbbf72666445da..33ca4b11cfe7a8de59c9ed09fb7fdce821a908ee 100644 (file)
@@ -280,7 +280,7 @@ asmlinkage long sys_mprotect(unsigned long start, size_t len, unsigned long prot
        end = start + len;
        if (end < start)
                return -EINVAL;
-       if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC))
+       if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC | PROT_SEM))
                return -EINVAL;
        if (end == start)
                return 0;
index 7c56fe573d911faa5f0a72481e3b08a52b8d093f..066c2c0170224e1c5ff3c1dcc896c8888c77f1bf 100644 (file)
@@ -776,8 +776,8 @@ void __init free_area_init_core(int nid, pg_data_t *pgdat, struct page **gmap,
                 * per zone.
                 */
                zone->wait_table_size = wait_table_size(size);
-               zone->wait_table_shift =
-                       BITS_PER_LONG - wait_table_bits(zone->wait_table_size);
+               zone->wait_table_bits =
+                       wait_table_bits(zone->wait_table_size);
                zone->wait_table = (wait_queue_head_t *)
                        alloc_bootmem_node(pgdat, zone->wait_table_size
                                                * sizeof(wait_queue_head_t));