• Please review our updated Terms and Rules here

PDP 11/03 running LSX with ak6dn's RX02 emulator board

Just to let you know... here my little cutie takes its exams...

LSX_compiling_itself.JPG

To be a proper UNIX computer it must be able to compile its own kernel. All the previous tests, fixes, try&errors and crashes I did on SIMH, of course.
This one was a successful kernel compilation on the real hardware. It passed, took about 22min for the entire rebuild. (It was on T 0 (fast) setting in SETUP.INI of the RX02 emulator.)
 
OK, got LSX in full 30kw of RAM running... (funny detail: it does not run in SIMH - IO page hardcoded!) :biggrin:

But I still think about it. On LSX, swap space is on the user disk. You may have noticed the /usr filesystem is somewhat smaller than / (which has 500 blocks). Behind the blocks of /usr is swap space.
Unfortunately, swap size is hardcoded in the kernel, depending on user space size. Which means, if your new kernel has larger user space, it will require a smaller /usr filesystem. I'm not happy with this because the current /usr file system with its 400 blocks hardly is enough to get the kernel compiled. Thus, I fixed swap space to the 99 blocks like in the vanilla LSX disks and can reuse /usr disks for every kernel.
Of course this comes at a price. It might happen you run out of swap space, resulting in a kernel panic. The latter is not a big problem, just drops you in ODT at an address of about 000140 (HALT instruction). From there it is possible to reenter LSX by a warm start - do a '0G' in ODT. And, of course run a 'check' on the filesystems. Any open-for-write file probably is garbage now.

Currently I wonder if this may be sufficient for hobby usage. I tried out a 30kw LSX with minimum swap is able to rebuild its own kernel - a good sign. On the other hand, of all system programs the c1 stage of cc is the largest - taking 12kw which is the reason why this is the minimum userspace for LSX.
Process scheduling is cooperative if I don't use the BGOPTION. Which means, even if a fancy homebrewed C program allocates all RAM it can find, this never will be swapped out. After exit, it is released so we have no swapped out huge process. Thus, the 99 blocks of the vanilla LSX still should suffice.

Any comments on this? Do I miss something?
 
Swap works a little differently in LSX than it does in normal UNIX V6. Essentially, swap space is broken up into a number of "slots" that are the size of user space. While the amount of data actually written to the slot during a swap operation is dynamic, the occupied space on disk never changes. Therefore, the number of processes that can be swapped out will always be equal to NPROC-1 regardless of the size of the swapped out processes.

The behavior of swap also works a little differently as well. The slots act as a stack of core images. When a "fork()" system call executes, the current contents of user space (user struct + [text + data + bss] + stack) is pushed to the stack. Likewise when "exit()" is called, the most recent core image is popped off.

Have a space space that is smaller than ((NPROC-1)*(USRSIZ*4+1)+1) can cause major issues. For small processes, it will work fine. If the core image size of the process is larger than the size of the slot (SWPSIZ), it will crash (and potentially corrupt the disk as well!).

It should be possible to modify LSX to dynamically allocate blocks on the swap stack instead of using pre-defined slots. I started planning how to do this on my LSX system, but never got around to implementing it.
 
The behavior of swap also works a little differently as well. The slots act as a stack of core images. When a "fork()" system call executes, the current contents of user space (user struct + [text + data + bss] + stack) is pushed to the stack. Likewise when "exit()" is called, the most recent core image is popped off.
This is interesting. I'd always assumed that swapping in LSX worked the same as in Mini-Unix (where swap space is allocated based on process id). I see now that it is, but only when BGOPTION is enabled. I wonder why the non-preemtive option (BGOPTION undefined) wasn't included in Mini-Unix.
 
Technically allocating is based on process id as well. It just ends up acting like a stack because there can only ever be one active process at once (without BGOPTION, that is).
 
I still wonder why it works for smaller swap (at least somewhat). You are right, found the line in swap() source.

But it is a stupid strategy for larger userspace memory. In the 30kw LSX, I have 22kw userspace. Which means, a swap slot must be 89 blocks wide. With (nproc - 1) == 2 this means 179 blocks total for swap. That's more than 1/3 of the disk for just two slots!
Most of the system programs have less than 5kB size. So, that's a huge waste of disk space.
Maybe the reason it works most of the time is: in the "hacked" swap size I used, total swap is fixed to 99 blocks like for the vanilla kernel. This means, one swap slot is complete plus another one would work for small processes <5kB.

There must be some better solution. In the BGOPTION version they use a swap table. Same could be done for non-BGOPTION, storing the first swap block the process used (if the process is swapped out). For the active running process, the value would point to the first unused swap block. The values of course no longer would be constants.
For the stack-like behaviour of the swapping, holes in the swap area never would occur.
 
Just took a walk in the park, thinking about my previous post...

I made some error in the calculations. What I hacked was not the total swap size but the define of SWPSIZ where I put in (12*4+1). This way it is same as in the vanilla kernel and results in total swap space on disk of 99 blocks.
But the calculation of the swap slot bases on SWPSIZ. Thus, the two slots are complete but have the small size of a kernel with only 12kw userspace.
This perfectly explains why the kernel still can rebuild itself without any error. All system programs fit into 12kw userspace so the swap is fine for them. Interesting would be what happens when the homebrewed C program grows larger than 12kw. I think all will be fine as long as fork() is not called.

Think I will allocate some large array in the 'Hello World' program and see what happens... 🤭
 
There must be some better solution. In the BGOPTION version they use a swap table. Same could be done for non-BGOPTION, storing the first swap block the process used (if the process is swapped out). For the active running process, the value would point to the first unused swap block. The values of course no longer would be constants.
For the stack-like behaviour of the swapping, holes in the swap area never would occur.
Actually, it looks like each swapped out process maintains it's size in the proc p_size field. That plus a global pointing to the first unused swap block could be used to compute the parent process's first swap block on process exit/swap-in.

I have to say, I like the non-BGOPTION process model of LSX. I've spent a lot of time thinking about how something like Mini-Unix might have played a role similar to MS-DOS on the PC -- a small single-user OS with passable performance on a floppy-only system. I've even gone so far as to modify Mini-Unix to run more efficiently on the 11/05, which IMO would have been necessary to make it truly usable on that platform. But I hadn't had a chance to look closely at LSX until now.
 
Yes, something like this is the way to go I guess... at least it is a reasonable way to decouple userspace size, number of processes and swap space.

As of the non-BGOPTION process model, that's classical cooperative scheduling. Works fine as long as no process hangs in an endless loop (which is its disadvantage). For small systems, it has the advantage it runs even without any interrupts and/or timers. On the 11/03 I can flip off the LTC switch and nothing happens. LSX runs same as before. Just the output of 'date' doesn't change any longer... 🥲

BTW, I tried allocating a large array (18kw) and free it before the program exits. Works without problems.
 
I did a bit of experimentation with dynamically allocating space in the "swap stack" for the non-BGOPTION kernel. Turns out the modifications were not as trivial as initially expected, but after a bit of trial and error I got it working properly. Here is a demonstration of a recursive shell program running 11 iterations deep on only 220 blocks worth of swap space. For reference, the standard non-BGOPTION configuration with that amount of swap space can only go 2 iterations deep.

LSX.PNG

Hopefully this will allow me to allocate less space on a RX01 system for swap, and still be able to self-host the kernel. It also allows for the size of the user space to be safely increased without needed to resize the swap portion of the disk.

I put all of my code and images up on github if anyone wants to play around with them.

 
That's really great!! Will have a look into it.

Edit: just cloned the repo so I can play with it offline. Booting from RX02 sounds very promising. Thanks for sharing! :)
 
Last edited:
Played a little bit with the root.rx02 and sys.rx02 from your repo... in SIMH since I have no real RXV211 interface hardware.
This is really fun. Feels absolutely like a full UNIX. Good work!

(I found myself now thinking about getting a RXV211 card ... :unsure:)
 
Back
Top