diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index b277501c3e..c08f5176bc 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -1909,3 +1909,78 @@ DisplayInfo Process::get_display_info() info.framebuffer = m_display_framebuffer_region->linearAddress.asPtr(); return info; } + +int Process::sys$select(const Syscall::SC_select_params* params) +{ + if (!validate_read_typed(params)) + return -EFAULT; + if (params->writefds && !validate_read_typed(params->writefds)) + return -EFAULT; + if (params->readfds && !validate_read_typed(params->readfds)) + return -EFAULT; + if (params->exceptfds && !validate_read_typed(params->exceptfds)) + return -EFAULT; + if (params->timeout && !validate_read_typed(params->timeout)) + return -EFAULT; + int nfds = params->nfds; + fd_set* writefds = params->writefds; + fd_set* readfds = params->readfds; + fd_set* exceptfds = params->exceptfds; + auto* timeout = params->timeout; + + // FIXME: Implement exceptfds support. + ASSERT(!exceptfds); + + // FIXME: Implement timeout support. + ASSERT(!timeout); + + if (nfds < 0) + return -EINVAL; + + // FIXME: Return -EBADF if one of the fd sets contains an invalid fd. + // FIXME: Return -EINTR if a signal is caught. + // FIXME: Return -EINVAL if timeout is invalid. + + auto transfer_fds = [nfds] (fd_set* set, auto& vector) { + if (!set) + return; + vector.clear_with_capacity(); + auto bitmap = Bitmap::wrap((byte*)set, FD_SETSIZE); + for (int i = 0; i < nfds; ++i) { + if (bitmap.get(i)) + vector.append(i); + } + }; + + transfer_fds(writefds, m_select_write_fds); + transfer_fds(readfds, m_select_read_fds); + + block(BlockedSelect); + Scheduler::yield(); + + int markedfds = 0; + + if (readfds) { + memset(readfds, 0, sizeof(fd_set)); + auto bitmap = Bitmap::wrap((byte*)readfds, FD_SETSIZE); + for (int fd : m_select_read_fds) { + if (m_fds[fd].descriptor->has_data_available_for_reading(*this)) { + bitmap.set(fd, true); + ++markedfds; + } + } + } + + if (writefds) { + memset(writefds, 0, sizeof(fd_set)); + auto bitmap = Bitmap::wrap((byte*)writefds, FD_SETSIZE); + for (int fd : m_select_write_fds) { + if (m_fds[fd].descriptor->can_write(*this)) { + bitmap.set(fd, true); + ++markedfds; + } + } + } + + return markedfds; +} diff --git a/Kernel/Process.h b/Kernel/Process.h index c696244fb7..2660a6f201 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -69,6 +69,7 @@ public: BlockedRead, BlockedWrite, BlockedSignal, + BlockedSelect, }; enum RingLevel { @@ -81,7 +82,7 @@ public: bool is_blocked() const { - return m_state == BlockedSleep || m_state == BlockedWait || m_state == BlockedRead || m_state == BlockedSignal; + return m_state == BlockedSleep || m_state == BlockedWait || m_state == BlockedRead || m_state == BlockedSignal || m_state == BlockedSelect; } PageDirectory& page_directory() { return *m_page_directory; } @@ -156,6 +157,7 @@ public: void* sys$mmap(const Syscall::SC_mmap_params*); int sys$munmap(void*, size_t size); int sys$set_mmap_name(void*, size_t, const char*); + int sys$select(const Syscall::SC_select_params*); ssize_t sys$get_dir_entries(int fd, void*, size_t); int sys$getcwd(char*, size_t); int sys$chdir(const char*); @@ -304,6 +306,8 @@ private: dword m_timesScheduled { 0 }; pid_t m_waitee_pid { -1 }; int m_blocked_fd { -1 }; + Vector m_select_read_fds; + Vector m_select_write_fds; size_t m_max_open_file_descriptors { 16 }; SignalActionData m_signal_action_data[32]; dword m_pending_signals { 0 }; @@ -394,6 +398,7 @@ static inline const char* toString(Process::State state) case Process::BlockedRead: return "Read"; case Process::BlockedWrite: return "Write"; case Process::BlockedSignal: return "Signal"; + case Process::BlockedSelect: return "Select"; case Process::BeingInspected: return "Inspect"; } ASSERT_NOT_REACHED(); diff --git a/Kernel/Scheduler.cpp b/Kernel/Scheduler.cpp index 4c99f6548e..ac4edd59cb 100644 --- a/Kernel/Scheduler.cpp +++ b/Kernel/Scheduler.cpp @@ -64,6 +64,22 @@ bool Scheduler::pick_next() return true; } + if (process.state() == Process::BlockedSelect) { + for (int fd : process.m_select_read_fds) { + if (process.m_fds[fd].descriptor->has_data_available_for_reading(process)) { + process.unblock(); + return true; + } + } + for (int fd : process.m_select_write_fds) { + if (process.m_fds[fd].descriptor->can_write(process)) { + process.unblock(); + return true; + } + } + return true; + } + if (process.state() == Process::Skip1SchedulerPass) { process.set_state(Process::Skip0SchedulerPasses); return true; diff --git a/Kernel/Syscall.cpp b/Kernel/Syscall.cpp index 547e67fc6f..a4d9c0e9de 100644 --- a/Kernel/Syscall.cpp +++ b/Kernel/Syscall.cpp @@ -98,6 +98,8 @@ static dword handle(RegisterDump& regs, dword function, dword arg1, dword arg2, return current->sys$waitpid((pid_t)arg1, (int*)arg2, (int)arg3); case Syscall::SC_mmap: return (dword)current->sys$mmap((const SC_mmap_params*)arg1); + case Syscall::SC_select: + return current->sys$select((const SC_select_params*)arg1); case Syscall::SC_munmap: return current->sys$munmap((void*)arg1, (size_t)arg2); case Syscall::SC_gethostname: diff --git a/Kernel/Syscall.h b/Kernel/Syscall.h index f0e315ec47..2159d491f2 100644 --- a/Kernel/Syscall.h +++ b/Kernel/Syscall.h @@ -71,8 +71,11 @@ __ENUMERATE_SYSCALL(gui_destroy_window) \ __ENUMERATE_SYSCALL(gui_get_window_backing_store) \ __ENUMERATE_SYSCALL(gui_invalidate_window) \ + __ENUMERATE_SYSCALL(select) \ +struct fd_set; + namespace Syscall { enum Function { @@ -102,6 +105,14 @@ struct SC_mmap_params { int32_t offset; // FIXME: 64-bit off_t? }; +struct SC_select_params { + int nfds; + fd_set* readfds; + fd_set* writefds; + fd_set* exceptfds; + struct timeval* timeout; +}; + void initialize(); int sync(); diff --git a/LibC/Makefile b/LibC/Makefile index 28ee26eb61..719781da42 100644 --- a/LibC/Makefile +++ b/LibC/Makefile @@ -41,6 +41,7 @@ LIBC_OBJS = \ math.o \ utime.o \ gui.o \ + sys/select.o \ entry.o OBJS = $(AK_OBJS) $(WIDGETS_OBJS) $(LIBC_OBJS) diff --git a/Terminal/main.cpp b/Terminal/main.cpp index 69343aec23..6b2fa8b3da 100644 --- a/Terminal/main.cpp +++ b/Terminal/main.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "Terminal.h" @@ -53,9 +54,14 @@ static void make_shell(int ptm_fd) } } +static int max(int a, int b) +{ + return a > b ? a : b; +} + int main(int, char**) { - int ptm_fd = open("/dev/ptm0", O_RDWR | O_NONBLOCK); + int ptm_fd = open("/dev/ptm0", O_RDWR); if (ptm_fd < 0) { perror("open"); return 1; @@ -63,7 +69,7 @@ int main(int, char**) make_shell(ptm_fd); - int event_fd = open("/dev/gui_events", O_RDONLY | O_NONBLOCK); + int event_fd = open("/dev/gui_events", O_RDONLY); if (event_fd < 0) { perror("open"); return 1; @@ -74,38 +80,53 @@ int main(int, char**) terminal.paint(); for (;;) { - byte buffer[1024]; - ssize_t ptm_nread = read(ptm_fd, buffer, sizeof(buffer)); - if (ptm_nread > 0) { - for (ssize_t i = 0; i < ptm_nread; ++i) + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(ptm_fd, &rfds); + FD_SET(event_fd, &rfds); + int nfds = select(max(ptm_fd, event_fd) + 1, &rfds, nullptr, nullptr, nullptr); + if (nfds < 0) { + dbgprintf("Terminal(%u) select() failed :( errno=%d\n", getpid(), errno); + } + + if (FD_ISSET(ptm_fd, &rfds)) { + byte buffer[1024]; + ssize_t nread = read(ptm_fd, buffer, sizeof(buffer)); + if (nread < 0) { + perror("read(ptm)"); + continue; + } + assert(nread != 0); + for (ssize_t i = 0; i < nread; ++i) terminal.on_char(buffer[i]); terminal.paint(); } - GUI_Event event; - ssize_t nread = read(event_fd, &event, sizeof(event)); - if (nread < 0) { - perror("read"); - return 1; - } - if (nread == 0) - continue; - assert(nread == sizeof(event)); - dbgprintf("(Terminal:%d) ", getpid()); - switch (event.type) { - case GUI_Event::Type::Paint: dbgprintf("WID=%x Paint [%d,%d %dx%d]\n", event.window_id, event.paint.rect.location.x, event.paint.rect.location.y, event.paint.rect.size.width, event.paint.rect.size.height); break; - case GUI_Event::Type::MouseDown: dbgprintf("WID=%x MouseDown %d,%d\n", event.window_id, event.mouse.position.x, event.mouse.position.y); break; - case GUI_Event::Type::MouseUp: dbgprintf("WID=%x MouseUp %d,%d\n", event.window_id, event.mouse.position.x, event.mouse.position.y); break; - case GUI_Event::Type::MouseMove: dbgprintf("WID=%x MouseMove %d,%d\n", event.window_id, event.mouse.position.x, event.mouse.position.y); break; - case GUI_Event::Type::KeyDown: dbgprintf("WID=%x KeyDown 0x%b (%c)\n", event.window_id, event.key.character, event.key.character); break; - default: - ASSERT_NOT_REACHED(); - } + if (FD_ISSET(event_fd, &rfds)) { + GUI_Event event; + ssize_t nread = read(event_fd, &event, sizeof(event)); + if (nread < 0) { + perror("read(event)"); + return 1; + } + assert(nread != 0); + assert(nread == sizeof(event)); + dbgprintf("(Terminal:%d) ", getpid()); + switch (event.type) { + case GUI_Event::Type::Paint: dbgprintf("WID=%x Paint [%d,%d %dx%d]\n", event.window_id, event.paint.rect.location.x, event.paint.rect.location.y, event.paint.rect.size.width, event.paint.rect.size.height); break; + case GUI_Event::Type::MouseDown: dbgprintf("WID=%x MouseDown %d,%d\n", event.window_id, event.mouse.position.x, event.mouse.position.y); break; + case GUI_Event::Type::MouseUp: dbgprintf("WID=%x MouseUp %d,%d\n", event.window_id, event.mouse.position.x, event.mouse.position.y); break; + case GUI_Event::Type::MouseMove: dbgprintf("WID=%x MouseMove %d,%d\n", event.window_id, event.mouse.position.x, event.mouse.position.y); break; + case GUI_Event::Type::KeyDown: dbgprintf("WID=%x KeyDown 0x%b (%c)\n", event.window_id, event.key.character, event.key.character); break; + default: + ASSERT_NOT_REACHED(); + } - if (event.type == GUI_Event::Type::Paint) { - terminal.paint(); - } else if (event.type == GUI_Event::Type::KeyDown) { - write(ptm_fd, &event.key.character, 1); + if (event.type == GUI_Event::Type::Paint) { + terminal.paint(); + } else if (event.type == GUI_Event::Type::KeyDown) { + write(ptm_fd, &event.key.character, 1); + } } } return 0; diff --git a/VirtualFileSystem/UnixTypes.h b/VirtualFileSystem/UnixTypes.h index 765dfe67ec..d4eb3aedcd 100644 --- a/VirtualFileSystem/UnixTypes.h +++ b/VirtualFileSystem/UnixTypes.h @@ -2,6 +2,9 @@ extern "C" { +#define FD_SETSIZE 64 +struct fd_set { unsigned char bits[FD_SETSIZE / 8]; }; + namespace Unix { #define WNOHANG 1