[Move to Docs later]
Any sample code contained here is for illustrative purposes only and is not by any means final. Actual implementation likely will vary.
Cosmos needs additional ways to access memory. These new methods are collectively called managed memory variables. Only kernel ring should have access to actual pointers and such access should be minimized. However higher rings should be able to allocate and even structure memory directly in more abstract and restricted manners.
Kernel Ring can use C# unsafe options to access memory. However the use of pointers frequently leads to bugs. Such pointers are necessary and are permitted in Kernel Ring, however other abstractions can exist to cover most cases and further minimize actual pointer usage.
Using plugs or attributes Cosmos will have the ability to map variables to specific memory locations, or I/O Ports. Using plugs would allow mapping or remapping at runtime, while attributes would allow compile/installation verification. Both types should be allowed as one is more restricted than the other. The most restrictive option possible should always be used. That is, if the address is fixed and never changes, use the attribute version.
For example, if a variable byte xSerialPort was mapped to 0x03F8, a write could be performed by simply writing xSerialPort = xValue;
A fixed map variable might be declared as so:
[IL2CPU.MappedMemory(0x00512345, Endian.Low)]
UInt32 xPciRegister;
Mapped variables can be verified and permitted to be used by code in Hardware Ring. That is the Kernel could allow a keyboard driver access to specific memory or I/O ports. Since code in the Hardware Ring does not have access to an actual pointer or direct access it is considered safe. While verifiability could be done using a generic function, mapping a variable also has the added benefit that verification only needs to occur once, not on every write. This increases performance.
C# supports a fixed statement, however it is a block construct. Cosmos will need an attribute that can mark a variable as forever fixed, i.e. unmovable. This variable would perform in a similar manner to a Mapped Variable, however in the case of a Forever Fixed the allocation would be taken care of by the memory manager. That is Cosmos decides where the variable is. The memory manager should of course treat these variables differently than movable variables and likely use a different memory space.
Forever Fixed variables are available to all rings and method arguments and properties can be marked with attributes that require assignments to be Forever Fixed variables only. Higher rings can allocate Forever Fixed variables as calls into lower rings may require them.
The actual pointer of Forever Fixed variables can only be obtained by Kernel routines however.
Locked variables function like Forever Fixed, however are meant at some point to be unlocked. If code knows that the variable will eventually be locked it should hint using an attribute that at some point the variable will be locked. This will allow the memory manager to optimize its allocation.
For example user code may create a variable with the hint "Lockable" and initialize data. Later this variable is passed into a file system object, then a storage hardware driver. The storage driver needs to pass it as a memory block to physical hardware for DMA access. At that point it would be locked. When the DMA is completed, the variable can be unlocked.
Any variable that is mapped to memory must deal with the issue of endianism. Because of this any managed memory variable that is not byte based must also specify when endian pattern it uses. X86 uses little endian, while network byte order is big endian. So for variables on X86 platforms that are little endian, no transformation exists. For variables declared big endian, each and every read and write must perform the transformation. This transformation is done in assembly and automatically emitted by IL2CPU. Since IL is stack based, this is rather easy. On each push/pop from the IL stack proper assembly can be inserted to alter the variable as it is read and written from CPU registers. On x86 the BSWAP operation is used.
Optimizations should exist in the optimization engines to avoid going from big to little back to big in cases such as assigning one big endian variable to another. Other cases surely exist and should be examined.
Some languages (C++, Delphi) have the ability to declare variant records. Two structs that have different mappings, but point to the same physical memory location. C# does not have this for reasons of unsafe code an endian issues. However with Cosmos's extensions many use cases are now safe.
A managed memory block is declared by declaring a byte array and marking it as a managed memory block. Still only kernel ring code can find the actual pointer of this block, but the block can be fixed forever and locked.
Each byte can be accessed by index into the array, however memory mapped variables can be created using an index into the array. For example an array of byte[12] could be declared, and then a UInt32 could be declared separately that points at byte[4] as little endian (or big endian). In addition generic functions to access as a UInt32 at location 3 with specified endianness are available so that it is not required to create a memory mapped variable for every access.
Since memory mapped variables are restricted in this case to managed memory blocks and no actual pointers are revealed to non kernel ring code, the methods are considered safe for all rings.