feat(kernel): implement interrupt-driven timer and console with nanosleep support

- Replace syscall-based time functions with CLOCK_MONOTONIC using clock_gettime
- Add 100 Hz PIT timer and COM1 serial input interrupt handling via 8259 PIC
- Implement nanosleep syscall with proper duration calculation and interruptible sleep
- Add blocking console reads that wait for IRQ4 input instead of busy polling
- Update clock resolution to 10 ms (100 Hz) for monotonic clock
- Add proper timer tick tracking and interrupt dispatch mechanism
- Enable hardware interrupts in user mode while maintaining syscall protection
- Add sleep function wrapper around nanosleep in
master
韩天峰 1 month ago
parent 875c726810
commit 7726f49cd7
  1. 5
      examples/typephp-os/README.md
  2. 11
      examples/typephp-os/ROADMAP.md
  3. 2
      examples/typephp-os/kernel/core/abi/typephp_os_abi.h
  4. 1
      examples/typephp-os/kernel/core/abi/typephp_os_syscall.h
  5. 41
      examples/typephp-os/kernel/core/kernel.c
  6. 61
      examples/typephp-os/kernel/core/process-entry.S
  7. 145
      examples/typephp-os/kernel/core/process.c
  8. 5
      examples/typephp-os/user/README.md
  9. 14
      examples/typephp-os/user/cmd/systest.c
  10. 1
      examples/typephp-os/user/include/sys/syscall.h
  11. 1
      examples/typephp-os/user/include/time.h
  12. 1
      examples/typephp-os/user/include/unistd.h
  13. 19
      examples/typephp-os/user/runtime/host.cc
  14. 14
      examples/typephp-os/user/runtime/posix.c

@ -224,13 +224,16 @@ segment.
The native x86-64 `SYSCALL` boundary provides synchronous `read`, `write`, `close`, The native x86-64 `SYSCALL` boundary provides synchronous `read`, `write`, `close`,
`lseek`, `openat`, `exit`/`exit_group`, `getcwd`, `chdir`, `mkdir`, `rmdir`, `lseek`, `openat`, `exit`/`exit_group`, `getcwd`, `chdir`, `mkdir`, `rmdir`,
`unlink`, file stat/access/persistence/truncation families, fixed identity `unlink`, file stat/access/persistence/truncation families, fixed identity
queries, `time`, `gettimeofday`, `clock_gettime`, `clock_getres`, `brk`, queries, `time`, `gettimeofday`, `clock_gettime`, `clock_getres`, `nanosleep`, `brk`,
anonymous private `mmap`, `mprotect`, `munmap`, `getdents64`, the initial anonymous private `mmap`, `mprotect`, `munmap`, `getdents64`, the initial
`fcntl` flag operations, fixed-console `ioctl(TIOCGWINSZ)`, and private `fcntl` flag operations, fixed-console `ioctl(TIOCGWINSZ)`, and private
spawn and same-directory rename operations. The former private directory-list spawn and same-directory rename operations. The former private directory-list
syscall has been removed; `ls` and PHP Nano use the standard directory ABI. Standard syscall has been removed; `ls` and PHP Nano use the standard directory ABI. Standard
input and output are backed by QEMU's COM1 serial console. Syscall numbers are input and output are backed by QEMU's COM1 serial console. Syscall numbers are
shared by the kernel and userspace through `typephp_os_syscall.h`. shared by the kernel and userspace through `typephp_os_syscall.h`.
The 8259 PIC routes a 100 Hz PIT timer on IRQ0 and COM1 input on IRQ4. Console
reads and sleeps block with `HLT` until an interrupt arrives, so the resident
shell no longer consumes a host CPU core while it waits at the prompt.
Entry immediately switches from the untrusted user RSP to a dedicated kernel Entry immediately switches from the untrusted user RSP to a dedicated kernel
stack. Return uses `iretq`, allowing synchronous spawn/exit to replace the stack. Return uses `iretq`, allowing synchronous spawn/exit to replace the
complete saved user context. complete saved user context.

@ -98,14 +98,15 @@ behavior.
does not yet replace the working userspace commands. does not yet replace the working userspace commands.
Single-task identity calls report PID/TID 1 for the resident shell, 2 for Single-task identity calls report PID/TID 1 for the resident shell, 2 for
its synchronous foreground command, and root UID/GID. Linux-compatible its synchronous foreground command, and root UID/GID. Linux-compatible
wall/monotonic clock structures are exposed at the current one-second RTC wall-clock time uses the RTC while monotonic time and blocking sleeps use a
resolution. The `systest` ELF exercises this complete syscall slice. 100 Hz PIT. The `systest` ELF exercises this complete syscall slice.
Syscall evolution follows the glibc migration rules in `user/README.md`. Syscall evolution follows the glibc migration rules in `user/README.md`.
7. **Native Class memory.** Exercise Wren GC through Zend MM and verify tracing 7. **Native Class memory.** Exercise Wren GC through Zend MM and verify tracing
of PHPX fields under sustained allocation. of PHPX fields under sustained allocation.
8. **Kernel services.** Interrupt-driven timer and keyboard, higher-level VM 8. **Kernel services.** The legacy PIC, a 100 Hz PIT clock, interrupt-driven
region management, Zend-chunk reclamation, and a capability-oriented native COM1 input, blocking console reads, and blocking `nanosleep` are implemented.
API. Higher-level VM region management, Zend-chunk reclamation, and a
capability-oriented native API remain future work.
9. **Packaging and CI.** Automate the two-stage ELF32/ELF64 build and QEMU boot 9. **Packaging and CI.** Automate the two-stage ELF32/ELF64 build and QEMU boot
smoke test in GitHub Actions. smoke test in GitHub Actions.

@ -43,6 +43,8 @@ void typephp_os_process_start(void);
long typephp_os_fs_path_type(const char *path); long typephp_os_fs_path_type(const char *path);
long typephp_os_fs_getdents64(int fd, void *buffer, size_t capacity); long typephp_os_fs_getdents64(int fd, void *buffer, size_t capacity);
long typephp_os_console_read(void *buffer, size_t size); long typephp_os_console_read(void *buffer, size_t size);
void typephp_os_console_enable_interrupts(void);
void typephp_os_console_interrupt(void);
#ifdef __cplusplus #ifdef __cplusplus
} }

@ -18,6 +18,7 @@ enum typephp_os_syscall_number {
TYPEPHP_SYS_BRK = 12, TYPEPHP_SYS_BRK = 12,
TYPEPHP_SYS_IOCTL = 16, TYPEPHP_SYS_IOCTL = 16,
TYPEPHP_SYS_ACCESS = 21, TYPEPHP_SYS_ACCESS = 21,
TYPEPHP_SYS_NANOSLEEP = 35,
TYPEPHP_SYS_GETPID = 39, TYPEPHP_SYS_GETPID = 39,
TYPEPHP_SYS_EXIT = 60, TYPEPHP_SYS_EXIT = 60,
TYPEPHP_SYS_UNAME = 63, TYPEPHP_SYS_UNAME = 63,

@ -8,11 +8,15 @@ enum {
VGA_WIDTH = 80, VGA_WIDTH = 80,
VGA_HEIGHT = 25, VGA_HEIGHT = 25,
COM1 = 0x3f8, COM1 = 0x3f8,
SERIAL_BUFFER_SIZE = 8192,
}; };
static volatile uint16_t *const vga = (volatile uint16_t *) 0xb8000; static volatile uint16_t *const vga = (volatile uint16_t *) 0xb8000;
static uint32_t row; static uint32_t row;
static uint32_t column; static uint32_t column;
static volatile uint32_t serial_read_head;
static volatile uint32_t serial_read_tail;
static volatile uint8_t serial_read_buffer[SERIAL_BUFFER_SIZE];
static inline void outb(uint16_t port, uint8_t value) static inline void outb(uint16_t port, uint8_t value)
{ {
@ -37,6 +41,25 @@ static void serial_init(void)
outb(COM1 + 4, 0x0b); outb(COM1 + 4, 0x0b);
} }
void typephp_os_console_enable_interrupts(void)
{
/* Received-data-available interrupt. MCR.OUT2 was enabled by serial_init
* and routes the UART interrupt to the legacy PIC's IRQ4 input. */
outb(COM1 + 1, 0x01);
}
void typephp_os_console_interrupt(void)
{
while ((inb(COM1 + 5) & 0x01u) != 0) {
const uint8_t value = inb(COM1);
const uint32_t next = (serial_read_head + 1u) % SERIAL_BUFFER_SIZE;
if (next != serial_read_tail) {
serial_read_buffer[serial_read_head] = value;
serial_read_head = next;
}
}
}
static void serial_put(uint8_t value) static void serial_put(uint8_t value)
{ {
while ((inb(COM1 + 5) & 0x20u) == 0) { while ((inb(COM1 + 5) & 0x20u) == 0) {
@ -50,14 +73,18 @@ long typephp_os_console_read(void *buffer, unsigned long size)
if (output == 0 || size == 0) { if (output == 0 || size == 0) {
return 0; return 0;
} }
/* COM1 is the standard input device for the headless QEMU target. Keep /* The system remains single-task, but a blocking read can still halt the
* this synchronous: the process model intentionally has one foreground * CPU until IRQ4 supplies input. STI followed immediately by HLT avoids
* task and no scheduler yet. */ * the empty-buffer/check-to-sleep lost-wakeup race on x86. */
while ((inb(COM1 + 5) & 0x01u) == 0) { for (;;) {
__asm__ volatile("pause"); __asm__ volatile("cli" : : : "memory");
} if (serial_read_tail != serial_read_head) {
output[0] = inb(COM1); output[0] = serial_read_buffer[serial_read_tail];
serial_read_tail = (serial_read_tail + 1u) % SERIAL_BUFFER_SIZE;
return 1; return 1;
}
__asm__ volatile("sti; hlt; cli" : : : "memory");
}
} }
static void vga_scroll(void) static void vga_scroll(void)

@ -89,6 +89,60 @@ TYPEPHP_EXCEPTION_WITH_ERROR 12
TYPEPHP_EXCEPTION_WITH_ERROR 13 TYPEPHP_EXCEPTION_WITH_ERROR 13
TYPEPHP_EXCEPTION_WITH_ERROR 14 TYPEPHP_EXCEPTION_WITH_ERROR 14
.macro TYPEPHP_IRQ number vector
.global typephp_os_irq_\number
.type typephp_os_irq_\number, @function
typephp_os_irq_\number:
pushq $0
pushq $\vector
jmp typephp_os_irq_common
.size typephp_os_irq_\number, . - typephp_os_irq_\number
.endm
TYPEPHP_IRQ 0 32
TYPEPHP_IRQ 4 36
.type typephp_os_irq_common, @function
.extern typephp_os_irq_dispatch
typephp_os_irq_common:
push %rbx
push %rcx
push %rdx
push %rsi
push %rdi
push %rbp
push %r8
push %r9
push %r10
push %r11
push %r12
push %r13
push %r14
push %r15
push %rax
mov %rsp, %rdi
call typephp_os_irq_dispatch
pop %rax
pop %r15
pop %r14
pop %r13
pop %r12
pop %r11
pop %r10
pop %r9
pop %r8
pop %rbp
pop %rdi
pop %rsi
pop %rdx
pop %rcx
pop %rbx
add $16, %rsp
iretq
.size typephp_os_irq_common, . - typephp_os_irq_common
.type typephp_os_exception_common, @function .type typephp_os_exception_common, @function
.extern typephp_os_exception_dispatch .extern typephp_os_exception_dispatch
typephp_os_exception_common: typephp_os_exception_common:
@ -133,14 +187,15 @@ typephp_os_exception_common:
.global typephp_os_enter_user .global typephp_os_enter_user
.type typephp_os_enter_user, @function .type typephp_os_enter_user, @function
typephp_os_enter_user: typephp_os_enter_user:
# RDI = ELF entry, RSI = top of the user stack. Interrupts remain masked; # RDI = ELF entry, RSI = top of the user stack. Hardware interrupts stay
# SYSCALL is synchronous and the first process manager has no scheduler. # enabled in Ring 3 so timer and serial input can arrive while a command is
# running. IA32_FMASK masks them again on every SYSCALL entry.
mov $0x1b, %ax mov $0x1b, %ax
mov %ax, %ds mov %ax, %ds
mov %ax, %es mov %ax, %es
pushq $0x1b pushq $0x1b
push %rsi push %rsi
pushq $0x2 pushq $0x202
pushq $0x23 pushq $0x23
push %rdi push %rdi
iretq iretq

@ -35,6 +35,14 @@ enum {
USER_DATA_SELECTOR = 0x1b, USER_DATA_SELECTOR = 0x1b,
KERNEL_CODE_SELECTOR = 0x08, KERNEL_CODE_SELECTOR = 0x08,
TSS_SELECTOR = 0x28, TSS_SELECTOR = 0x28,
PIC_MASTER_COMMAND = 0x20,
PIC_MASTER_DATA = 0x21,
PIC_SLAVE_COMMAND = 0xa0,
PIC_SLAVE_DATA = 0xa1,
PIC_END_OF_INTERRUPT = 0x20,
PIT_CHANNEL_0 = 0x40,
PIT_COMMAND = 0x43,
TIMER_FREQUENCY = 100,
MAX_USER_ARGUMENTS = 8, MAX_USER_ARGUMENTS = 8,
USER_PATH_MAX = 128, USER_PATH_MAX = 128,
UTSNAME_LENGTH = 65, UTSNAME_LENGTH = 65,
@ -216,6 +224,8 @@ extern void typephp_os_exception_11(void);
extern void typephp_os_exception_12(void); extern void typephp_os_exception_12(void);
extern void typephp_os_exception_13(void); extern void typephp_os_exception_13(void);
extern void typephp_os_exception_14(void); extern void typephp_os_exception_14(void);
extern void typephp_os_irq_0(void);
extern void typephp_os_irq_4(void);
extern int rename(const char *old_path, const char *new_path); extern int rename(const char *old_path, const char *new_path);
extern uint64_t physical_page_available(void); extern uint64_t physical_page_available(void);
extern uint64_t physical_page_total(void); extern uint64_t physical_page_total(void);
@ -229,6 +239,7 @@ static task_state_segment tss;
unsigned char typephp_os_syscall_stack[64u * 1024u] __attribute__((aligned(16))); unsigned char typephp_os_syscall_stack[64u * 1024u] __attribute__((aligned(16)));
uint64_t typephp_os_syscall_user_rsp; uint64_t typephp_os_syscall_user_rsp;
static process_state foreground_process = {.pid = 1, .cwd = "/"}; static process_state foreground_process = {.pid = 1, .cwd = "/"};
static volatile uint64_t timer_ticks;
enum { enum {
IA32_EFER = 0xc0000080u, IA32_EFER = 0xc0000080u,
@ -245,6 +256,57 @@ static uint64_t read_msr(uint32_t index)
return ((uint64_t) high << 32u) | low; return ((uint64_t) high << 32u) | low;
} }
static void outb(uint16_t port, uint8_t value)
{
__asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port));
}
static void io_wait(void)
{
outb(0x80, 0);
}
static void install_interrupt_controller(void)
{
const uint16_t divisor = (uint16_t) (1193182u / TIMER_FREQUENCY);
/* Remap the legacy PIC away from the CPU exception vectors. */
outb(PIC_MASTER_COMMAND, 0x11);
io_wait();
outb(PIC_SLAVE_COMMAND, 0x11);
io_wait();
outb(PIC_MASTER_DATA, 0x20);
io_wait();
outb(PIC_SLAVE_DATA, 0x28);
io_wait();
outb(PIC_MASTER_DATA, 0x04);
io_wait();
outb(PIC_SLAVE_DATA, 0x02);
io_wait();
outb(PIC_MASTER_DATA, 0x01);
io_wait();
outb(PIC_SLAVE_DATA, 0x01);
io_wait();
/* A 100 Hz periodic PIT is sufficient for the single-task sleep ABI. */
outb(PIT_COMMAND, 0x36);
outb(PIT_CHANNEL_0, (uint8_t) divisor);
outb(PIT_CHANNEL_0, (uint8_t) (divisor >> 8u));
typephp_os_console_enable_interrupts();
/* IRQ0 (PIT) and IRQ4 (COM1) only. The slave PIC stays fully masked. */
outb(PIC_SLAVE_DATA, 0xff);
outb(PIC_MASTER_DATA, (uint8_t) ~(UINT8_C(1) | UINT8_C(1) << 4u));
}
static void pic_end_of_interrupt(unsigned int irq)
{
if (irq >= 8u) {
outb(PIC_SLAVE_COMMAND, PIC_END_OF_INTERRUPT);
}
outb(PIC_MASTER_COMMAND, PIC_END_OF_INTERRUPT);
}
static void write_msr(uint32_t index, uint64_t value) static void write_msr(uint32_t index, uint64_t value)
{ {
__asm__ volatile("wrmsr" : : "c"(index), "a"((uint32_t) value), __asm__ volatile("wrmsr" : : "c"(index), "a"((uint32_t) value),
@ -348,6 +410,8 @@ static void install_descriptor_tables(void)
install_idt_gate(12, typephp_os_exception_12, 0x8e); install_idt_gate(12, typephp_os_exception_12, 0x8e);
install_idt_gate(13, typephp_os_exception_13, 0x8e); install_idt_gate(13, typephp_os_exception_13, 0x8e);
install_idt_gate(14, typephp_os_exception_14, 0x8e); install_idt_gate(14, typephp_os_exception_14, 0x8e);
install_idt_gate(32, typephp_os_irq_0, 0x8e);
install_idt_gate(36, typephp_os_irq_4, 0x8e);
pointer.limit = sizeof(idt) - 1u; pointer.limit = sizeof(idt) - 1u;
pointer.base = (uint64_t) (uintptr_t) idt; pointer.base = (uint64_t) (uintptr_t) idt;
__asm__ volatile("lidt %0" : : "m"(pointer) : "memory"); __asm__ volatile("lidt %0" : : "m"(pointer) : "memory");
@ -362,6 +426,7 @@ static void install_descriptor_tables(void)
write_msr(IA32_LSTAR, (uint64_t) (uintptr_t) typephp_os_syscall_entry); write_msr(IA32_LSTAR, (uint64_t) (uintptr_t) typephp_os_syscall_entry);
write_msr(IA32_FMASK, UINT64_C(0x700)); /* TF, IF and DF */ write_msr(IA32_FMASK, UINT64_C(0x700)); /* TF, IF and DF */
write_msr(IA32_EFER, read_msr(IA32_EFER) | UINT64_C(1)); write_msr(IA32_EFER, read_msr(IA32_EFER) | UINT64_C(1));
install_interrupt_controller();
} }
static int read_exact(int fd, void *buffer, size_t size) static int read_exact(int fd, void *buffer, size_t size)
@ -790,6 +855,7 @@ static long syscall_gettimeofday(user_timeval *result)
static long syscall_clock(int clock_id, user_timespec *result, int resolution) static long syscall_clock(int clock_id, user_timespec *result, int resolution)
{ {
uint64_t ticks;
if (clock_id != 0 && clock_id != 1) { if (clock_id != 0 && clock_id != 1) {
return -EINVAL; return -EINVAL;
} }
@ -799,9 +865,67 @@ static long syscall_clock(int clock_id, user_timespec *result, int resolution)
if (!user_buffer(result, sizeof(*result))) { if (!user_buffer(result, sizeof(*result))) {
return -EFAULT; return -EFAULT;
} }
result->seconds = resolution ? 1 : typephp_os_time_seconds(); if (resolution) {
result->seconds = clock_id == 0 ? 1 : 0;
result->nanoseconds = clock_id == 0
? 0
: 1000000000L / TIMER_FREQUENCY;
return 0;
}
if (clock_id == 0) {
result->seconds = typephp_os_time_seconds();
result->nanoseconds = 0; result->nanoseconds = 0;
return 0; return 0;
}
ticks = timer_ticks;
result->seconds = (long) (ticks / TIMER_FREQUENCY);
result->nanoseconds = (long) (ticks % TIMER_FREQUENCY)
* (1000000000L / TIMER_FREQUENCY);
return 0;
}
static long syscall_nanosleep(
const user_timespec *requested, user_timespec *remaining)
{
uint64_t seconds;
uint64_t subsecond_ticks;
uint64_t duration_ticks;
uint64_t deadline;
long nanoseconds;
if (!user_buffer(requested, sizeof(*requested))) {
return -EFAULT;
}
if (remaining != 0 && !user_buffer(remaining, sizeof(*remaining))) {
return -EFAULT;
}
if (requested->seconds < 0 || requested->nanoseconds < 0
|| requested->nanoseconds >= 1000000000L) {
return -EINVAL;
}
seconds = (uint64_t) requested->seconds;
nanoseconds = requested->nanoseconds;
subsecond_ticks = ((uint64_t) nanoseconds
+ (1000000000u / TIMER_FREQUENCY) - 1u)
/ (1000000000u / TIMER_FREQUENCY);
if (seconds > (UINT64_MAX - subsecond_ticks) / TIMER_FREQUENCY) {
return -EINVAL;
}
duration_ticks = seconds * TIMER_FREQUENCY + subsecond_ticks;
if (duration_ticks > INT64_MAX) {
return -EINVAL;
}
deadline = timer_ticks + duration_ticks;
while ((int64_t) (timer_ticks - deadline) < 0) {
/* The current single foreground process sleeps inside its syscall.
* IRQ0 advances timer_ticks and wakes HLT; no scheduler is required. */
__asm__ volatile("sti; hlt; cli" : : : "memory");
}
if (remaining != 0) {
remaining->seconds = 0;
remaining->nanoseconds = 0;
}
return 0;
} }
static long syscall_uname(user_utsname *result) static long syscall_uname(user_utsname *result)
@ -1199,6 +1323,22 @@ void typephp_os_exception_dispatch(exception_frame *frame)
restore_shell_after_fault(frame); restore_shell_after_fault(frame);
} }
void typephp_os_irq_dispatch(exception_frame *frame)
{
switch (frame->vector) {
case 32:
++timer_ticks;
pic_end_of_interrupt(0);
return;
case 36:
typephp_os_console_interrupt();
pic_end_of_interrupt(4);
return;
default:
panic("unexpected hardware interrupt\n");
}
}
long typephp_os_syscall_dispatch(syscall_frame *frame) long typephp_os_syscall_dispatch(syscall_frame *frame)
{ {
switch (frame->rax) { switch (frame->rax) {
@ -1255,6 +1395,9 @@ long typephp_os_syscall_dispatch(syscall_frame *frame)
case TYPEPHP_SYS_ACCESS: case TYPEPHP_SYS_ACCESS:
return syscall_access_path(AT_FDCWD, (const char *) frame->rdi, return syscall_access_path(AT_FDCWD, (const char *) frame->rdi,
(int) frame->rsi, 0); (int) frame->rsi, 0);
case TYPEPHP_SYS_NANOSLEEP:
return syscall_nanosleep((const user_timespec *) frame->rdi,
(user_timespec *) frame->rsi);
case TYPEPHP_SYS_GETPID: case TYPEPHP_SYS_GETPID:
case TYPEPHP_SYS_GETTID: case TYPEPHP_SYS_GETTID:
return (long) foreground_process.pid; return (long) foreground_process.pid;

@ -46,7 +46,7 @@ logic, while C/C++ is limited to the syscall or PHPX boundary.
The current shared userspace runtime provides `syscall`, `read`, `write`, `openat`, The current shared userspace runtime provides `syscall`, `read`, `write`, `openat`,
`open`, `close`, `lseek`, `getcwd`, `chdir`, `mkdir`, `rmdir`, `unlink`, `open`, `close`, `lseek`, `getcwd`, `chdir`, `mkdir`, `rmdir`, `unlink`,
`rename`, `stat`, `lstat`, `fstat`, `access`, `fsync`, `fdatasync`, `truncate`, `rename`, `stat`, `lstat`, `fstat`, `access`, `fsync`, `fdatasync`, `truncate`,
`ftruncate`, `time`, `gettimeofday`, `clock_gettime`, `clock_getres`, `uname`, `ftruncate`, `time`, `gettimeofday`, `clock_gettime`, `clock_getres`, `nanosleep`, `sleep`, `uname`,
`getpid`, `getppid`, `gettid`, the root UID/GID queries, `brk`, `sbrk`, `mmap`, `getpid`, `getppid`, `gettid`, the root UID/GID queries, `brk`, `sbrk`, `mmap`,
`mprotect`, `munmap`, `opendir`, `fdopendir`, `readdir`, `rewinddir`, `mprotect`, `munmap`, `opendir`, `fdopendir`, `readdir`, `rewinddir`,
`closedir`, `dirfd`, the `F_GETFD`/`F_SETFD`/`F_GETFL`/`F_SETFL` subset of `closedir`, `dirfd`, the `F_GETFD`/`F_SETFD`/`F_GETFL`/`F_SETFL` subset of
@ -85,7 +85,8 @@ The kernel additionally accepts Linux x86-64 `newfstatat`, `faccessat`,
metadata uses the Linux x86-64 144-byte `struct stat` metadata uses the Linux x86-64 144-byte `struct stat`
layout. FAT16 currently has no owners, ACLs, symlinks, executable file bit, or layout. FAT16 currently has no owners, ACLs, symlinks, executable file bit, or
sub-second timestamps: UID/GID are always root, ordinary files are `0666`, sub-second timestamps: UID/GID are always root, ordinary files are `0666`,
directories are `0777`, and clock resolution is one second. directories are `0777`. Wall-clock values currently retain one-second RTC
resolution; the monotonic clock and sleeping use the 100 Hz PIT.
`rename()` currently accepts only source and destination paths with the same `rename()` currently accepts only source and destination paths with the same
parent directory and does not replace an existing entry. Its transport uses a parent directory and does not replace an existing entry. Its transport uses a

@ -39,7 +39,9 @@ int main(int argc, char **argv)
struct timeval wall; struct timeval wall;
struct timespec realtime; struct timespec realtime;
struct timespec monotonic; struct timespec monotonic;
struct timespec after_sleep;
struct timespec resolution; struct timespec resolution;
const struct timespec sleep_duration = {0, 30000000};
struct winsize window; struct winsize window;
DIR *directory; DIR *directory;
struct dirent *entry; struct dirent *entry;
@ -113,10 +115,18 @@ int main(int argc, char **argv)
|| clock_gettime(CLOCK_REALTIME, &realtime) != 0 || clock_gettime(CLOCK_REALTIME, &realtime) != 0
|| clock_gettime(CLOCK_MONOTONIC, &monotonic) != 0 || clock_gettime(CLOCK_MONOTONIC, &monotonic) != 0
|| clock_getres(CLOCK_MONOTONIC, &resolution) != 0 || clock_getres(CLOCK_MONOTONIC, &resolution) != 0
|| realtime.tv_sec <= 0 || monotonic.tv_sec <= 0 || realtime.tv_sec <= 0 || monotonic.tv_nsec < 0
|| resolution.tv_sec != 1 || resolution.tv_nsec != 0) { || monotonic.tv_nsec >= 1000000000L
|| resolution.tv_sec != 0 || resolution.tv_nsec != 10000000L) {
return fail("clock ABI"); return fail("clock ABI");
} }
if (nanosleep(&sleep_duration, NULL) != 0
|| clock_gettime(CLOCK_MONOTONIC, &after_sleep) != 0
|| after_sleep.tv_sec < monotonic.tv_sec
|| (after_sleep.tv_sec == monotonic.tv_sec
&& after_sleep.tv_nsec - monotonic.tv_nsec < 20000000L)) {
return fail("interruptible nanosleep");
}
(void) write(STDOUT_FILENO, "basic syscalls: OK\n", (void) write(STDOUT_FILENO, "basic syscalls: OK\n",
sizeof("basic syscalls: OK\n") - 1); sizeof("basic syscalls: OK\n") - 1);

@ -16,6 +16,7 @@
#define SYS_brk TYPEPHP_SYS_BRK #define SYS_brk TYPEPHP_SYS_BRK
#define SYS_ioctl TYPEPHP_SYS_IOCTL #define SYS_ioctl TYPEPHP_SYS_IOCTL
#define SYS_access TYPEPHP_SYS_ACCESS #define SYS_access TYPEPHP_SYS_ACCESS
#define SYS_nanosleep TYPEPHP_SYS_NANOSLEEP
#define SYS_getpid TYPEPHP_SYS_GETPID #define SYS_getpid TYPEPHP_SYS_GETPID
#define SYS_exit TYPEPHP_SYS_EXIT #define SYS_exit TYPEPHP_SYS_EXIT
#define SYS_uname TYPEPHP_SYS_UNAME #define SYS_uname TYPEPHP_SYS_UNAME

@ -14,5 +14,6 @@ struct timespec {
time_t time(time_t *result); time_t time(time_t *result);
int clock_gettime(clockid_t clock_id, struct timespec *value); int clock_gettime(clockid_t clock_id, struct timespec *value);
int clock_getres(clockid_t clock_id, struct timespec *value); int clock_getres(clockid_t clock_id, struct timespec *value);
int nanosleep(const struct timespec *duration, struct timespec *remaining);
#endif #endif

@ -30,6 +30,7 @@ uid_t getuid(void);
uid_t geteuid(void); uid_t geteuid(void);
gid_t getgid(void); gid_t getgid(void);
gid_t getegid(void); gid_t getegid(void);
unsigned int sleep(unsigned int seconds);
int brk(void *address); int brk(void *address);
void *sbrk(intptr_t increment); void *sbrk(intptr_t increment);
void _exit(int status) __attribute__((noreturn)); void _exit(int status) __attribute__((noreturn));

@ -4,6 +4,7 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
extern "C" int typephp_nano_project_main(); extern "C" int typephp_nano_project_main();
@ -54,17 +55,23 @@ extern "C" void php_nano_host_system_time(
extern "C" std::uint64_t php_nano_host_monotonic_nanoseconds() extern "C" std::uint64_t php_nano_host_monotonic_nanoseconds()
{ {
return static_cast<std::uint64_t>(syscall(SYS_time, nullptr)) timespec value{};
* UINT64_C(1000000000); if (clock_gettime(CLOCK_MONOTONIC, &value) != 0) {
typephp_os_panic("unable to read the monotonic clock");
}
return static_cast<std::uint64_t>(value.tv_sec) * UINT64_C(1000000000)
+ static_cast<std::uint64_t>(value.tv_nsec);
} }
extern "C" void php_nano_host_sleep( extern "C" void php_nano_host_sleep(
std::uint64_t seconds, std::uint32_t nanoseconds) std::uint64_t seconds, std::uint32_t nanoseconds)
{ {
const auto now = static_cast<std::uint64_t>(syscall(SYS_time, nullptr)); const timespec duration{
const auto deadline = now + seconds + (nanoseconds != 0 ? 1u : 0u); static_cast<time_t>(seconds),
while (static_cast<std::uint64_t>(syscall(SYS_time, nullptr)) < deadline) { static_cast<long>(nanoseconds),
__asm__ volatile("pause"); };
if (nanosleep(&duration, nullptr) != 0) {
typephp_os_panic("unable to sleep");
} }
} }

@ -142,18 +142,16 @@ int clock_getres(clockid_t clock_id, struct timespec *value)
unsigned int sleep(unsigned int seconds) unsigned int sleep(unsigned int seconds)
{ {
const time_t deadline = time(NULL) + seconds; struct timespec duration = {(time_t) seconds, 0};
while (time(NULL) < deadline) { struct timespec remaining = {0, 0};
__asm__ volatile("pause"); return nanosleep(&duration, &remaining) == 0
} ? 0
return 0; : (unsigned int) remaining.tv_sec + (remaining.tv_nsec != 0);
} }
int nanosleep(const struct timespec *duration, struct timespec *remaining) int nanosleep(const struct timespec *duration, struct timespec *remaining)
{ {
(void) remaining; return (int) syscall(TYPEPHP_SYS_NANOSLEEP, duration, remaining);
sleep((unsigned int) duration->tv_sec + (duration->tv_nsec != 0));
return 0;
} }
int uname(struct utsname *value) int uname(struct utsname *value)

Loading…
Cancel
Save