From bd3e0abe37b8268dfc6dba62e0afe9bf24552bb1 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Sat, 8 Dec 2018 00:09:28 +0000 Subject: [PATCH] kernel: Major rework of the single process in memory model - Support 'parent runs first' in fork. We will also need this for flat memory models like 68000 to work nicely. In our case we write the child to swap but for flat models we'd duplicate the update and write a short stub stack to the child - Introduce a makeproc to replace newproc. Different arguments so deliberately break all the old code - Fix some interestingly bad scheduling corner cases where we have a process running for a long time with nothing else that then causes fork to thrash - Make tmpfree always a function. Useful for debug and also avoids icky asm dependencies - Fix the Z80 single process model to use the new features - Fix the Z80 banked process model to pass the new arguments - Also adjust the switching rate on the Plus 3 as our test platform for the single process in memory tunings Takes our boot time down about 33% ! --- Kernel/devio.c | 5 ++ Kernel/include/kernel.h | 5 +- Kernel/lib/z80fixedbank-banked.s | 9 ++- Kernel/lib/z80fixedbank-core.s | 8 ++- Kernel/lib/z80single.s | 109 +++++++++++++++++++++++-------- Kernel/platform-zx+3/README | 13 +++- Kernel/platform-zx+3/config.h | 2 + Kernel/process.c | 74 +++++++++++++-------- Kernel/simple.c | 39 ++++++----- Kernel/start.c | 15 +++-- Kernel/syscall_proc.c | 6 +- 11 files changed, 196 insertions(+), 89 deletions(-) diff --git a/Kernel/devio.c b/Kernel/devio.c index e8a26975..284f981b 100644 --- a/Kernel/devio.c +++ b/Kernel/devio.c @@ -183,6 +183,11 @@ void *tmpbuf(void) bp->bf_time = ++bufclock; /* Time stamp it */ return bp->__bf_data; } + +void tmpfree(void *p) +{ + brelse(p); +} #endif /* diff --git a/Kernel/include/kernel.h b/Kernel/include/kernel.h index eef3c44b..614a6014 100644 --- a/Kernel/include/kernel.h +++ b/Kernel/include/kernel.h @@ -212,7 +212,6 @@ typedef struct blkbuf { uget((uaddr),(buf)->__bf_data + (off), (len)) #define blkptr(buf, off, len) ((void *)((buf)->__bf_data + (off))) #define blkzero(buf) memset(buf->__bf_data, 0, BLKSIZE) -#define tmpfree(x) brelse((void *)x) #else extern void blktok(void *kaddr, struct blkbuf *buf, uint16_t off, uint16_t len); extern void blkfromk(void *kaddr, struct blkbuf *buf, uint16_t off, uint16_t len); @@ -221,7 +220,6 @@ extern void blkfromu(void *kaddr, struct blkbuf *buf, uint16_t off, uint16_t len /* Worst case is needing to copy over about 64 bytes */ extern void *blkptr(struct blkbuf *buf, uint16_t offset, uint16_t len); extern void blkzero(struct blkbuf *buf); -extern void tmpfree(void *p); #endif /* TODO: consider smaller inodes or clever caching. 2BSD uses small @@ -862,6 +860,7 @@ extern void brelse(bufptr); extern void bawrite(bufptr); extern int bfree(bufptr bp, uint8_t dirty); /* dirty: 0=clean, 1=dirty (write back), 2=dirty+immediate write */ extern void *tmpbuf(void); +extern void tmpfree(void *p); extern bufptr zerobuf(void); extern void bufsync(void); extern bufptr bfind(uint16_t dev, blkno_t blk); @@ -957,7 +956,7 @@ extern void psleep_nosig(void *event); extern void wakeup(void *event); extern void pwake(ptptr p); extern ptptr getproc(void); -extern void newproc(ptptr p); +extern void makeproc(ptptr p, u_data *u); extern ptptr ptab_alloc(void); extern void ssig(ptptr proc, uint8_t sig); extern void recalc_cursig(void); diff --git a/Kernel/lib/z80fixedbank-banked.s b/Kernel/lib/z80fixedbank-banked.s index 6a6a839a..f859831f 100644 --- a/Kernel/lib/z80fixedbank-banked.s +++ b/Kernel/lib/z80fixedbank-banked.s @@ -9,7 +9,7 @@ .module z80fixedbank .globl _ptab_alloc - .globl _newproc + .globl _makeproc .globl _chksigs .globl _getproc .globl _platform_monitor @@ -24,6 +24,7 @@ .globl _need_resched .globl _nready .globl _platform_idle + .globl _udata .globl map_kernel_restore .globl map_process_a @@ -306,12 +307,14 @@ _dofork: pop bc ; Make a new process table entry, etc. - ld hl, (fork_proc_ptr) + ld hl, #_udata + ld hl, (fork_proc_ptr) push hl push af - call _newproc + call _makeproc pop af pop bc + pop bc ; runticks = 0; ld hl, #0 diff --git a/Kernel/lib/z80fixedbank-core.s b/Kernel/lib/z80fixedbank-core.s index 3b69222a..90483dbb 100644 --- a/Kernel/lib/z80fixedbank-core.s +++ b/Kernel/lib/z80fixedbank-core.s @@ -9,7 +9,7 @@ .module z80fixedbank .globl _ptab_alloc - .globl _newproc + .globl _makeproc .globl _chksigs .globl _getproc .globl _platform_monitor @@ -25,6 +25,7 @@ .globl _nready .globl _platform_idle .globl _int_disabled + .globl _udata .globl map_kernel .globl map_process @@ -279,10 +280,13 @@ _dofork: pop bc ; Make a new process table entry, etc. + ld hl,#_udata + push hl ld hl, (fork_proc_ptr) push hl - call _newproc + call _makeproc pop bc + pop bc ; runticks = 0; ld hl, #0 diff --git a/Kernel/lib/z80single.s b/Kernel/lib/z80single.s index b42e333f..9afb89be 100644 --- a/Kernel/lib/z80single.s +++ b/Kernel/lib/z80single.s @@ -2,11 +2,15 @@ ; Generic handling for single process in memory Z80 systems. ; ; This code could really do with some optimizing. +; +; FIXME: IRQ enable logic during swap +; +; FIXME: turn on parent first behaviour when ready ; .module tricks .globl _ptab_alloc - .globl _newproc + .globl _makeproc .globl _chksigs .globl _getproc .globl _platform_monitor @@ -19,7 +23,11 @@ .globl interrupt_handler .globl _swapper .globl _swapout + .globl _swapout_new .globl _int_disabled + .globl _udata + .globl _tmpbuf + .globl _tmpfree ; imported debug symbols .globl outstring, outde, outhl, outbc, outnewline, outchar, outcharhex @@ -37,6 +45,7 @@ _platform_switchout: ; save machine state ld hl, #0 ; return code set here is ignored, but _switchin can + ; return from either _switchout OR _dofork, so they must both write ; U_DATA__U_SP with the following on the stack: push hl ; return code @@ -60,6 +69,8 @@ swapped: .ascii "_switchin: SWAPPED" _switchin: di + ld a,#1 + ld (_int_disabled),a pop bc ; return address pop de ; new process pointer ; @@ -113,7 +124,7 @@ not_swapped: ; check u_data->u_ptab matches what we wanted ld hl, (U_DATA__U_PTAB) ; u_data->u_ptab or a ; clear carry flag - sbc hl, de ; subtract, result will be zero if DE==IX + sbc hl, de ; subtract, result will be zero if DE==HL jr nz, switchinfail ; wants optimising up a bit @@ -170,22 +181,15 @@ _dofork: ld (fork_proc_ptr), hl - ; prepare return value in parent process -- HL = p->p_pid; - ld de, #P_TAB__P_PID_OFFSET - add hl, de - ld a, (hl) - inc hl - ld h, (hl) - ld l, a - ; Save the stack pointer and critical registers. ; When this process (the parent) is switched back in, it will be as if ; it returns with the value of the child's pid. - push hl ; HL still has p->p_pid from above, the return value in the parent + ld hl,#0 + push hl ; #0 child push ix push iy - ; save kernel stack pointer -- when it comes back in the parent we'll be in + ; save kernel stack pointer -- when it comes back in the child we'll be in ; _switchin which will immediately return (appearing to be _dofork() ; returning) and with HL (ie return code) containing the child PID. ; Hurray. @@ -194,32 +198,81 @@ _dofork: ; now we're in a safe state for _switchin to return in the parent ; process. - ld hl, (U_DATA__U_PTAB) + + ; Make a new process table entry, etc. + + ; Copy the parent properties into the temporary udata copy + call _tmpbuf + ld a,h + or l + jp z,ohpoo push hl - call _swapout + ex de,hl + ld hl, #_udata + ld bc, #U_DATA__TOTALSIZE + ldir + + ; Recover the buffer pointer pop hl + push hl + + ; Make the child udata out of the temporary buffer + push hl + ld hl, (fork_proc_ptr) + push hl + call _makeproc + pop bc + pop bc + + ; in the child process, fork() returns zero. + ; + ; And we exit, with the kernel mapped, the child assembled in the + ; copy area as if it had done a switchout, the parent meanwhile + ; continues happily on + + ; now we're in a safe state for _switchin to return in the child + ; process swap out the image and the new udata + ; Stack the buffer as a second argument + pop hl + push hl + push hl + + ld hl, (fork_proc_ptr) + push hl + call _swapout_new + pop hl + pop hl + + ; Buffer pointer is sitting top of stack still + ; so use it as an argument to tmpfree + call _tmpfree + + pop hl + + ld hl, (fork_proc_ptr) + ; prepare return value in parent process -- HL = p->p_pid; + ld de, #P_TAB__P_PID_OFFSET + add hl, de + ld a, (hl) + inc hl + ld h, (hl) + ld l, a ; now the copy operation is complete we can get rid of the stuff ; _switchin will be expecting from our copy of the stack. pop bc pop bc pop bc + ; Return pid of child we forked into swap + ret - ; Make a new process table entry, etc. - ld hl, (fork_proc_ptr) - push hl - call _newproc - pop bc +ohpoo: + ld hl,#nobufs + call outstring + jp _platform_monitor - ; runticks = 0; - ld hl, #0 - ld (_runticks), hl - ; in the child process, fork() returns zero. - ; - ; And we exit, with the kernel mapped, the child now being deemed - ; to be the live uarea. The parent is frozen in time and space as - ; if it had done a switchout(). - ret +nobufs: + .asciz 'nobufs' ; ; We can keep a stack in common because we will complete our ; use of it before we switch common block. In this case we have diff --git a/Kernel/platform-zx+3/README b/Kernel/platform-zx+3/README index a6e5c60b..3fc8962e 100644 --- a/Kernel/platform-zx+3/README +++ b/Kernel/platform-zx+3/README @@ -56,15 +56,12 @@ load an app from 5D00-wherever. Still ugly. TODO -Debug floppy handling - Fix memory size reporting 64 v 48K Floppy drive B - set drive info and propogate it Floppy drive B - set parameters at init time -Petty coloured stripes when reading/writing floppy Move buffers into upper part of bank 7 to see if can get kernel below C000 + E000 upwards to get bigger user image ? @@ -79,6 +76,16 @@ user but screen mapped and buffers in rest of 7 ? 0-3 Kernel 4-7 User, video in 7 +May also be able to later do + +0-2 Kernel 3 high kernel common + 7 upper 8K buffers and high stubs + 7 lower scree +4/5/6 User 64K 3 high kernel common , 3 low user + +4/5/6 User 48K 7 screen, upper 8K buffers + + 0/4 hold page0 copies, 3 7 both hold pageh copies - fine as video is in bottom of that chunk. diff --git a/Kernel/platform-zx+3/config.h b/Kernel/platform-zx+3/config.h index 981a5372..97a14716 100644 --- a/Kernel/platform-zx+3/config.h +++ b/Kernel/platform-zx+3/config.h @@ -27,11 +27,13 @@ /* Swap based one process in RAM */ #define CONFIG_SWAP_ONLY +#define CONFIG_PARENT_FIRST #define CONFIG_SPLIT_UDATA #define UDATA_BLKS 1 #define UDATA_SIZE 0x200 #define CONFIG_DYNAMIC_BUFPOOL #define CONFIG_DYNAMIC_SWAP +#define MAXTICKS 20 /* Has to be high because we are swap only */ /* Custom banking */ diff --git a/Kernel/process.c b/Kernel/process.c index 09044ed8..8bbb92ae 100644 --- a/Kernel/process.c +++ b/Kernel/process.c @@ -268,31 +268,40 @@ ptptr getproc(void) /* Newproc fixes up the tables for the child of a fork but also for init * Call in the processes context! * This process MUST be run immediately (since it sets status P_RUNNING) + * + * The fork code has already copied the udata into u so we only need to + * touch things that changed. u may or may not be the current udata */ -void newproc(regptr ptptr p) +void makeproc(regptr ptptr p, u_data *u) { /* Passed New process table entry */ - uint8_t *j; + uint8_t *j, *e; irqflags_t irq; + ptptr pp; irq = di(); /* Note that ptab_alloc clears most of the entry */ /* calculate base page of process based on ptab table offset */ - udata.u_page = p->p_page; - udata.u_page2 = p->p_page2; + u->u_page = p->p_page; + u->u_page2 = p->p_page2; program_vectors(&p->p_page); /* set up vectors in new process and if needed copy any common code */ - +#ifdef CONFIG_PARENT_FIRST + p->p_status = P_READY; +#else p->p_status = P_RUNNING; +#endif nready++; /* runnable process count */ - p->p_pptr = udata.u_ptab; - p->p_sig[0].s_ignored = udata.u_ptab->p_sig[0].s_ignored; - p->p_sig[1].s_ignored = udata.u_ptab->p_sig[1].s_ignored; - p->p_sig[0].s_held = udata.u_ptab->p_sig[0].s_held; - p->p_sig[1].s_held = udata.u_ptab->p_sig[1].s_held; - p->p_tty = udata.u_ptab->p_tty; - p->p_uid = udata.u_ptab->p_uid; + pp = u->u_ptab; /* Because it is a copy of the parent */ + + p->p_pptr = pp; + p->p_sig[0].s_ignored = pp->p_sig[0].s_ignored; + p->p_sig[1].s_ignored = pp->p_sig[1].s_ignored; + p->p_sig[0].s_held = pp->p_sig[0].s_held; + p->p_sig[1].s_held = pp->p_sig[1].s_held; + p->p_tty = pp->p_tty; + p->p_uid = pp->p_uid; /* Set default priority */ p->p_priority = MAXTICKS; @@ -301,18 +310,20 @@ void newproc(regptr ptptr p) p->p_udata = &udata; #endif - udata.u_ptab = p; + u->u_ptab = p; /* Fixup from parent */ memset(&p->p_utime, 0, 4 * sizeof(clock_t)); /* Clear tick counters */ rdtime32(&p->p_time); - if (udata.u_cwd) - i_ref(udata.u_cwd); - if (udata.u_root) - i_ref(udata.u_root); - udata.u_cursig = 0; - udata.u_error = 0; - for (j = udata.u_files; j < (udata.u_files + UFTSIZE); ++j) { + if (u->u_cwd) + i_ref(u->u_cwd); + if (u->u_root) + i_ref(u->u_root); + u->u_cursig = 0; + u->u_error = 0; + + e = u->u_files + UFTSIZE; + for (j = u->u_files; j < e; ++j) { if (*j != NO_FILE) ++of_tab[*j].o_refs; } @@ -453,15 +464,22 @@ void timer_interrupt(void) /* Check run time of current process. We don't charge time while swapping as the last thing we want to do is to swap a process in and decide it took time to swap in so needs to go away again! */ - /* FIXME: can we kill off inint ? */ if (!inswap && (++runticks >= udata.u_ptab->p_priority) - && !udata.u_insys && inint && nready > 1) { - need_resched = 1; + && !udata.u_insys && inint) { + /* It might appear to make the best sense to just leave + runticks ticking upwards if nobody else needs to run but + this has two problems. The obvious one is that it may wrap + but less obviously it can also cause thrashing on fork() + in some memory models */ + if (nready > 1) { + need_resched = 1; #ifdef DEBUG_PREEMPT - kprintf("[preempt %p %d]", udata.u_ptab, - udata.u_ptab->p_priority); + kprintf("[preempt %p %d]", udata.u_ptab, + udata.u_ptab->p_priority); #endif - } + } else /* Nobody else to run, user gets new time quantum */ + runticks = 0; + } #endif } @@ -502,6 +520,9 @@ void unix_syscall(void) di(); if (runticks >= udata.u_ptab->p_priority && nready > 1) { +#ifdef DEBUG_PREEMPT + kprintf("P: %d %x %d\n", runticks, udata.u_ptab, udata.u_ptab->p_priority); +#endif /* Time to switch out? - we may have overstayed our welcome inside a syscall so swtch straight afterwards */ udata.u_ptab->p_status = P_READY; @@ -892,7 +913,6 @@ void doexit(uint16_t val) signal_parent(udata.u_ptab); nready--; nproc--; - switchin(getproc()); panic(PANIC_DOEXIT); } diff --git a/Kernel/simple.c b/Kernel/simple.c index 7e225bf7..e3562a2f 100644 --- a/Kernel/simple.c +++ b/Kernel/simple.c @@ -30,6 +30,8 @@ #include #include +#undef DEBUG + #ifdef CONFIG_SWAP_ONLY void pagemap_free(ptptr p) @@ -64,14 +66,14 @@ void pagemap_init(void) * Swap out the memory of a process to make room * for something else */ -int swapout(ptptr p) +int swapout_new(ptptr p, void *u) { uint16_t page = p->p_page; uint16_t blk; uint16_t map; #ifdef DEBUG - kprintf("Swapping out %x (%d)\n", p, p->p_page); + kprintf("Swapping out %x (%d)\n", p, p->p_pid); #endif if (!page) panic(PANIC_ALREADYSWAP); @@ -80,24 +82,32 @@ int swapout(ptptr p) if (map == 0) return ENOMEM; blk = map * SWAP_SIZE; - /* Write the app (and possibly the uarea etc..) to disk */ + /* Write the app (and uarea etc..) to disk */ #ifdef CONFIG_SPLIT_UDATA - /* Note the page for the udata bit as it goes direct to udata */ - swapwrite(SWAPDEV, blk, UDATA_SIZE, (uaddr_t)&udata, 0); + /* Write the udata block as kernel. */ + udata.u_dptr = u; + udata.u_block = blk; + udata.u_nblock = UDATA_SIZE >> BLKSHIFT; /* 1 */ + ((*dev_tab[major(SWAPDEV)].dev_write) (minor(SWAPDEV), 0, 0)); + /* Use the standard swapwrite helper for the rest */ swapwrite(SWAPDEV, blk + UDATA_BLKS, SWAPTOP - SWAPBASE, SWAPBASE, 1); #else - swapwrite(SWAPDEV, blk, SWAPTOP - SWAPBASE, - SWAPBASE, 1); +#error "Not supported" #endif p->p_page = 0; p->p_page2 = map; #ifdef DEBUG - kprintf("%x: swapout done %d\n", p, p->p_page); + kprintf("%x: swapout done %d\n", p, p->p_page2); #endif return 0; } +int swapout(ptptr p) +{ + return swapout_new(p, &udata); +} + /* * Swap ourself in: must be on the swap stack when we do this */ @@ -106,24 +116,21 @@ void swapin(ptptr p, uint16_t map) uint16_t blk = map * SWAP_SIZE; #ifdef DEBUG - kprintf("Swapin %x, %d\n", p, p->p_page); + kprintf("Swapin %x (%d, %d)\n", p, p->p_page2, p->p_pid); #endif if (!p->p_page) { kprintf("%x: nopage!\n", p); return; } -#ifdef CONFIG_SPLIT_UDATA - /* Note the page for the udata bit as it goes direct to udata */ + /* Note the page for the udata bit as it goes direct to udata and + is always in common */ swapread(SWAPDEV, blk, UDATA_SIZE, (uaddr_t)&udata, 0); swapread(SWAPDEV, blk + UDATA_BLKS, SWAPTOP - SWAPBASE, SWAPBASE, 1); -#else - swapread(SWAPDEV, blk, SWAPTOP - SWAPBASE, - SWAPBASE, 1); -#endif + #ifdef DEBUG - kprintf("%x: swapin done %d\n", p, p->p_page); + kprintf("%x: swapin done %d\n", p, p->p_page2); #endif } diff --git a/Kernel/start.c b/Kernel/start.c index 1abf55fc..56d0c491 100644 --- a/Kernel/start.c +++ b/Kernel/start.c @@ -81,23 +81,26 @@ void add_argument(const char *s) void create_init(void) { - uint8_t *j; + uint8_t *j, *e; udata.u_top = PROGLOAD + 512; /* Plenty for the boot */ init_process = ptab_alloc(); udata.u_ptab = init_process; init_process->p_top = udata.u_top; map_init(); - newproc(init_process); + + /* wipe file table */ + e = udata.u_files + UFTSIZE; + for (j = udata.u_files; j < e; ++j) + *j = NO_FILE; + + makeproc(init_process, &udata); + init_process->p_status = P_RUNNING; udata.u_insys = 1; init_process->p_status = P_RUNNING; - /* wipe file table */ - for (j = udata.u_files; j < (udata.u_files + UFTSIZE); ++j) { - *j = NO_FILE; - } /* Poke the execve arguments into user data space so _execve() can read them back */ /* Some systems only have a tiny window we can use at boot as most of this space is loaded with common memory */ diff --git a/Kernel/syscall_proc.c b/Kernel/syscall_proc.c index de767664..6fb8a1a2 100644 --- a/Kernel/syscall_proc.c +++ b/Kernel/syscall_proc.c @@ -384,8 +384,13 @@ arg_t _fork(void) /* * We're going to run our child process next, so mark this process as * being ready to run + * + * FIXME: push this down into dofork */ +#ifndef CONFIG_PARENT_FIRST udata.u_ptab->p_status = P_READY; +#endif + /* * Kick off the new process (the bifurcation happens inside here, we * *MAY* returns in both the child and parent contexts, however in a @@ -412,7 +417,6 @@ arg_t _fork(void) nready--; } irqrestore(irq); - return r; } -- 2.34.1