In the world of alternative OS development, portability across multiple architectures is a challenging goal. Sometimes, it may be intrinsically hard to come up with hardware abstractions that work well everywhere, but many times the core problem is one of missing information. Here, I aim at learning more about the way non-x86 architectures deal with CPU IO ports, and in particular how they prevent user-mode software from accessing them.
First, let’s start by summing up what I know. IO ports are the conceptually simplest way for computer CPUs to communicate with external hardware in a bidirectional fashion. They work using standard CPU instructions that send and receive fixed-size integers to distant hardware. Unlike with memory-mapped IO, these instructions always work as expected, and aren’t moved anarchically by optimizing compilers if you forget a “volatile” keyword somewhere. IO ports are also as standard as communication with external hardware can get, but on the minus side they are probably the slowest IO mechanism available, and using them requires architecture-specific ASM snippets.
On architectures which supports hardware-enforced separation of kernel-mode and user-mode software, every CPU feature which is used to control external hardware must be kept under the control of privileged software, and IO ports are no exception. On x86, this is done by using so-called “IO permission bitmaps”, which allows kernel-mode software to control the way other software accesses IO ports at the individual port scale. This hardware feature, which is really nice for microkernel OSs, leads me to ask a first question : How do other architectures restrict user-mode software’s access to IO ports ?
Another thing worth noting about x86 IO ports is that they seem to be a thing of the past. For better and worse, x86 has apparently moved to memory-mapped IO, DMA, and nonstandard (“Model-specific”) CPU registers nowadays, and all uses which I’ve found so far for IO ports on this architecture are legacy IBM PC features : 8259 PIC, CMOS, PS/2-compatible keyboards… Which leads me to a second question : What are the main means of communication with external hardware on other architectures ?
Many thanks in advance for your answers.
From what I know, the 80×86 CPUs are the only family of CPUs to have a separate address space for I/O. The rest of the CPUs use memory mapped I/O i.e. hardware registers are mapped as memory cells, and the CPU uses the available load/store instructions to read/write the registers.
This has the advantage that the MMU can be used for controlling access to the I/O ports; no special circuitry is required.
Edited 2011-12-30 08:35 UTC
Memory mapped IO is good. As the parent says it’s simpler and more efficient and allows many neat tricks such as allowing other bus master devices to do stuff with hardware without the need of the CPU (ie: DMA for audio, video, etc).
I wonder if it doesn’t make virtualisation more difficult?
Explicit IO instructions seems much more easy to detect&handle for virtualisation.
Z80 has separation, but it doesn’t have restrictions of user mode and non-user mode.
On a non-x86 architecture it’s easy and simple, you just map the memory addresses used for I/O out of user-space.
If we look at it a different way, I/O access is merely equivalent to an extra address bit, that is, you need an extra IO signal on a CPU, which could have been used to provide an extra address bit.
So, specific I/O instructions not only complicate work for the instruction set, compilers and device driver portability, but reduce memory space by a factor of two: and on the original 8-bit 8080 from which the x86 inherits its I/O model; the extra 64Kb would have been really valuable.
An I/O address space also doesn’t really help even as an address space because virtually all practical computers from the 8-bit era onwards contained both I/O addressed hardware and memory-mapped hardware.
Video memory (actually an output device) was a common example. It was also true of the early 80s x86 PCs where addresses from 0xA0000 to 0xFFFFF were reserved for I/O, because the 1024 addresses provided by their (incompletely decoded) I/O slots weren’t enough even then, 30 years ago.
So as you note, I/O portability is a problem for x86 PCs too since some I/O will be memory-mapped.
So why did Intel do this? Two reasons spring to mind: Firstly, the early Intel processors were really seen as embedded systems for controlling I/O, having a separate I/O signal reduced hardware (though this isn’t convincing, you would still need additional decoding to handle more than 1 chip).
Secondly, and more probably, Intel were really memory chip producers and were new to processor design; employing people with no prior experience in computer architecture to design their early processors. Consequently, they simply copied I/O concepts from old minicomputers such as the pdp-8 and HP2100 without quite understanding why these machines had such features.
Hope this helps! (Julian Skidmore, designer of the DIY 8-bit computer FIGnition).
Whoa! I just looked at the project that you mentioned…That is offing awesome!
Hi,
Just map the memory addresses used for I/O… and then spend weeks trying to figure out why something only works sometimes, just to find out you used the wrong explicit barrier/fence.
If an (out-of-order execution) CPU can’t tell the difference between a memory mapped IO read/write and a normal RAM read/write, then it can be a nightmare for developers, tool-chains, etc.
If an (out-of-order execution) CPU has special instructions to access IO ports (and therefore can easily tell the difference between IO and RAM), then it can enforce strong ordering for these instructions and avoid the nightmare.
Of course if a CPU never supports out-of-order execution and/or enforces strong ordering for everything; then the problem mostly disappears (but so does a significant amount of potential performance).
– Brendan
I don’t think there is a big problem, because modern hardware provides all the means (disabling caches, memory barriers etc) to solve such issues.
If you’re writing an operating system then that’s exactly the sort of thing you need to think about. You may as well have said “Just use I/O ports and then spend weeks trying to figure out why something doesn’t work, just to find you were sending the wrong command byte.”
IN/OUT is a special instructions with shorter encoding. external HW can be very simple like dedicated bit decoder in ZX-Spectrum. (8 bits -> 8 devices)
With 16bit register pairs, >64k is done via bank switching anyway.
Edited 2011-12-31 04:53 UTC
Can’t speak for the HP2100, but the PDP-8 didn’t really have ports, per se. It had a single opcode, IOT, that caused I/O to happen. The remainder of the instruction was used to select a device and an operation. A device could do any combination of:
– Reading the value from the accumulator
– Clearing the accumulator
– ORing a value into the accumulator
– Skipping the next instruction
On later models like the PDP-8/e, the processor essentially halts and allows the I/O device access to the control points within the processor. An IOT on those systems can do pretty much anything the processor can.
Hmmm.
You seem to have it backwards, port mapped I/O does not affect memory addressing since the CPU sees 2 different address spaces for all intents and purposes. that is one of the reasons why it was a common place design choice among early micros which had limited addressing capabilities.
Also since early micros from intel were mostly used as embedded controllers, it also made sense to segregate memory and I/O maps, since I/O devices tend to be much slower than memories. So they dealt with the latencies by adding complexity to the CPU, whereas memory mapped CPUs translated that complexity to the chipset.
Yes, under a modern 32+ bit address space with the levels of integration designers have access to (MMUs etc), either port or memory mapping I/O really has no much advantage over the other. But at the time, Intel actually knew what they were doing and port mapping made perfect sense.
Edited 2012-01-02 00:30 UTC
Its been years since I’ve been that close to bare metal, but when I was we were using memory mapped io on motorola 68k procs,
[Sorry for the double post]
Edited 2011-12-30 14:37 UTC
Isn’t that one of the main reasons the Amiga rocked so hard, yet only had a 7.14mhz CPU (pretty sure that was the speed). Most things were handled through the bus, instead of the CPU having to handle all the I/O.
Exactly. The Amiga (and its spiritual predecessor, the beloved Atari 800) used custom chips to allow the processor to concentrate on processing rather than managing I/O.
Unfortunately, it also made upgrades to those architectures very expensive compared to commodity IBM PC designs.
There’s no free lunch (to coin a phrase), but in the early years, the Atari / Amiga computers ran rings around the designs that finally conquered the market.
(An old codger reminisces – sorry ’bout that…)
Ha, no worries. Loved my Atari 800XL. I still have it, but the last time I fired it up, the RF modulator was only sending black and white signals to the screen. Not sure if it was the cable, or something internal, but I did at one point drop it down my stairs…
Fortunately at one point in time I also managed to acquire an Atari 130XE.
Speaking of reminiscing…. at one point in time we had somehow managed to get strawberry jam on a floppy disk.
We washed it, and some of the stuff started working. Washed it two more times and all of it was working again. Those 5 1/4″ floppies were hard to destroy!
Only at a much later date did I realize that Jay Miner created both the Atari 8-bits and the Amiga. Unfortunately, as most people I’m sure did, I upgraded to an Atari ST instead of the Amiga. Even though the C64 had it’s share of bugs, the owners of those upgraded to the superior Amiga. The makers being opposite.
Now I’m rambling… cheers!
At the risk of rekindling one long-forgotten Holy War – 8bit Ataris and C64 were fairly comparable all around (the former also having its share of bugs) …C64 somewhat better in some interesting areas (kinda discussed here, support chips and such), which made it the machine of the demoscene, one which essentially started it all. Seemingly much more long-lived, too.
Atari 16bit being what they were… overall it was probably better to be on the Commodore side of things throughout, unless maybe for MIDI (IIRC) of ST. At the least, my buddy who had both (8bit) machines, opted to get rid of small Atari much sooner than C64 (and BTW, most of the world didn’t standardise on floppies in that generation, so any flaws of them on C64 were irrelevant; OTOH, later 8bit Atari models were legendarily… neurotic, when loading a game; probably also a case of different worlds, different “ecosystems” already http://www.osnews.com/thread?481346 )
Edited 2012-01-07 00:19 UTC
I love looking back at the early Amiga days and wonder what might have been, wasn’t going to reply but I’m really pedantic:
“to coin a phrase” means to invent a phrase, if it’s then followed by a cliché I find it really jarring… enough to write a reply to someone I don’t know, in the hope they don’t do it again.
When I list a cliché followed by “(to coin a phrase)”, I’m making a mildly humorous reference to the fact that it is a cliché – not claiming that I actually invented the phrase! It’s a little like saying “the Internet (as invented by Al Gore)…” – it’s humor by farcical excess.
And yes, I’ll do it again, as you’re the first person in my 22 years of Internet posts to have admitted missing the intended humor. 😉
Edited 2011-12-31 23:47 UTC
It’s not about humor, it’s about the fact that you simply misused the phrase. “To coin a phrase” means to invent or come up with a new phrase. You were using a phrase, not inventing it.
*whoosh*
It’s a pretty dumb joke.
Sheldon, is that you?
Look, I’m trying really hard to be nice here. It’s OK not to appreciate someone else’s humor, but you’re really being a PITA about it. The proper social response to a joke you consider weak is a tight-lipped smile and a gentle shake of the head. Repeatedly trying to explain the literal meaning of the joke, as if the person who made the joke is too dense to understand what they said, is poor form on the best of days.
And yes, you’re being REALLY pedantic!
But Happy New Year anyway. 😀
The Atari series (XT/XL/ST) did not have the same mechanisms as the Amiga regarding DMA, and therefore things like playing a game on them while something loaded from floppy was not really possible to the same extent as the Amiga.
The Amiga was not more expensive than IBM compatibles. In fact, it was cheaper. An Amiga 600 would set you back for around 1000 dollars, whereas a PC with equivalent performance (think about the early days of 386 with VGA cards), would cost a lot more.
Amiga 600 – hence heyday in 1993. An inexpensive machine, to be sure (much less expensive than you portray it; closer to what would be, after exchange, 200 dollars or so – of course without HDD and monitor, however a. they were hardly available anywhere in the first place b. what was almost always just a “gaming computer” didn’t really need them) …but essentially identical to Amiga 500, over half a decade old. As was 386, but even those didn’t exactly stand still (I used some “surplus” late 386 in 97, it was quite nice with Win 3.11)
Which was more the point, I think – the tightly integrated architecture made the upgrades of the architecture (manufacturer side) expensive – engineering, implementation, it was almost either all or nothing (worse: with the dynamics of “installed base” very console-like …but lacking appropriate in such case business model; Commodore essentially repeating US video game crash of the early 80s; coincidentally, the Amiga was supposed to be a console initially). Sure, that tight integration of hardware made Amiga nice & speedy for some time, but ultimately it largely limited its progress, arguably contributed to its demise.
Yes, there was 1200 – but not so affordable any more and… not really that much better, hardly compelling (most of the software people were interested in, ran happily on 500/600).
Edited 2012-01-07 00:12 UTC
{double post some how]
Edited 2011-12-30 14:37 UTC
I actually found port I/O refreshingly easy to do in the DOS days. It could be done without regards to segment registers and caching modes.
As others have already mentioned, memory mapped IO might cause problems due to compiler optimisation quirks. But there are other subtle hardware differences too. For one, reading a port is explicit (such that reading changes a latch state), reading RAM may be inadvertent as the CPU reads in multiples of arbitrary cache-line sizes (possibly even if not cached?). Also, MMIO is affected by not only whether a page is cachable or not, but also by the MTRR.
For example, video memory is often set to write-combined to improve performance, but this could cause problems with other IO devices.
http://lwn.net/Articles/282250/
http://wiki.osdev.org/Paging
I’m not saying PIO is better, because it’s not. However, it was simpler. I wish that PIO/MMIO could have been unified in a better way on the x86. Today PIO represents the legacyness of the platform. The consequence of this is that all CPUs designed to drive a PCI bus have to incorporate these legacy designs.
The PowerPC also has IO space. As part of the segment control, they can restrict IO space to supervisor-only access if needed. The PowerMac took advantage of this mechanism. As for memory mapped hardware (since PCI allows both hardware in IO space and in standard memory regions), the most common control mechanism is the MMU – all MMUs that I am familiar with allow for user/super access control. This is also how the x86/AMD64 controls access to memory mapped regions. It was how most older platforms controlled access to all the hardware (e.g., the old 68K Macs where ALL hardware was memory mapped).
JLF65,
“The PowerPC also has IO space. As part of the segment control, they can restrict IO space to supervisor-only access if needed. The PowerMac took advantage of this mechanism.”
Yes, but I believe the original question posed by the article was how to allow user space micro-kernel drivers to access only those ports which they should have access to. Clearly you are correct that any (so called) ring 0 drivers can access the ports, but is it possible to allow drivers to access only a subset of ports?
I don’t know the answer to this on non-x86 hardware.
Assuming the hardware doesn’t provide a means to allow access to a subset of ports, then secure PIO would have to be delegated to a kernel helper function or some kind of secure user space stub. Arguably drivers don’t *need* to be truly secure against malicious code, just secure enough to prevent most accidental crashes.
A kernel syscall for PIO is terribly slow and should be avoided. However a secure user space stub has it’s own problems. The OS would need some sophisticated mechanism to comb through the driver’s code to ensure it cannot generate unauthorised instructions (which is difficult, but VMWare uses the technique).
Additionally, even the secure stub function itself would be vulnerable to exploitation.
Consider:
if (port >= PX && port <= PY) {
outb(port, value);
}
The malicious driver could simply jump/call to the outb instruction directly to bypass the check.
In a managed language, this wouldn’t be a problem, but it is with C/C++ which is what drivers are written in. It’s one of the reasons I’ve been advocating using managed code in the OS for some time.
For the sake of completeness, another solution is just to give the drivers no port access and then just let the OS handle the resulting CPU faults and emulate the requested PIO. However it’s not clear that this would perform any better than having an explicit PIO syscall.
The trouble with fault handlers is that the handler has to decode the instruction that caused the fault, which may or may not be a straitforward thing to do and it’s architecture specific.
Edit: I’m probably overthinking the problem here. If platform doesn’t permit fine grained control over port access, then just allow access to all ports and leave it at that. While it sucks that this goes against micro-kernel ideals, there’s nothing wrong stating that it’s a hardware limitation.
Edited 2011-12-30 17:44 UTC
Managed code would still have a problem. Managed code does not solve security problems, just makes it harder for developers to do them by accident and harder to solve them when they do happen.
TemporalBeing,
“Managed code would still have a problem. Managed code does not solve security problems, just makes it harder for developers to do them by accident and harder to solve them when they do happen.”
It’s still possible to write buggy code in a managed language, however managed languages do actually solve many security problems which have plagued C for years by making it impossible* to generate unauthorised/illegal instructions (such as jumping into the middle of a function as I illustrated earlier).
* to the extent that there aren’t bugs in the VM.
In this instance, it would be possible for a VM to enforce port restrictions from a secure userspace stub without further kernel interactions.
Edited 2011-12-30 19:41 UTC
Even with a pretty secure VM, you could still jump into a unauthorized/illegal instruction.
Managed languages do not magically solve those problems; they just make you do it on purpose as opposed to by accident.
That is, a newbie programmer won’t likely do it; however, an experienced programmer who was setting out to do that kind of thing would be able to as they’d have a better understanding of the language they are using, the constraints, and the functionality to make it do so – but, only when they are purposely deciding to.
So, fully expect crackers to still abuse systems in fully managed – even TPM protected – code. That’s their goal after all.
But those same constraints of the managed environment will also make it harder for even experienced programmers to protected from some of those things.
TemporalBeing,
“Even with a pretty secure VM, you could still jump into a unauthorized/illegal instruction.”
Can you provide any example where a bug-free VM produces unauthorized/illegal instructions?
“Managed languages do not magically solve those problems; they just make you do it on purpose as opposed to by accident.”
I disagree with that. The concept of a VM is architecturally sound, even if an implementation of one may have faults.
“…an experienced programmer who was setting out to do that kind of thing would be able to as they’d have a better understanding of the language they are using, the constraints, and the functionality to make it do so – but, only when they are purposely deciding to.”
One question for you: are you trying to imply that all VM code will always be buggy? Or are you implying that even a bug-free VM will inherently exploitable?
Take a simple managed language like Forth. All the external functions which can be called by the Forth program must be explicitly declared, so long as those functions are secure and don’t allow the program to escape it’s sandbox, then there will be no possibility for the program to call any other external functions.
Take another example, I can run a program inside of a QEMU VM. The code running under it will not have unauthorised access to the user-space which QEMU itself has access to. So again, the concept of QEMU is architecturally sound even if an implementation of it could have bugs.
Take a third example: the webbrowser has access to all user files, yet the programs running in javascript do not because the VM doesn’t expose functionality to access those files. Short of bugs in the implementation, malicious javascript programs will not be able to access user files.
These are all examples of software VMs enforcing the access of external code. After all, the program running inside the VM is just a state machine. That state machine will be stuck inside the VM unless the VM provides it with handles to the outside world.
Of course, I can take nearly any secure VM and make it exploitable, for example by giving the program access to /proc/kcore, but that doesn’t mean the VM MUST provide access to those exploits.
Well, show a VM that doesn’t have bugs first.
The concept of a VM is sound yes, in theory.
However, in practice it is not quite as sound; this is mainly due, however, to the nature of programming languages.
However, you are getting off topic as the issue is not so much a VM as it is Managed Code. While Managed Code may use a VM, it does not necessarily need to do so; nor does a VM imply Managed Code.
Again, you are off-topic. The issue is not a VM, but Managed Code itself; one does not necessarily imply the other.
A well written VM will not let you escape it – thus VMware, QEMU, Bochs, etc. allow programs to operate inside the virtual machine environment without having access to the host operating system. But they do not execute managed code – at least directly – they simply emulate what a CPU and the associated hardware does.
.NET is comprised of two things – a Virtual Machine and Managed Code. The Virtual Machine provides the operating environment much like QEMU does though for a different purpose – it does allow access to the host system because it has to in order to the the software do its job; it’s more an OS Abstraction Layer than a VM.
The Managed Code portion of .NET is provided by C#, VB#, F#, and others. And, they comprise of programming structures like GOTO, which provides the functionality with the VM to get around exactly what the OP/GP talked about – skipping over a line (a check) to get to an otherwise protected line of code.
Of your examples, this is the only one that actually touches the topic. Yet note that FORTH has a GOTO statement, which is all that is necessary to achieve the attack which the OP/GP was saying was impossible to achieve under Managed Code.
This is not quite on target (see above), but I’ll bite anyway.
While yes, you won’t be necessarily moving outside the virtual environment provided by QEMU, it still operates using the x86 assembly language (from ASM for 80086 to AMD64), which provides jmp,ljmp, far jmp, near jump, etc. – again, all that is necessary to thwart the attack that was being mentioned by the OP/GP.
Again, ECMAScript/JavaScript has a GOTO statement in the language. Yes, you won’t necesarily leave the VM that the web-browser provided, but you can make it do things that were unauthorized/unintended – for example, crashing another browser plugin module, or exploiting it to achieve a higher level of exploit (e.g. breaking out of the VM).
Please note the above – the topic was not about VMs but about Managed Code, even in the OS kernel.
A VM environment can certainly protect stuff running outside the VM from what is running inside it. However, that is neither here nor there when talking about Managed Code and enabling code to thwart a safety check put in by a programmer.
That is, if you have access to the source you can remove the code. If you don’t, but you can put in something that is run in the same environment (e.g. sandbox, VM, etc.) then you can call into the other program by various means – whether a GOTO or Assembly jumps (the Assembly equivalent of GOTO) – and do things that were not otherwise intended by the original programmer.
VMs can only protect so far as in how the sandbox the applications.
Managed Code can only protect in so far as what the Programming Language does or does not provide in terms of functionality; but unmanaged code is pretty much the same. So when it comes to exploiting the two there really is no difference.
A VM must provide access in so far as what it is targeting to achieve. It is no different than an OS in that manner.
A hardware VM – e.g. QEMU, Bochs, VMware, etc – emulates the hardware and allows full operating system to operate. It need not provide any access to the actual hardware it is running on; though in practice and with the advent of the Virtual Machine instructions they do for performance reasons – namely making use of ring -1 and letting the guest OS thereby use ring 0-3 just like the host OS is doing.
An API VM – e.g. Java, .Net, SmallTalk, Python, etc – must allow access to the host OS so that they can achieve their desired functionality – writing programs in a given language that can do things desirable for the user – e.g. editing documents, browsing the web, etc. It is more of an API wrapper; it does provide some sand boxing of the running applications to protect two instances from each other but any plug-ins, etc. that the applications load are still within the memory confides of the application – in the same sandbox. The application is no more secure than a non-API VM language – e.g. C, C++, Pascal, Ada, etc. In fact, it may be less secure as the OS will use hardware functionality – pages, etc – to protect two applications from unnecessarily interacting; while the API VM cannot do so in itself – other than spawn another entire process to run the other applications and let the OS handle that side of it.
So again, the attack that was mentioned is still 100% possible under a Managed Code – it does not buy you anything in terms of protection by using Managed Code; it just changes the skill set required to make the exploit, and the likelihood that it will be done by accident.
TemporalBeing,
“Well, show a VM that doesn’t have bugs first.”
It shouldn’t matter, but for the sake of argument, take a look at the following turing machine implementation
http://ironphoenix.org/tril/tm/
“Again, you are off-topic. The issue is not a VM, but Managed Code itself; one does not necessarily imply the other.”
Why is it off topic? My assertion was that managed languages, through the use of a VM, can provide security that unmanaged languages cannot. I would like you to address this if you think I’m wrong. What makes me wrong? Are managed language VM’s inherently insecure such that exploits are necessarily possible, or is it just difficult to prove the VM’s don’t have bugs?
“Please note the above – the topic was not about VMs but about Managed Code, even in the OS kernel.”
I realise this, but my point was that some managed languages CAN provide additional security over unmanaged languages. Generally speaking managed languages do aim to enforce that code doesn’t corrupt itself or execute illegal instructions – they do this by enforcing every state transition is legal and accounted for. I don’t intend to get into a tussle over definitions, if you’re uncomfortable with my use of the term “VM”, then I’ll simply retract that term and acknowledge that there are different ways (without VMs) to enforce managed code semantics whether in hardware or in software.
So getting back to the point…
“So again, the attack that was mentioned is still 100% possible under a Managed Code – it does not buy you anything in terms of protection by using Managed Code; it just changes the skill set required to make the exploit, and the likelihood that it will be done by accident.”
If the managed language is sand boxed and does not provide a means to do port IO, then the program will effectively be unable to do port IO. Y/N?
Edited 2011-12-30 22:56 UTC
You assertion had nothing to do with a VM, just Managed Code – and thereby Managed Languages. As noted, that does not mean that a VM is used, nor does it by the nature of the language solve the issue as pointed out in response to each example you provided.
Please read your original post (http://www.osnews.com/thread?501716). No where in there do you mention a VM – just Managed Languages, to which I responded that does not solve the problem at hand. Thereby, by going down the rabbit hole of VMs you are off topic as they may or may not be used by a Managed Language/Code.
Correct, it would be unable to do Port IO; but that was not the point being made. The point being made was with respect to this following snippet of code:
And specifically your assertion that Managed Languages could by nature prevent code from skipping the IF statement part of the block and go directly to the outb() part of the block. It has nothing in particular to with Port IO, but how the language works to protect the software and the environment, and Managed Languages do not in their nature block the ability to skip a line of code like that. As noted, all the examples you provided have GOTO functionality which would enable exactly that kind of behavior – skipping the IF check and going directly to what the IF check was bounds checking.
Now a VM inside the kernel would not solve the issue either, it would only make the kernel more complex.
If it is Port IO you are worried about, then the only solution is to allow only a very small amount of code to do actual Port IO in the kernel and everything else must call that portion of code, which theoretically would be able to check the call for integrity – but then you’d have to have checks in the kernel for any kind of code that could be passed through the Port IO interface for every kind of device using the Port IO interface – a performance nightmare, which is why no one does it.
TemporalBeing,
“You assertion had nothing to do with a VM, just Managed Code – and thereby Managed Languages. As noted, that does not mean that a VM is used, nor does it by the nature of the language solve the issue as pointed out in response to each example you provided.”
I really wish you would just answer the question, but since you won’t, I’ll let it go.
“Please read your original post (http://www.osnews.com/thread?501716). No where in there do you mention a VM – just Managed Languages”
It’s no secret that many managed languages use a VM, but I have nothing to prove here so I’ll let it go too.
“Correct, it would be unable to do Port IO;”
So, in the same vein, do you acknowledge that one could add a built in function to this language to do range checked PIO, such that only a subset of ports can be accessed?
If you don’t think so, please specify why.
If you agree that it would work, then that’s really all I was trying to say.
” but that was not the point being made. The point being made was with respect to this following snippet of code:”
With respect to that piece of code, I was referring to potential security problems caused by placing security validation code in userspace and linking it to an unmanaged C program – it’s just not secure. With managed code, on the other hand, the managed language doesn’t HAVE to provide a way to call arbitrary unauthorized unmanaged code. If it doesn’t, then it is possible to place security functions in userspace libraries OUTSIDE (*) of the managed code and expose it to the managed driver code using a well defined managed language interface.
* I think this is the source of confusion, my point has nothing to do with whether the managed language supports gotos or whatnot. Those gotos use the semantics defined by the managed language, not the semantics of jumping to a memory address on an x86 CPU. So, even with goto’s, managed languages generally do not permit programs to jump to arbitrary x86 memory addresses (where the validation function exists in this example).
Edited 2011-12-31 02:52 UTC
The all problem is that you keep mentioning that managed languages are based on VM, this is not true.
First of all managed languages is just a marketing term invented by Microsoft, as I referred on a previous post.
Most languages that you would call managed are just what in computer science is known as strong type systems. Most languages do have native compilers as well, even though most people use them with some VM.
So if you have binaries generated from a “managed language” those binaries are also open to exploit as any other language. The only benefit is that most “managed languages” use higher level constructs that are harder to break than say what C generates.
As an example it is harder to exploit buffer overflows, because by design these languages provide proper strings and arrays with bounds checking, unlike C.
Still it is possible to change the assembly, only harder to do.
moondevil,
“The all problem is that you keep mentioning that managed languages are based on VM, this is not true.”
The managed languages I care about in the context I am talking about are those that are based on a VM where software is used to define hard execution boundaries on the program instead of using the usual hardware userspace boundaries. I regret that the term has been overloaded to mean different things, but please stop getting stuck on this.
“First of all managed languages is just a marketing term invented by Microsoft, as I referred on a previous post.”
I don’t care who invented it, unless you can think of a better term, it’s the most accurate term I have at hand.
“Most languages that you would call managed are just what in computer science is known as strong type systems. Most languages do have native compilers as well, even though most people use them with some VM.”
This is not true, some of the most popular managed languages in use today are actually very weakly typed. I am certainly not going to substitute “strongly typed language” for “managed language” as you’ve suggested because it’s far less accurate than the term I’ve been using. Consider that C++ is both “strongly typed” and also “unmanaged”:
http://en.wikipedia.org/wiki/Strong_typing
“So if you have binaries generated from a ‘managed language’ those binaries are also open to exploit as any other language.”
I know exploitable code can be written in both managed and unmanaged languages, but your missing my point. A managed language using a VM can create boundaries in software similar to the userspace boundaries existing in hardware. We can use those software boundaries to avoid the need to implement security checks using more expensive hardware boundaries.
“Still it is possible to change the assembly, only harder to do.”
I’m going to ask you the question TemporalBeing has been avoiding: Is it NECESSARILY possible due to unfixable architectural design faults, or is it possible ON CONDITION that a bug exists in the implementation?
Edited 2011-12-31 19:27 UTC
You can only be right if you force people to target only a VM environment.
Heck, even C and C++ are managed according to your definition because there are compilers targeting VM environments.
VMs are safer than native code, specially when using code verifiers for the bytecode sequences. But they can also be exploited if the attack exploits bugs in the interpreter/JIT/libraries, since most of the time the VMs are implemented in “unmanaged languages”.
moondevil,
“You can only be right if you force people to target only a VM environment.”
I don’t know what this means, but I’m thinking something *like* the JVM or .net where native code compilers can be used instead of pure emulation.
“Heck, even C and C++ are managed according to your definition because there are compilers targeting VM environments.”
I’m not sure what you mean, I explicitly said C++ was unmanaged in the prior post. Apparently MS has tried to create managed extensions for C++, but it’s most certainly not C++ as we know it.
http://en.wikipedia.org/wiki/Managed_Extensions_for_C%2B%2B
“VMs are safer than native code, specially when using code verifiers for the bytecode sequences. But they can also be exploited if the attack exploits bugs in the interpreter/JIT/libraries, since most of the time the VMs are implemented in ‘unmanaged languages’.”
Ok, so I’m going to take this as an agreement that the idea of software barriers is architecturally sound, and that your concern lies with implementation flaws.
Of course, that’s a totally valid concern. For a simple interpreter running a trivial state machine, input scrubbing and true isolation may not be so difficult, but it performs poorly. However JIT compilation technology is several magnitudes more complex, and brings all the subtle CPU quirks into play such that attack vectors are going to be difficult to locate and account for.
With regards to library vulnerabilities behind the software barrier, it’s certainly a concern too, but I consider it a wash compared to library vulnerabilities behind a hardware barrier.
All in all, it’s always troublesome if software barriers are broken, but let’s put it back into context of the microkernels. The goal is to achieve isolation between drivers, but in the event that security flaws allow one driver to pawn another, we’re still no worse off than the macrokernel situation where they can do this implicitly.
Languages are not managed or unmanaged by computer science definition, implementations are.
So as there are VMs which implement C and C++, these languages can also be “managed”.
Some examples:
– NaCL (https://developers.google.com/native-client/)
– CINT (http://root.cern.ch/drupal/content/cint)
– C++/CLI (already mentioned by you)
– Ch (http://www.softintegration.com/)
– DotGNU C (http://www.dotgnu.org/pnet.html)
moondevil,
“Languages are not managed or unmanaged by computer science definition, implementations are.”
I actually disagree, an unmanaged language has different semantics from a managed one. But if this is all we disagree with on the previous topic, then that’s good enough for me.
Edited 2012-01-02 03:08 UTC
Well, C# has the same exact syntax as C++; so does Microsoft’s Managed C++ – aka C++/CLI; however, the semantics behind the syntax differ greatly – and the C++ ISO committee rejected the C++/CLI extensions as a result of both the similarities and the semantic differences recommending that it be called another language entirely.
However, Moondevil is correct.
“Managed languages” – like Java with the JVM and C#/VB#/F# with the .NET run-time are not what you think they are.
“Manage languages” do not protect against things like you are asserting, they are managed mostly from the aspect of having automatic memory management (e.g. garbage collection) built-in.
No language requires the use of a VM – managed or otherwise.
For example, any Java program that typically runs under a JVM can be compiled to native code with tools like GNU Java Compiler (gcj). Most .Net software is actually compiled to native code, not run under the .NET CLR ByteCode VM Run-time.
Java and the .NET languages both allow for use of Assembly language – it’s part of how they build out the VM (required to be able to do so). As such, users of those languages will always have the tools to by-pass security checks.
So, I go back to my assertion – the languages themselves are not the solution. GOTO – built into nearly every language in some form – is a simple construct that ultimately provides the ability to by-pass security checks. So the languages themselves are by architectural design insecure.
So going back to your original argument, and to answer your question – it’s the kernel.
If the kernel allows drivers to operate in ring zero, then there is nothing the kernel can do to protect the system. If the drivers operate at least at ring one, then the kernel can make things (like PIO) be not allowed from even its own internals.
The same applies for user-space (typically ring three).
So, the language itself cannot prevent you from doing anything – any construct can be made by nearly any language. It is rather the operating environment of the resulting software that determines whether or not the software can do what it is programmed to do – or if the operating system (e.g. kernel) will deny it; whether as a kernel extension (e.g. device driver) or user-space software the same rules apply.
FYI – this is why things like the SELinux operate in Kernel Space and determine privilege levels for any kind of task. If the software – even something internal to the kernel – doesn’t meet the required privilege level then it can’t do what it wants to do. This is enforced from the core of the kernel, and this is the only kind of mechanism that can provide that kind of security. AppArmor (one of the competing implementations) works the same way.
TemporalBeing,
“Managed languages – like Java with the JVM and C#/VB#/F# with the .NET run-time are not what you think they are.”
All I said in that post is that unmanaged languages have different semantics than managed ones, something that you yourself echoed. So I feel like you are trying to be disagreeable than to counter anything I actually said.
“No language requires the use of a VM – managed or otherwise.”
I never asserted that they did. But for the purposes of creating software barriers in the micro-kernel, we need a language that can contain the program’s behavior within a sandbox without using hardware barriers (like kernel/userspace). I don’t believe any unmanaged languages can do that, though if I’m somehow wrong, I’m all ears.
“Java and the .NET languages both allow for use of Assembly language.”
Assembly language code is unmanaged and would be unsuitable and not permitted within a software barrier.
“So, I go back to my assertion – the languages themselves are not the solution. GOTO – built into nearly every language in some form – is a simple construct that ultimately provides the ability to by-pass security checks. So the languages themselves are by architectural design insecure.”
I am afraid this is a misunderstanding on your part. A language like GWBASIC makes extensive use of the GOTO. But like I said earlier, “GOTO” is a construct of a language – it doesn’t imply the language offers a way to issue arbitrary (unmanaged) x86 “JMP” instructions to code segments/offsets outside of the source code.
“So, the language itself cannot prevent you from doing anything – any construct can be made by nearly any language.”
Think about it a little more. What about javascript or the turning machine I linked to earlier? Those are both turing complete languages which run inside a sandbox and nothing about either language implies the ability to compromise the software barriers they’re contained in.
All I ask is that you try to understand the model I am proposing instead of just assuming it’s wrong.
A software barrier would be impossible without it. 😉
A Turing Complete language can be used to define itself, and even be used to write any kind of software. Due to hardware interfaces, providing the ability to drop to Assembly because a requirement to make the language Turing Complete, and any language that is not Turing Complete will not be of any use for Operating System development, or be of much use outside of niche environments.
A GOTO will always be implemented as an JMP instruction when converting source or bytecode to machine language; the target of that jump is however otherwise determined.
No, you just keep making assertions that lead to that.
And this is the fault of your assertion. It is not possible by the language itself to construct such a barrier.
Even JavaScript (aka ECMAScript) can be utilized in manners that can break the barriers – access OS APIs, etc. It’s more difficult to do but not impossible. This of course, depends on the Operating Environment of the JavaScript program -that is, how complete the environment of the JavaScript VM is for.
For example, Windows provides a very complete JavaScript Environment through Windows Scripting Host. As such, you can do pretty much anything with the computer through JavaScript. Comparatively, the JavaScript environment provided by web browsers is much more limited, but only in so far as the APIs available – e.g. Firefox on Windows does not provide the JavaScript VM access to the WSH APIs, only its own internal APIs so the VM is limited in that respect; if however those APIs provided other means to do the work then it would still be of issue.
A Turing Machine by definition (http://en.wikipedia.org/wiki/Turing_machine) can do anything possible in software (technically anything that can be made in hardware too), therefore a Turing Machine would still be able to get around said barriers if the barriers were in software alone.
As I said, any software written in any language can get around the issue. Only a hardware fail-safe that is properly used can provide what you are looking for. Processors have had said fail-safes for a long time – the various ring levels of the processor – and those ring levels can only really be managed by the Operating System kernel to do the enforcements necessary.
So, it doesn’t matter whether you write it in C, C#, VB, JavaScript, Java, etc. The ability of programming languages to limit said functionality is non-existent.
TemporalBeing,
It’s clear you are just being argumentative without understanding the model. If you understood it and disagreed with it’s merits, that’d be one thing, but the fallacies in your posts indicate that it seems to be above your head. I have no interest in pursuing this conversation any further if you are not willing to learn, sorry.
Edited 2012-01-03 20:10 UTC
I understand the model just fine; the problem is that the model is not realistic in any manner for at least two reasons:
– VMs are not feasible inside a kernel
– Managed Languages do not provide the protections your model assumes they do
Stop being an academic and learn from the conversation.
It’s quite clear to me (and others) that you are simply looking at the theoretical and not accounting for reality or what the terms actually mean.
Until then…
Managed languages is a bad expression.
Managed language is just a term Microsoft created when they introduced .NET. Any so called managed language can also have compilers that target native code instead of bytecode generation.
Managed languages are better described as strongly typed languages.
It is true that it is also possible to exploit applications written in strongly typed languages, but they are harder to do.
If we restrict the exploits to VM environments, even if they have security managers for the bytecodes like the JVM and the CLR do, you might try to exploit the library. Many library methods are currently written in C/C++/Assembly, as such you can try to exploit the VM by passing invalid data to the library.
For example a corrupt image to the image handling library methods.
moondevil,
“Managed languages is a bad expression.”
Gosh, one of you has a problem with my use of “VM”, another has a problem with my use of “managed language”. I can’t win, however I think you’re both being overly pedantic, haha.
“If we restrict the exploits to VM environments, even if they have security managers for the bytecodes like the JVM and the CLR do, you might try to exploit the library. Many library methods are currently written in C/C++/Assembly, as such you can try to exploit the VM by passing invalid data to the library.”
I totally agree, however it’s still an implementation bug which can be fixed.
Obviously the kernel vs userspace separation can have exploits with buggy code in the kernel, but it doesn’t invalidate the security concept of a kernel-userspace architecture in general. I think binding managed code to unmanaged libraries should be considered in the same light.
I’ve heard this opinion expressed before, but I’m not sure I understand why anyone would feel this way.
What’s the effective difference between a VM running in kernel space and a microkernel? Either you’re trapping dangerous/unauthorized instructions in hardware or you’re doing it in software. I don’t really see much advantage to doing it in software except that bugs in that access layer can be updated when they are discovered.
Unless you’re advocating something like what Open Firmware had where you could have drivers in bytecode that are cross-architecture compatible, but there’s a performance reason why that was only used for bootstrapping and the OS shipped with CPU-specific drivers.
Why would anyone have a problem with the word “managed” as used to describe dotnet?
Because there was nothing new there that didn’t already exist in other execution environments. They were not considered “managed”.
It was PURE marketing.
It wasn’t manged any more than java, or heck, interpretted languages like bash or BASIC, which run under the supervision of another process.
bartgrantham,
“What’s the effective difference between a VM running in kernel space and a microkernel? Either you’re trapping dangerous/unauthorized instructions in hardware or you’re doing it in software. I don’t really see much advantage to doing it in software except that bugs in that access layer can be updated when they are discovered.”
I’m happy you brought this up, although I’m pretty sure the author would consider this off topic.
There are some well known impediments with traditional micro-kernel designs, which are typically implemented using hardware userspace boundaries, as you observed. I’m sure I don’t need to tell you, but I will elaborate anyways:
1. Kernel syscalls are slow, they often involve saving/loading various CPU register states and transferring data between userspace and kernel stacks.
2. IPC between drivers is usually much slower between micro-kernel drivers because data has to be copied from one address space to another. The equivalent operations in a macro-kernel involve simply passing around object pointers since macro-kernel drivers always share the same address space and drivers implicitly trust each other not to clobber the data of other drivers.
It’s difficult to do anything to fix #1, we’re basically at the whim of intel’s engineers. The inefficiency can be measured in the difference in time between a normal function call and a syscall.
There are various things one might do to improve #2. One is to minimise the use of copied byte streams and design the IPC around shared memory instead. I’ll let you ponder it, but none of the solutions are ideal.
A custom VM based micro kernel using a managed language would offer a potentially novel solution to both those problems.
Consider that in our managed language, a function can only access objects which it has a pointer/reference to. Even if other objects exist in the same address space, the managed language semantics prevent the function from touching objects for which no reference is available. We can take advantage of this fact by running two or more separate processes in the same address space (using one or more memory allocaters). Since the managed language guaranties that one process can never generate references to objects of the other process, the two processes can not corrupt each other. This achieves perfect process isolation without using hardware modes and without using any syscalls.
Now for IPC, we could designate specially typed references/pointers to guaranty the object pointed to is only referenced once by a process. Then these references/objects could be instantly transferred to the other processes running in the same address space using specialised constructs of the managed language. So transferring objects between drivers is as simple as passing a pointer in the macro-kernel approach.
So in a nutshell, you have microkernel drivers running in isolation under a kernel level VM without using hardware separation, and it doesn’t have the overhead traditionally associated with userspace micro-kernels. You’d get all the other benefits of managed code as well, like those you mentioned.
Honestly there’s nothing conventional about the micro-kernel approach I’ve illustrated. It’s purely my own opinion and probably has little in common with the reasons others may have had to use VM based kernels.
bartgrantham,
I wanted to list some other possible optimisations using managed code in a VM based micro-kernel:
3. A traditional micro-kernel with unmanaged code would typically need to validate that all parameters and data structures passed in are of the correct type and pointers are valid, which costs CPU cycles. However with managed code, the correctness of data structures and pointers might be inferred through strict types without checks.
4. It would take a hell of a good code optimiser to pull it off, but conceptually there’s no reason the kernel VM couldn’t automatically “inline” inter-driver function calls, such that there’s *zero* function call overhead between microkernel drivers while keeping security semantics in place. After all, any steps taken by two functions, no matter what they do, could be done in one function instead.
Note: this might already happen with macro-kernel code using -O3, but this would be a very impressive speedup for a micro-kernel which typically requires a couple secure context switches for each IPC request between userspace drivers.
The main point to remember is that none of these microkernel optimisations are securely feasible using unmanaged code and hardware isolation.
Edited 2011-12-31 06:59 UTC
Both managed and unmanaged code do the same thing and require the same kinds of checks. Both offer strict-type checking when desired, and even with strict-type checking you can convert information from one type to another in manners to get around the compiler’s strict type checking – even in manners that get around run-time strict type checking.
So the correctness of the data is not necessarily guaranteed even by simply using a Managed language.
Same goes for pointers – you can still end up with invalid pointers even in a managed language, and they can be just as damaging as in an unmanaged language.
TemporalBeing,
If you want help to understand something, then please ask it, I am still happy to answer genuine questions. However I am done responding to your doubts stated as negative assertions.
The fact that you don’t understand the assertions or the difference between managed and unmanaged languages speaks for itself.
I have no doubts in the discussion; and am only correcting your incorrect view of what is available to you.
And, btw, if you really want to see what a Managed Language can/cannot do per an OS, look to Microsoft – only, they found they couldn’t do it with a pure .NET language – e.g. C#, etc. – and had to create a special extension of C#. See here for more information:
http://singularity.codeplex.com/
http://en.wikipedia.org/wiki/Singularity_%28operating_system~*~…
As I and others have stated, it really offers no advantage over existing OS’s, and rather has performance penalties as a result of the .NET side of things – a little more than a C++ or other OO-based languages, but similar to a Java-based OS, again for the same reasons.
Except that managed languages do not make those guarantees.
Indeed, even if the language could make that guarantee, you could also use the language to locate the object without being strictly given access to it:
– create a reference to a memory segment to locate the object (e.g. searching memory to find it)
– decode another object that has a reference to it and steal a reference
In either case you were not explicitly granted a reference to the data but achieved it. These are both methods of cracking software, and both are also valid under managed languages.
So using a managed language buys you nothing in the end while costing a lot, in terms of performance
As mentioned, the PPC uses segment registers to restrict/allow access to IO. The way you allow users access to a subset of IO is simple – the user calls the kernel before doing any IO and gets assigned a segment register by the kernel to do what they want, if authorized. Segment registers cannot be set by the user since they are a system level part of the PPC architecture. IO segments control access to a variable amount of data, set in the segment itself. So the kernel has fine control over what the user may or may not access through the way it sets the segment register(s).
Any more detail on the subject is getting into OS design for PowerPC architecture, and I’m sure that actual code isn’t the intention of the article, especially kernel level OS code. He just wanted to know the control mechanism.
JLF65,
“As mentioned, the PPC uses segment registers to restrict/allow access to IO. The way you allow users access to a subset of IO is simple – the user calls the kernel before doing any IO and gets assigned a segment register by the kernel to do what they want,”
Thanks for clarifying. With my x86 background, the way it was written gave the impression that you were referring to a supervisor flag on the code segments which permitted all/nothing access to the IO ports (similar to ring# on x86).
So you are saying that segment registers can be used to grant port I/O access. I assume that PIO is done using against these segments using normal R/W instructions? If so, that seems better than the x86 approach.
Unfortunately I’m having trouble finding more information on the PPC using segments to do PIO. It looks like there are 16 segment registers available to do this with?
http://www.usenix.org/events/osdi99/full_papers/dougan/dougan_html/…
Edited 2011-12-31 18:51 UTC
It’s rather different than the x86. You want to read the Programming Environment Manual for the PPC. While there might be a web page that makes it easier to follow, I prefer the reference manuals.
Note that 64 bit is completely different from 32 bit on the PowerPC. They don’t have segment registers any more, but rather it’s part of the MMU handling with tables of entries that control the same stuff as the segment regs… so it’s like virtual segment registers with entries assigned by the OS.
32 bit: https://www-01.ibm.com/chips/techlib/techlib.nsf/techdocs/852569B200…
64 bit: https://www-01.ibm.com/chips/techlib/techlib.nsf/techdocs/F7E732FF81…
Usually whenever you have a CPU with a cache and a MMU, the MMU has the ability to define a page or a segment as non-cacheable, so that R/W operations hit the bus directly. A page can also be locked to physical addressing (no VM translation) and that is pretty much your memory-mapped I/O of the yesteryear.
Typically such facilities are only available in the kernel for hardware driver programming.
Edited 2011-12-30 17:24 UTC
Hi,
I thought I’d write some lists of things to consider. I split “IO” into 3 separate things – IO ports, memory mapped IO, and bus mastering/DMA.
For IO ports:
* what minimum granularity access control do you want (all or nothing, selected IO port ranges, or individual bits)
* what to do if you can’t achieve the desired minimum granularity on some systems (emulate, or accept worse granularity)
For memory mapped IO only:
* If you can control caches, how does the driver set a memory mapped IO area’s caching (e.g. “uncached” or “write combining” or “write through” or whatever the driver wants).
For memory mapped IO and bus mastering/DMA:
* what sort of fences/barriers do you need to support
* for systems where caches can’t be controlled and systems where there is no cache coherency, how do you plan to handle cache coherency (e.g. explicit cache flushing that is ignored on targets that support cache coherency?)
For bus mastering/DMA only:
* are there IOMMUs that could be used to prevent a malicious driver from using bus mastering/DMA to access things it shouldn’t be allowed to access
* if a device driver initiates a bus mastering/DMA transfer to RAM and then crashes before the transfer completes; can you isolate the RAM (so that it’s not freed and re-allocated while the transfer is still in progress); and alternatively, can you forcibly terminate the transfer without a usable driver
For all of the above:
* can you safely give control of selected devices to arbitrary processes (e.g. so you can use a real network card from inside a virtual machine that’s running as a process under your OS)
That’s about all I can think of at the moment (but I’m sure I’ve missed something somewhere, as I mainly only deal with 80×86).
– Brendan
This port might be the best ever invented for a keyboard. It standardises the input method. And the fact that it was the standard for so long (and still is for fast kvm switches) makes for a certain point. And WHY manufacturers still have a ps/2 port on most new motherboards is simple.
1:Why not,
2:Free (as in beer and no per chip/port cost like usb) so why not.
3: The best coders in the world still use the IBM-M Series or equal (no! lexmark with rubber thingies does not qualify) So why not?
4: It has direct hardware connection, USB does not and need a layer for usb support in the OS in a way that PS/2 does not. If you have one of those AMI BIOSES you know what i mean.
6: (I know it is the wrong number but i really wanted to make a point) BACKWARDS and FUTURE compatible. Ps/2 ports are still available to all shops for those using an apple or other retardedly maded laptop without ps/2, You can still use your ps/2 keyboard with computers from 1984 to this day without keyboard defucnt signals or dead keys (amiga fans know what i mean)
Also powering things from usb only have a theoretical voltage of 1.1V afaik PS/2 can power an external sound card,video out and a mug warmer on just one line of power…. I will not count that as an advantage, as that might also be its main flaw =D
And i forgot *silly me* The cpu cycles to do basic input is a LOT less with ps/2.
That might be negligible for most computers today, but waste is never a good idea. I prefer specialized ports ALL the way as long as the universal ones are available too. I hardly use them anyways but i bet most people use usb for more than installing Debian. So in other words, i am not most people,
Edited 2011-12-31 12:06 UTC
Comparison: interrupt-driven vs. polling. 🙂
There is still hardware that “plugs into” the “keyboard input chain”, such as barcode scanners and also card readers (as they are for example used in Germany for health insurance chips cards). Power is sometimes provided per PS/2 connector, control signals traditionally came per serial port, and “answers” were sent into the “keyboard input chain” and were immediately available to any program without any further device driver magic.
The PS/2 port still does not claim to be able of hot plugging. While I never had any issues disconnecting and reconnecting my IBN model M keyboard (often using its HIL plug), I’ve seen keyboard connectors dying several times – with other people, less lucky than me. 🙂
IO ports go way back. The PDP1 (first computer under $100,000) had IO ports in 1960.
Memory mapped I/O (MMIO) was originally done by IBM, though it was cumbersome.
MMIO was used exclusively for I/O on the PDP11 (1970). Any address starting with 160000 and higher was reserved for I/O – the high order 3 bits would select/enable I/O activity. It was up to the interface as to how much support for memory actions (read/write/readlock/writelock) were supported. Some devices had only write support, others had various combinations. Most notably, the control status registers frequently had a “go” bit, which was always read as a 0.
This was carried out to the ultimate level with DEC VAX system – the high order 2 bits selected the system level – 00 – user, 01 – supervisor, 10 -interrupt, 11 device (as I recall).
MMIO is simpler to program, to debug, and faster, even on the X86, than using I/O instructions with ports.
The problem with IO instructions is three fold
1 – yet another instruction added to the CPU, and mandates CPU interaction with the peripheral – which can be HORRIBLY slow.
2 – yet another bus… or you make the memory bus perform double duty. A second bus is EXPENSIVE for a CPU to implement, so reusing the memory data bus is the usual result – with added complexity and expense on the bus (everything interfacing with it must implement a memory/IO select logic)
3 – another place to introduce bugs in CPU privilege separation (users cannot do I/O, for an example of the complexity just look at the X86 privilege bits…) MMIO is simpler as it can be controlled the same way memory is controlled – the MMU.
The advantage MMIO has is that any instruction can serve as an I/O instruction. System protection is just a pagefault away.
And normally you don’t NEED the complexity of memory barriers other than the normal read/write locks supported by memory operation. This is required only for those system that have to reorder instruction sequences to achieve any kind throughput (an architectural problem, not a requirement).
The reason such locks are needed on the X86 is that the instructions are not executed by the processor – they are first translated into a stream of RISC instructions, where some get dropped as a “don’t care” in the translation process. The memory barriers prevent the translator from making improper reordering operations.
Not necessarily. S/360 I/O channels are certainly not slow. Although I guess it’s arguable that programmable I/O channel controllers are not the same thing as I/O ports!
Edited 2011-12-31 15:18 UTC
It can get into a semantic mess.
Programmable I/O Channel processors usually are not considered the same as I/O ports, because they don’t execute processor instructions.
Whereas port I/O involves the processor executing I/O instructions explicitly.
You’re actually mixing two different view of the VAX address space.
In a physical address, bit 29 typically selects I/O or memory. Bits 30 and 31 are typically used (at least in the microprocessor implementations) to encode the length of a transaction on the bus.
In a virtual address, bits 30 and 31 select the address space, where 00 is a user space growing up (i.e., code and data), 01 is a user space growing down (i.e., the user stack), 10 I’m fuzzy on, and 11 is system space. I/O typically winds up mapped into system space, although it’s possible (with suitable privileges) to map regions of the other address spaces to I/O space.
Not quite.
00 is user space. But the stack started at 01 and worked down (so the first usable byte is in 00).
DCL, RMS,and such lived in 01.
10 was where the kernel resided.
11 was reserved, but where all the physical devices lived. It mapped entire bus systems (UNIBUS,QBUS, BI…)
The system was mapped this way because that was the only way system calls could be made. The CMx (change mode to Kernel/whatever) instructions would not work in P0 space, system calls were an entry into P1/P2 address pages, and the first instruction was required to be a CMx instruction (then followed by a jump), otherwise an illegal access fault occurred.
As far as I recall (and I don’t have my books handy) all physical I/O was defined by the MMU, and as such could use/map any physical bit of the bus for whatever it wanted. The CPU didn’t interact with it (other than loading it via OS functions).
It was interesting with the boot code – it always emulated a PDP-11 during initial boot, with the default peripherals always mapped to 017xxxx (octal) address range. Didn’t matter which bus was mapped (QBus/UNIBUS or the BI), that was where the peripherals were mapped by default. Once SYSBOOT was loaded, it (meaning the loaded boot program) would switch to native mode and initialize all the hardware and load the kernel.
Granted, I only worked on one VAX driver (a printer/plotter), but it’s device address as used by the driver was starting with a 11 bit pattern.
Yeah, I knew I had botched 10 and 11 just after I posted it.
Hmm. I don’t see those restrictions on the CHMx instructions in the VAX Architecture Reference Manual. The only restrictions I see are that CHMx cannot be executed while running on the interrupt stack and the new mode has to have enough privilege to access the old mode’s stack.
I’m curious about which machine you worked with. The big machines tended to have a captive PDP-11 (LSI-11 in the case of the /780, various Pro models in 8xxx machines). As I understand it, the PDP-11 was responsible for loading the microcode into the processor and the bootstrap into memory, then lighting off the VAX.
QBus machines tended to be built with processors that didn’t support compatibility mode. At reset, they’d start running VAX code from ROM, which would go fetch the bootstrap.
I’m not sure how the /730 and /750 booted.
ugh, you’re a bit confused as to why memory barriers are needed.
They are not a “x86 thing,” although technically it should be an out-of-order thing really since any speculative RISC design also has to deal with the same problem.
Memory mapped I/O becomes tricky in speculative architectures due to the complex interaction between the load/store queues and the cache/memory controller. And even in in-order architectures memory barriers may be needed due to cache/memory controller/chipset interactions.
For normal (i.e non I/O) operations the specific order of a write is not crucial as long as it happens before another instruction needs that result executes. And the processor can guarantee that data dependencies are met, since it is the only one scheduling those instructions. However, once we’re dealing with I/O instructions, we are now accessing the scheduling realm of the I/O device not just the processor’s. So the assumptions from non I/O memory accesses do not apply in this case.
Edited 2012-01-04 09:34 UTC