LATX, fix: lazily start the RCU callback thread
Starting the RCU callback consumer during process initialization exposes an extra host thread before the guest creates one. Namespace operations that require a single-threaded caller can then fail for a single-threaded guest. Start the consumer when callback work first needs it, while preserving the existing deferred start required by namespace children. Add unit and integration coverage for the lazy lifecycle and host-thread visibility. Signed-off-by: yuerengan <y347812075@163.com>
This commit is contained in:
parent
7fc3fb1366
commit
c90d0b6901
|
|
@ -33,4 +33,15 @@ if host_machine.cpu_family() == 'loongarch64' and \
|
|||
suite: 'latx-integration',
|
||||
timeout: 30,
|
||||
)
|
||||
test(
|
||||
'test-rcu-thread-visibility',
|
||||
find_program('test-rcu-thread-visibility.sh'),
|
||||
args: [
|
||||
emulators['latx-x86_64'],
|
||||
files('rcu-thread-visibility.S'),
|
||||
],
|
||||
protocol: 'exitcode',
|
||||
suite: 'latx-integration',
|
||||
timeout: 30,
|
||||
)
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
.equ __NR_getpid, 39
|
||||
.equ __NR_setpgid, 109
|
||||
.equ __NR_getpgid, 121
|
||||
.equ __NR_exit, 60
|
||||
.equ SCAN_LIMIT, 128
|
||||
|
||||
.section .text
|
||||
.global _start
|
||||
.type _start, @function
|
||||
_start:
|
||||
mov $__NR_setpgid, %eax
|
||||
xor %edi, %edi
|
||||
xor %esi, %esi
|
||||
syscall
|
||||
test %rax, %rax
|
||||
js setup_failed
|
||||
|
||||
mov $__NR_getpid, %eax
|
||||
syscall
|
||||
mov %eax, %r12d
|
||||
|
||||
mov $__NR_getpgid, %eax
|
||||
xor %edi, %edi
|
||||
syscall
|
||||
test %rax, %rax
|
||||
js setup_failed
|
||||
mov %eax, %r13d
|
||||
|
||||
lea 1(%r12), %r14d
|
||||
lea SCAN_LIMIT(%r14), %r15d
|
||||
|
||||
scan_next:
|
||||
mov $__NR_getpgid, %eax
|
||||
mov %r14d, %edi
|
||||
syscall
|
||||
cmp %r13d, %eax
|
||||
je internal_thread_visible
|
||||
inc %r14d
|
||||
cmp %r15d, %r14d
|
||||
jl scan_next
|
||||
|
||||
xor %edi, %edi
|
||||
jmp exit
|
||||
|
||||
setup_failed:
|
||||
mov $11, %edi
|
||||
jmp exit
|
||||
|
||||
internal_thread_visible:
|
||||
mov $10, %edi
|
||||
|
||||
exit:
|
||||
mov $__NR_exit, %eax
|
||||
syscall
|
||||
.size _start, .-_start
|
||||
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
emulator=$1
|
||||
source_file=$2
|
||||
workdir=$(mktemp -d)
|
||||
trap 'rm -rf "$workdir"' EXIT HUP INT TERM
|
||||
|
||||
if command -v clang-19 >/dev/null 2>&1; then
|
||||
clang=clang-19
|
||||
elif command -v clang >/dev/null 2>&1; then
|
||||
clang=clang
|
||||
else
|
||||
echo "SKIP: clang is required to build the x86_64 guest"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
"$clang" --target=x86_64-linux-gnu -fuse-ld=lld -nostdlib -static \
|
||||
-Wl,--build-id=none "$source_file" -o "$workdir/rcu-thread-visibility"
|
||||
|
||||
set +e
|
||||
LATX_AOT=0 LATX_KZT=0 "$emulator" "$workdir/rcu-thread-visibility"
|
||||
ret=$?
|
||||
set -e
|
||||
|
||||
case $ret in
|
||||
0)
|
||||
echo "PASS: no internal LATX thread is visible through getpgid"
|
||||
;;
|
||||
10)
|
||||
echo "FAIL: an internal LATX thread is visible through getpgid" >&2
|
||||
;;
|
||||
11)
|
||||
echo "FAIL: process-group setup failed" >&2
|
||||
;;
|
||||
*)
|
||||
echo "FAIL: unexpected guest exit status $ret" >&2
|
||||
;;
|
||||
esac
|
||||
|
||||
exit "$ret"
|
||||
|
|
@ -39,3 +39,16 @@ if 'CONFIG_LATX' in config_host
|
|||
suite: 'lat-pr-fast',
|
||||
)
|
||||
endif
|
||||
|
||||
test_rcu_lazy_thread = executable(
|
||||
'test-rcu-lazy-thread',
|
||||
files('test-rcu-lazy-thread.c'),
|
||||
include_directories: include_directories('../..'),
|
||||
dependencies: [glib, qemuutil],
|
||||
)
|
||||
|
||||
test(
|
||||
'test-rcu-lazy-thread',
|
||||
test_rcu_lazy_thread,
|
||||
suite: 'lat-pr-fast',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
#include "qemu/osdep.h"
|
||||
|
||||
#include "qemu/rcu.h"
|
||||
#include "qemu/thread.h"
|
||||
|
||||
typedef struct TestRcuCallback {
|
||||
struct rcu_head rcu;
|
||||
QemuEvent done;
|
||||
} TestRcuCallback;
|
||||
|
||||
static void test_callback(struct rcu_head *head)
|
||||
{
|
||||
TestRcuCallback *callback = container_of(head, TestRcuCallback, rcu);
|
||||
|
||||
qemu_event_set(&callback->done);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
TestRcuCallback callback;
|
||||
|
||||
#ifdef CONFIG_LATX
|
||||
g_assert_false(rcu_call_thread_is_running());
|
||||
#endif
|
||||
|
||||
qemu_event_init(&callback.done, false);
|
||||
call_rcu1(&callback.rcu, test_callback);
|
||||
qemu_event_wait(&callback.done);
|
||||
g_assert_true(rcu_call_thread_is_running());
|
||||
qemu_event_destroy(&callback.done);
|
||||
|
||||
return 0;
|
||||
}
|
||||
29
util/rcu.c
29
util/rcu.c
|
|
@ -182,6 +182,10 @@ static struct rcu_head dummy;
|
|||
static struct rcu_head *head = &dummy, **tail = &dummy.next;
|
||||
static int rcu_call_count;
|
||||
static QemuEvent rcu_call_ready_event;
|
||||
static bool atfork_child_deferred;
|
||||
static bool call_rcu_thread_started;
|
||||
|
||||
static void rcu_start_call_thread(void);
|
||||
|
||||
static void enqueue(struct rcu_head *node)
|
||||
{
|
||||
|
|
@ -290,6 +294,9 @@ void call_rcu1(struct rcu_head *node, void (*func)(struct rcu_head *node))
|
|||
node->func = func;
|
||||
enqueue(node);
|
||||
qatomic_inc(&rcu_call_count);
|
||||
if (!atfork_child_deferred) {
|
||||
rcu_start_call_thread();
|
||||
}
|
||||
qemu_event_set(&rcu_call_ready_event);
|
||||
}
|
||||
|
||||
|
|
@ -367,12 +374,17 @@ static void rcu_start_call_thread(void)
|
|||
{
|
||||
QemuThread thread;
|
||||
|
||||
if (qatomic_cmpxchg(&call_rcu_thread_started, false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_thread_create(&thread, "call_rcu", call_rcu_thread,
|
||||
NULL, QEMU_THREAD_DETACHED);
|
||||
}
|
||||
|
||||
static void rcu_init_complete(bool start_thread)
|
||||
{
|
||||
qatomic_set(&call_rcu_thread_started, false);
|
||||
qemu_mutex_init(&rcu_registry_lock);
|
||||
qemu_mutex_init(&rcu_sync_lock);
|
||||
qemu_event_init(&rcu_gp_event, true);
|
||||
|
|
@ -387,7 +399,6 @@ static void rcu_init_complete(bool start_thread)
|
|||
}
|
||||
|
||||
static int atfork_depth = 1;
|
||||
static bool atfork_child_deferred;
|
||||
|
||||
void rcu_enable_atfork(void)
|
||||
{
|
||||
|
|
@ -422,7 +433,7 @@ void rcu_start_deferred_thread(void)
|
|||
|
||||
bool rcu_call_thread_is_running(void)
|
||||
{
|
||||
return !atfork_child_deferred;
|
||||
return qatomic_read(&call_rcu_thread_started);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_POSIX
|
||||
|
|
@ -457,7 +468,11 @@ static void rcu_init_child(void)
|
|||
}
|
||||
|
||||
memset(®istry, 0, sizeof(registry));
|
||||
#ifdef CONFIG_LATX
|
||||
rcu_init_complete(false);
|
||||
#else
|
||||
rcu_init_complete(!atfork_child_deferred);
|
||||
#endif
|
||||
}
|
||||
|
||||
void rcu_raw_clone_prepare(void)
|
||||
|
|
@ -484,10 +499,14 @@ static void __attribute__((__constructor__)) rcu_init(void)
|
|||
#endif
|
||||
#ifdef CONFIG_LATX
|
||||
/*
|
||||
* Chromium execs its PID namespace child before entering the nested user
|
||||
* namespace. Keep that child single-threaded until the guest unshare.
|
||||
* Keep translator-only threads out of a single-threaded guest until RCU
|
||||
* work actually needs a consumer. Chromium additionally defers that
|
||||
* on-demand start until its namespace child enters the nested user
|
||||
* namespace.
|
||||
*/
|
||||
atfork_child_deferred = g_strcmp0(g_getenv("SBX_USER_NS"), "1") == 0;
|
||||
rcu_init_complete(false);
|
||||
#else
|
||||
rcu_init_complete(true);
|
||||
#endif
|
||||
rcu_init_complete(!atfork_child_deferred);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue