Add Pthread test
This commit is contained in:
parent
13e4a898dd
commit
660d0931cd
@ -6,6 +6,7 @@
|
||||
#![feature(alloc)]
|
||||
#![feature(allocator_api)]
|
||||
#![feature(range_contains)]
|
||||
#![feature(core_intrinsics)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use std::intrinsics::atomic_store;
|
||||
|
||||
// TODO: make sure Processes are released eventually
|
||||
|
||||
@ -26,7 +27,13 @@ pub fn do_exit(exit_status: i32) {
|
||||
}
|
||||
current.children.clear();
|
||||
|
||||
// Notify parent if necessary
|
||||
// Notify another process, if any, that waits on ctid (see set_tid_address)
|
||||
if let Some(ctid) = current.clear_child_tid {
|
||||
unsafe { atomic_store(ctid, 0); }
|
||||
futex_wake(ctid as *const i32, 1);
|
||||
}
|
||||
|
||||
// Notify the parent process if necessary
|
||||
let parent_ref = current.get_parent().clone();
|
||||
let (mut parent, current) = {
|
||||
// Always lock parent before its child
|
||||
|
@ -36,12 +36,13 @@ bitflags! {
|
||||
pub fn do_clone(
|
||||
flags: CloneFlags,
|
||||
stack_addr: usize,
|
||||
ptid: Option<*mut i32>,
|
||||
ctid: Option<*mut i32>,
|
||||
new_tls: usize
|
||||
ptid: Option<*mut pid_t>,
|
||||
ctid: Option<*mut pid_t>,
|
||||
new_tls: Option<usize>,
|
||||
) -> Result<pid_t, Error> {
|
||||
info!("clone: flags: {:?}, stack_addr: {:?}, ptid: {:?}, ctid: {:?}, new_tls: {:?}",
|
||||
flags, stack_addr, ptid, ctid, new_tls);
|
||||
// TODO: return error for unsupported flags
|
||||
|
||||
let current_ref = get_current();
|
||||
let current = current_ref.lock().unwrap();
|
||||
@ -54,6 +55,11 @@ pub fn do_clone(
|
||||
Process::new(cwd, task, vm_ref, files_ref)?
|
||||
};
|
||||
|
||||
if let Some(ctid) = ctid {
|
||||
let mut new_thread = new_thread_ref.lock().unwrap();
|
||||
new_thread.clear_child_tid = Some(ctid);
|
||||
}
|
||||
|
||||
// TODO: always get parent lock first to avoid deadlock
|
||||
{
|
||||
let parent_ref = current.parent.as_ref().unwrap();
|
||||
@ -64,11 +70,16 @@ pub fn do_clone(
|
||||
}
|
||||
|
||||
process_table::put(new_thread_pid, new_thread_ref.clone());
|
||||
|
||||
if let Some(ptid) = ptid {
|
||||
unsafe { *ptid = new_thread_pid; }
|
||||
}
|
||||
|
||||
task::enqueue_task(new_thread_ref);
|
||||
Ok(new_thread_pid)
|
||||
}
|
||||
|
||||
fn new_thread_task(user_stack: usize, new_tls: usize) -> Result<Task, Error> {
|
||||
fn new_thread_task(user_stack: usize, new_tls: Option<usize>) -> Result<Task, Error> {
|
||||
// The calling convention of Occlum clone syscall requires the user to
|
||||
// restore the entry point of the new thread at the top of the user stack.
|
||||
let user_entry = unsafe {
|
||||
@ -78,7 +89,8 @@ fn new_thread_task(user_stack: usize, new_tls: usize) -> Result<Task, Error> {
|
||||
Ok(Task {
|
||||
user_stack_addr: user_stack,
|
||||
user_entry_addr: user_entry,
|
||||
user_fsbase_addr: new_tls,
|
||||
// TODO: use 0 as the default value is not safe
|
||||
user_fsbase_addr: new_tls.unwrap_or(0),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
@ -87,8 +87,8 @@ pub extern "C" fn dispatch_syscall(
|
||||
SYS_CLONE => do_clone(
|
||||
arg0 as u32,
|
||||
arg1 as usize,
|
||||
arg2 as *mut i32,
|
||||
arg3 as *mut i32,
|
||||
arg2 as *mut pid_t,
|
||||
arg3 as *mut pid_t,
|
||||
arg4 as usize,
|
||||
),
|
||||
SYS_FUTEX => do_futex(
|
||||
@ -116,6 +116,11 @@ pub extern "C" fn dispatch_syscall(
|
||||
arg3 as i32,
|
||||
arg4 as usize,
|
||||
),
|
||||
SYS_MPROTECT => do_mprotect(
|
||||
arg0 as usize,
|
||||
arg1 as usize,
|
||||
arg2 as u32,
|
||||
),
|
||||
SYS_BRK => do_brk(arg0 as usize),
|
||||
|
||||
SYS_PIPE => do_pipe2(arg0 as *mut i32, 0),
|
||||
@ -217,14 +222,14 @@ fn do_spawn(
|
||||
pub fn do_clone(
|
||||
flags: u32,
|
||||
stack_addr: usize,
|
||||
ptid: *mut i32,
|
||||
ctid: *mut i32,
|
||||
ptid: *mut pid_t,
|
||||
ctid: *mut pid_t,
|
||||
new_tls: usize,
|
||||
) -> Result<isize, Error> {
|
||||
let flags = CloneFlags::from_bits_truncate(flags);
|
||||
check_mut_ptr(stack_addr as *mut u64)?;
|
||||
let ptid = {
|
||||
if ptid != ptr::null_mut() {
|
||||
if flags.contains(CloneFlags::CLONE_PARENT_SETTID) {
|
||||
check_mut_ptr(ptid)?;
|
||||
Some(ptid)
|
||||
}
|
||||
@ -233,7 +238,7 @@ pub fn do_clone(
|
||||
}
|
||||
};
|
||||
let ctid = {
|
||||
if ctid != ptr::null_mut() {
|
||||
if flags.contains(CloneFlags::CLONE_CHILD_CLEARTID) {
|
||||
check_mut_ptr(ctid)?;
|
||||
Some(ctid)
|
||||
}
|
||||
@ -241,7 +246,15 @@ pub fn do_clone(
|
||||
None
|
||||
}
|
||||
};
|
||||
check_mut_ptr(new_tls as *mut u64)?;
|
||||
let new_tls = {
|
||||
if flags.contains(CloneFlags::CLONE_SETTLS) {
|
||||
check_mut_ptr(new_tls as *mut usize)?;
|
||||
Some(new_tls)
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let child_pid = process::do_clone(flags, stack_addr, ptid, ctid, new_tls)?;
|
||||
|
||||
@ -494,6 +507,15 @@ fn do_mremap(
|
||||
Ok(ret_addr as isize)
|
||||
}
|
||||
|
||||
fn do_mprotect(
|
||||
addr: usize,
|
||||
len: usize,
|
||||
prot: u32,
|
||||
) -> Result<isize, Error> {
|
||||
// TODO: implement it
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn do_brk(new_brk_addr: usize) -> Result<isize, Error> {
|
||||
let ret_brk_addr = vm::do_brk(new_brk_addr)?;
|
||||
Ok(ret_brk_addr as isize)
|
||||
|
@ -4,7 +4,7 @@ PROJECT_DIR := $(realpath $(CUR_DIR)/../)
|
||||
# Dependencies: need to be compiled but not to run by any Makefile target
|
||||
TEST_DEPS := dev_null
|
||||
# Tests: need to be compiled and run by test-% target
|
||||
TESTS := empty argv hello_world malloc file getpid spawn pipe time truncate readdir mkdir link clone tls
|
||||
TESTS := empty argv hello_world malloc file getpid spawn pipe time truncate readdir mkdir link tls pthread
|
||||
# Benchmarks: need to be compiled and run by bench-% target
|
||||
BENCHES := spawn_and_exit_latency pipe_throughput
|
||||
|
||||
|
@ -1,86 +0,0 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
*/
|
||||
|
||||
static inline int a_load(volatile int* x) {
|
||||
return __atomic_load_n((int*)x, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
static inline int a_add_fetch(volatile int* x, int a) {
|
||||
return __atomic_add_fetch((int*)x, a, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
/*
|
||||
* Futex wrapper
|
||||
*/
|
||||
|
||||
#define FUTEX_NUM 202
|
||||
|
||||
#define FUTEX_WAIT 0
|
||||
#define FUTEX_WAKE 1
|
||||
|
||||
// Libc does not provide a wrapper for futex, so we do it our own
|
||||
static int futex(volatile int *futex_addr, int futex_op, int val) {
|
||||
return (int) syscall(FUTEX_NUM, futex_addr, futex_op, val);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Child threads
|
||||
*/
|
||||
|
||||
#define NTHREADS 4
|
||||
#define STACK_SIZE (8 * 1024)
|
||||
|
||||
volatile int num_exit_threads = 0;
|
||||
|
||||
static int thread_func(void* arg) {
|
||||
int* tid = arg;
|
||||
//printf("tid = %d\n", *tid);
|
||||
// Wake up the main thread if all child threads exit
|
||||
if (a_add_fetch(&num_exit_threads, 1) == NTHREADS) {
|
||||
futex(&num_exit_threads, FUTEX_WAKE, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
unsigned int clone_flags = CLONE_VM | CLONE_FS | CLONE_FILES |
|
||||
CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | CLONE_DETACHED;
|
||||
|
||||
printf("Creating %d threads...", NTHREADS);
|
||||
int thread_ids[NTHREADS];
|
||||
for (int tid = 0; tid < NTHREADS; tid++) {
|
||||
void* thread_stack = malloc(STACK_SIZE);
|
||||
if (thread_stack == NULL) {
|
||||
printf("ERROR: malloc failed for thread %d\n", tid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
thread_ids[tid] = tid;
|
||||
void* thread_arg = &thread_ids[tid];
|
||||
if (clone(thread_func, thread_stack, clone_flags, thread_arg) < 0) {
|
||||
printf("ERROR: clone failed for thread %d\n", tid);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
printf("done.\n");
|
||||
|
||||
printf("Waiting for %d threads to exit...", NTHREADS);
|
||||
// Wait for all threads to exit
|
||||
int curr_num_exit_threads;
|
||||
while ((curr_num_exit_threads = a_load(&num_exit_threads)) != NTHREADS) {
|
||||
futex(&num_exit_threads, FUTEX_WAIT, curr_num_exit_threads);
|
||||
}
|
||||
printf("done.\n");
|
||||
|
||||
return 0;
|
||||
}
|
41
test/pthread/main.c
Normal file
41
test/pthread/main.c
Normal file
@ -0,0 +1,41 @@
|
||||
#include <sys/types.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* Child threads
|
||||
*/
|
||||
|
||||
#define NTHREADS 4
|
||||
#define STACK_SIZE (8 * 1024)
|
||||
|
||||
static void* thread_func(void* arg) {
|
||||
int* tid = arg;
|
||||
printf("tid = %d\n", *tid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
pthread_t threads[NTHREADS];
|
||||
int thread_data[NTHREADS];
|
||||
|
||||
printf("Creating %d threads...", NTHREADS);
|
||||
for (int ti = 0; ti < NTHREADS; ti++) {
|
||||
thread_data[ti] = ti;
|
||||
if (pthread_create(&threads[ti], NULL, thread_func, &thread_data[ti]) < 0) {
|
||||
printf("ERROR: pthread_create failed (ti = %d)\n", ti);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
printf("done.\n");
|
||||
|
||||
printf("Waiting for %d threads to exit...", NTHREADS);
|
||||
for (int ti = 0; ti < NTHREADS; ti++) {
|
||||
if (pthread_join(threads[ti], NULL) < 0) {
|
||||
printf("ERROR: pthread_join failed (ti = %d)\n", ti);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
printf("done.\n");
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user