by: burt rosenberg |
at: university of miami |
update: 11 sept 2019 |
4 sept 2020 |
4 sept 2021 |
It is called an 8-bit microprocessor because its normal data paths were the 8 bit byte. However, the address lines were normally two bytes, 16-bits, so 65536 bytes (or 64 KiB) of memory was supported.
The 8085 was made possible by the increasing density of components etched and deposited into a single wafer of silicon in a photographically based process called VLSI, Very Large Scale Integration. Ever increasing numbers of transistors could be printed onto a single wafer. To create the 8085 6,500 transistors were required. The Intel i5, the CPU in the computer I was using when I started writing this page, has two billion transistors.
The addressing modes supported are,
*(--SP) = RA pop increments the SP by two bytes, then loads the contents of that address into the register pair R.
R = *(SP++)
In response to an electrical signal on the interrupt control lines, in collaboration with support chips, the next instruction at the PC is ignored and the instruction RST(n) is pushed into the data stream. The RST(n) is a call to memory location 8*n, for n equal to 0 through 7. As it is a call, the PC is pushed onto the stack,
*(--SP) = PC PC = 0 // (or 8, 16, ... , 56 depending on n)Typically the target of the RST is a jump instruction (JMP) to code handling the interrupt at level n. To later resume the interrupted instruction flow, the PC is popped off the stack, and any interrupt mask bits are cleared.
PC = *(SP++)
| | +--------+ | b hi | +--------+ assume sizeof(int)==2 | b lo | +--------+ | a hi | +--------+ assume sizeof(int)==2 | a lo | +--------+ -+ | | | | | | +- s -+ > sizeof(struct S) | | | | | | +--------+ -+ | PC hi | +--------+ return address SP --> | PC lo | +--------+ | | The stack after the call to f().
struct S f(int a, int b) ; int main(int argc, char * argv[]) { struct S s ; int i, j ; s = f( i, j ) ; return 0 ; }The convention by which subroutines pass arguments is called the Application Binary Interface (ABI). Aspects of the ABI are arbitrary, but all code must agree on the ABI to interoperate
The ABI described here will use the stack to pass all arguments and the return value. The arguments are pushed rightmost argument first, then the stack pointer is descended by the storage size of the return value, in this example, the number of bytes of struct S.
This ABI requires that the caller clean up the stack. When f returns, the old PC value is at the top of the stack, and is popped off into the PC register. The return value is conveniently on the top of stack, and is copied into s. The stack is then moved up by the total number of bytes of the parameters and return value,
SP += sizeof(struct S) + 2 * sizeof(int)A note about ABI's.
The compiler knows the default ABI for its platform, and compiles code consistent the ABI's requirements. However, the calling conventions used in the kernel are not the same as is used for user programs. We, when writing kernel code, will need to use a different ABI. C language uses the storage class of a function to notify the compiler of the correct ABI to use, if it is not the default. In the case of the linux kernel, use storage class asmlinkage.
Also note that the return of a struct in a preallocated region on the stack, as if it were the first parameter, is non-standard. In cdecl, the caller would dynamically allocate a temporary struct on the heap, and pass a pointer to the struct as a hidden first parameter.
Modern microarchitectures support a privileged mode kernel, virtual memory, and a more expansive protocol for interrupts. To obtain their goals, each of these three features leans on the others in interesting ways.
The following is an explanation of a fictitious MMU the 8085 never had. It is inspired however by the Page Size Extension method of the Intel Pentium Pro (schematic above).
A special MMU register is loaded with a physical address called the page table base B. A 16 bit virtual address V is separated into the top 7 bits of page number P and the lower 9 bits of offset O, such that V = P * 29 + O.
The MMU fetches the one byte page table entry E from the page table at physical address B+P. If the least significant bit of E is zero, the entry is not valid, and the MMU interrupts the processor. Else, the 7 top bits of E are copied to the 7 top bits of V, producing the mapped physical address.
When changing between processes or entering or leaving the kernel, reloading the page table base in the MMU installs the appropriate page table.
Protection of the kernel is achieved through the processor preventing any direct hardware access unless in privileged mode, and only the kernel runs in privileged mode. The kernel can prepare any hardware access requested by the user program while it runs in privileged mode, and on return to the user program will drop privilege mode so the user program cannot circumvent the kernel's preparations. There are three forms of hardware resource to protect: those provided by the CPU, such as the manipulation of the privilege mode itself, those provide by physical memory, and then hardware other than CPU such as storage devices (hard disk of SSD).
Privileged mode protects CPU resources by blocking certain instructions from executing unless the processor is in privileged mode. Memory access is protected by declaring the addresses used by software to be addresses to virtual memory that will need to be mapped to physical memory in order to actually read or write at physical memory. Processors now include a Memory Management Unit (MMU) that implements this mapping. The MMU is a CPU resource and so can only be manipulated during privileged mode. The virtual to physical mapping require access to data structures which are themselves stored in physical memory. These are called the page tables. There are distinct page tables for each process running, and a unique, distinct page table for the kernel.
Software speaks only virtual addresses. Even the kernel, to access physical memory, must place the physical address in a page table, associated with a particular virtual address, and then do the fetch or store at that virtual address. On every memory access the MMU intervenes to translate virtual to physical, and places the physical address on the bus shared with the memory. By the combination of restricting MMU instructions to the kernel running in privileged mode, and the kernel limiting what is in the page table of a non-privileged user, the kernel can maintain complete control over what memory can be accessed by a non-privileged user.
Interrupts:
While in privileged mode, an instruction is allowed that will drop the privilege. Entering privileged mode must be completely protected and under the entire control of the kernel.
Interrupts to service hardware must enter privileged mode, to carry out what is required by hardware. If it is an interrupt from the disk, it must be able to issues instructions to the disk to handle the interrupt. These service routines are the drivers, and are considered part of the kernel, so it is correct they share with the kernel the privilege mode. Therefore, among the processors responses to an interrupt, it will obtain privilege mode coincidentally with calling the service routine.
When a user program requires a privileged mode service, it mimics the interrupt by issuing a software-initiated interrupt, called a trap. As does the interrupt, the trap will force a call into the kernel, while at the same time obtaining privileged mode.
The response to a trap or an interrupt is completely under control of the kernel. Before enabling interrupts or starting a user mode process, the kernel places the addresses of interrupt and trap service routines in a table. This table is in memory inaccessible to any process but the kernel. The location of the table is installed into the interrupt handling mechanism using a privileged mode instruction. Once privilege mode is dropped, the response to an interrupt or trap cannot be modified. A user mode process is denied the hardware access necessary to manipulate the response.
Kernel stacks:
Intel provides virtual memory space by segments that describe a range and a permission on the range. When in kernel mode the segment spans the entire 4G range. Moving between kernel and user, the segment is restricted to just the lower 3G. While different page tables map the lower 3G differently, they all map the upper 1G the same — that virtual memory region is that of the kernel.
Therefore, when running in the kernel the operating system can access the current user memory space as well as the kernel space in a single range. The kernel can swap between user spaces without affecting its access to the kernel space. It does however limit the user to only 3G of virtual memory.
The transition to or from privileged mode is accompanied by additional swaps of context. To provide complete protection of the kernel from the user process, interrupts and traps do not push onto the user's stack, nor use the user's page tables. Entry to privilege mode swaps in the kernel's stack and kernel's page tables just as exit from privilege mode restores the user's stack and page tables.
In the case of Intel, there is one interrupt stack per core that is activated when handling an interrupt. Each thread has its own kernel stack that is activated on exception by that thread.
Context switch:
The kernel resident stack, whether the interrupt or thread stack, can be completed to contain all registers of the CPU. In the case of the 8085, this means that the handler of the interrupt or trap will push the SP, BC, DE, HL and flags register. Note that the SP register must be interpreted using the page table of the interrupted or trapped process.
Also, the referents to the page tables, that which is installed into the MMU to activate the virtual-physical address mapping for this process, are pushed.
The contents of the stack can then be copied off in to a Thread Context Block (TCB) that is stored among other such TCB's in a linked list inside the memory space of the kernel. If a different TCP is selected to copy the saved registers into the interrupt or thread stack, the physical thread of the core will return into the process context and specific code execution of that thread — picking off where that computation left off.
This is called a context switch, and pre-emptive multiprocessing can be achieved by attaching such a context switch to the handling of a clock interrupt, say every tenth of a second, for a scheduling time quantum of one tenth of a second.
Author: Burton Rosenberg
Created: 10 September 2019
Last Update: 4 September 2020