diary @ telent

Nothing to write /home about#

Sun 02 Apr 2023 05:10:43 PM UTC

Topics: liminix

This week in Liminix: a change in the plan. Much to think about, nothing to show

The original plan

In this phase we revisit this decision, and figure out how to gain the advantages of the Nixos options/type system while still allowing multiple instances of the same module

I spent a day or so thinking about this. The two fundamental tools for abstraction in Liminix are the module and the service.

With that in mind, I've decided to defer the redesign of modules until later down the line when I also need to do some serious thinking about services. There's no point considering one without the other.

Writable filesystem

So onto the next part of the plan. As of March 2023 the Liminix filesystem is a “squashfs” image, meaning that the filesystem is generated at build time and read-only at runtime. Any updates or reconfiguration must be made by producing and flashing an entire new image, which is fine if you're making a big change anyway but not very convenient if you just wanted to install netcat or change the wifi channel.

I don't have anything here in commitable form yet, but I note the following as bullet points:

Where I am right now, it looks like this means we'll be using initramfs to boot the system and then have an early userland that mounts the store from jffs2/ubifs and runs a script to recreate /etc and friends based on what's in it.

It ought to work, anyway. Next week maybe I can talk about it in the past tense and not the future conditional.

Writes of way#

Mon 10 Apr 2023 10:11:41 PM UTC

Topics: liminix

It ought to work, anyway. Next week maybe I can talk about it in the past tense and not the future conditional.

This update is a day late and a few hundred words short, but we're in a state of "it appears to work":

testbed login: root
Password:
login[326]: root login on 'console'


BusyBox v1.35.0 () built-in shell (ash)

# df
Filesystem           1024-blocks    Used Available Use% Mounted on
mtd5                     13184     12664       520  96% /persist
mtd5                     13184     12664       520  96% /nix
dev                      60156         0     60156   0% /dev
tmpfs                    60848       856     59992   1% /run
# ls /
bin      etc      lib      persist  run      sys
dev      home     nix      proc     srv
# touch /persist/HELLO
# ls -l /persist/HELLO
-rw-r--r--    1         0 Jan  1 05:43 /persist/HELLO

How did we get here?

There is of course a bunch of cleanup to do, and some serious "what happened to all my storage space?" work - it might be a bit academic that I can write to the filesystem if there's less than half a megabyte of space left on it. Once we have some free space to play with we can try running nix-copy-closure

Some assembly required#

Sun 16 Apr 2023 08:18:04 PM UTC

Topics: liminix

tl;dr Premature optimization in the rootfs of my emu

Last week:

There is of course a bunch of cleanup to do, and some serious "what happened to all my storage space?" work

This week I've been playing with reducing the amount of storage used, by rewriting shell scripts in the initramfs as C programs. Nothing in the initramfs is accessible in the second stage environment, so once the system is booted it's just dead weight. Gien that a statically linked minimal busybox is around 200k, if we can get rid of it, that's a decent sized chunk of a 16MB flash.

It turns out that not only can we get rid of busybox, we can get rid of the entire C library. preinit.c uses the Nolibc minimal C-library replacement - a header file defining inline functions that implement common syscalls - instead of linking against Musl, and this results in an initramfs approximately 4k in size. So I'm pretty happy about that.

I'm less happy about having had to insert inline MIPS assembly at the top of main() to get it to work, mostly because of the strong possiblity I've got it wrong...

    asm("la $gp, _gp\nsw $gp,16($sp)");

Some slightly handwavey I-dont-fully-understand-this context here: $gp is the "global pointer" register on MIPS, which is used to make references to global variables use one instruction instead of two (this Nintendo 64 programming blog is the clearest explanation I've found). Why do we need this magic?

So first we load $gp from the _gp symbol using the la pseudoinstruction, and then we stick it into the offset on the stack where the compiler will expect to find it about twelve instructions later. If we disassemble the file we can see this in action: first the instructions we added, then it sets up the registers for a call to write (using gp-relative addresses), then when write returns it reloads $gp from an offset from $sp.

004000f0 <main>:
[...]
  40011c:       3c1c0042        lui     gp,0x42        # we added this
  400120:       279c8970        addiu   gp,gp,-30352   # and this
  400124:       afbc0010        sw      gp,16(sp)      # and this
  400128:       8f838018        lw      v1,-32744(gp)
  40012c:       00003025        move    a2,zero
  400130:       00661021        addu    v0,v1,a2
  400134:       80420000        lb      v0,0(v0)
  400138:       14400040        bnez    v0,40023c <main+0x14c>
  40013c:       8f90801c        lw      s0,-32740(gp)
  400140:       8f858018        lw      a1,-32744(gp)
  400144:       26100620        addiu   s0,s0,1568
  400148:       0200c825        move    t9,s0
  40014c:       04110134        bal     400620 <write>
  400150:       24040001        li      a0,1
  400154:       8fbc0010        lw      gp,16(sp)      # gcc wrote this
  400158:       00003825        move    a3,zero

It was a fun and entertaining voyage of learning new things, but also one for the unwritten Risk Register as I'm not happy I fully understand it. I suppose at least if it does fail then the mode of failure will be a quite obvious "doesn't boot".

Removing the shell from the initramfs also meant having to rewrite the activate script. This script lives in the actual root filesystem but is run in the initramfs context (no nix store, no shared libraries, minimal /dev, etc, and now no shell interpreter). For now this is just linked statically against musl and weighs in at around 70k, but really the same argument for using nolibc would apply just as much here - even though it's not part of the initramfs proper, that's still 70k that's not shared with anything else and we'll never use again after boot.

I have to be honest and admit that there are probably other parts of the system where I could make savings bigger than 200k. At least I hope there are - the jffs2 compression doesn't seem to be nearly as effective as squashfs, so we're paying quite a price for a writable filesystem. But there was still a certain amount of pride in making it almost-cost-neutral to add an initramfs.

Next week: adding multi-output derivations in the overlay. Looking at the generated filesystem I see that verious packages contain man pages, static libraries, random python files etc which are probably not necessary to the running of the system and could save us kB or even MB.

Erase and rewind#

Thu 27 Apr 2023 10:10:58 AM UTC

Topics: liminix

Late update this week, and it's little more than "hey, I'm still alive". I've been a bit under the weather and spending my discretionary computer time on playing GP Bikes instead of getting anything useful done.

What have we done? We reduced the rotuer configuration from over 16MB to about 11MB by

Also dicovered that jffs2 doesn't like being written onto a flash if you only erase the blocks that you're writing it to: it seems to want the rest of the flash to have been erased as well. That took a bit of figuring out.

More progress next week, I hope.