Porting zsh found four defects in the kernel and one in the filesystem. None of them were about zsh; all of them had been there the whole time. **exec threw the environment away.** kernel/fs/exec.c wrote a single zero for envp with the comment "(no environment)" -- not yet, but ever, for every process. musl has been passing a perfectly good envp to execve (Linux 59, which SchOS answers) and it was never read. So $TERM was unset, $PATH was whatever each program invented, and a variable exported in a shell did not survive the exec meant to carry it. exec() takes an envp, sys_exec reads argument three, and schinit manifests grew an Environment key. The userland two-argument exec(path, argv) stays as a wrapper that passes null explicitly -- the raw stub is execve now, so a short caller cannot leave %rdx holding a stray pointer. **Which overflowed the kernel stack.** Three pointer arrays, ~1.8 KB, on a 4096-byte stack, under exec -> loaduvm -> readi -> SchFS -> btree. Pid 1 went down on the first exec. The canary caught it and named the process, which is what it is for. They live in one kalloc'd page now. **waitpid returned a bare -1** for both "no children" and "interrupted". musl reads [-4095,-1] as -errno, so userspace saw EPERM; zsh printed "wait failed: operation not permitted" and abandoned the rest of /etc/zsh/zshrc. -ECHILD and -EINTR now. Third time a bare -1 has cost real time -- on a system with no permission model, EPERM should be read as "a syscall returned -1". **NVMA was 64.** musl's allocator takes a mapping per size class and does not release them eagerly, so compinit ran out of slots and got -1, which is EPERM, which malloc turns into NULL, which zsh reports as "fatal error: out of memory" -- on a machine with 1000 MB free. 256 slots, and -ENOMEM. **And a small write to SchFS costs 58 ms.** 200 writes to one open file take 11.75 s; the same to /dev/null take 0.14 s. That is the design stated at the top of kmount.c -- one transaction group per operation, and a commit is two flushes and an uberblock -- not a bug. But compdump writes its digest with thousands of separate prints, so it takes minutes and looks like a hang. compinit -D, which skips the dump, finishes in 3.6 s. This one is a decision rather than a fix, so it is written up and left: /etc/zsh/zshrc keeps compinit switched off and explains exactly why, with the measurements. zsh runs as /bin/zsh and is the console shell. ZLE works -- arrow keys recall history through terminfo, $COLUMNS reads the real 160 because TIOCGWINSZ was fixed, colour and prompt expansion work, startup is 0.14 s. mksh keeps /bin/sh, so schinit's no-jobs fallback is a different program from the one that could have failed. Also fixed while here: AT_CLKTCK was the literal 100 and is USER_HZ; the extract rule in four ports could move a source tree inside itself when make re-ran it, which is how zsh's configure came to write modfile=zsh-5.9/... into config.modules and fail three steps later. schfs-check passes. Full writeup in docs/history/kernel.md section 14. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
637 lines
28 KiB
C
637 lines
28 KiB
C
#pragma once
|
|
|
|
struct buf;
|
|
struct context;
|
|
struct file;
|
|
struct inode;
|
|
struct pipe;
|
|
struct proc;
|
|
struct rtcdate;
|
|
struct spinlock;
|
|
struct sleeplock;
|
|
struct stat;
|
|
struct superblock;
|
|
struct trapframe;
|
|
struct dirent;
|
|
struct device;
|
|
|
|
// entryother.S / syscall.S
|
|
void syscall_entry(void);
|
|
void ignore_sysret(void);
|
|
|
|
// bio.c
|
|
void binit(void);
|
|
struct buf* bread(uint, uint64);
|
|
void brelse(struct buf*);
|
|
void bwrite(struct buf*);
|
|
// Modify without writing. The buffer stays dirty in the cache until an
|
|
// eviction or a bflush() takes it out; the caller is promising to ask for the
|
|
// ordering it needs explicitly rather than relying on write order.
|
|
void bdirty(struct buf*);
|
|
// Write out every dirty buffer for `dev`, then make the device commit them.
|
|
// Returns 0, or -1 if the device could not confirm durability.
|
|
int bflush(uint dev);
|
|
|
|
// console.c
|
|
struct termios;
|
|
void consoleinit(void);
|
|
extern int tty_fg_pgrp;
|
|
// The terminal's line-discipline settings. TCGETS/TCSETS in sys_fs.c are the
|
|
// only callers; the console honours ICANON, ECHO, ECHOE, ECHONL, ISIG,
|
|
// ICRNL, INLCR, IGNCR, ISTRIP and VMIN.
|
|
void cons_get_termios(struct termios *);
|
|
void cons_set_termios(const struct termios *);
|
|
void cons_flush_input(void);
|
|
extern uint lapic_timer_hz; // measured, kernel/arch/x86_64/lapic.c
|
|
extern uint64 tsc_hz;
|
|
void cprintf(char*, ...);
|
|
|
|
// A log line: the serial port only, never the display.
|
|
//
|
|
// console_takeover() hands the framebuffer to userspace on the first exec, and
|
|
// after that the screen belongs to whoever is using the machine. A kernel
|
|
// message arriving then is not information they asked for -- it lands in the
|
|
// middle of their shell, or their editor, or a compositor's output. The DHCP
|
|
// lease is what made this necessary: it turns up seconds after the prompt does,
|
|
// so the first thing anyone sees after booting is the kernel talking over them.
|
|
//
|
|
// The same rule the boot path already follows, and the reason cons_serial_only
|
|
// starts set: the display is the product, the serial line is the diagnostics.
|
|
// klog is that rule for messages that cannot be timed to happen before
|
|
// userspace starts.
|
|
//
|
|
// A macro rather than a function because cprintf's varargs cannot be forwarded
|
|
// -- there is no vcprintf, and adding one to duplicate the formatter would cost
|
|
// more than it buys. cprintf holds cons.lock for the whole message, so the flag
|
|
// cannot be seen half-changed by another CPU printing at the same time.
|
|
//
|
|
// Nothing is lost: serial is where bootcheck reads from and where you want
|
|
// these when something is wrong, and anything a *user* should see belongs in a
|
|
// file they can read -- /proc/netif, for the example above.
|
|
extern int cons_serial_only;
|
|
#define klog(...) do { \
|
|
int _klog_was = cons_serial_only; \
|
|
cons_serial_only = 1; \
|
|
cprintf(__VA_ARGS__); \
|
|
cons_serial_only = _klog_was; \
|
|
} while (0)
|
|
void consoleintr(int(*)(void));
|
|
void panic(char*) __attribute__((noreturn));
|
|
|
|
// exec.c
|
|
int exec(char*, char**, char**);
|
|
|
|
// file.c
|
|
struct file* filealloc(void);
|
|
int fdalloc(struct file*);
|
|
void fileclose(struct file*);
|
|
struct file* filedup(struct file*);
|
|
void fileinit(void);
|
|
int fileread(struct file*, char*, int n);
|
|
int file_poll(struct file*, int events);
|
|
int filestat(struct file*, struct stat*);
|
|
int filewrite(struct file*, char*, int n);
|
|
|
|
// fs.c
|
|
void fsinit(int);
|
|
int dirlink(struct inode*, char*, uint64);
|
|
struct inode* dirlookup(struct inode*, char*, uint*);
|
|
// Remove the entry `name` at the cookie dirlookup returned in *poff.
|
|
int dirunlink(struct inode*, char*, uint);
|
|
// 1 if the directory holds no real entries, 0 if it does, -1 on error.
|
|
int dirempty(struct inode*);
|
|
// Fill `out` with getdents64 records starting at *poff, which is an opaque
|
|
// position the filesystem advances. Returns bytes written, 0 at end of
|
|
// directory. The inode must be locked.
|
|
int getdents(struct inode*, char *out, int cap, uint64 *poff);
|
|
// Mount table: a substitution applied during path resolution. Nothing on disk
|
|
// records a mount, so unmounting is forgetting.
|
|
void mountinit(void);
|
|
// 1 if the filesystem on this device refuses writes -- a mounted snapshot.
|
|
// Ask before mutating, not after: the create path panics on a failed dirlink.
|
|
int fs_readonly(uint dev);
|
|
int mount_add(uint dev, uint64 inum, uint fsdev, uint64 root,
|
|
const char *path, const char *source, const char *type);
|
|
// Walk the mount table for /proc/mounts. `path` and `source` need MAXPATH
|
|
// bytes, `type` 16. Returns 0 for a live entry, -1 for an empty slot.
|
|
int mount_get(int i, char *path, char *source, char *type,
|
|
uint *fsdev);
|
|
int mount_del_by_root(uint fsdev, uint64 root);
|
|
struct inode* ialloc(uint, short);
|
|
struct inode* iget(uint, uint64);
|
|
struct inode* idup(struct inode*);
|
|
void iinit(void);
|
|
void ilock(struct inode*);
|
|
void iput(struct inode*);
|
|
void iunlock(struct inode*);
|
|
void iunlockput(struct inode*);
|
|
void iupdate(struct inode*);
|
|
int namecmp(const char*, const char*);
|
|
struct inode* namei(char*);
|
|
struct inode* nameiparent(char*, char*);
|
|
struct inode* nameifrom(struct inode*, char*);
|
|
struct inode* nameiparentfrom(struct inode*, char*, char*);
|
|
int readi(struct inode*, int, uint64, uint64, uint);
|
|
void stati(struct inode*, struct stat*);
|
|
int writei(struct inode*, int, uint64, uint64, uint);
|
|
void itrunc(struct inode*);
|
|
void itrunc_to(struct inode*, uint64);
|
|
unsigned int BKDRHash(char * str);
|
|
uint tree_expand(struct inode *ip, uint off);
|
|
void tree_search(struct inode* dp, char* name, uint* poff, struct dirent* de,uint add);
|
|
void build_dir_tree(struct inode* dp,uint64 pinum);
|
|
struct inode* divesymlink(struct inode *);
|
|
void readsb(int dev, struct superblock *sb);
|
|
|
|
// pci.c -- bus enumeration. Drivers that need to *find* their hardware use
|
|
// these; drivers for things that have lived at a fixed address since 1984
|
|
// (the PIC, the PIT, the legacy IDE ports) do not.
|
|
struct pci_dev;
|
|
struct pci_driver;
|
|
void pci_init(void);
|
|
uint32 pci_read32(uint8, uint8, uint8, uint8);
|
|
uint16 pci_read16(uint8, uint8, uint8, uint8);
|
|
uint8 pci_read8(uint8, uint8, uint8, uint8);
|
|
void pci_write32(uint8, uint8, uint8, uint8, uint32);
|
|
void pci_enable(struct pci_dev *, uint16 bits);
|
|
uint8 pci_find_cap(struct pci_dev *, uint8 id);
|
|
int pci_msi_enable(struct pci_dev *, int vector, int cpu);
|
|
int pci_count(void);
|
|
struct pci_dev *pci_get(int);
|
|
struct pci_dev *pci_find_class(uint8 class, uint8 subclass);
|
|
struct pci_dev *pci_find_id(uint16 vendor, uint16 device);
|
|
void pci_bind(const struct pci_driver *);
|
|
int pci_info(char *buf, int cap);
|
|
|
|
// drivers/registry.c -- the one list of every driver in the kernel.
|
|
void pci_bind_all(void);
|
|
|
|
// xhci.c — `echo u > /proc/sysrq`
|
|
void xhci_dump(void);
|
|
|
|
// ide.c
|
|
void ideinit(void);
|
|
void ideintr(void);
|
|
void iderw(struct buf*);
|
|
// For probes, not for reads: iderw() panics on an absent drive, so anything
|
|
// that is guessing whether a disk exists has to ask here first.
|
|
int ide_disk_present(int dev);
|
|
// ATA FLUSH CACHE: block until the drive has committed its write cache.
|
|
int ideflush(int dev);
|
|
|
|
// fs/schfs/kmount.c -- SchFS.
|
|
//
|
|
// Objects are named by 64-bit object id, not by a path: resolution is the
|
|
// caller's business, so these compose with a VFS rather than assuming one.
|
|
// Every schfs_k* that changes anything is one transaction and is durable by the
|
|
// time it returns.
|
|
struct schfs_inode;
|
|
|
|
// Mounting. Slot 0 is the root filesystem, mounted by proc.c before pid 1 has a
|
|
// working directory; schfs_kmount_dev takes any other SchFS disk and returns
|
|
// the filesystem device number it landed on, or a negative errno.
|
|
void schfs_kmount(uint bdev);
|
|
int schfs_kmount_dev(uint bdev);
|
|
int schfs_kumount(uint dev);
|
|
int schfs_kmounted(void);
|
|
|
|
// Read side. `dev` is a filesystem device number -- a live volume or a mounted
|
|
// snapshot -- not the block device underneath it.
|
|
uint64 schfs_klookup(uint dev, const char *path);
|
|
long schfs_kread_dev(uint dev, uint64 ino, uint64 off, void *buf,
|
|
unsigned n);
|
|
int schfs_kstat_dev(uint dev, uint64 ino, struct schfs_inode *out);
|
|
uint64 schfs_klookup_at_dev(uint dev, uint64 dir, const char *name,
|
|
unsigned len);
|
|
int schfs_kreaddir_dev(uint dev, uint64 dir,
|
|
int (*fn)(uint64, unsigned short, const char *,
|
|
unsigned, void *),
|
|
void *arg);
|
|
// Resume a directory walk at a tree key. The callback gets each entry's hash,
|
|
// which is what getdents hands back to userland as its position.
|
|
int schfs_kreaddir_from_dev(uint dev, uint64 dir, uint64 start_hash,
|
|
int (*fn)(uint64, unsigned short, const char *,
|
|
unsigned, uint64, void *),
|
|
void *arg);
|
|
uint64 schfs_kroot(uint dev);
|
|
void schfs_lockinfo(int slot, int *locked, int *owner_pid, int *depth);
|
|
int schfs_kinfo(char *buf, int cap);
|
|
|
|
// Write side. Each is one transaction and is durable by the time it returns,
|
|
// unless the caller has a bracket open -- see schfs_ktx_open.
|
|
int schfs_kcreate(uint dev, uint64 dir, const char *name,
|
|
unsigned namelen, unsigned short type, uint mode,
|
|
unsigned short major, unsigned short minor, uint64 *out);
|
|
long schfs_kwrite(uint dev, uint64 ino, uint64 off, const void *buf,
|
|
unsigned n);
|
|
int schfs_ktruncate(uint dev, uint64 ino, uint64 size);
|
|
int schfs_kunlink(uint dev, uint64 dir, const char *name,
|
|
unsigned namelen);
|
|
int schfs_klink(uint dev, uint64 dir, const char *name,
|
|
unsigned namelen, uint64 ino);
|
|
int schfs_krename(uint dev, uint64 olddir, const char *oldname,
|
|
unsigned oldlen, uint64 newdir, const char *newname,
|
|
unsigned newlen);
|
|
int schfs_ksetattr(uint dev, uint64 ino,
|
|
const struct schfs_inode *in);
|
|
int schfs_ksync(uint dev);
|
|
int schfs_ksync_all(void);
|
|
// Share src's data blocks with dst without copying. Both stay writable; the
|
|
// first write to either copies only the run it touches. Behind FICLONE.
|
|
int schfs_kreflink(uint dev, uint64 src, uint64 dst);
|
|
|
|
// Attributes, and the index over them. See kernel/fs/schfs/attr.c.
|
|
int schfs_kattr_set(uint dev, uint64 ino, const char *name,
|
|
unsigned namelen, const void *val, unsigned vallen,
|
|
unsigned short type);
|
|
int schfs_kattr_get(uint dev, uint64 ino, const char *name,
|
|
unsigned namelen, void *out, unsigned cap,
|
|
unsigned *vallen);
|
|
int schfs_kattr_del(uint dev, uint64 ino, const char *name,
|
|
unsigned namelen);
|
|
int schfs_kindex(uint dev, const char *name, unsigned namelen);
|
|
int schfs_kquery(uint dev, const char *name, unsigned namelen,
|
|
const void *val, unsigned vallen, uint64 *out, int cap);
|
|
|
|
// Bracket a run of changes into one transaction group. Every schfs_k* call made
|
|
// between open and close joins it and commits once, at the close; the bracket
|
|
// nests, and holds the volume lock for its whole extent. Costly to leave open —
|
|
// nothing else touches that volume meanwhile — so bracket a bounded piece of
|
|
// work such as one write(2), not a whole syscall's worth of unrelated steps.
|
|
// schfs_ktx_close returns the commit's status, or -1 if the caller had no
|
|
// bracket open.
|
|
void schfs_ktx_open(uint dev);
|
|
int schfs_ktx_close(uint dev);
|
|
|
|
// Volume counts for statfs. 0 if `dev` is a mounted SchFS volume (live or
|
|
// snapshot) and the counts are filled in, -1 if it is not.
|
|
int schfs_kstatfs(uint dev, uint64 *total, uint64 *freeblocks,
|
|
uint64 *objects);
|
|
|
|
// Pieces the VFS layer (fs/schfs/vfs.c) needs, where a directory entry and the
|
|
// object it names are separate operations. The VFS keeps the link count.
|
|
int schfs_kdirempty(uint dev, uint64 dir);
|
|
int schfs_kalloc_object(uint dev, unsigned short type, uint mode,
|
|
uint64 *out);
|
|
int schfs_kdiradd(uint dev, uint64 dir, const char *name,
|
|
unsigned len, uint64 ino, unsigned short type);
|
|
int schfs_kdirdel(uint dev, uint64 dir, const char *name,
|
|
unsigned len);
|
|
int schfs_kfree(uint dev, uint64 ino);
|
|
|
|
// A snapshot mounted read-only: the same block device with an older tree root.
|
|
// The read path takes a device number so the VFS can address either view; there
|
|
// is no write path, because a snapshot that could be written to would not be
|
|
// one. See kernel/fs/schfs/kmount.c.
|
|
// Returns the device number of the slot the snapshot landed in, or negative.
|
|
int schfs_ksnap_mount(uint dev, const char *name, unsigned namelen);
|
|
void schfs_ksnap_unmount(uint dev);
|
|
uint64 schfs_ksnap_root(uint dev);
|
|
|
|
// ramdisk.c
|
|
uint uptime(void);
|
|
|
|
// procfs.c
|
|
int procread(struct inode*, uint64, char*, int);
|
|
int procwrite(struct inode*, uint64, char*, int);
|
|
// Shared with the /proc filesystem (kernel/fs/procfs.c): one owns the
|
|
// namespace, the other the content.
|
|
int proc_gen_global(int id, char *buf, int cap);
|
|
int proc_write_sysrq(int user_src, uint64 src, uint n);
|
|
void procfs_mount(void);
|
|
void devfs_mount(void);
|
|
|
|
// input.c -- raw evdev event devices
|
|
void inputinit(void);
|
|
int inputread(struct inode*, uint64, char*, int);
|
|
int inputwrite(struct inode*, uint64, char*, int);
|
|
int inputready(struct inode*);
|
|
void inputclose(struct inode*);
|
|
void inputopen(struct inode*);
|
|
int input_ioctl(struct inode*, int request, addr_t arg);
|
|
int input_grab(int dev, int on);
|
|
int input_is_grabbed(int dev);
|
|
void input_report(int dev, int type, int code, int value);
|
|
// An absolute pointing device (usb-tablet). Coordinates are already in screen
|
|
// pixels; see input_mouse_abs in kernel/dev/input.c for why this is separate
|
|
// from the relative path.
|
|
void input_mouse_abs(int x, int y, int buttons);
|
|
void mouse_set_abs(int x, int y, int buttons);
|
|
void input_sync(int dev);
|
|
int input_ready(int dev);
|
|
void input_ps2_scancode(int raw, int e0);
|
|
void input_key(int hid_usage, int down);
|
|
int input_hid_keycode(int hid_usage);
|
|
void input_mouse(int dx, int dy, int buttons);
|
|
|
|
void ramdiskinit(void);
|
|
void ramdiskrw(struct buf*);
|
|
int ramdisk_present(void);
|
|
|
|
// usb_storage.c
|
|
void usb_storage_rw(struct buf*);
|
|
int usb_storage_present(void);
|
|
// SCSI SYNCHRONIZE CACHE: the same barrier as ideflush, over bulk transport.
|
|
int usb_storage_flush(void);
|
|
void usb_storage_dump(void); // `echo s > /proc/sysrq`
|
|
|
|
// ioapic.c
|
|
void ioapicenable(int irq, int cpu);
|
|
void ioapicroute(int irq, int cpu, int level_low);
|
|
void ioapicmask(int irq);
|
|
extern uchar ioapicid;
|
|
void ioapicinit(void);
|
|
|
|
// dma.c — see include/dma.h for the full contract
|
|
void dma_init(void*, void*);
|
|
void *dma_alloc(uint64 size, uint64 align);
|
|
void dma_free(void*, uint64 size);
|
|
addr_t dma_phys(const void*);
|
|
uint64 dma_free_bytes(void);
|
|
uint64 dma_total_bytes(void);
|
|
|
|
// kalloc.c
|
|
char* kalloc(void);
|
|
uint64 kalloc_free_bytes(void);
|
|
void kfree(char*);
|
|
void kinit1(void*, void*);
|
|
void kinit2(void);
|
|
|
|
// kbd.c
|
|
void kbdintr(void);
|
|
|
|
// mouse.c
|
|
void mouseinit(void);
|
|
void mouseintr(void);
|
|
void mouse_inject(int dx, int dy, int buttons);
|
|
int get_mouse_x(void);
|
|
int get_mouse_y(void);
|
|
uint8 get_mouse_buttons(void);
|
|
|
|
// lapic.c
|
|
void cmostime(struct rtcdate *r);
|
|
|
|
// time.c -- wall-clock time, from the RTC read once at boot plus uptime.
|
|
void time_init(void);
|
|
void wallclock(uint64 *sec, uint64 *usec);
|
|
extern uint64 boot_epoch;
|
|
int cpunum(void);
|
|
extern volatile uint* lapic;
|
|
void lapiceoi(void);
|
|
void lapicinit(void);
|
|
void lapicstartap(uchar, uint);
|
|
void microdelay(int);
|
|
|
|
// log.c
|
|
void initlog(int dev);
|
|
void log_write(struct buf*);
|
|
void begin_op();
|
|
void end_op();
|
|
|
|
// mp.c
|
|
extern int ismp;
|
|
void mpinit(void);
|
|
|
|
// pipe.c
|
|
int pipealloc(struct file**, struct file**);
|
|
void pipeclose(struct pipe*, int);
|
|
int piperead(struct pipe*, char*, int);
|
|
int pipewrite(struct pipe*, char*, int);
|
|
|
|
// net/unix.c
|
|
struct socket;
|
|
void sockinit(void);
|
|
struct socket* socknew(int domain, int type, int proto);
|
|
void sockclose(struct socket*);
|
|
void sockdup(struct socket*);
|
|
int sockread(struct socket*, char*, int);
|
|
int sockwrite(struct socket*, char*, int);
|
|
int sockbind(struct socket*, const char *path);
|
|
int socklisten(struct socket*, int backlog);
|
|
struct socket* sockaccept(struct socket*);
|
|
int sockconnect(struct socket*, const char *path);
|
|
int sockreadable(struct socket*);
|
|
int sock_hup(struct socket*);
|
|
int sock_shutdown(struct socket*, int how);
|
|
int sockpair(struct socket **a, struct socket **b);
|
|
const char* sockname(struct socket*);
|
|
// AF_INET addressing. Separate from sockbind/sockconnect because the families
|
|
// do not name the same kind of thing; `addr` and `port` are network order.
|
|
int sockrecvfrom(struct socket*, char *buf, int n,
|
|
uint *addr, ushort *port);
|
|
int socksendto(struct socket*, char *buf, int n,
|
|
uint addr, ushort port);
|
|
int sockbind_in(struct socket*, uint addr, ushort port);
|
|
int sockconnect_in(struct socket*, uint addr, ushort port);
|
|
int sock_domain(struct socket*);
|
|
// Socket options. The syscall layer does the user-memory marshalling and these
|
|
// do the work, because struct socket's fields live in kernel/net/unix.c and the
|
|
// syscall layer deliberately cannot see them.
|
|
int sockopt_set(struct socket*, int level, int name, int val);
|
|
int sockopt_get(struct socket*, int level, int name, int *out);
|
|
|
|
// net/inet.c -- AF_INET on lwIP's raw API. Called only from net/unix.c, which
|
|
// is where the per-domain dispatch lives.
|
|
void inet_init(void);
|
|
void* inet_new(int type, int proto);
|
|
int inet_bind(void *h, uint addr, ushort port);
|
|
int inet_listen(void *h, int backlog);
|
|
int inet_setopt(void *h, int level, int name, int val);
|
|
void* inet_accept(void *h);
|
|
int inet_connect(void *h, uint addr, ushort port);
|
|
int inet_read(void *h, char *buf, int n);
|
|
int inet_write(void *h, char *buf, int n);
|
|
int inet_recvfrom(void *h, char *buf, int n,
|
|
uint *addr, ushort *port);
|
|
int inet_sendto(void *h, char *buf, int n,
|
|
uint addr, ushort port);
|
|
int inet_readable(void *h);
|
|
int inet_hup(void *h);
|
|
void inet_close(void *h);
|
|
|
|
// proc.c
|
|
void exit(int status);
|
|
// Start a kernel thread: a process that runs `fn` in kernel mode forever. It
|
|
// has no files, no cwd and no parent, and must never return. Returns the pid.
|
|
int kthread_create(void (*fn)(void), const char *name);
|
|
int fork(void);
|
|
int growproc(int64);
|
|
int kill(int);
|
|
int sigsend_pgrp(int pgid, int sig);
|
|
int proc_pgid(int pid);
|
|
int proc_setpgid(int pid, int pgid);
|
|
int proc_setsid(void);
|
|
int sigsend(int pid, int sig);
|
|
int proc_exists(int);
|
|
int proc_num(void);
|
|
struct procinfo;
|
|
int proc_snapshot(int idx, struct procinfo *out);
|
|
int proc_snapshot_pid(int pid, struct procinfo *out);
|
|
void pinit(void);
|
|
void procdump(void);
|
|
void scheduler(void) __attribute__((noreturn));
|
|
void sched(void);
|
|
void sleep(void*, struct spinlock*);
|
|
int wait(void);
|
|
int waitpid(int, int*, int);
|
|
void wakeup(void*);
|
|
void yield(void);
|
|
void sinit(void);
|
|
void kstack_check_canary(const char *where);
|
|
|
|
// swtch.S
|
|
void swtch(struct context**, struct context*);
|
|
|
|
// spinlock.c
|
|
void acquire(struct spinlock*);
|
|
void getcallerpcs(void*, addr_t*);
|
|
void getstackpcs(addr_t*, addr_t*);
|
|
int holding(struct spinlock*);
|
|
void initlock(struct spinlock*, char*);
|
|
void release(struct spinlock*);
|
|
void pushcli(void);
|
|
void popcli(void);
|
|
|
|
// sleeplock.c
|
|
void acquiresleep(struct sleeplock*);
|
|
void releasesleep(struct sleeplock*);
|
|
int holdingsleep(struct sleeplock*);
|
|
void initsleeplock(struct sleeplock*, char*);
|
|
|
|
// string.c
|
|
int memcmp(const void*, const void*, uint64);
|
|
void* memmove(void*, const void*, uint64);
|
|
void* memset(void*, int, uint64);
|
|
char* safestrcpy(char*, const char*, int);
|
|
int strlen(const char*);
|
|
int strnlen(const char*, int);
|
|
int strcmp(const char*, const char*);
|
|
int strncmp(const char*, const char*, uint64);
|
|
char* strncpy(char*, const char*, int);
|
|
|
|
// syscall.c
|
|
void syscall(struct trapframe *);
|
|
void syscallinit(void);
|
|
void deliver_signals(struct trapframe *);
|
|
int argint(int, int*);
|
|
int argptr(int, char**, int);
|
|
int argstr(int, char**);
|
|
int argaddr(int, addr_t*);
|
|
int fetchaddr(addr_t, addr_t*);
|
|
int fetchstr(addr_t, char**);
|
|
int fetchint(addr_t, int*);
|
|
|
|
// trap.c
|
|
void idtinit(void);
|
|
extern uint ticks;
|
|
void tvinit(void);
|
|
extern struct spinlock tickslock;
|
|
|
|
// uart.c
|
|
void uartearlyinit(void);
|
|
void uartinit(void);
|
|
void uartintr(void);
|
|
void uartputc(int);
|
|
|
|
// Drawing, the text console and the framebuffer geometry are NOT declared
|
|
// here: they live in include/gfx.h, and the display drivers that back them in
|
|
// include/display.h. Declaring vga_fill_rect() here is what let the crash
|
|
// screen draw on one named driver.
|
|
|
|
// fb.c -- /dev/fb0, major 2.
|
|
void fbclose(struct inode*);
|
|
// Called from sys_mmap: a mapping of /dev/fb0 is a licence to draw with no
|
|
// further syscalls, so it is the last the kernel hears about it.
|
|
void fb_mapped(void);
|
|
int fbread(struct inode*, uint64, char*, int);
|
|
int fbwrite(struct inode*, uint64, char*, int);
|
|
int fb_ioctl(int request, addr_t arg);
|
|
// dev/disk.c -- /dev/hdN, raw seekable access to a whole disk. The minor is the
|
|
// IDE device number. Writes bypass every filesystem above them.
|
|
int diskread(struct inode*, uint64, char*, int);
|
|
int diskwrite(struct inode*, uint64, char*, int);
|
|
// Network *drivers* are not declared here at all. They are reached through the
|
|
// netdev class in include/netdev.h -- a driver publishes a vtable and nothing
|
|
// above it ever learns its name. Declaring e1000_transmit() here was what let
|
|
// the protocol stack call it directly.
|
|
|
|
// net/net.c -- the TCP/IP stack (lwIP). netinit starts the network thread;
|
|
// net_is_up reports whether an address has been obtained.
|
|
void netinit(void);
|
|
int net_is_up(void);
|
|
int net_is_ready(void);
|
|
void net_wake_counts(uint64 *rx, uint64 *tick);
|
|
int net_info(char *buf, int cap);
|
|
|
|
// net/lwip/ -- the port layer and the Ethernet interfaces, called only from
|
|
// net.c. One netif per bound network device; the index is that interface's.
|
|
void lwip_sys_init(void);
|
|
void lwip_sys_lock(void);
|
|
void lwip_sys_unlock(void);
|
|
int net_eth_attach(struct device *);
|
|
int net_eth_poll(void);
|
|
int net_eth_count(void);
|
|
void net_eth_counts(int i, uint64 *rx, uint64 *tx);
|
|
struct netif *net_eth_netif(int i);
|
|
uint netif_addr4(struct netif *);
|
|
uint netif_mask4(struct netif *);
|
|
uint ntohl_u32(uint);
|
|
uint htonl_u32(uint);
|
|
// rtnetlink: turn one request into a whole dump. See kernel/net/netlink.c.
|
|
int netlink_request(const char *req, uint reqlen,
|
|
char *out, uint outcap);
|
|
struct device *net_eth_device(int i);
|
|
|
|
// registry.c
|
|
// Platform devices, and the boot framebuffer. Takes the framebuffer address
|
|
// the loader handed over, because bringing that up is the first thing it does.
|
|
void platform_register_devices(uint64 fb_addr);
|
|
// drivers/gpu/vga -- the VBE framebuffer the loader programmed. Called only
|
|
// from registry.c; everything else draws through include/gfx.h.
|
|
void vga_fb_init(uint64 phys_addr);
|
|
uint64 vga_fb_phys(void);
|
|
uint64 vga_fb_size(void);
|
|
|
|
void e820_init(void);
|
|
int acpi_init(void);
|
|
|
|
// console.c / panic.c
|
|
void cons_panic_mode(void);
|
|
extern int cons_serial_only;
|
|
void console_takeover(void);
|
|
|
|
// vm.c
|
|
void seginit(void);
|
|
void kvmalloc(void);
|
|
// Map device registers. Returns a kernel address for `size` bytes of physical
|
|
// MMIO at `pa`, or 0 if the window is exhausted. Use this rather than P2V for
|
|
// anything that came out of a BAR: firmware may place a 64-bit BAR far above
|
|
// the 4 GB the direct map covers.
|
|
void* ioremap(uint64 pa, uint64 size);
|
|
pml4e_t* setupkvm(void);
|
|
char* uva2ka(pml4e_t*, char*);
|
|
addr_t allocuvm(pml4e_t*, uint64, uint64);
|
|
addr_t deallocuvm(pml4e_t*, uint64, uint64);
|
|
void freevm(pml4e_t*);
|
|
void inituvm(pml4e_t*, char*, uint64);
|
|
int loaduvm(pml4e_t*, char*, struct inode*, uint64, uint64);
|
|
pml4e_t* copyuvm(pml4e_t*, uint64);
|
|
void switchuvm(struct proc*);
|
|
void switchkvm(void);
|
|
int pipe_poll(struct pipe*, int readable, int writable);
|
|
int console_input_ready(void);
|
|
int copyin(pml4e_t*, addr_t, void*, uint64);
|
|
int copyout(pml4e_t*, addr_t, void*, uint64);
|
|
void vm_unmap_range(pml4e_t*, uint64, uint64);
|
|
// Unmap without freeing: for pages this process does not own — device memory
|
|
// and shared segments. vm_unmap_range() would hand them to kfree.
|
|
void vm_unmap_norelease(pml4e_t*, uint64, uint64);
|
|
// Shared memory (sys_mem.c). shm_ref/shm_detach maintain the attachment
|
|
// count that decides when a segment's pages go back.
|
|
void shm_ref(int id);
|
|
void shm_detach(int id);
|
|
void proc_release_shared(struct proc *);
|
|
int copy_vma_range(pml4e_t *dstpgdir, pml4e_t *srcpgdir, uint64 va, uint64 length, int is_fb);
|
|
void clearpteu(pml4e_t *pgdir, char *uva);
|
|
|
|
#define NELEM(x) (sizeof(x)/sizeof((x)[0]))
|