bug-hurd
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFC] kern: simple futex for gnumach (version 9)


From: Marin Ramesa
Subject: [RFC] kern: simple futex for gnumach (version 9)
Date: Sat, 4 Jan 2014 11:12:42 +0100

Red-black tree is used which simplifies the code. Instead of
clear_wait(), thread_wakeup_prim() is used with the waiter's
offset as the event, which removes the need for a seperate
cross wake function. Value at the futex address is retrieved
via a copyin() call and the compare before the block is atomic
with GCC builtins. First version of futex_wait_timeout() is 
added, but untested.

This is not yet ready to be called from userspace since glibc
build on the Hurd doesn't seem to compile the new RPCs. I don't
know how to actually call them. I tested this with GDB in kernel, 
without vm_map_lookup() and not actually blocking the thread.

---
 kern/futex.c   |  287 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 kern/futex.h   |   64 +++++++++++++
 kern/startup.c |    3 +
 3 files changed, 354 insertions(+)
 create mode 100644 kern/futex.c
 create mode 100644 kern/futex.h

diff --git a/kern/futex.c b/kern/futex.c
new file mode 100644
index 0000000..e73165e
--- /dev/null
+++ b/kern/futex.c
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2013, 2014 Free Software Foundation, Inc.
+ *
+ * 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *     Author: Marin Ramesa
+ */
+
+/*
+ * Fast userspace locking
+ *
+ */
+
+#include <kern/futex.h>
+#include <kern/sched_prim.h>
+#include <kern/rbtree.h>
+#include <vm/vm_map.h>
+#include <kern/thread.h>
+#include <machine/locore.h>
+#include <kern/lock.h>
+#include <kern/kalloc.h>
+
+/*
+ * When color of the node is red, parent of the node is the futex address, 
left child is 
+ * the futex waiter offset and right child is the another futex address. When 
color of 
+ * the node is black, parent of the node is the futex waiter's offset.
+ */
+static struct rbtree futex_tree;
+
+decl_simple_lock_data(static, futex_lock); 
+
+void futex_setup(void)
+{
+       rbtree_init(&futex_tree);
+       simple_lock_init(futex_lock); 
+}
+
+/* Returns the difference in futex addresses; assertion in rbtree_insert() 
fails if it returns 0. */
+static inline int compare_nodes_insert_futex(struct rbtree_node *node1, struct 
rbtree_node *node2)
+{
+       return node1->parent - node2->parent;
+}
+
+/* Add the differences between the node addresses and offsets. */
+static inline int compare_nodes_insert_waiter(struct rbtree_node *node1, 
struct rbtree_node *node2)
+{
+       return node1 - node2 + node1->parent - node2->parent;
+}
+
+/* For the lookup to succeed the return value must be zero. Which means the 
key node's parent must not be the offset. */
+static inline int compare_nodes_lookup(struct rbtree_node *node1, struct 
rbtree_node *node2)
+{
+       if (node1 == futex_tree.root)
+               return node1->parent - node2->parent;
+       else    
+               return node1->parent - node2->parent + ((node1->parent & 
RBTREE_COLOR_MASK) == RBTREE_COLOR_BLACK);
+}
+
+/* Insert a new address as the node in the futex tree. */
+static int futex_init(int *address)
+{
+       struct rbtree_node *futex;
+
+       simple_lock(&futex_lock);
+
+       futex = (struct rbtree_node *)kalloc(sizeof(*futex));
+       if (futex == NULL)
+               return FUTEX_RESOURCE_SHORTAGE;
+
+       rbtree_insert(&futex_tree, futex, compare_nodes_insert_futex);
+       futex->parent = (unsigned long)address;
+               
+       simple_unlock(&futex_lock);
+
+       return 0;
+}
+
+/* Find the node slot of the futex address in the tree. */
+static unsigned long futex_lookup_address(int *address)
+{
+       unsigned long node_slot = 0;
+       struct rbtree_node node;
+
+       simple_lock(&futex_lock);
+
+       node.parent = (unsigned long)address;
+       rbtree_lookup_slot(&futex_tree, &node, compare_nodes_lookup, node_slot);
+
+       simple_unlock(&futex_lock);
+
+       return node_slot;
+}      
+
+/* Atomic compare using GCC builtins.
+ * First call fetches and substracts the values.
+ * Second call compares the return value with zero and returns with success
+ * if the swap was done.
+ */
+static inline _Bool atomic_compare(int value1, int value2)
+{      
+       return 
__sync_bool_compare_and_swap((int[]){__sync_sub_and_fetch(&value1, value2)}, 0, 
1);
+}
+
+int futex_wait(int *address, int value)
+{
+       unsigned long node_slot = 0;
+
+       lookup:
+
+       node_slot = futex_lookup_address(address);
+
+       if (node_slot != 0) {
+               
+               simple_lock(&futex_lock);
+               
+               int addr_value;
+               copyin(address, &addr_value, sizeof(int));
+               
+               if (atomic_compare(addr_value, value)) {
+
+                       vm_offset_t offset;
+                       struct rbtree_node node;
+                       
+                       /* Fetch the offset from the (task map, futex address) 
value pair. */
+                       kern_return_t kr;
+                       kr = vm_map_lookup(&current_map(), 
(vm_offset_t)address, VM_PROT_READ, NULL, NULL, &offset, NULL, NULL);
+                       if (kr != KERN_SUCCESS) 
+                               return FUTEX_MEMORY_ERROR;                      
        
+
+                       /* Link with the futex. */
+                       rbtree_slot_parent(node_slot)->children[RBTREE_LEFT] = 
&node;
+
+                       /* Mark the node black and write the offset. */
+                       node.parent = offset | RBTREE_COLOR_BLACK;
+
+                       rbtree_insert(&futex_tree, &node, 
compare_nodes_insert_waiter);
+
+                       /* Block with the offset as the event. */
+                       thread_sleep((event_t)node.parent, 
(simple_lock_t)&futex_lock, FALSE);
+                       
+                       /* Thread was awakened in thread_wakeup_prim(). Remove 
the offset. */
+                       rbtree_remove(&futex_tree, &node);
+
+                       return FUTEX_RESUMED_BY_WAKE;
+
+               } else {
+                       simple_unlock(&futex_lock);
+                       return FUTEX_NO_THREAD_SUSPENDED;
+               }
+
+       } else {
+               
+               if (futex_init(address) != 0)
+                       return FUTEX_RESOURCE_SHORTAGE; 
+               
+               goto lookup;
+       }
+}
+
+int futex_wake(int *address, _Bool wake_all)
+{
+       #define offset(x) x->children[RBTREE_LEFT]->parent
+       #define next_futex(x) x->children[RBTREE_RIGHT]
+
+       unsigned node_slot = 0; 
+       node_slot = futex_lookup_address(address);
+
+       if (node_slot != 0) {
+
+               simple_lock(&futex_lock);
+
+               struct rbtree_node *futex;              
+
+               if (wake_all) {
+
+                       for (futex = rbtree_first(&futex_tree); futex != NULL; 
futex = next_futex(futex)) {
+
+                               /* Clean up the futex with zero waiters. */
+                               if (futex->children[RBTREE_LEFT] == NULL) {
+                                       kfree((vm_offset_t)futex, 
sizeof(*futex));
+                                       rbtree_remove(&futex_tree, futex);      
                                
+                                       continue;
+                               }
+
+                               /* If addresses match and the child node is 
black wake the threads. */
+                               if (((offset(futex) & RBTREE_COLOR_MASK) == 
RBTREE_COLOR_BLACK) && ((int *)futex->parent == address)) {
+                                       thread_wakeup((event_t)offset(futex));  
                        
+                               }
+
+                               /* Break if this was the last node. */
+                               if (futex == rbtree_last(&futex_tree)) {
+                                       break;
+                               }
+
+                       }
+
+               }  else {
+                       
+                       for (futex = rbtree_first(&futex_tree); futex != NULL; 
futex = next_futex(futex)) {
+                       
+                               /* Clean up the futex with zero waiters. */
+                               if (futex->children[RBTREE_LEFT] == NULL) {
+                                       kfree((vm_offset_t)futex, 
sizeof(*futex));
+                                       rbtree_remove(&futex_tree, futex);      
                                
+                                       continue;
+                               }
+
+                               /* If addresses match and the child node is 
black wake one thread. */
+                               if (((offset(futex) & RBTREE_COLOR_MASK) == 
RBTREE_COLOR_BLACK) && ((int *)futex->parent == address)) {
+                                       
thread_wakeup_one((event_t)offset(futex));
+                                       break;                                  
+                               }
+                       }               
+               }
+
+               simple_unlock(&futex_lock);
+               return FUTEX_SOME_NUMBER_RESUMED;
+
+       } else {
+               return FUTEX_NO_THREAD;
+       }
+#undef offset
+#undef next_futex
+}
+
+/* Timed wait for msec time. */
+static int futex_wait_timeout(int *address, int value, unsigned int msec)
+{
+       unsigned long node_slot = 0;
+
+       lookup:
+
+       node_slot = futex_lookup_address(address);
+
+       if (node_slot != 0) {
+               
+               simple_lock(&futex_lock);
+
+               int addr_value;
+               copyin(address, &addr_value, sizeof(int));
+
+               if (atomic_compare(addr_value, value)) {
+
+                       thread_timeout_setup(current_thread());
+
+                       assert_wait(NULL, TRUE);
+                       thread_will_wait_with_timeout(current_thread(), msec);
+                       simple_unlock(&futex_lock);
+
+                       thread_block(NULL);
+
+                       return FUTEX_TIMED_OUT;
+
+               } else {
+                       simple_unlock(&futex_lock);
+                       return FUTEX_NO_THREAD_SUSPENDED;
+               }
+
+       } else {
+               
+               if (futex_init(address) != 0)
+                       return FUTEX_RESOURCE_SHORTAGE; 
+               
+               goto lookup;
+       }
+}
+
+int r_futex_wait(task_t task, pointer_t address, int value)
+{
+       return futex_wait((int *)address, value);
+}
+
+int r_futex_wake(task_t task, pointer_t address, boolean_t wake_all)
+{
+       return futex_wake((int *)address, (_Bool)wake_all);
+} 
diff --git a/kern/futex.h b/kern/futex.h
new file mode 100644
index 0000000..74f2dc2
--- /dev/null
+++ b/kern/futex.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2013, 2014 Free Software Foundation, Inc.
+ *
+ * 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *     Author: Marin Ramesa
+ */
+
+#ifndef _KERN_FUTEX_H_
+#define _KERN_FUTEX_H_
+
+#include <mach/boolean.h>
+#include <mach/std_types.h>
+#include <kern/task.h>
+
+/* Definitions for return values. */
+#define FUTEX_RESUMED_BY_WAKE 0
+#define FUTEX_NO_THREAD_SUSPENDED 1
+#define FUTEX_SOME_NUMBER_RESUMED 2
+#define FUTEX_NO_THREAD 3
+#define FUTEX_MEMORY_ERROR 4
+#define FUTEX_RESOURCE_SHORTAGE 6
+#define FUTEX_TIMED_OUT 7
+
+/*
+ * This is a method for a thread to wait for a change of value at a given 
address.
+ * 
+ * Function performs a lookup of the address in the futex tree. If address is
+ * found, value at the address is atomically compared with the argument value.
+ * If the values are same, offset from the task's map and futex address is
+ * calculated and thread blocks.
+ */
+int futex_wait(int *address, int value);
+
+/*
+ * This is a method for waking up one or all threads waiting for a change of 
+ * value at a given address.
+ * 
+ * Function performs a lookup of the address in the futex tree. If address is
+ * found and wake_all argument is TRUE, all threads with the same offsets are
+ * awakened. If wake_all is FALSE, only one thread from the futex is awakened. 
+ */
+int futex_wake(int *address, _Bool wake_all);
+
+/* Initialization of the futex tree and the locks. */
+void futex_setup(void);
+
+/* RPCs to call from userspace. */
+int r_futex_wait(task_t task, pointer_t address, int value);
+int r_futex_wake(task_t task, pointer_t address, boolean_t wake_all);
+
+#endif /* _KERN_FUTEX_H_ */
diff --git a/kern/startup.c b/kern/startup.c
index 12f5123..447c528 100644
--- a/kern/startup.c
+++ b/kern/startup.c
@@ -50,6 +50,7 @@
 #include <kern/bootstrap.h>
 #include <kern/time_stamp.h>
 #include <kern/startup.h>
+#include <kern/futex.h>
 #include <vm/vm_kern.h>
 #include <vm/vm_map.h>
 #include <vm/vm_object.h>
@@ -158,6 +159,8 @@ void setup_main(void)
        recompute_priorities(NULL);
        compute_mach_factor();
 
+       futex_setup();
+
        /*
         *      Create a kernel thread to start the other kernel
         *      threads.  Thread_resume (from kernel_thread) calls
-- 
1.7.10.4




reply via email to

[Prev in Thread] Current Thread [Next in Thread]