Mini16 Part 1: FAT12, Near & Far Pointers, and Thinking Before Coding

I was so scared that I would've wasted 7 days for absolutely nothing - an fate even worse than wasting 7 days on a 16-bit toy operating system (which, of course, is also the same as nothing). Finally finished something presentable at the end of this week.

The current state of things

I have another floppy disk image created with MSDOS mounted as the B: drive. `@read_file` now currently reads to 1000:0000. All the

I have another floppy disk image created with MSDOS mounted as the B: drive. `@read_file` now currently reads to 1000:0000. All the "NIHAO" are folders. The sigma sign seems to be a "deleted" mark used by MSDOS.

I'll probably rewrite the whole OS after isolating the few necessities - the bootloader, the linker script, the keyboard handler and the FAT12 driver. These parts when combined together can serve as the basis for a 16-bit toy operating system. I have a few other ideas that are drastically different from the conventional approach; I'll probably stop working on Mini16 before realizing those ideas.

The near and far pointer hussle

The fact that I really didn't have any consistent rules about what kind of pointer should I use finally bites me back. When will the difference between near and far pointer become a trouble? When you need to work across multiple segment. When your kernel fits within one segment (64KBytes), it's better to use near pointers internally and only use far pointers when implementing syscall. 32-bit protected mode does not have this issue because there's just one kind of pointer.


As someone who have never worked towards a RAM-confined situation, I have never felt so much pain before. Maybe I'm just this bad at programming.

Thinking before coding

File drivers aren't the kind of thing that you can just write a "Minimal Viable Product" and iterate upon it: the full spec *is* the MVP, so unless you did everything you have nothing. After a few days of mindlessly generating garbage and getting nowhere, I decided to start again and write stuff like this:

// find next cluster with a FATClusterPointer
// NOTE: FAT values are pointers pointing to the next cluster
// 0.  require a DriveParameter(dp)
//             a FATDescriptor(desc)
//         and a FATClusterPointer(cp)
// 1.  calculate how many cluster there is in 3 sector (n_cluster_in_group):
//         (3 * desc.bytes_per_sector) / 12
// 2.  calculate which group is cp.cluster_id in (group_current)
// 3.  calculate which group is cp.cluster_value in (group_next)
// 4.  if group_current != group_next:
//         load fat table (group group_n)
// 5.  calculate the offset
//     because we are talking about fat12 here so things are trickier. for fat12
//     the clusters are arranged like this:
//             AB CD EF GH IJ KL ...
//         three bytes maps to 2 cluster & the whole thing is in little-endian,
//         so it's actually:
//             cluster 1 & 0: EFCDAB --> 0xEFC 0xDAB
//                 cluster 0: offset 1 lower nibble ~ offset 0 byte
//                 cluster 1: offset 2 byte ~ offset 1 higher nibble
//             cluster 3 & 2: KLIJGH --> 0xKLI 0xJGH
//                 cluster 2: offset 4 lower nibble ~ offset 3 byte
//                 cluster 1: offset 5 byte ~ offset 4 higher nibble
//     we should make next_cluster_id be next_cluster_id % length_of_group_in_cluster first.
//     let modded_id = next_cluster_id % (desc.bytes_per_sector * 3 / 12)
//     so for even cluster id:
//         offset [modded_id / 2 * 3 + 1] lower nibble ~ offset [modded_id / 2 * 3] byte
//     for odd cluster id:
//         offset [modded_id / 2 * 3 + 2] byte ~ offset [modded_id / 2 * 3 + 1] higher nibble
// 6.  retrieve the cluster value.
// 7.  update cp.
// RETURNS 1 and don't do anything if end of cluster chain.

It works like a charm. The coding was way easier after I wrote stuff down, and there were way less bugs after I have done the coding.