A new direction...

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Curufir

A new direction...

Post by Curufir »

I was sitting down, thinking things over, and kinda came up with a twist for my OS.

Up until now I've been doing things in a fairly standard fashion, but I've decided to juice things up a bit for amusement value. Thought I'd run a couple of the basics (I haven't thought this totally through yet) past the board and see if there are any huge gaping flaws.

The entire OS (Aside from what's required for the interpreter) gets written in code which is interpreted at runtime. The OS will not run binaries (Except from possibly a compiled cache), only byte-code.

Only code with certain privileges will be able to use instructions that translate to privileged processor instructions. So things like IO ports are restricted to privileged code.

Because I control the language, what context certain instructions are legal in and refuse to run arbitrary binaries there is no need to use any hardware memory protection. So no context switches.

The language will have no pointer operations (Which should be interesting to work around). So everything can run in the same address space.

Because of the level of control being imposed here, and having everything operate in a single address space, I can do away with paging entirely and let a software memory manager handle everything without fragmentation and with garbage collection (Probably with some virtual junk thrown in as well).

***

Not much, but hopefully you get the main thrust of the idea.

The major problem would be with the speed of interpreted code, which is why I'm leaving open the possibility of a compiled code cache.

There will be other problems I'm sure, I need to give it a lot more thought before it takes full shape, but I thought I'd ask for some input.

***

Some of you (There may be few of us left) might recognise that scheme as being very similar to a MUD driver. It should be, because it was finding my old LPC code that gave me the idea.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:A new direction...

Post by Brendan »

Hi,

A couple points here... Questions are intended to provoke thought, rather than to be taken literally.
Curufir wrote: Because of the level of control being imposed here, and having everything operate in a single address space, I can do away with paging entirely and let a software memory manager handle everything without fragmentation and with garbage collection (Probably with some virtual junk thrown in as well).
You could forget paging, but you'd be severely restricting the amount of space an application could use. What will you do if the application wants an array like "float points[512][512][512]", which could take 1 Gb of space? Most OSs would use demand paging and swap space to cope with this, both of which rely on paging. Your OS could still swap things to/from disk, but you'd need to be able to send parts of things, rather than whole things (e.g. part of the array rather than the whole array). Therefore everything would need to be broken down into parts that are a multiple of 512 bytes. In the end using paging would be much easier...
Curufir wrote: The major problem would be with the speed of interpreted code, which is why I'm leaving open the possibility of a compiled code cache.
If you where going to implement a compiled code cache, why not allow this compiled code to be stored somewhere in the filesystem, so that the next time the code is needed it can be loaded directly without needing to be compiled again? If you do store the cache on disk, why not compile the code when it is installed? Notice how we went from interpretted code to *nix in 2 sentences :)

Curufir wrote: Because I control the language, what context certain instructions are legal in and refuse to run arbitrary binaries there is no need to use any hardware memory protection. So no context switches.
You'd still need to be able to switch between one process/thread/application to another, so you'd still have context switches unless your OS was single-tasking (e.g. like DOS). Even then your IRQ handlers would involve small context switches (e.g. switching from the application's context to the IRQ handler's context). I guess what you mean here is that your context switches wouldn't need to save/restore as much state. Depending on how you do things (e.g. if the code is interpretted without any compiled cache) the context switch could be as simple as saving and loading a single general register.
Curufir wrote: Some of you (There may be few of us left) might recognise that scheme as being very similar to a MUD driver. It should be, because it was finding my old LPC code that gave me the idea.
The benefits of using interpretted code for a MUD is that it allows the code to be modified while it's running. I'd assume your OS wouldn't allow that though.

The benefits of using interpretted code for an OS are different. It you had a different compiler/interpretter for each platform your OS could easily become completely portable, much like JAVA (but this can be done by compiling the code as part of installation, like *nix apps). Another benefit would be complete control for security (yet binary code can be as secure if run at CPL=3 without interpretting). Can you think of any other advantages?


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
df
Member
Member
Posts: 1076
Joined: Fri Oct 22, 2004 11:00 pm
Contact:

Re:A new direction...

Post by df »

my os is a vm system.

i have a virtual cpu / virtual machine. it all runs inside of that.
and it can go two ways, run as an .exe on host system, or write a wrapper and boot it as a traditioanl os, only you never see the VM that way..

the vm/interpreter is mostly done. writing the assembler now.
after the assembler comes the compiler for my language/system.
-- Stu --
Curufir

Re:A new direction...

Post by Curufir »

All good points Brendan. As I said I need to put a lot more thought into the idea. Putting the topic on the board will hopefully generate some things I wouldn't immediately think of.
Brendan wrote: ...What will you do if the application wants an array like "float points[512][512][512]", which could take 1 Gb of space? Most OSs would use demand paging and swap space to cope with this, both of which rely on paging...
Hmm.

Let's say, as a brainstorming concept, I allow only application code to exist in main memory, with all data being forced to exist (Conceptually at least) on disk only. So allocating more memory (As with the array) would involve opening a new file (With the truncate/append advantages that brings). If the file server manages its caches (This would be a corner instance of needing to allocate memory in real memory, video etc would be others) with some sophistication then I think I could get away with it. The file server would effectively also be acting as the virtual memory manager. The only problem starts with swapping out application code if memory gets low, and IMHO if memory is that low (In my experience data is the big user of memory) then swapping out the entirety of the code will be a reasonable mechanism.
If you where going to implement a compiled code cache, why not allow this compiled code to be stored somewhere in the filesystem, so that the next time the code is needed it can be loaded directly without needing to be compiled again? If you do store the cache on disk, why not compile the code when it is installed? Notice how we went from interpretted code to *nix in 2 sentences :)
To tell the truth I didn't really think through the code cache concept very well (It dawned on me while I was writing the post).

I guess the idea would be that the compiler would keep track of what it had compiled. Then when a program is started the loader (Or whatever loads the code) checks against the compiler to see if the cached code is older than the source bytecode. Making a first issue of code to the cache at install time makes a lot of sense, but it doesn't really make it *nix. You still can't run arbitrary binary code, so the protection provided by the language/compiler is still in place.

You'd still need to be able to switch between one process/thread/application to another, so you'd still have context switches unless your OS was single-tasking (e.g. like DOS).
Yeah, bad language on my part. Should have said ring transitions not context switches.

I have been thinking about it actually. Most of that thinking revolved around one question.

"How much multi-tasking on a desktop is not event driven?"

People can really only do one thing at one time. On a uniprocessor even the computer can only do one thing at one time. So my thinking is revolving around what extent can I get away with only having one running process, some system of alarms, and allowing applications to register for events.

It wouldn't be as flexible as a system with true multi-tasking, but it might be enough to give at least the illusion of multi-tasking (Which is all we're doing anyway on a uniprocessor).
Can you think of any other advantages?
It was portability that got my attention at first. Write the base, and everything is instantly ported.

Another thing might be IPC. Since the language has such constraints and the internals of an application are known then IPC can be done by two applications talking directly to one another. Bad explanation maybe. What I mean is that all applications have access to all other applications' data (Kind like a monolithic kernel), but only under constrained conditions imposed by the compiler/interpreter. So IPC gets hugely simplified.
Curufir

Re:A new direction...

Post by Curufir »

The file server would effectively also be acting as the virtual memory manager. The only problem starts with swapping out application code if memory gets low, and IMHO if memory is that low (In my experience data is the big user of memory) then swapping out the entirety of the code will be a reasonable mechanism.
I was sitting on the porcelain throne and had a minor revelation.

ALL parts of an application can get treated like a file. The compiler/interpreter just uses the byte-code file in exactly the same way as it would read in a binary file. So all the grunt work of virtual memory can take place within the file server. The caching system will need to be reasonably sophisticated of course, and handling the compiled cache will require some finesse (Probably dealt with in the compiled binary format), but aside from that it should work fine I think.

Bonus here would be that the amount of installed RAM really doesn't matter (So long as there's enough for the base components). Everything will still work, it'll just work slower (More cache misses) if there is less RAM available. I think its kinda like coming halfway to including removable storage as part of the address space.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re:A new direction...

Post by Brendan »

Hi,
Curufir wrote: Let's say, as a brainstorming concept, I allow only application code to exist in main memory, with all data being forced to exist (Conceptually at least) on disk only. So allocating more memory (As with the array) would involve opening a new file (With the truncate/append advantages that brings). If the file server manages its caches (This would be a corner instance of needing to allocate memory in real memory, video etc would be others) with some sophistication then I think I could get away with it. The file server would effectively also be acting as the virtual memory manager. The only problem starts with swapping out application code if memory gets low, and IMHO if memory is that low (In my experience data is the big user of memory) then swapping out the entirety of the code will be a reasonable mechanism.
This would give you another advantage - with a 64 bit file system your applications wouldn't be restricted to the size of the linear address space that the CPU supports (currently 32, 36 or 48 bit depending on CPU). Your storage device drivers (ATA, ATAPI, SCSI, floppy, etc) and your file system drivers (FAT, EXT2, NTFS) would all have to be built into the kernel though, otherwise these drivers may need to use themselves to get to their own data (deadlock).
I guess the idea would be that the compiler would keep track of what it had compiled. Then when a program is started the loader (Or whatever loads the code) checks against the compiler to see if the cached code is older than the source bytecode. Making a first issue of code to the cache at install time makes a lot of sense, but it doesn't really make it *nix. You still can't run arbitrary binary code, so the protection provided by the language/compiler is still in place.
IMHO the standard method of installing an application on *nix is to download it, untar/decompress it, configure it and then compile it. The standard method of installing an application on your OS would be to download it, untar/decompress it, and then install it (where the installer automatically configures and compiles it). On *nix the binary ends up being one of more files in the file system. On your OS the binary would end up being one or more entries in the cache (which would be in the file system). The main differences I see is that on *nix "make" is run when it's told to, while on your OS "make" would happen each time the application is run, and *nix allows binary code to be installed too (where your OS might not).
It was portability that got my attention at first. Write the base, and everything is instantly ported.
Most high level languages can be just as portable (if all target platforms support similar features). When the target platforms are "Curufir's 80x86 OS", "Curufir's Alpha OS" and "Curufir's Apple/Mac OS" you'd expect very similar features. IMHO portability is acheived well with both methods. Linux can be considered an example, where the same source code is used to create platform dependant kernel/s, drivers and applications.
Another thing might be IPC. Since the language has such constraints and the internals of an application are known then IPC can be done by two applications talking directly to one another. Bad explanation maybe. What I mean is that all applications have access to all other applications' data (Kind like a monolithic kernel), but only under constrained conditions imposed by the compiler/interpreter. So IPC gets hugely simplified.
As an alternative, if you used paging you'd be able to alias pages (same physical memory mapped into multiple address spaces), and if you used segmentation applications would be able to share specific data segments. If your data is going to exist (conceptually) on disk only, then the data transferred via IPC would also exist (conceptually) on disk only, and your OS's file system code would be the IPC.

Perhaps you could make it more like a MUD, where there's only one piece of code (the OS) that consists of many "objects". The kernel, drivers and applications would all be compiled/combined into a single binary. Then you'd have no need for IPC at all ("call near foo" would suffice). I hereby name this concept "megalithic" (e.g. "exo-kernel OS", "micro-kernel OS", "monolithic OS" and "megalithic OS" :)).


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Therx

Re:A new direction...

Post by Therx »

As you're planning to manage everything yourself (i.e. no paging protection, context switching etc.), this idea seems to be highly portable itself as well as allowing the apps to be portable. As part of the development you could write an interpretor for *nix or windows so you can develop apps in an easier debug environment etc.

As far as the code cache goes I think by keeping a code cache for any period of time removes the advantage of having interpreted bytecode. As any information loaded from the disk can not be taken to be 100% secure as it may have been fiddled. I would recommend only keeping a cache in RAM as this is entirely under your control

Nice idea! Good Luck!

Pete
Legend

Re:A new direction...

Post by Legend »

Perhaps some signature would be necessary for a cache on a disk this way, but yet, where to store the keys for it? :o
User avatar
Colonel Kernel
Member
Member
Posts: 1437
Joined: Tue Oct 17, 2006 6:06 pm
Location: Vancouver, BC, Canada
Contact:

Re:A new direction...

Post by Colonel Kernel »

It sounds like what you want to do is implement something like the CLR in kernel-mode. I tossed the idea around before, mainly because of the realization that separate address spaces would no longer be required. Then I got to thinking about practical realities -- it still makes a lot of sense to support "raw" C/C++-compiled binaries, if only to implement device drivers (there's no way I would attempt to write a driver in C# ;) ). Also, if there is only one address space, you could run out on 32-bit machines (although treating data as though it belongs to the file system is an interesting twist on this... I'll have to think about that). The difficulties with trying to live without paging have already been discussed in this thread.

On a purely conceptual level, there aren't really any good reasons for implementing a CLR- or Java-like VM in the kernel IMO. Such a VM deals with higher-level concerns than a kernel should. IMO it's really an attempt at performance optimization, at the expense of losing potential compatibility with existing "native" apps compiled from C/C++ et. al. The only non-performance-related problem I can think of that would be potentially solved by such a system is the implementation of a global policy for garbage collection (one weakness of systems such as Java and .NET is that GC policy is process-local). Otherwise, I don't see much point... If I were to incorporate such a VM, I'd do it in user space.
As far as the code cache goes I think by keeping a code cache for any period of time removes the advantage of having interpreted bytecode. As any information loaded from the disk can not be taken to be 100% secure as it may have been fiddled.
FWIW, MS' CLR implementation doesn't cache JIT-compiled code between runs of a process. So, every time you launch an app, the JIT compiler has to do its work all over again. However, it is possible to pre-compile everything to native code at any point before actually running it (using a tool like ngen). JIT compilation is actually pretty fast. I've played a version of Quake II that was ported to .NET, and there was no perceptible slowdown when starting up the game.
Top three reasons why my OS project died:
  1. Too much overtime at work
  2. Got married
  3. My brain got stuck in an infinite loop while trying to design the memory manager
Don't let this happen to you!
Curufir

Re:A new direction...

Post by Curufir »

Brendan:
Perhaps you could make it more like a MUD, where there's only one piece of code (the OS) that consists of many "objects". The kernel, drivers and applications would all be compiled/combined into a single binary. Then you'd have no need for IPC at all ("call near foo" would suffice).
That might be the missing part of the puzzle. Would certainly be an advantage over ordinary IPC mechanisms. However it'd mean overcoming my dislike of OO ;D.
I hereby name this concept "megalithic" (e.g. "exo-kernel OS", "micro-kernel OS", "monolithic OS" and "megalithic OS" ).
Nice term, think I'll steal it (With your permission) :).

Pete:
As far as the code cache goes I think by keeping a code cache for any period of time removes the advantage of having interpreted bytecode. As any information loaded from the disk can not be taken to be 100% secure as it may have been fiddled.
That depends on how access to the filesystem is handled I think.
We could think of a single (simple) compiled application actually being three files.

my_appA - source file
my_appB - bytecode file
my_appC - compiled binary

Now let's say the system can see all three, but the user can't. The user only gets to see the source file (Or the bytecode file if there is no source file, eg distributing an app). So long as the user is prevented from tampering with bytecode/compiled files directly there's no danger in running the cached compilation. This can be done because the system can control what the user can see/tamper with.

Should the user ever get raw access to the filesystem (Eg By using a different OS to read the disk) then your security is hosed anyhow.
JIT compilation is actually pretty fast. I've played a version of Quake II that was ported to .NET, and there was no perceptible slowdown when starting up the game.
This is good to know. I was scared that without compiled binaries performance would be seriously inhibited.

I wonder what proportion of compilation time is spent in the parsing phase. That would be the point where all the (slow) string matching stuff is done AFAIK. So is the final symbol -> binary step actually fairly fast? Fast enough to be done on the fly if the parsing phase is done only when the code changes?

Darn, more research required. Too many questions without answers.

***

Slightly off-topic:

I have to say I've fallen in love with this idea, and I've been giving it a lot (Some might say too much) thought since it first hit me.

A couple of non architecture things hit me this afternoon.

The first is something approaching a philosophy of the idea:

In a normal (*nix/Windows/mainstream) OS the range of actions the user may attempt is unrestricted. Because of this the operating system must protect itself, and other users, from the stupid or malicious actions of a user. Hence the need for memory protection.

By restricting the range of actions through control of language and filesystem we can produce an environment in which the OS can remove the possibility of those stupid/malicious actions and therefore the mechanisms needed to guard against them.

Hence, in effect, we move the protection mechanisms out of the hardware, and into user's pattern of thinking. Because their range of actions is restricted, they are forced to use only those actions which are deemed permissible by the OS.

Dunno if I explained that well, it took me a while to think it through myself and it's a slippery concept, but an important one I think.

The second is related:

How simple can a processor (And associated chips) be and still run an OS based on these lines?

There's no paging, no segmentation, no hardware memory management of any kind. All we're really doing with the processor is logic calculations and memory moves.

So could we get away with just a simple DSP, IO memory space and some memory mapped devices? Is the traditional general purpose CPU really necessary in such an OS?

Final one is just a thought:

The megalithic ( ;D) OS provides an application with only two things. Methods of receiving data, and methods of storing data. An application doesn't get presented with detailed information about how the data is gathered, or stored, only a high level description of the source/destination (Eg Send X data to Y file). The only objective of an application is to manipulate the received data in some fashion and then (Optionally) store the results of that manipulation. Corollary of this is that applications have no access to hardware that is not granted to them through the operating system. Once again the range of actions is restricted to simplify the system.

***

That's kinda daydream thinking I know, but it represents the start of the thought process, not a final fixed idea.
Therx

Re:A new direction...

Post by Therx »

This can be done because the system can control what the user can see/tamper with.
Err. I don't think that's really possible. You'd have to allow low level access to apps so that they can manage hardware. (hardware config tools) and an app with IO priviledges can overwrite anything by writing its own HD driver. Also if the OS was being dual booted then stuff could be being modified from the other OS. Very unlikely I know but still...

Pete
Curufir

Re:A new direction...

Post by Curufir »

Pete wrote: Err. I don't think that's really possible. You'd have to allow low level access to apps so that they can manage hardware. (hardware config tools) and an app with IO priviledges can overwrite anything by writing its own HD driver.
No, no no, no no no no and no again.

You don't need to give anything in userspace IO privileges EVER. The driver provides all the functionality, any config program just uses the driver to alter the current state of the device. No direct IO access required.

IO access, and all the other fun privileged instructions on x86, would be restricted by means of language and filesystem. Only system code will be able to use the superset of the programming language that contains the x86 privileged instructions. Therefore only system code canl ever run those instructions.
Also if the OS was being dual booted then stuff could be being modified from the other OS. Very unlikely I know but still...
If someone has enough access to the computer to run a different OS then your security is hosed anyway. This counts for every operating system on the planet. Without physical security elaborate software security is just peeing in the wind.
Dreamsmith

Re:A new direction...

Post by Dreamsmith »

Curufir wrote:The major problem would be with the speed of interpreted code, which is why I'm leaving open the possibility of a compiled code cache.
It's a myth that interpretted code is necessarily slower than compiled code. That depends on the design of the language. For an interesting counterexample to the myth, compare the speed of "direct threaded" (DTC) Forth code to "subroutine threaded" (STC) Forth code. You get STC code by taking the interpretted thread of words and compiling it as machine code calls. It runs slower that way. OTOH, if you then start inlining some of those words, you can speed things up. With sufficient inlining, you can exceed the speed of the interpretted code, but rarely by enough to make it worth the effort, not to mention the dramatic increase in your memory footprint this causes. I'm also reminded of a case where a "faster" subroutine actually ran slower because the "speed-optimized" code no longer fit in the processor's L1 cache. Sometimes optimizing for size gets you speed as well...

You'd need to have a lot of details worked out about the language you're using before it becomes clear whether a cached JIT makes sense or not.
Schol-R-LEA

Re:A new direction...

Post by Schol-R-LEA »

Well, I can tell you right now that it is in fact possible to write an OS based around an interpreter, because a number of systems have been written in this manner in the past. I have been considering it myself, as it has a number of advantages for both security and stability. Both direct language interpreters (e.g., various Forth systems) and pseudomachine systems (UCSD p-System, Smalltalk-80, JavaOS) have been successfully used to implement operating systems.

I will note that most such systems do have a facility for compiling timing dependent functions to native code, but it is usually under the control of the system, not the programmer, and by no means is it strictly necessary for it to work.

Also, concerning the issues of virtual memory and such, one could always take a hybrid approach, with an underlying virtualizer/exo-kernel multiplexing the hardware, and each process getting its own interpreter - in effect, its own OS. While this loses many of the advantages of the interpreter-based approach, it would allow you to have different interpreters for different languages, and minimize the risk of an interpreter crash bringing the whole system down, as well as allowing you to mix interpreted and native code without the risk of destabilizing the interpreter.

As for the lack of pointer operations, I wouldn't worry too much about it. If you actually look at languages aside from C and C++, hardly any of them have program-level pointers at all, and those which do (PL/1, Algol-68, Pascal, Ada, Modula-2 - those are most the important ones, actually) severely restrict their usage. The heavily pointer-centric approach is a quality (some would say a perversion) that is pretty much unique to C and it's derivatives. Pointers are often heavily used in the language implementations - Lisp is an excellent example of this, for obvious reasons - but the user generally does not have any way of manipulating them directly. The interpreter will need pointers form memry management, but the rest of the system probably shouldn't - after all, that's what garbage collection is for. OTOH, you will probably want implement lists and collections as primitives in the language, or as a native library for it, as those would be rather hard to write without pointers.

You might want to look up some previous art in this area; one which I particularly recommend is Smalltalk-80: The Language and Its Implementation, an insightful if rather difficult to find (and read) book. Part of it can be found here; any good college library ought to have a copy if the book itself.
mystran

Re:A new direction...

Post by mystran »

I started thinking... like.. this would take a somewhat large performance hit, but it might be optimizable..

Basicly, implement object-aware virtual memory in your system, so you can run it all in one address space. The idea would be that instead of referring to an object by pointer, one would refer to the object by "ObjectID" which would be an entry in a table. Or not actually table, but any random access datastructure, like a B-tree (with hashtable cache for quick lookups of the late objects) and "everytime" an object is accessed, it goes through this table, so the actual object can move around in memory. The VMM is then responsible for loading accessed objects SOMEWHERE into the physical memory.

Ofcourse going through a hashtable (and possibly b+tree) for every object access is a killer for any performance, but adding some protocol for controlling when things can change would help quite a bit. Since the compiler (if such exists) is system controlled, we can automatically insert such management code according to some policy, or we could even have a static policy like "all references must be revalidated from VMM after a context switch". The compiler inserted system could do things like "lock/unlock this object in memory".

Finally, going through a static object table isn't that bad for performance (and is sometimes used) so VMM could ofcourse invalidate references by inserting something special into the lookup table, which isn't mapped into memory, catch the trap, and supply the object, updating the reference in the object table, and resume operation. This has the disadvantage that whole object table must fit into the address space at once.
Post Reply