![]() |
Cenbe’s Commentary on GeckOS(last updated 2024-05-25) |
![]() |
GeckOS is under active development; André has just
released version
2.2, so by now this page is quite out of date. You can
find the latest source in André's repo
on GitHub. In the
source, you'll find a directory named doc/
with
thorough documentation
in AsciiDoc.
GeckOS is a Unix-like 6502 operating system by André Fachat with preemptive multi-tasking, signals, semaphores, redirection, a standard library, and its own relocatable file format. I gave a talk about GeckOS at VCFMW 2019; here's the video, and here are my slides. I also gave a talk at the World of Commodore show in Toronto (December 2019), highlighting some of the recent enhancements in GeckOS; here are the slides and video from that talk.
I've been studying the GeckOS source code, and this page is where
I'm documenting my analysis of the Commodore 64 version. The code is a
bit of a labyrinth since it supports so many possible architectures
and devices via #define
directives (C64, PET, André's
homebrew machine...), but here's what I've found out about it so
far. Note that there may be errors; this is a work in progress (search
for "TODO") as I continue to learn GeckOS (please send corrections to
cenbe at protonmail dot com). If
you'd like to follow along at home, you can have a look at the source
code
on GitHub. NOTE: Filenames and line numbers in this
document refer to
the 2.0.9 release.
Building GeckOS for the Commodore 64
System Initialization
Starting the ROM Image Programs
The Scheduler
Running Programs from the lsh
Shell
Enhancements
Long-Range Questions
For information on building GeckOS on a C64 and a memory map, see README.c64 in the docs. Also of interest is doc/files.txt, which briefly describes what's in each source file. You can build with the latest version of xa, as it has had some recent bugfixes.
For those who believe that "real men never read the docs", the short version goes like this (assuming Linux):
make
, make
test
, and (as root) make install
make clean
, then
descend into arch/c64/boot and run make
osa.d64
LOADER
in the resulting disk
imageAfter a successful build, you'll find a file named arch/c64/c64rom.lab, which is a listing of labels and corresponding addresses. Sorting that file will give you an invaluable tool for exploration.
The central source file of the C64 port is arch/c64/c64rom.a65 (the
"ROM image", so called because it appears in ROM on André's homebrew
machine). Note the load address of $1800 in line 172. The following
occurs when you boot the operating system from disk by loading and
running LOADER
:
LOADER
(a BASIC program) loads C64ROM
from $1800-$9FFFLOADER
loads a small ML program
called BOOT
(arch/c64/boot/boot.a65) from $0C00 to
$0D05.LOADER
passes control to BOOT
, which
initializes the hardware and then relocates C64ROM
from $7800 to $FFFF, leaving a "hole" from $D000 to $EFFF (see
lines 285-291 of c64rom.a65). $D000-$DFFF, of course, is where
the C64 maps in I/O, and GeckOS maps the screen buffers for its
four consoles at $E000-$EFFF.BOOT
jumps
to $F000The source making up the ROM image (arch/c64/c64rom.a65) starts with the device drivers: at line 180, devices/c64dev.a65 is included, which starts with a small header, then includes:
CONSOLE_DEVICE
,
defined as devices/con_64.a65)Back in c64rom.a65, the following are included:
FSIEC_DEVICE
,
defined as devices/siec_64.a65)Then follows an autostart header for the lsh shell, which is loaded from disk.
After this, lib6502.a65 (the lib6502 runtime) is included, which brings in the following files:
We then come to the "hole" from $D000-$EFFF (see lines 285-291 of c64rom.a65). Continuing at $F000, kernel/kernel.a65 is included, which brings in:
The first entry in the jump table (at $F000) points to
preset
in kernel/init.a65, which is the entry point
jumped to by BOOT
after relocation. After disabling
interrupts and clearing decimal mode, the C64-specific portion is run
(in arch/c64/kernel/kinit.a65), which does the following:
ininmi
(kernel/init.a65, line 390) sets the NMI
vector to point to an RTI.initthreads
(kernel/tasks.a65, line 110) zeros the
thread and task tables.inienv
(arch/c64/kernel/kenv.a65, line 67) sets the
active thread and task IDs
(actThread
, actTask
) to #$ff and sets
the kernel interrupt flag (Syscnt
).inidev
(kernel/devices.a65, line 57) sets the
device count to zero and sets the in-device-driver flag
(adev
) to #$FF. MAXDEV
(maximum number
of devices) is defined as 16 in arch/c64/config.i65. The system
frequency (freq
) is set to 0 on a C64, meaning
1MHz.
adev
does. It
seems to be used to trick the kernel into thinking that we're
not in kernel space during a call to DEVCMD (see
kernel/devices.a65, line 239).initstream
(kernel/streams.a65, line 81) zeros the
streams table. ANZSTRM
(number of streams) is set
to 16 in arch/c64/config.i65.inisem
(kernel/tasks.a65, line 1212) clears the
semaphore tables. ANZSEM
(number of semaphores) is
set to 8 in arch/c64/config.i65; SYSSEM
(number of
system semaphores) is also set to 8.fminit
(kernel/files.a65, line 42)
sets anzfs
(number of file systems) to 0 and
clears fstab
(the file server
table). MAXFS
(maximum number of fileserver tasks)
is set to 4 in arch/c64/config.i65. See
the filesystem
interface documentation for more details.CIA timer usage The CIA timers are used as follows:
CIA1 timer A | RS-232 send |
CIA1 timer B | scheduler, device service |
CIA2 timer A | unused |
CIA2 timer B | serial I/O (IEC), RS-232 receive |
The TOD clocks are not used.
After all of the initialization routines have been called, the ROM
image is scanned for executable headers (line 158 in
kernel/init.a65). For an overview of this process, see
the ROM bootup section of
the kernel documentation. Only programs of type PK_DEV
or PK_INIT
are started here; since the ROM image begins
with devices and then sysapps/init/init.a65, those are the autostarts
that get run first. When the init in sysapps runs, it makes a second
pass through the headers and starts programs of
types PK_PRG
(a standalone pogram not depending on
lib6502), PK_FS
(a filesystem), and PK_LIB
(a lib6502 program). It's this process that eventually starts both an
"old-style" shell/monitor on
console 2 and an lsh shell (which
superseded it) on console 1.
PK_DEV
(device initialization: start ROM programs,
pass 1)
The first ROM program header (c64dev.a65, line 48) has a
P_KIND
of PK_DEV
and
a P_ADDR
pointing to devstart
(line
64). The kernel init (kernel/init.a65, line 189) passes this
address to the DEVCMD
kernel API in .X and .Y, with
a device command of DC_REGDEV
(register devices) in
.A. The address points to a structure described in the
kernel API docs
(see DEVCMD
): it's a linked list with each item
containing a jump to the device's message handler and the device
name. Note that PK_DEV
programs do not get
entries in the task table.
When DEVCMD
registers devices
at regdev
(kernel/devices.a65, line 157), it walks
this table. If the number of devices (ANZDEV
) has
reached the maximum (MAXDEV
), the device is
ignored. Otherwise, the address of the jump is copied to the
device table, DEVTAB
(kernel/devices.a65, lines
48-54), indexed by the number of devices (i.e. the device being
added). DEVTAB
is actually three tables with
one-byte elements: DVT_ADRL
, DVT_ADRH
,
and DVT_ENV
.
After incrementing ANZDEV
, DEVCMD
calls itself recursively, this time with a device command
of DC_RES
(initialize/restart device). Control gets
passed to exejmp
(kernel/devices.a65, line 221),
which ends up doing an indirect JSR to the jump command.
The remainder of the DC_REGDEV
table is walked,
and eventually the next ROM program is evaluated.
The devices in the C64 version are:
video1
- video4
These are
the four consoles. Variables for each are stored in a table
called vtab
(devices/console.a65, lines 431 - 443)
as well as a few smaller tables (lines 67 - 71). For the first
console only, hardware initialization is performed
by console_init
(arch/c64/devices/con_c64.a65, line
54). This sets up the VIC chip, cursor variables, and keyboard
hardware. The VIC chip is set not to generate any
interrupts.nuldev
Null device. A 16-byte character
buffer at instr
is initialized to all $FF.spooler
Status and number of input
streams are initialized to zero.ser1, ser2
Serial (RS232, not IEC)
ports. Both devices/ser_9600.a65 (a "up9600" driver) and
devices/ser_acia.a65 (custom hardware) are included, in that
order. The code is written so that the first device will be
appear as ser1
, the next as ser2
,
etc. (I've commented out the ser_acia driver in my
build). ser_9600
sets up CIA1 for sending (timer A
continuous mode, shift register out, no IRQ) and enables an IRQ
on shift register full. CIA2's shift register is set for input,
the user port data pins are configured, and the NMI vector is
set to NMIserial
(ser_9600.a65, line 330).PK_INIT
(prepare sysapps/init to run)
The next startup program is sysapps/init/init.a65 (header at line
76). When kernel/init.a65 encounters the PK_INIT header at line 186,
it branches to ifs
(line 201; C64's code actually begins
at line 250), which sets up a FORK call to
run sysapps/init
using information in the header (passed
in PCBUF
):
STDERR
, STDOUT
, and STDIN
are set to STDNUL
($FC).FORK
call is executed, and assigns task ID 0 to
sysapps/init, filling in its entries in taskTab
and threadTab
. The scheduler is not yet running, so
sysapps/init doesn't start.Control returns to kernel/init, and the loop to start ROM programs
eventually terminates, as none of the rest are of
type PK_DEV
or PK_INIT
. Now kernel/init has
finished his work, and at line 172, he jumps to pstart
to
start the scheduler (kernel/tasks.a65, line 478). This jump performs
the following:
Xenv
).TS_RDY
,
so sysapps/init runs immediately.sysapps/init
(start remaining ROM programs: pass 2)SIG_CHLD
(handler at
line 651).PK_PRG
, PK_FS
,
and PK_LIB
. For each program with the PK_AUTOEXEC
flag set, call dostart
(line 385):PK_LIB
, jump to execlib
(see lsh
startup), else:PCBUF
is free (call
PSEM w/carry clear).PCBUF
for a fork.
FORK
to run the program
(init_forkto
for PK_LIB
programs),
using the information in the ROM header.dostart
(at line 245), if the
PK_RESTART ($40) flag is set, add the ROM struct address and
the task ID to tables
(lpa
, hpa
, pid
) used
for restarting such processes when they die, and print
"Prepared restart!" to console.The remaining autostart programs run here are:
sysapps/fs/fsdev.a65 (PK_FS
)
F_FL_FRE
.SEND
, passing message
type FM_REG
(register filesystem) in .A and target
task SEND_FM
in .X. PCBUF
contains
the number of drives to register (2)
and fsdev
's task ID. SEND
calls fm
(kernel/files.a65, line 48) to store
the number of drives in the fstab
table and the
task ID in the fstask
table (but see the note
below for the fsiec
driver). This driver
responds for drives a: (device list) and b: (system programs
list).PCBUF
semaphore (call VSEM).SEND
returned E_OK with carry clear, so
continue at loopt
(line 128).anz
) is 0,
call RECEIVE
with carry set (i.e. wait for a
message).RECEIVE
looks for a thread with
status TS_WFRX
(waiting for receive); not
finding one, it sets fsdev
's status to TS_WFTX
(waiting for transmission) and exits by jumping
to nexttask
. fsdev
is now waiting
for messages to be sent to it; when the scheduler runs it
again, it will resume after the call
to RECEIVE
.init
, which returns to
the line after the YIELD
in dostart
(line 388) to continue processing
fsiec
.sysapps/fs/fsiec.a65 (PK_FS
), which includes
devices/siec_64.a65
IECINIT
(in siec_64) to initialize CIA2
for serial I/O.F_FL_FRE
.SEND
, passing message
type FM_REG
and target
task SEND_FM
. PCBUF
contains the
number of drives to register (4) and fsiec
's
task ID. SEND
calls fm
(kernel/files.a65, line 48) to store the number of drives in
the fstab
table and the task ID in
the fstask
table. These four drives correspond
to device numbers 8-11 on the C64 and drive letters c:
through f: in GeckOS. Note: the number of drives
is added to the previous entry so that the correct
filesystem driver can be found from a drive number by
the fm
routine in kernel/files.a65.PCBUF
semaphore (call VSEM).YIELD
,
then calling RECEIVE
. If there is no
message, fsiec
's state is set
to TS_WFTX
(waiting for
transmission). fsiec
is now waiting for
messages to be sent to it. If a message is received, it's
processed at rxmess
(line 764).See the GeckOS documentation for more information on the filesystem interface.
sysapps/mon/mon.a65 (embedded
"old-style" shell and ML
monitor, both PK_PRG
): shell
's
program type has the autostart bit set,
while mon
's does not. Note that this
shell's monitor
builtin has SHORTMON
defined (c64rom.a65, line 219), which means that it has been
made smaller by the removal of the assemble and disassemble
commands. These commands work if you load mon
from the lsh
shell.
PCBUF
semaphore (call VSEM).SIG_CHLD
.
lsh shell
(PK_LIB
, program header is directly in
c64rom.a65): PK_LIB
programs are loaded from disk,
and take the branch to execlib
(sysapps/init/init.a65, line 696).
PCBUF
is free
(call PSEM w/carry clear).init_forkto
(calls init_forkto_i
at lib6502/libexec.a65,
line 104). The differences between a
regular FORK
call and init_forkto
are as follows:FORK_SIZE
is set to MemendFORK_SHARED
is set
to #>-ROMSTART
FORK_PRIORITY
is set to 0FORK_ADDR
is set to the address
of libfork
(lib6502/libexec.a65, line
291)FORK_NAME
is set to the stream number for
the loader (init_forkto
later copies the
name to FORK_NAME+1
).Start "c:lsh -d c: ":
to console.init_forkto
(lib6502/libexec.a65, line
104). This is a lib6502 entry point, which sets the fork
address in PCBUF
to libfork
(line
291) before calling FORK
. When the task runs
and libfork
is called, the program name is
passed to doload
, which opens the program file
and calls loader
(line 545).loader
(lib6502/loader.a65, line 58) takes care
of reading the program into memory, aligning, relocating,
&c., and returns the start address.libfork
jumps to the program's start
address (at line 383).init_forkto
,
print ok!
to console and return.When all the autostarts have been forked, jump
to restarts
(sysapps/init/init.a65, line 598),
which spins in a loop calling YIELD
(kernel/tasks.a65, line 591, a.k.a. "suspend") and then waiting
for kpid
(number of killed tasks,
sysapps/init/init.a65, line 105) to become
non-zero...
TODO: document this routine (is this
where restartable programs are restarted?).
When startup is complete, running info
on
console 2 should show five processes. Their names aren't shown,
but they
are init
, fsdev
, fsiec
, shell
,
and lsh
. (Note: in newer versions of GeckOS, the
names are shown; you can also use the ps
command.)
TODO:
ls a:
returns a list of device names.exejmp
?The important constants for task switching (arch/c64/config.i65, lines 53-55) are:
MAXNTASKS
= 12 (max. number of tasks)MAXNTHREADS
= 12 (max. number of threads)STACKSIZE
= 64 (stack size per user thread)The task table (taskTab
) is MAXNTASKS
* TT_SLEN
long (12 * 14 = 168 bytes); the offsets for
each task structure are found in kernel/tasks.a65 at line 53:
TT_STDIN
0 (Stdin)TT_STDOUT
1 (Stdout)TT_STDERR
2 (Stderr)TT_PARENT
3 (parent process ID)TT_SIGADR
4 (addr of signal routine)TT_LIBSAVE
6 (word pointer to lib6502 data,
allocated by taskinit
at lib6502/libglob.a65, line
57)TT_ENV
8 (environment number)TT_NTHREADS
9 (number of running threads)TT_SIGMASK
10 (mask of allowed signals)TT_SIGPEND
11 (mask of pending signals)TT_PRIORITY
12 (task priority)TT_RETCODE
13 (return code after kill)The thread table (threadTab
)
is MAXNTHREADS
* TH_SLEN
long (12 * 8 = 96);
the offsets for each thread structure are found on line 67:
TH_ST
= 0 (thread state)TH_TASK
= 1 (parent task ID)TH_SP
= 2 (thread's stack pointer)TH_LIBSAVE
= 3 (word pointer to lib6502 data, set
at sysapps/init.a65, line 723)TH_PAR
= 5 (3 bytes, parameters for kernel calls)The possible states for a thread (include/kdefs.i65, line 211) are:
TS_FREE
= 0TS_ENV
= 1 (allocated but not running)TS_IBRK
= 2 (task has BRK'd in IRQ routine)TS_BRK
= 3 (task has BRK'd)TS_RDY
= 4 (task did YIELD call)TS_IRQ
= 5 (task is interrupted)TS_WFRX
= 6 (waiting for other task to RECEIVE)TS_WFTX
= 7 (waiting for other task to SEND)TS_WXTX
= 8 (wait for a specific task to SEND)TS_WFSEM
= 9 (wait for semaphore)TS_WSIG
= 10 (wait for signal)Each task has at least one thread; the task ID in the thread table is an offset into the task table. Thread IDs are also an offset into the corresponding table (see opening comments in kernel/tasks.a65).
arch/c64/c64rom.a65 #defines STACKCOPY
, which results
in the allocation of Stacks
with a size
of MAXNTHREADS
* STACKSIZE
(12 * 64 =
768). The stack is split into two parts, one for the kernel and one
for threads. When a new task's thread is created in
the fork
kernel API call (kernel/tasks.a65, line
192), initsp
is called at line 259. initsp
(arch/c64/kernel/kenv.a65, line 188) sets the stack pointer for the
new task's thread to STACKSIZE
- 1; i.e. the lower 64
bytes of the stack are used by tasks/threads, and the upper 192 bytes
by the kernel.
To perform a context switch (see setthread
in
arch/c64/kernel/kenv.a65, line 77), the current thread's stack is
first copied into Stacks
. The offset
into Stacks
is computed by first multiplying the thread
ID by 8. Since a thread ID is an index into the thread table (which
has 8-byte entries) this has the effect of indexing by 64-byte
increments, which is the value of STACKSIZE
. The thread's
stack pointer is then used as an index to copy from the 6510 stack to
the thread's entry in the Stacks
table. The same process
is then used to copy the new thread's stack from Stacks
to the 6510 stack.
Kernel APIs switch from user space to kernel space by
calling memsys
(arch/c64/kernel/kenv.a65, line 304),
which saves the calling thread's registers and stack pointer and
switches the stack pointer to the system stack
(SSP
). memtask
(line 268) reverses this
process to return to a task from the kernel.
Forking a new process via the FORK
kernel API call works like this:
PCBUF
.FORK
doesn't actually start the new
process; that's left to the scheduler.FORK
expects a structure with the following offsets at
PCBUF (defined in include/kdefs.i65, line 325):
The IRQ routine is responsible for process scheduling and device
handling; it's at pirq
(kernel/init.a65, line 576), and
is called directly from the 6502 vector at $FFFE (see
kernel/end.a65). It runs as follows:
pbrk
(kernel/tasks.a65, line
1367). This routine just sets the thread's state
to TS_BRK
, which the scheduler doesn't appear to
check for.memsys
.irqdev
(kernel/devices.a65, line 65). For each
device, a DC_IRQ
command is
sent.Syscnt
= 1), return from IRQ.adev
high bit set), return from IRQ.irqloop
(kernel/tasks.a65,
line 550) to run the scheduler. The current thread's priority
was stored in Irqcnt
; start by decrementing that,
and if it's non-zero, return from IRQ (this acts as a simple way
to keep high-priority threads running longer).
TS_IRQ
,
followed by a jump to nexttask
(line 460), which is
the heart of the scheduler. In the C64 version, it always
branches to ok
(line 492) to walk the thread table
looking for the next eligible thread to run.
checksig
(line 967).TS_RDY
or TS_WSIG
(waiting for signal), make the thread
active, clear its signal pending mask
(TT_SIGPEND
), and run the signal routine whose
address is stored in the thread table.TS_IRQ
(interrupted
by scheduler), set it to TS_RDY
and run it.SIG_INTABLE
(interruptable flag). If
it's clear, do nothing and return.TS_WFRX
, TS_WXTX
, TS_WFTX
,
or TS_WFSEM
, set it to TS_RDY
and
run it. The accumulator will be set
to E_INT
.TS_RDY
, restore its
registers, store its priority in Irqcnt
and run
it. The three parameter bytes at TH_PAR
are loaded
to .X, .Y, and .A respectively. (Note that the registers are
stored in this order when calling YIELD
.)TS_IRQ
, store its
priority in Irqcnt
and run it.RESET
to restart
the operating system.lsh
Shelllsh
shell is at
apps/lsh/lsh.a65.lsh
starts, it installs
a SIG_CHLD
signal handler which points
to sigrchld
(line 1115). This routine saves the
registers in tables
(ctabh
, ctabl
, ctabr
) and
increments the table pointer, cnum
. At the top of
the command loop (see below), checksig
is called to
print an error message for each entry and purge the
table. TODO: What's in the registers at that point and
how does it get there? It looks like .X/.Y is the PID and .A is
an error code.getcmd
(line
181), which calls tokenize
(line
868). tokenize
replaces blanks between program name
and arguments with nulls. Arguments in quotes have blanks
preserved. This is the format expected by forkto
(see below).execfork
(line 933),
which checks for builtin commands from a table at line 1001. If
found, the builtin is run (line 977) from a table of addresses
at line 1000.forkto
(libexec.a65,
line 55). This sets up a call to FORK
in the
kernel, with the start address set to libfork
(libexec line 291). When the process runs, libfork
calls taskinit
(libglob.a65, line 57) to set up
the LT_*
structure, then doload
(line
499), which sets up a file descriptor and
calls loader
(libloader.a65, line 88) to read the
file from disk and do the relocation.zalloc
is called. This allocates zero-page
in 4-byte blocks, each of which have an owner (in
the zmem
table). Allocation is done
above Zerostart
, which marks the end of the area
used by the kernel (defined as $70).doload
returns with the start of the text
segment (or the main
method) in .A and .Y.libfork
),
what does the PUTC do?libfork
, the address of term
is
pushed onto the stack (so the program can terminate with an RTS),
the address of the program's command-line is put to .A/.Y, and
there's a jump to the start address. This means that programs
started from the shell will find their complete command line
(including program name) in .A/.Y on startup.lsh
, if the program was not
backgrounded or piped, there's a call to waitpid
,
which waits for the program's PID to show up in
the SIG_CHLD
table (see signal handler discussion
above). Once the program has terminated there's a jump back to
the top of the command loop.Some of the enhancements I mentioned in my VCFMW talk have already found their way into GeckOS; I'll describe them here. I also spoke about them at my "Hacking GeckOS" talk at World of Commodore in Toronto.
process name, exec address in info
command
The old shell has an info
built-in command that is
like the Unix ps
command. It calls the
kernel GETINFO
API (which reads the task table),
but GETINFO
didn't originally return process names or
exec addresses. There is space for the first six characters of the
process name in the getinfo struct (include/kdefs.i65, line 227), but
the task table had no entry for process name. Storing a task's execute
address was not implemented at all. Having both of these items in
a ps
command for the lsh
shell is invaluable
for debugging.
The first screenshot on the right shows the output of the original
version of the info
command. Note the lack of process
names (lsh
is in fact incorrect and should
be init
) and exec addresses.
The task table could have been expanded to include these two items, but it would require changing a lot of code that would have to deal with index register overflow. André's solution was to split it into two smaller tables of the same length, which solved this (the PID is still an index into these tables).
But there's a catch: populating these two new fields is not
straightforward for lib6502 programs because of how they
call FORK
:
FORK_NAME
fieldlibfork
(see above
discussion about running programs from the lsh
shell) in the FORK_ADDR field (it doesn't know the exec address
until the program has been loaded and relocated)FORK
so that the
stream number is sent as a parameterSETINFO
API that
lib6502's execfork
could use to update the task
table with the execute address and the process name after it
calls FORK
(a process can only change its own exec
address)standalone ps
command with process name and exec
address
At this point, André ported the info
command to
the lsh
shell as ps
; the second screenshot
on the right shows this new code. Note that the process names and exec
addresses are now present. There are even -a
(show all
task table entries, even inactive) and -l
(show all
fields, resulting in two lines per process) options. Of course, it's
now theoretically possible for the output to scroll off the screen, so
a more
command was added. On the C64, there is no pipe
character on the keyboard, so use a single-quote, e.g. ps -al '
more
.
a proper kill
command
I wrote a kill
command for the lsh
shell
based on the one in the old shell, except that it can send arbitrary
signals like the standard Unix command (this program was merged to the
master branch). Note that the SETSIG/SENDSIG
section of
the kernel documentation makes a
distinction between calling the kernel KILL
API and
sending a SIG_TERM
signal, in order to allow programs to
release their resources before ending. However, no such signal is
listed in include/kdefs.i65. Interestingly, there is
a SIG_KILL
, which doesn't appear to be used anywhere, and
a SIG_BRK
(also not used), whose comment reads "ctrl-C
received". Things to keep in mind for future reference!
ps
and kill
commands), it's now possible to achieve the Grand Unification of the
Shells (cut a build without the old shell starting at boot time, and
the monitor available as a standalone app).lsh
could be made clearer; some
of them are a bit cryptic.ls
command in lsh
could be improved
to show more detail (André has already added an ls -l
option). A long-range project would be to show date stamps and
subdirectories on devices that support it; you'd have to be able to
tell what kind of device the drive is, and I don't know if
the fsiec
driver stores that information.possible bugs (needs more study)
lsh
, re-use of the argp
variable to
hold the .X/.Y registers after the call to execfork
in
line 118: some debugging indicated that the value of argp
could change before .X/.Y were reloaded at lines 127-128 (presumably
due to multi-tasking).0046,00 : Received sigchld error
, although it
appears to have ended normally. See the discussion
about how the shell runs programs for more
information.lsh
shell on console 3, then kill
the first one from there, the first one fails to restart with the
messages Received sigchld from 30
(the first shell's PID)
and Could not set device stream!
It's my understanding
that the first shell should restart, as the kernel starts it with
the PK_RESTART
flag.segment boundaries and load addresses -- a warning
When doing anything that changes the size of memory structures
in c64rom, make sure to look at the build output for the line
"c64rom.o65: o65 version 0 executable file" and check the
following lines to make sure none of the segments overlap. If so,
adjust the segment locations on line 11 of arch/c64/Makefile.
The -b
option in the xa assembler sets the segment start
addresses: t (text), d (data), z (zero), and b (bss).
For example, here is the output if you split the task table into two tables with 12-byte entries:
c64rom.o65: o65 version 0 executable file mode: 0000 =[executable][16bit][byte relocation][CPU 6502][align 1] text segment @ $77fe - $10000 [$8802 bytes] data segment @ $0300 - $0b04 [$0804 bytes] bss segment @ $0a90 - $15d5 [$0b45 bytes] zero segment @ $0008 - $006f [$0067 bytes]Note the overlap of the data segment into the bss segment! The original line in the makefile is:
${XA} -R -bt 30718 -bd 768 -bz 8 -bb 2704 c64rom.a65 -o c64rom.o65 -l c64rom.lab ;\and it would have to be changed to:
${XA} -R -bt 30718 -bd 768 -bz 8 -bb 2822 c64rom.a65 -o c64rom.o65 -l c64rom.lab ;\
It may also be necessary to adjust the load address upward for
c64rom.a65 (Memstart
, at the end of the source file).
Could CMD-style directory and partition commands be supported on CMD devices and μIEC?
Could the networking capabilities of a 1541 Ultimate II+ be exposed as a device?
Could the stack swap during a context switch be done using an REU?
A text editor with emacs-like keybindings would be very nice.