occlum/test/pipe/main.c
Tate, Hongliang Tian 6fdfa57a14 Add the new epoll implementation
Before this commit, the epoll implementation works by simply delegating to the
host OS through OCall. One major problem with this implementation is
that it can only handle files that are backed by a file of the host OS
(e.g., sockets), but not those are are mainly implemented by the LibOS
(e.g., pipes). Therefore, a new epoll implementation that can handle all
kinds of files is needed.

This commit completely rewrites the epoll implementation by leveraging
the new event subsystem. Now the new epoll can handle all file types:

1. Host files, e.g., sockets, eventfd;
2. LibOS files, e.g., pipes;
3. Hybrid files, e.g., epoll files.

For a new file type to support epoll, it only neends to implement no
more than four methods of the File trait:

* poll (required for all file types);
* notifier (required for all file files);
* host_fd (only required for host files);
* recv_host_events (only required for host files).
2020-11-10 14:34:40 +08:00

331 lines
8.9 KiB
C

#include <errno.h>
#include <sys/epoll.h>
#include <sys/select.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <spawn.h>
#include <string.h>
#include "test.h"
// ============================================================================
// Helper function
// ============================================================================
static void free_pipe(int *pipe) {
close(pipe[0]);
close(pipe[1]);
}
// ============================================================================
// Test cases
// ============================================================================
int test_fcntl_get_flags() {
int pipe_fds[2];
if (pipe(pipe_fds) < 0) {
THROW_ERROR("failed to create a pipe");
}
if ((fcntl(pipe_fds[0], F_GETFL, 0) != O_RDONLY) ||
(fcntl(pipe_fds[1], F_GETFL, 0) != O_WRONLY)) {
free_pipe(pipe_fds);
THROW_ERROR("fcntl get flags failed");
}
free_pipe(pipe_fds);
return 0;
}
int test_fcntl_set_flags() {
int pipe_fds[2];
if (pipe(pipe_fds) < 0) {
THROW_ERROR("failed to create a pipe");
}
fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK);
if ((fcntl(pipe_fds[0], F_GETFL, 0) != (O_NONBLOCK | O_RDONLY)) ||
(fcntl(pipe_fds[1], F_GETFL, 0) != O_WRONLY)) {
free_pipe(pipe_fds);
THROW_ERROR("fcntl set flags failed");
}
free_pipe(pipe_fds);
return 0;
}
int test_create_with_flags() {
int pipe_fds[2];
if (pipe2(pipe_fds, O_NONBLOCK) < 0) {
THROW_ERROR("failed to create a pipe");
}
if ((fcntl(pipe_fds[0], F_GETFL, 0) != (O_NONBLOCK | O_RDONLY)) ||
(fcntl(pipe_fds[1], F_GETFL, 0) != (O_NONBLOCK | O_WRONLY))) {
free_pipe(pipe_fds);
THROW_ERROR("create flags failed\n");
}
free_pipe(pipe_fds);
return 0;
}
int test_select_timeout() {
fd_set rfds;
int pipe_fds[2];
if (pipe(pipe_fds) < 0) {
THROW_ERROR("failed to create a pipe");
}
struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
FD_ZERO(&rfds);
FD_SET(pipe_fds[0], &rfds);
struct timeval tv_start, tv_end;
gettimeofday(&tv_start, NULL);
select(pipe_fds[0] + 1, &rfds, NULL, NULL, &tv);
gettimeofday(&tv_end, NULL);
double total_s = tv_end.tv_sec - tv_start.tv_sec;
if (total_s < 1) {
printf("time consumed is %f\n",
total_s + (double)(tv_end.tv_usec - tv_start.tv_usec) / 1000000);
THROW_ERROR("select timer does not work correctly");
}
free_pipe(pipe_fds);
return 0;
}
int test_epoll_timeout() {
int pipe_fds[2];
if (pipe(pipe_fds) < 0) {
THROW_ERROR("failed to create a pipe");
}
int pipe_read_fd = pipe_fds[0];
int pipe_write_fd = pipe_fds[1];
int ep_fd = epoll_create1(0);
if (ep_fd < 0) {
THROW_ERROR("failed to create an epoll");
}
int ret;
struct epoll_event event;
event.events = EPOLLIN; // we want the write end to be readable
event.data.u32 = pipe_write_fd;
ret = epoll_ctl(ep_fd, EPOLL_CTL_ADD, pipe_write_fd, &event);
if (ret < 0) {
THROW_ERROR("failed to do epoll ctl");
}
event.events = EPOLLOUT; // we want the read end to be writable
event.data.u32 = pipe_read_fd;
ret = epoll_ctl(ep_fd, EPOLL_CTL_ADD, pipe_read_fd, &event);
if (ret < 0) {
THROW_ERROR("failed to do epoll ctl");
}
// We are waiting for the write end to be readable or the read end to be
// writable, which can never happen. So the epoll_wait must end with
// timeout.
errno = 0;
struct epoll_event events[2];
ret = epoll_wait(ep_fd, events, ARRAY_SIZE(events), 10 /* ms */);
if (ret != 0 || errno != 0) {
THROW_ERROR("failed to do epoll ctl");
}
free_pipe(pipe_fds);
close(ep_fd);
return 0;
}
int test_poll_timeout() {
// Start the timer
struct timeval tv_start, tv_end;
gettimeofday(&tv_start, NULL);
int fds[2];
if (pipe(fds) < 0) {
THROW_ERROR("pipe failed");
}
struct pollfd polls[] = {
{ .fd = fds[0], .events = POLLOUT },
{ .fd = fds[1], .events = POLLIN }
};
poll(polls, 2, 1000);
// Stop the timer
gettimeofday(&tv_end, NULL);
double total_s = tv_end.tv_sec - tv_start.tv_sec;
if ((int)total_s < 1) {
printf("time consumed is %f\n",
total_s + (double)(tv_end.tv_usec - tv_start.tv_usec) / 1000000);
THROW_ERROR("poll timer does not work correctly");
}
return 0;
}
int test_select_no_timeout() {
fd_set wfds;
int ret = 0;
int pipe_fds[2];
if (pipe(pipe_fds) < 0) {
THROW_ERROR("failed to create a pipe");
}
FD_ZERO(&wfds);
FD_SET(pipe_fds[1], &wfds);
ret = select(pipe_fds[1] + 1, NULL, &wfds, NULL, NULL);
if (ret != 1) {
free_pipe(pipe_fds);
THROW_ERROR("select failed");
}
if (FD_ISSET(pipe_fds[1], &wfds) == 0) {
free_pipe(pipe_fds);
THROW_ERROR("bad select return");
}
free_pipe(pipe_fds);
return 0;
}
int test_poll_no_timeout() {
int pipe_fds[2];
if (pipe(pipe_fds) < 0) {
THROW_ERROR("failed to create a pipe");
}
struct pollfd polls[] = {
{ .fd = pipe_fds[0], .events = POLLIN },
{ .fd = pipe_fds[1], .events = POLLOUT },
{ .fd = pipe_fds[1], .events = POLLOUT },
};
int ret = poll(polls, 3, -1);
if (ret < 0) { THROW_ERROR("poll error"); }
if (polls[0].revents != 0 || (polls[1].revents & POLLOUT) == 0 ||
(polls[2].revents & POLLOUT) == 0 || ret != 2) { THROW_ERROR("wrong return events"); }
return 0;
}
int test_epoll_no_timeout() {
int pipe_fds[2];
if (pipe(pipe_fds) < 0) {
THROW_ERROR("failed to create a pipe");
}
int pipe_read_fd = pipe_fds[0];
int pipe_write_fd = pipe_fds[1];
int ep_fd = epoll_create1(0);
if (ep_fd < 0) {
THROW_ERROR("failed to create an epoll");
}
int ret;
struct epoll_event event;
event.events = EPOLLOUT; // writable
event.data.u32 = pipe_write_fd;
ret = epoll_ctl(ep_fd, EPOLL_CTL_ADD, pipe_write_fd, &event);
if (ret < 0) {
THROW_ERROR("failed to do epoll ctl");
}
event.events = EPOLLIN; // readable
event.data.u32 = pipe_read_fd;
ret = epoll_ctl(ep_fd, EPOLL_CTL_ADD, pipe_read_fd, &event);
if (ret < 0) {
THROW_ERROR("failed to do epoll ctl");
}
struct epoll_event events[2];
ret = epoll_wait(ep_fd, events, ARRAY_SIZE(events), -1);
// pipe_write_fd is ready, while pipe_read_fd is not
if (ret != 1) {
THROW_ERROR("failed to do epoll ctl");
}
free_pipe(pipe_fds);
close(ep_fd);
return 0;
}
int test_select_read_write() {
int pipe_fds[2];
if (pipe(pipe_fds) < 0) {
THROW_ERROR("failed to create a pipe");
}
int pipe_rd_fd = pipe_fds[0];
int pipe_wr_fd = pipe_fds[1];
posix_spawn_file_actions_t file_actions;
posix_spawn_file_actions_init(&file_actions);
posix_spawn_file_actions_adddup2(&file_actions, pipe_wr_fd, STDOUT_FILENO);
posix_spawn_file_actions_addclose(&file_actions, pipe_rd_fd);
const char *msg = "Echo!\n";
const char *child_prog = "/bin/hello_world";
const char *child_argv[3] = { child_prog, msg, NULL };
int child_pid;
if (posix_spawn(&child_pid, child_prog, &file_actions,
NULL, (char *const *)child_argv, NULL) < 0) {
THROW_ERROR("failed to spawn a child process");
}
close(pipe_wr_fd);
const char *expected_str = msg;
size_t expected_len = strlen(expected_str);
char actual_str[32] = {0};
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(pipe_fds[0], &rfds);
if (select(pipe_fds[0] + 1, &rfds, NULL, NULL, NULL) != 1) {
free_pipe(pipe_fds);
THROW_ERROR("select failed");
}
if (read(pipe_rd_fd, actual_str, sizeof(actual_str) - 1) < 0) {
THROW_ERROR("reading pipe failed");
};
if (strncmp(expected_str, actual_str, expected_len) != 0) {
THROW_ERROR("received string is not as expected");
}
close(pipe_rd_fd);
int status = 0;
if (wait4(child_pid, &status, 0, NULL) < 0) {
THROW_ERROR("failed to wait4 the child process");
}
return 0;
}
// ============================================================================
// Test suite
// ============================================================================
static test_case_t test_cases[] = {
TEST_CASE(test_fcntl_get_flags),
TEST_CASE(test_fcntl_set_flags),
TEST_CASE(test_create_with_flags),
//TEST_CASE(test_select_timeout),
//TEST_CASE(test_poll_timeout),
TEST_CASE(test_epoll_timeout),
//TEST_CASE(test_select_no_timeout),
//TEST_CASE(test_poll_no_timeout),
TEST_CASE(test_epoll_no_timeout),
//TEST_CASE(test_select_read_write),
};
int main(int argc, const char *argv[]) {
return test_suite_run(test_cases, ARRAY_SIZE(test_cases));
}