blackoil wrote:Thanks for your programming advice. I use my own language & bootstrapped compiler to build/test my logic.
My current ATA access logic is: One thread will be marked/unmarked to suspended in task queue, when the resource it requires is unavailble/available respectively. To support more concurrent threads, a queue mechanism or something etc. must be established.
If you use any language of a higher level than assembly, starting with C, Java, JavaScript, BASIC, Visual Basic... you can do things much easier if you reimplement stack operations for push, pop, read, etc., be it for the low level CPU stack or for a higher level stack.
Have you ever thought about using stack operations to save GDT descriptor fields or to push/pop paging structures so that you can interpret and rebuild them later?
By reimplementing the stack functions and using a custom stack, you can easily treat all of the local and global variables (and even structures or structure fields) as regular CPU registers that you can preserve and restore. That stack can then contain any sort of mixed data. All you need is make sure that you have at least one of those stacks per thread and that you pop the stored values in order just like for the stack kept in SS:ESP so that the process state doesn't get destroyed.
If you think about it, using a stack again (which is in general very rare to do normally once we get to use a language with the level of C and above) allows you to simplify any component of the program. Thinking about it, there are few things that cannot be saved with a stack (for example, reserving more memory, the I/O ports of a hardware device), and even those things could be pushed and popped in the higher level stack to simplify things, reduce the number of variables required, implement a fundamental memory management sublayer managed more by the user than just by the kernel.
You could probably use 2 high level stacks (at the very least). One would be to keep the pending processes/threads and other would be to keep the processes you haven't run.
Then in the middle you could have the single running task for the current CPU. You would start with a stack with a single process. When requested by the scheduler, you would send it the pointer of the process stack. If there is more than 1 thread or task apart from the main process, you would need to pop the stack of threads from the process you are already running field by field and put it in the stack of suspended threads (you can save the fields or packed subfields one by one and rebuild a task descriptor or structure later or have the task/thread scheduler do so).
Once you are left with the main thread (which probably would do little more than handle/start/stop/create/delete the rest of the threads, and do basic processing on system/user events to send them such events), you can pop the fields of the suspended tasks back to the tasks to run and you could pass a structure to a pointer of the built task structure of the most urgent task/thread ID while doing so (for instance inspecting the relevance or wake time of a task while popping back to the running processes task), or run every task while moving structures from the stack of the ones that are supposed to run and those that are suspended.
You could have an
event stack per thread, then you would always have the most recent event on top of the stack.
You could mark an event in the stack as
INVALID_EVENT followed by the number of bytes it contains. For example you could have the following header for all of the events of the stack:
Code: Select all
dword EVENT_NAME
dword SIZE_IN_BYTES
Then the stack would be increased or decreased according to the size of the data including the size of the event name DWORD and header.
In this way you can mark an event you wish to discard and you can leave discarding it pending for later. It will distribute both time and reduce fragmentation because you just need to free the offset of the latest invalid event and then find the next valid one. Then the program will process pending free operations only when there is the immediate demand to do so (on demand).
As you can see, you get a simple and automatic sublayer that can allocate and free memory.
For a higher level stack, it's probably to have a forward stack instead of an inverted stack such as that of the x86 CPU itself. It's better for a regular application level because it allows you to allocate more memory and increase it and it also allows to do so without having to contain mostly inverted structure fields.
You can later implement functions to realign fields in the stack in a coherent way (for instance keeping the size of the most recent padding in the latest byte itself or in an external field), and in general to navigate in any order even when you have just one header per contained stack element/event/variable, etc...
The stack can have mixed data, raw bytes, words, dwords and structures such as events as long as only your thread itself handles it and knows what it left there since last time.