From 30c99add6f89120a92c717d6019383e7b3516bc9 Mon Sep 17 00:00:00 2001 From: "Tate, Hongliang Tian" Date: Tue, 3 Sep 2019 14:00:38 +0000 Subject: [PATCH] Add nanosleep syscall --- src/Enclave.edl | 1 + src/libos/src/syscall/mod.rs | 14 +++ src/libos/src/time/mod.rs | 25 ++++- src/libos/src/util/mem_util.rs | 8 ++ src/pal/pal.c | 5 + test/Makefile | 2 +- test/sleep/Makefile | 5 + test/sleep/main.c | 174 +++++++++++++++++++++++++++++++++ 8 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 test/sleep/Makefile create mode 100644 test/sleep/main.c diff --git a/src/Enclave.edl b/src/Enclave.edl index 0667ab20..5e9ff290 100644 --- a/src/Enclave.edl +++ b/src/Enclave.edl @@ -19,6 +19,7 @@ enclave { int ocall_run_new_task(void); void ocall_gettimeofday([out] long* sec, [out] long* us); void ocall_clock_gettime(int clockid, [out] long* sec, [out] long* ns); + void ocall_nanosleep(long sec, long nsec); 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_setaffinity([out] int *error, int pid, size_t cpusize, [in, size=cpusize] const unsigned char* buf); diff --git a/src/libos/src/syscall/mod.rs b/src/libos/src/syscall/mod.rs index 072dbdd3..44610828 100644 --- a/src/libos/src/syscall/mod.rs +++ b/src/libos/src/syscall/mod.rs @@ -211,6 +211,8 @@ pub extern "C" fn dispatch_syscall( 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_NANOSLEEP => do_nanosleep(arg0 as *const timespec_t, arg1 as *mut timespec_t), + SYS_UNAME => do_uname(arg0 as *mut utsname_t), SYS_PRLIMIT64 => do_prlimit( @@ -812,6 +814,18 @@ fn do_clock_gettime(clockid: clockid_t, ts_u: *mut timespec_t) -> Result Ok(0) } +// TODO: handle remainder +fn do_nanosleep(req_u: *const timespec_t, rem_u: *mut timespec_t) -> Result { + 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 const MAP_FAILED: *const c_void = ((-1) as i64) as *const c_void; diff --git a/src/libos/src/time/mod.rs b/src/libos/src/time/mod.rs index 2e1f07aa..901ad7db 100644 --- a/src/libos/src/time/mod.rs +++ b/src/libos/src/time/mod.rs @@ -43,6 +43,16 @@ pub struct timespec_t { nsec: i64, } +impl timespec_t { + pub fn from_raw_ptr(ptr: *const timespec_t) -> Result { + 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)] pub type clockid_t = i32; @@ -77,6 +87,10 @@ impl ClockID { } pub fn do_clock_gettime(clockid: ClockID) -> Result { + 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 nsec = 0; unsafe { @@ -89,12 +103,17 @@ pub fn do_clock_gettime(clockid: ClockID) -> Result { Ok(timespec_t { sec, nsec }) } -extern "C" { - fn ocall_clock_gettime(clockid: clockid_t, sec: *mut time_t, ns: *mut i64) -> sgx_status_t; +pub fn do_nanosleep(req: ×pec_t) -> Result<()> { + extern "C" { + fn ocall_nanosleep(sec: time_t, nsec: i64) -> sgx_status_t; + } + unsafe { + ocall_nanosleep(req.sec, req.nsec); + } + Ok(()) } // For SEFS - pub struct OcclumTimeProvider; impl TimeProvider for OcclumTimeProvider { diff --git a/src/libos/src/util/mem_util.rs b/src/libos/src/util/mem_util.rs index a41ab222..ecca42d4 100644 --- a/src/libos/src/util/mem_util.rs +++ b/src/libos/src/util/mem_util.rs @@ -9,21 +9,29 @@ pub mod from_user { /// Check the user pointer is within the readable memory range of the user process pub fn check_ptr(user_ptr: *const T) -> Result<()> { + if user_ptr.is_null() { + return_errno!(EINVAL, "Address 0 is invalid"); + } Ok(()) } /// Check the mutable user pointer is within the writable memory of the user process pub fn check_mut_ptr(user_ptr: *mut T) -> Result<()> { + if user_ptr.is_null() { + return_errno!(EINVAL, "Address 0 is invalid"); + } Ok(()) } /// Check the readonly array is within the readable memory of the user process pub fn check_array(user_buf: *const T, count: usize) -> Result<()> { + check_ptr(user_buf); Ok(()) } /// Check the mutable array is within the writable memory of the user process pub fn check_mut_array(user_buf: *mut T, count: usize) -> Result<()> { + check_mut_ptr(user_buf); Ok(()) } diff --git a/src/pal/pal.c b/src/pal/pal.c index 7cca922a..65acb89b 100644 --- a/src/pal/pal.c +++ b/src/pal/pal.c @@ -265,6 +265,11 @@ void ocall_clock_gettime(int clockid, time_t* sec, long* ns) { *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 ret = syscall(__NR_sched_getaffinity, pid, cpusize, buf); if (error) { diff --git a/test/Makefile b/test/Makefile index a80e4c78..619ced79 100644 --- a/test/Makefile +++ b/test/Makefile @@ -7,7 +7,7 @@ TEST_DEPS := dev_null client # 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 \ 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 BENCHES := spawn_and_exit_latency pipe_throughput unix_socket_throughput diff --git a/test/sleep/Makefile b/test/sleep/Makefile new file mode 100644 index 00000000..01fd907c --- /dev/null +++ b/test/sleep/Makefile @@ -0,0 +1,5 @@ +include ../test_common.mk + +EXTRA_C_FLAGS := -Wno-return-stack-address +EXTRA_LINK_FLAGS := +BIN_ARGS := diff --git a/test/sleep/main.c b/test/sleep/main.c new file mode 100644 index 00000000..cd025702 --- /dev/null +++ b/test/sleep/main.c @@ -0,0 +1,174 @@ +#include +#include +#include +#include +#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)); +}