Thread (17 messages) flat view 17 messages, 5 authors, 2021-07-13

Re: [RFC PATCH 2/3 v0.2] sched/umcg: RFC: add userspace atomic helpers

From: Peter Oskolkov <hidden>
Date: 2021-07-09 16:57:49
Also in: lkml

On Fri, Jul 9, 2021 at 1:02 AM Peter Zijlstra [off-list ref] wrote:
On Thu, Jul 08, 2021 at 12:46:37PM -0700, Peter Oskolkov wrote:
quoted
+static inline int umcg_atomic_cmpxchg_64(u64 *uval, u64 __user *uaddr,
+                                             u64 oldval, u64 newval)
+{
+     int ret = 0;
+
+     if (!user_access_begin(uaddr, sizeof(u64)))
+             return -EFAULT;
+     asm volatile("\n"
+             "1:\t" LOCK_PREFIX "cmpxchgq %4, %2\n"
+             "2:\n"
+             "\t.section .fixup, \"ax\"\n"
+             "3:\tmov     %3, %0\n"
+             "\tjmp     2b\n"
+             "\t.previous\n"
+             _ASM_EXTABLE_UA(1b, 3b)
+             : "+r" (ret), "=a" (oldval), "+m" (*uaddr)
+             : "i" (-EFAULT), "r" (newval), "1" (oldval)
+             : "memory"
+     );
+     user_access_end();
+     *uval = oldval;
+     return ret;
+}
quoted
+static inline int fix_pagefault(unsigned long uaddr, bool write_fault)
+{
+     struct mm_struct *mm = current->mm;
+     int ret;
+
+     mmap_read_lock(mm);
+     ret = fixup_user_fault(mm, uaddr, write_fault ? FAULT_FLAG_WRITE : 0,
+                     NULL);
+     mmap_read_unlock(mm);
+
+     return ret < 0 ? ret : 0;
+}
quoted
+static inline int umcg_cmpxchg_64_user(u64 __user *uaddr, u64 *prev, u64 val)
+{
+     while (true) {
+             int ret;
+             u64 expected = *prev;
+
+             pagefault_disable();
+             ret = umcg_atomic_cmpxchg_64(prev, uaddr, expected, val);
+             pagefault_enable();
+
+             if (!ret)
+                     return *prev == expected ? 0 : -EAGAIN;
+
+             if (WARN_ONCE(ret != -EFAULT, "Unexpected error"))
+                     return -EFAULT;
+
+             ret = fix_pagefault((unsigned long)uaddr, true);
+             if (ret)
+                     return -EFAULT;
+     }
+}
+
+/**
+ * atomic_stack_push_user - push a node onto the stack
+ * @head - a pointer to the head of the stack;
+ * @node - a pointer to the node to push.
+ *
+ * Push a node onto a single-linked list (stack). Atomicity/correctness
+ * is guaranteed by locking the head via settings its first bit (assuming
+ * the pointer is aligned).
+ *
+ * Return: 0 on success, -EFAULT on error.
+ */
+static inline int atomic_stack_push_user(u64 __user *head, u64 __user *node)
+{
+     while (true) {
+             int ret;
+             u64 first;
+
+             smp_mb();  /* Make the read below clean. */
+             if (get_user(first, head))
+                     return -EFAULT;
+
+             if (first & 1UL) {
+                     cpu_relax();
+                     continue;  /* first is being deleted. */
+             }
+
+             if (put_user(first, node))
+                     return -EFAULT;
+             smp_mb();  /* Make the write above visible. */
+
+             ret = umcg_cmpxchg_64_user(head, &first, (u64)node);
+             if (!ret)
+                     return 0;
+
+             if (ret == -EAGAIN) {
+                     cpu_relax();
+                     continue;
+             }
+
+             if (WARN_ONCE(ret != -EFAULT, "unexpected umcg_cmpxchg result"))
+                     return -EFAULT;
+
+             return -EFAULT;
+     }
+}

This is horrible... Jann is absolutely right, you do not, *ever* do
userspace spinlocks. What's wrong with the trivial lockless single
linked list approach?.
I'm not sure how to get around the ABA problem with a lockless single
linked list: https://en.wikipedia.org/wiki/ABA_problem

Our semantics can probably be relied on to prevent ABA from happening
with idle workers (popped/removed by the userspace), but idle servers
can trivially have it:

(initial state): head => Server A => Server B => NULL

task1 :
- read head (get A), read A (get B), {/* delayed */}

tasks 2-3-4:
- pop A, pop B, push A

task 1:
- cmp_xchg(head, A /* old */, B /* new */) - succeeds, now B is in the
list erroneously
On top of that, you really want to avoid all that endless stac/clac
nonsense and only have that once, at the outer edges of things.

Something like the *completely* untested below (except it needs lots of
extra gunk to support compilers without asm-goto-output, and more widths
and ...
I'll try the approach you suggest below once it is clear how to deal
with the ABA issue (and the wake server issue raised in patch 3).

#define __try_cmpxchg_user_size(ptr, oldp, new, size, label)            \
({                                                                      \
        _Bool __success;                                                \
        __chk_user_ptr(ptr);                                            \
        __typeof__(ptr) _old = (__typeof__(ptr))(oldp);                 \
        __typeof__(*(ptr)) __old = *_old;                               \
        __typeof__(*(ptr)) __new = (new);                               \
        switch (size) {                                                 \
        case 8:                                                         \
                volatile u64 *__ptr = (volatile u64 *)(ptr);            \
                asm_volatile_goto("1: " LOCK_PREFIX "cmpxchgq %[new], %[ptr]" \
                                  CC_SET(z)                             \
                                  _ASM_EXTABLE_UA(1b, %l[label])        \
                                  : CC_OUT(x) (__success),              \
                                    [ptr] "+m" (*__ptr),                \
                                    [old] "+a" (__old),                 \
                                  : [new] "r" (__new)                   \
                                  : "memory"                            \
                                  : label);                             \
                break;                                                  \
        }                                                               \
        if (unlikely(!success))                                         \
                *_old = __old;                                          \
        __success;                                                      \
})

#define unsafe_try_cmpxchg_user(ptr, oldp, new, label)                  \
        __try_cmpxchg_user_size((ptr), (oldp), (new), sizeof(*(ptr)), label);

int user_llist_add(u64 __user *new, u64 __user *head)
{
        u64 first;
        int ret;

        if (unlikely(!access_ok(new, sizeof(*new)) ||
                     !access_ok(head, sizeof(*head))))
                return -EFAULT;

again:
        __uaccess_begin_nospec();

        unsafe_get_user(first, head, Efault_head);
        do {
                unsafe_put_user(first, new, Efault_new);
        } while (!unsafe_try_cmpxchg_user(head, &first, new, Efault_head));

        user_access_end();

        return 0;

Efault_new:
        user_access_end();

        ret = fixup_fault(new);
        if (ret < 0)
                return ret;

        goto again;

Efault_head:
        user_access_end();

        ret = fixup_fault(head);
        if (ret < 0)
                return ret;

        goto again;
}
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help