SchOS/kernel/sys/sys_compat.c
Nexoniarz f4d7f9100d Threads
A thread is a struct proc that shares three counted objects with others of
its group: the address space (struct mm: page tables, heap top, VMAs and a
lock for changing them), the descriptor table, and the signal dispositions.
clone() shares what its flags say and fork() copies.

- clone(): pthread_create's shape (VM, FILES, SIGHAND, THREAD, SETTLS,
  PARENT_SETTID, CHILD_CLEARTID) and posix_spawn's (VM, VFORK, a stack).
- futex: WAIT, WAIT_BITSET with timeouts, WAKE, REQUEUE, CMP_REQUEUE, keyed
  by the word's kernel address so shared pages meet. set_tid_address keeps
  the word that exit zeroes and wakes, for pthread_join.
- exit ends a thread; exit_group, a fatal signal, or the leader exiting ends
  them all. reapd frees exited threads; the leader is reaped by its parent
  once the rest are gone. getpid is the group, gettid the thread.
- Stops and resumes reach every thread; other process signals the leader.
- exec kills the other threads and unshares the dispositions.
- No TLB shootdown, so pages unmapped from a shared address space are freed
  only once every CPU has reloaded CR3 or is idle.
- procfs: /proc lists processes, task/ lists threads, comm, Threads:, Tgid.
- The console honours VMIN 0 / VTIME (btop's input drain hung on it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 03:03:50 +01:00

234 lines
6.5 KiB
C

// POSIX / Linux-ABI compatibility syscalls.
// These are stubs or thin wrappers that make musl and ksh happy
// without implementing full semantics.
#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "arch/memlayout.h"
#include "arch/mmu.h"
#include "arch/traps.h"
#include "core/proc.h"
#include "arch/x86.h"
#include "core/syscall.h"
#include "uapi/posix.h"
#include "core/uaccess.h"
#include "uapi/errno.h"
#include "core/klog.h" /* KLOG_BYTES, for syslog(2) action 10 */
extern int linux_syscall_map[];
extern int linux_syscall_map_size;
// enable_linux_abi: switch this process to Linux syscall numbering.
addr_t
sys_enable_linux_abi(void)
{
myproc()->syscall_map = linux_syscall_map;
myproc()->map_size = linux_syscall_map_size;
return 0;
}
// set_tid_address: musl calls this at startup to register its TID location.
addr_t
sys_set_tid_address(void)
{
// The word zeroed and woken when this thread exits -- what pthread_join
// waits on. It used to be *written*, with the tid, which is CHILD_SETTID's
// job and not this one's.
addr_t tidp;
argaddr(0, &tidp);
myproc()->clear_child_tid = tidp;
return myproc()->pid;
}
// set_robust_list: robust mutex cleanup on exit — stub.
addr_t sys_set_robust_list(void) { return 0; }
// futex: used by musl for single-threaded lock primitives.
addr_t
sys_futex(void)
{
addr_t uaddr, utime, uaddr2;
int op, val, val3;
argaddr(0, &uaddr);
argint(1, &op);
argint(2, &val);
argaddr(3, &utime);
argaddr(4, &uaddr2);
argint(5, &val3);
return futex_op(uaddr, op, val, utime, uaddr2, val3);
}
// arch_prctl: save FS_BASE for TLS.
addr_t
sys_arch_prctl(void)
{
int code;
addr_t addr;
argint(0, &code);
argaddr(1, &addr);
if (code == ARCH_SET_FS) {
myproc()->fs_base = addr;
return 0;
}
if (code == ARCH_GET_FS) {
if (put_user(myproc()->fs_base, addr) < 0)
return -EFAULT;
return 0;
}
return -EINVAL;
}
// Limits are reported, not enforced: everything is unlimited except what the
// kernel actually has a ceiling for. RLIMIT_NOFILE was "unlimited" too, and
// musl turns that into sysconf(_SC_OPEN_MAX) == -1 -- after which zsh decided
// it had no descriptors at all, and `exec {fd}<file` failed on the first try.
#define RLIMIT_NOFILE_ 7
static void
rlimit_of(int resource, struct rlimit *r)
{
r->rlim_cur = r->rlim_max = (uint64)-1;
if (resource == RLIMIT_NOFILE_)
r->rlim_cur = r->rlim_max = NOFILE;
}
addr_t
sys_getrlimit(void)
{
int resource;
struct rlimit *rlp;
argint(0, &resource);
if (argptr(1, (char**)&rlp, sizeof(*rlp)) < 0)
return -1;
rlimit_of(resource, rlp);
return 0;
}
addr_t
sys_prlimit(void)
{
int resource;
struct rlimit *oldrlp;
argint(1, &resource);
if (argptr(3, (char**)&oldrlp, sizeof(*oldrlp)) == 0 && oldrlp)
rlimit_of(resource, oldrlp);
return 0;
}
// uname: return fake but plausible kernel/host info
addr_t
sys_uname(void)
{
struct utsname {
char sysname[65];
char nodename[65];
char release[65];
char version[65];
char machine[65];
char domainname[65];
};
addr_t ubuf;
argaddr(0, &ubuf);
struct utsname *u = (struct utsname*)user_ptr(ubuf, sizeof(*u));
if (!u) return -EFAULT;
memset(u, 0, sizeof(*u));
memmove(u->sysname, "SchIFT", 7);
memmove(u->nodename, "schos", 6);
memmove(u->release, "0.1.0", 6);
memmove(u->version, "#1", 3);
memmove(u->machine, "x86_64", 7);
memmove(u->domainname, "(none)", 7);
return 0;
}
// socket/bind/listen/accept/connect live in sys_net.c now that they do
// something.
// syslog(2) -- what `dmesg` actually calls.
//
// This returned 0 for every action, and 0 to action 10 means "the log buffer
// is zero bytes". So `dmesg` printed nothing, on a system whose boot banner
// says *"or dmesg once you are in"* and whose entire quiet-boot design is
// "put the detail in the kernel log and tell them how to read it". The detail
// was there the whole time -- `cat /proc/kmsg` has always worked -- but the
// one command anybody would reach for could not see it.
//
// dmesg does not read /proc/kmsg. It calls klogctl(10) for the buffer size and
// klogctl(3) to read, which is this syscall, and a stub that answers 0 is
// indistinguishable from a machine that logged nothing.
//
// Only the actions that mean something here are implemented. The rest say so
// rather than returning 0: a console log level this kernel does not have is
// not a request that succeeded.
#define SYSLOG_CLOSE 0
#define SYSLOG_OPEN 1
#define SYSLOG_READ 2
#define SYSLOG_READ_ALL 3
#define SYSLOG_CLEAR 5
#define SYSLOG_SIZE_UNREAD 9
#define SYSLOG_SIZE_BUFFER 10
addr_t sys_syslog(void)
{
int type, len;
addr_t bufaddr;
argint(0, &type);
argaddr(1, &bufaddr);
argint(2, &len);
switch (type) {
case SYSLOG_CLOSE:
case SYSLOG_OPEN:
return 0; // genuinely nothing to do, as on Linux
case SYSLOG_SIZE_BUFFER:
return KLOG_BYTES;
case SYSLOG_SIZE_UNREAD:
return (addr_t)klog_len();
case SYSLOG_READ_ALL: {
char *dst;
if (len < 0)
return -EINVAL;
if (len == 0 || klog_len() == 0)
return 0;
if (argptr(1, &dst, len) < 0)
return -EFAULT;
return (addr_t)klog_render_syslog(dst, len);
}
// Reading destructively needs a per-reader cursor the ring does not carry,
// and clearing needs the ring to forget -- which is the one thing it exists
// not to do, since its whole purpose is surviving the kernel that wrote it.
case SYSLOG_READ:
case SYSLOG_CLEAR:
default:
return -EINVAL;
}
}
// getrandom: fill buffer with pseudo-random bytes.
addr_t sys_getrandom(void)
{
addr_t bufaddr;
int len, flags;
argaddr(0, &bufaddr); argint(1, &len); argint(2, &flags);
if (len <= 0) return 0;
uint8 *buf = (uint8*)user_ptr(bufaddr, (uint64)len);
if (!buf) return -EFAULT;
// LCG seeded from proc pointer + pid; no lock needed (read-only fields).
uint rseed = (uint)(uintptr_t)myproc() ^ (uint)myproc()->pid;
for (int i = 0; i < len; i++) {
rseed = rseed * 1664525u + 1013904223u;
buf[i] = (uint8)(rseed >> 16);
}
return len;
}
// pselect6 / ppoll live in sys_poll.c alongside select/poll.
// epoll, eventfd, timerfd and signalfd live in sys_event.c.