Add nanosleep syscall

This commit is contained in:
Tate, Hongliang Tian 2019-09-03 14:00:38 +00:00
parent c8a921fd4b
commit 30c99add6f
8 changed files with 230 additions and 4 deletions

@ -19,6 +19,7 @@ enclave {
int ocall_run_new_task(void); int ocall_run_new_task(void);
void ocall_gettimeofday([out] long* sec, [out] long* us); void ocall_gettimeofday([out] long* sec, [out] long* us);
void ocall_clock_gettime(int clockid, [out] long* sec, [out] long* ns); void ocall_clock_gettime(int clockid, [out] long* sec, [out] long* ns);
void ocall_nanosleep(long sec, long nsec);
void ocall_sync(void); void ocall_sync(void);
int ocall_sched_getaffinity([out] int *error, int pid, size_t cpusize, [out, size=cpusize] unsigned char* buf); int ocall_sched_getaffinity([out] int *error, int pid, size_t cpusize, [out, size=cpusize] unsigned char* buf);
int ocall_sched_setaffinity([out] int *error, int pid, size_t cpusize, [in, size=cpusize] const unsigned char* buf); int ocall_sched_setaffinity([out] int *error, int pid, size_t cpusize, [in, size=cpusize] const unsigned char* buf);

@ -211,6 +211,8 @@ pub extern "C" fn dispatch_syscall(
SYS_GETTIMEOFDAY => do_gettimeofday(arg0 as *mut timeval_t), SYS_GETTIMEOFDAY => do_gettimeofday(arg0 as *mut timeval_t),
SYS_CLOCK_GETTIME => do_clock_gettime(arg0 as clockid_t, arg1 as *mut timespec_t), SYS_CLOCK_GETTIME => do_clock_gettime(arg0 as clockid_t, arg1 as *mut timespec_t),
SYS_NANOSLEEP => do_nanosleep(arg0 as *const timespec_t, arg1 as *mut timespec_t),
SYS_UNAME => do_uname(arg0 as *mut utsname_t), SYS_UNAME => do_uname(arg0 as *mut utsname_t),
SYS_PRLIMIT64 => do_prlimit( SYS_PRLIMIT64 => do_prlimit(
@ -812,6 +814,18 @@ fn do_clock_gettime(clockid: clockid_t, ts_u: *mut timespec_t) -> Result<isize>
Ok(0) Ok(0)
} }
// TODO: handle remainder
fn do_nanosleep(req_u: *const timespec_t, rem_u: *mut timespec_t) -> Result<isize> {
check_ptr(req_u)?;
if !rem_u.is_null() {
check_mut_ptr(rem_u)?;
}
let req = timespec_t::from_raw_ptr(req_u)?;
time::do_nanosleep(&req)?;
Ok(0)
}
// FIXME: use this // FIXME: use this
const MAP_FAILED: *const c_void = ((-1) as i64) as *const c_void; const MAP_FAILED: *const c_void = ((-1) as i64) as *const c_void;

@ -43,6 +43,16 @@ pub struct timespec_t {
nsec: i64, nsec: i64,
} }
impl timespec_t {
pub fn from_raw_ptr(ptr: *const timespec_t) -> Result<timespec_t> {
let ts = unsafe { *ptr };
if ts.sec < 0 || ts.nsec < 0 || ts.nsec >= 1_000_000_000 {
return_errno!(EINVAL, "Invalid timespec fields");
}
Ok(ts)
}
}
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub type clockid_t = i32; pub type clockid_t = i32;
@ -77,6 +87,10 @@ impl ClockID {
} }
pub fn do_clock_gettime(clockid: ClockID) -> Result<timespec_t> { pub fn do_clock_gettime(clockid: ClockID) -> Result<timespec_t> {
extern "C" {
fn ocall_clock_gettime(clockid: clockid_t, sec: *mut time_t, ns: *mut i64) -> sgx_status_t;
}
let mut sec = 0; let mut sec = 0;
let mut nsec = 0; let mut nsec = 0;
unsafe { unsafe {
@ -89,12 +103,17 @@ pub fn do_clock_gettime(clockid: ClockID) -> Result<timespec_t> {
Ok(timespec_t { sec, nsec }) Ok(timespec_t { sec, nsec })
} }
pub fn do_nanosleep(req: &timespec_t) -> Result<()> {
extern "C" { extern "C" {
fn ocall_clock_gettime(clockid: clockid_t, sec: *mut time_t, ns: *mut i64) -> sgx_status_t; fn ocall_nanosleep(sec: time_t, nsec: i64) -> sgx_status_t;
}
unsafe {
ocall_nanosleep(req.sec, req.nsec);
}
Ok(())
} }
// For SEFS // For SEFS
pub struct OcclumTimeProvider; pub struct OcclumTimeProvider;
impl TimeProvider for OcclumTimeProvider { impl TimeProvider for OcclumTimeProvider {

@ -9,21 +9,29 @@ pub mod from_user {
/// Check the user pointer is within the readable memory range of the user process /// Check the user pointer is within the readable memory range of the user process
pub fn check_ptr<T>(user_ptr: *const T) -> Result<()> { pub fn check_ptr<T>(user_ptr: *const T) -> Result<()> {
if user_ptr.is_null() {
return_errno!(EINVAL, "Address 0 is invalid");
}
Ok(()) Ok(())
} }
/// Check the mutable user pointer is within the writable memory of the user process /// Check the mutable user pointer is within the writable memory of the user process
pub fn check_mut_ptr<T>(user_ptr: *mut T) -> Result<()> { pub fn check_mut_ptr<T>(user_ptr: *mut T) -> Result<()> {
if user_ptr.is_null() {
return_errno!(EINVAL, "Address 0 is invalid");
}
Ok(()) Ok(())
} }
/// Check the readonly array is within the readable memory of the user process /// Check the readonly array is within the readable memory of the user process
pub fn check_array<T>(user_buf: *const T, count: usize) -> Result<()> { pub fn check_array<T>(user_buf: *const T, count: usize) -> Result<()> {
check_ptr(user_buf);
Ok(()) Ok(())
} }
/// Check the mutable array is within the writable memory of the user process /// Check the mutable array is within the writable memory of the user process
pub fn check_mut_array<T>(user_buf: *mut T, count: usize) -> Result<()> { pub fn check_mut_array<T>(user_buf: *mut T, count: usize) -> Result<()> {
check_mut_ptr(user_buf);
Ok(()) Ok(())
} }

@ -265,6 +265,11 @@ void ocall_clock_gettime(int clockid, time_t* sec, long* ns) {
*ns = ts.tv_nsec; *ns = ts.tv_nsec;
} }
void ocall_nanosleep(time_t sec, long nsec) {
struct timespec tv = { .tv_sec = sec, .tv_nsec = nsec };
nanosleep(&tv, NULL);
}
int ocall_sched_getaffinity(int* error, int pid, size_t cpusize, unsigned char* buf) { int ocall_sched_getaffinity(int* error, int pid, size_t cpusize, unsigned char* buf) {
int ret = syscall(__NR_sched_getaffinity, pid, cpusize, buf); int ret = syscall(__NR_sched_getaffinity, pid, cpusize, buf);
if (error) { if (error) {

@ -7,7 +7,7 @@ TEST_DEPS := dev_null client
# Tests: need to be compiled and run by test-% target # Tests: need to be compiled and run by test-% target
TESTS := empty env hello_world malloc mmap file fs_perms getpid spawn sched pipe time \ TESTS := empty env hello_world malloc mmap file fs_perms getpid spawn sched pipe time \
truncate readdir mkdir link tls pthread uname rlimit server \ truncate readdir mkdir link tls pthread uname rlimit server \
server_epoll unix_socket cout hostfs cpuid rdtsc device server_epoll unix_socket cout hostfs cpuid rdtsc device sleep
# Benchmarks: need to be compiled and run by bench-% target # Benchmarks: need to be compiled and run by bench-% target
BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput

5
test/sleep/Makefile Normal file

@ -0,0 +1,5 @@
include ../test_common.mk
EXTRA_C_FLAGS := -Wno-return-stack-address
EXTRA_LINK_FLAGS :=
BIN_ARGS :=

174
test/sleep/main.c Normal file

@ -0,0 +1,174 @@
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include "test.h"
// ============================================================================
// Helper macros
// ============================================================================
#define S (1000 * 1000 * 1000)
#define MS (1000 * 1000)
#define US (1000)
#define NS (1)
// ============================================================================
// Helper functions
// ============================================================================
static inline void validate_timespec(const struct timespec* tv) {
assert(tv->tv_sec >= 0 && tv->tv_nsec >= 0 && tv->tv_nsec < S);
}
// retval = (a < b) ? -1 : ((a > b) ? 1 : 0)
static int timespec_cmp(const struct timespec *a, const struct timespec *b)
{
validate_timespec(a);
validate_timespec(b);
if (a->tv_sec < b->tv_sec) {
return -1;
} else if (a->tv_sec > b->tv_sec) {
return 1;
} else {
return a->tv_nsec < b->tv_nsec ? -1 :
(a->tv_nsec > b->tv_nsec ? 1 : 0);
}
}
// diff = | a - b |
static void timespec_diff(const struct timespec *a, const struct timespec *b,
struct timespec *diff)
{
validate_timespec(a);
validate_timespec(b);
const struct timespec *begin, *end;
if (timespec_cmp(a, b) <= 0) {
begin = a; end = b;
} else {
begin = b; end = a;
}
diff->tv_nsec = end->tv_nsec - begin->tv_nsec;
diff->tv_sec = end->tv_sec - begin->tv_sec;
if (diff->tv_nsec < 0) {
diff->tv_nsec += S;
diff->tv_sec -= 1;
}
validate_timespec(diff);
}
// retval = | a - b | <= precision
static int timespec_equal(const struct timespec *a, const struct timespec *b,
const struct timespec *precision)
{
struct timespec diff;
timespec_diff(a, b, &diff);
return timespec_cmp(&diff, precision) <= 0;
}
static int check_nanosleep(struct timespec* expected_sleep_period) {
// The time obtained from Occlum is not very precise.
// Here we take 1 millisecond as the time precision of Occlum.
static struct timespec OS_TIME_PRECISION = {
.tv_sec = 0,
.tv_nsec = 1 * MS,
};
struct timespec begin_timestamp, end_timestamp;
clock_gettime(CLOCK_MONOTONIC, &begin_timestamp);
if (nanosleep(expected_sleep_period, NULL) != 0) {
throw_error("nanosleep failed");
}
clock_gettime(CLOCK_MONOTONIC, &end_timestamp);
struct timespec actual_sleep_period;
timespec_diff(&begin_timestamp, &end_timestamp, &actual_sleep_period);
return timespec_equal(expected_sleep_period, &actual_sleep_period,
&OS_TIME_PRECISION);
}
// ============================================================================
// Test cases
// ============================================================================
int test_nanosleep_0_second() {
struct timespec period_of_0s = { .tv_sec = 0, .tv_nsec = 0 };
return check_nanosleep(&period_of_0s);
}
int test_nanosleep_1_second() {
struct timespec period_of_1s = { .tv_sec = 1, .tv_nsec = 0 };
return check_nanosleep(&period_of_1s);
}
int test_nanosleep_10ms() {
struct timespec period_of_10ms = { .tv_sec = 0, .tv_nsec = 10 * MS };
return check_nanosleep(&period_of_10ms);
}
// ============================================================================
// Test cases with invalid arguments
// ============================================================================
int test_nanosleep_with_null_req() {
if (nanosleep(NULL, NULL) != -1 && errno != EINVAL) {
throw_error("nanosleep should report error");
}
return 0;
}
int test_nanosleep_with_negative_tv_sec() {
// nanosleep returns EINVAL if the value in the tv_sec field is negative
struct timespec invalid_period = { .tv_sec = -1, .tv_nsec = 0};
if (nanosleep(&invalid_period, NULL) != -1 && errno != EINVAL) {
throw_error("nanosleep should report EINVAL error");
}
return 0;
}
int test_nanosleep_with_negative_tv_nsec() {
// nanosleep returns EINVAL if the value in the tv_nsec field
// was not in the range 0 to 999999999.
struct timespec invalid_period = { .tv_sec = 0, .tv_nsec = -1};
if (nanosleep(&invalid_period, NULL) != -1 && errno != EINVAL) {
throw_error("nanosleep should report EINVAL error");
}
return 0;
}
int test_nanosleep_with_too_large_tv_nsec() {
// nanosleep returns EINVAL if the value in the tv_nsec field
// was not in the range 0 to 999999999 (10^6 - 1).
struct timespec invalid_period = { .tv_sec = 0, .tv_nsec = S};
if (nanosleep(&invalid_period, NULL) != -1 && errno != EINVAL) {
throw_error("nanosleep should report EINVAL error");
}
return 0;
}
// ============================================================================
// Test suite main
// ============================================================================
// TODO: test interruption
static test_case_t test_cases[] = {
TEST_CASE(test_nanosleep_0_second),
TEST_CASE(test_nanosleep_1_second),
TEST_CASE(test_nanosleep_10ms),
TEST_CASE(test_nanosleep_with_null_req),
TEST_CASE(test_nanosleep_with_negative_tv_sec),
TEST_CASE(test_nanosleep_with_negative_tv_nsec),
TEST_CASE(test_nanosleep_with_too_large_tv_nsec)
};
int main() {
return test_suite_run(test_cases, ARRAY_SIZE(test_cases));
}