elks icon indicating copy to clipboard operation
elks copied to clipboard

Enhancement: support for noncontiguous memory blocks

Open pawosm-arm opened this issue 4 years ago • 9 comments

I've managed to buy lo-tech's UMB memory 8-bit ISA card for my Amstrad PC 2086. It can fill unused chunks of upper memory addresses with RAM. It works with FreeDOS where USE!UMBS.SYS driver must be used. As this driver is unable to autodetect where upper memory RAM blocks are located, it needs to be patched before use (it's easy with the user manual provided). Each block is defined by hardware address (in upper memory area) and size.

To achieve the same with ELKS, ability to define memory map (allowing to have holes between available memory blocks) in .config would be required. A page granularity should be ok.

pawosm-arm avatar Aug 16 '20 11:08 pawosm-arm

The ELKS memory manager is elks/arch/i86/mm/malloc.c, which you might want to take a look at.

In the function mm_init, the beginning and end of conventional memory is passed at boot, and a single "free segment" in the MM segment list is created. It would not be hard to add additional segments (paragraph boundary granularity) through perhaps a new mm_add function, which would add additional free segments, being passed an address and length.

However, the MM does try to combine/merge segments on free, which would require some careful modifications to the segment flag bit to prohibit that, since that won't work with your card memory.

I assume there are no hardware access issues with this card, other than knowing the address and length of each memory block allocated, since it doesn't require the A20 line fiddling etc required for EMS.

Adding upper memory capability to ELKS may be worth the effort, since no special code other than enhancing the memory manager as described is needed. However, I don't have access to such a card for development or testing. If you'd like to take a pass at it, study malloc.c and I can help you.

If the card could be set such that the upper memory start address is just above your machine's exiting (640k?) memory, I am thinking some very simple modifications could be made that would allow the memory scan performed in setup.S to just add the UMA to the 640k and pass the entire contiguous memory size to mm_init.

ghaerr avatar Aug 16 '20 21:08 ghaerr

If the card could be set such that the upper memory start address is just above your machine's exiting (640k?) memory, I am thinking some very simple modifications could be made that would allow the memory scan performed in setup.S to just add the UMA to the 640k and pass the entire contiguous memory size to mm_init.

Unfortunately, VGA memory starts right after 640k boundary and it should not be used for general purposes. The first free block I could find is at D000h, right after area occupied by lo-tech's XT-IDE BIOS.

pawosm-arm avatar Aug 16 '20 22:08 pawosm-arm

Unfortunately, VGA memory starts right after 640k boundary and it should not be used for general purposes.

Oops - of course. I forgot about the video memory. So we'd need to make some mods to the ELKS MM as discussed.

ghaerr avatar Aug 16 '20 22:08 ghaerr

I am thinking some very simple modifications could be made that would allow the memory scan performed in setup.S to just add the UMA to the 640k and pass the entire contiguous memory size to mm_init.

I'm sceptic about any memory-scan-based auto-detection techniques for finding extra RAM in UMB. None of such utilities written for DOS had it right (and each time the authors warned about possible inaccuracies), there are always false positives found in case of R/W accessible devices which are not RAM yet still occupying parts of the UMB address space.

Also, seems I never asked questions regarding ELKS memory layout. How ELKS could benefit from extra memory in a first place? Would it use for more buffers for disk or network I/O? Or could it move some drivers there (similar to DOS and its 'devicehigh' statements in 'config.sys')? Or, could userspace benefit from this allowing more processes to be started simultaneously or allowing bigger userspace memory allocations? How userspace is organized? Can userspace program have more than one 64kB segment allocated?

pawosm-arm avatar Aug 23 '20 14:08 pawosm-arm

I'm sceptic about any memory-scan-based auto-detection techniques for finding extra RAM in UMB. None of such utilities written for DOS had it right (and each time the authors warned about possible inaccuracies), there are always false positives found in case of R/W accessible devices which are not RAM yet still occupying parts of the UMB address space.

Agreed. What I meant to say is that early in system initialization, (pre)set values for UMB areas would be added to the memory allocator's free space. This could be done either through a set of config defines, (would could be difficult to specify if there were more than one of these areas), or through umb= lines in the /bootopts parser (likely simpler).

How ELKS could benefit from extra memory in a first place?

It depends on where the memory is. I'll describe UMB memory below. For EMS/XMS, the benefit would only be for additional I/O buffers, which would only be accessible through the EMS "window".

Would it use for more buffers for disk or network I/O?

More so-called L2/external buffers could be allocated for disk I/O.

The ELKS global system memory allocator seg_alloc allocates memory from the lower 1M memory. This would include UMB memory, if mm_init were modified as discussed previously in this thread. At system startup, all the L2 buffers (the number of which to allocate now specified from config) are allocated, outside of the kernel's address space. This memory is allocated "globally", just like memory allocated for process execution is allocated, but used for external buffers.

The memory used by the executing kernel is divided into two 64 segments (not allocated, but removed from the overall memory available to seg_alloc); the CS (code) segment, which is nearly full at around 57k bytes, and the DS (data segment), which is always fully allocated to 64k. Within the kernel data segment, there are 12 so-called L1 buffers allocated (12 at 1k bytes each, matching the ELKS buffer block size). These must be allocated within the DS segment, so they are directly addressable without using far pointers. When the kernel needs a buffer, it calls map_buffer which copies an L2 buffer into an L1 buffer so the data can actually be accessed.

Thus, the L1/L2 buffers are used in a somewhat slow fashion currently: I/O is done into an external L2 buffer (which is not in the kernel data segment), and then copied in an out of an L1 buffer for internal use.

Long story short: yes, UMB would allow for more L2 buffers, but not any more L1 buffers.

Network I/O currently uses a single static buffers, so UMB would not help network I/O.

Or could it move some drivers there (similar to DOS and its 'devicehigh' statements in 'config.sys')?

No, there is no support currently in ELKS for separately-loaded (non-linked) drivers.

Or, could userspace benefit from this allowing more processes to be started simultaneously or allowing bigger userspace memory allocations?

Yes, there would be more memory in the < 1MB region, so more memory for larger or more user applications would be a benefit.

How userspace is organized?

Each application is loaded by allocating its CS (code segment) using seg_alloc, then its DS (data segment), also using seg_alloc. Thats it. The data segment size is computed using the initialized data segment (.data), the uninitialized data (.bss), and the requested stack and heap space (current 2k stack and 2k heap).

Can userspace program have more than one 64kB segment allocated?

All programs have both a CS and DS segment allocated, and that's it. They are limited to 64k max each, and there is not yet any provision for a user program to request memory outside its data segment, nor grow or shrink its data segment, since the stack is allocated on the high address side of the data segment and cannot be moved.

I hope that helps!

ghaerr avatar Aug 23 '20 16:08 ghaerr

The memory used by the executing kernel is divided into two 64 segments (not allocated, but removed from the overall memory available to seg_alloc); the CS (code) segment, which is nearly full at around 57k bytes, and the DS (data segment), which is always fully allocated to 64k.

It's almost impossible to have more than two segments of the UMB memory in XT machines, the only free 64kB blocks are at D000h and E000h. To make the matter worse, when EMS card is inserted, it occupies 64kB block at E000h, so there's only 64kB of UMB accessible. From your description above I can see that the easiest way to benefit from those one or two blocks of UMB is to just place kernel (and use entire 64kB for it) and its data segment (always 64kB) there (or just the 64kB data segment if only one UMB block is available). Currently ELKS config has CONFIG_MEM_PAGES setting which (I presume) tells how many of the low memory 64kB blocks is available. Two more settings could be provided: kernel CS and kernel DS. If set, they should be above CONFIG_MEM_PAGES address and it would indicate that for them, no blocks should be removed from the overall memory available to seg_alloc.

Each application is loaded by allocating its CS (code segment) using seg_alloc, then its DS (data segment), also using seg_alloc. Thats it. The data segment size is computed using the initialized data segment (.data), the uninitialized data (.bss), and the requested stack and heap space (current 2k stack and 2k heap).

I still have question regarding data segment. I've found two different descriptions on how it is laid out. The one in Documentation/html/technical/process_man.html file depicts it as such:

 +------------+--------------------------------------------------+
 | data + bss |  heap ---> |                          <--- stack |
 +------------+--------------------------------------------------+
                           ^                                     ^
                   current->t_enddata                  current->t_endstack

...while the one in Documentation/text/binformat.txt depicts is as such:

    current->t_begstack                       current->t_endseg
             v                                         v
 +------------------+------------+---------------------+
 |  |<--stack|  env | data + bss |     heap --> |      |
 +------------------+------------+---------------------+
 ^                               ^              ^
 0x0                    current->t_enddata  current->t_endbrk

Now, which one is actually used in nowadays ELKS?

All programs have both a CS and DS segment allocated, and that's it. They are limited to 64k max each, and there is not yet any provision for a user program to request memory outside its data segment, nor grow or shrink its data segment, since the stack is allocated on the high address side of the data segment and cannot be moved.

This model is so simple that I guess need for page migration was never considered...

For EMS/XMS, the benefit would only be for additional I/O buffers, which would only be accessible through the EMS "window".

If page migration was ever introduced, pages of inactive processes could be 'swapped out' to EMS. It sounds bit wasteful to utilize 2MB of EMS just for the disk I/O buffers... From the other side, considering maximal number of processes ELKS can spawn and 64kB cap on each data/code segment, it may be hard to ever need to be swapped out... Maybe we need to introduce shared memory to the model and utilize EMS for it? How IPC in ELKS looks like now?

pawosm-arm avatar Aug 23 '20 20:08 pawosm-arm

From your description above I can see that the easiest way to benefit from those one or two blocks of UMB is to just place kernel (and use entire 64kB for it) and its data segment (always 64kB) there (or just the 64kB data segment if only one UMB block is available).

Putting the kernel up in a UMB block or two is not a bad idea. I wish it were so easy though. Currently, the relocation (along with a bit of magic as well as also covering two different other cases which all need to be considered, CONFIG_ROMCODE, REL_SYS and !REL_SYS, see setup.S) doesn't yet handle splitting the data segment out from the code segment. In addition, there is a setup segment and an init segment. I don't fully understand exactly how the boot loader/relocator works yet in all three cases.

Far easier, should UMB ever get implemented, would be to add the UMB memory to the segment allocator seg_alloc. As previously discussed, there's still issues there as the seg allocator tries to merge segments, which would fail in between the UMBs and other memory the way its written now.

Currently ELKS config has CONFIG_MEM_PAGES setting which (I presume) tells how many of the low memory 64kB blocks is available.

Well, now you've done it: found another completely unused .config setting. The current setup.S relocation calls a BIOS function to get the top of memory, and does not use CONFIG_MEM_PAGES. I've made a note to delete it in a future PR.

Two more settings could be provided: kernel CS and kernel DS. If set, they should be above CONFIG_MEM_PAGES address and it would indicate that for them, no blocks should be removed from the overall memory available to seg_alloc.

Ignoring CONFIG_MEM_PAGES, that could work. But it would probably be better to just label them CONFIG_UMB1 and 2, in the case that UMB memory might be desired used differently.

I still have question regarding data segment. I've found two different descriptions on how it is laid out. The one in Documentation/html/technical/process_man.html file depicts it as such:

 +------------+--------------------------------------------------+
 | data + bss |  heap ---> |                          <--- stack |
 +------------+--------------------------------------------------+
                           ^                                     ^
                   current->t_enddata                  current->t_endstack

This is the current method in use.

...while the one in Documentation/text/binformat.txt depicts is as such:

    current->t_begstack                       current->t_endseg
             v                                         v
 +------------------+------------+---------------------+
 |  |<--stack|  env | data + bss |     heap --> |      |
 +------------------+------------+---------------------+
 ^                               ^              ^
 0x0                    current->t_enddata  current->t_endbrk

This model is still supported with the unselectable option CONFIG_EXEC_LOW_STACK in the kernel. It's not selectable anymore because no compiler (including the original bcc it was designed for) sets that header bit. With the recently introduced ability to specify fixed stack and heap values, there really isn't any need for it, either.

If page migration was ever introduced, pages of inactive processes could be 'swapped out' to EMS.

Remember, there are no pages on the 8086 or ELKS. The data and code segments are of variable size, and not necessarily adjacent, and the code segment can be shared. So you're really just talking about swapping memory.

It sounds bit wasteful to utilize 2MB of EMS just for the disk I/O buffers... From the other side, considering maximal number of processes ELKS can spawn and 64kB cap on each data/code segment, it may be hard to ever need to be swapped out...

All the disk swapping code was removed earlier in the year by a contribution. I wasn't following as closely then but I don't think the swapping actually worked anyways.

We can still run out of memory, but agreed, the ELKS system can't really get very big given the 64k kernel code and data segment limitations. I'm working with @tkchia now to try to work through some ideas of how the kernel might run with multiple code segments, which will help, as we'd likely run out of kernel code space implementing the ideas you're proposing otherwise anyways!

Maybe we need to introduce shared memory to the model and utilize EMS for it? How IPC in ELKS looks like now?

There is no IPC in ELKS. I don't think the problem is applications needing to communicate with each other, but just the old-fashioned < 1M memory. Applications can't run in EMS memory as it is not directly addressable. And of course, ELKS needs to run well on those systems without an EMS card. Currently, running two internal simultaneous telnet connections to our own telnetd server, the system runs out of processes long before it runs out of memory (there is only one process slot left in this scenario).

ghaerr avatar Aug 23 '20 22:08 ghaerr

Remember, there are no pages on the 8086 or ELKS. The data and code segments are of variable size, and not necessarily adjacent, and the code segment can be shared. So you're really just talking about swapping memory.

Ouch, seems I was taking into considerations something that never existed!

ELKS needs to run well on those systems without an EMS card.

the system runs out of processes long before it runs out of memory

Sadly, the two points above serve the purpose of reality check.

pawosm-arm avatar Aug 24 '20 11:08 pawosm-arm

Still, this has been an educational track to observe.

Then there is the RAM disk option - on EMS/XMS via a UMB window, which would be a great storage pool for process swapping. And the /tmp file system.

—Mellvik

  1. aug. 2020 kl. 13:59 skrev pawosm-arm [email protected]:

Remember, there are no pages on the 8086 or ELKS. The data and code segments are of variable size, and not necessarily adjacent, and the code segment can be shared. So you're really just talking about swapping memory.

Ouch, seems I was taking into considerations something that never existed!

ELKS needs to run well on those systems without an EMS card.

the system runs out of processes long before it runs out of memory

Sadly, the two points above serve the purpose of reality check.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/jbruchon/elks/issues/709#issuecomment-679083665, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA3WGOE2L2E4WAEJZSIJEVLSCJI3BANCNFSM4QAYZVMA.

Mellvik avatar Aug 24 '20 12:08 Mellvik