6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Fri Nov 22, 2024 2:21 pm

All times are UTC




Post new topic Reply to topic  [ 22 posts ]  Go to page Previous  1, 2
Author Message
 Post subject:
PostPosted: Wed Jan 27, 2010 6:42 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10985
Location: England
I see Brad's scheme as being like the INT x scheme from the x86 world - for the cost of minimal external hardware, it offers several (or many) handlers without the time cost of fetching the operand 'by hand'

I think of it as more of a curiosity than anything else. But Dr Jefyll has shown that much can be accomplished by hanging external hardware off a 6502.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 27, 2010 11:20 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8505
Location: Midwestern USA
On a system that is not designed to be multitasking, I agree a kernel entry jump table is less cumbersome, although not necessarily more memory efficient. However, if a multitasking kernel is being developed, a single entry point whose front end preserves the MPU state makes a lot of sense. Since OSes such as UNIX operate on the user space/kernel space protection principle, a context switch is required when kernel service is requested. Entry via a software interrupt rather than via JSR allows a common entry point to be used, in which the machine state can be preserved and the switch from user-mode to kernel-mode can be transparently made.

Consider the following: if your kernel entry is via BRK (or COP), you could code something like:

Code:
int n

to make your kernel call, where n defines the kernel service that is to be accessed. int n would, of course, be a macro, e.g.:

Code:
int      .macro operand
         .byte $00,operand
         .endm

Higher level macros could, in turn, create friendlier abstraction, so something like:

Code:
creat path,mode

would be possible, where the creat macro would look something like this:

Code:
creat    .macro path,mode
         lda #mode
         ldx #<path
         ldy #>path
         int i_creat       ;i_creat = interrupt number to create a file
         .endm

somewhat emulating the C code used to create a file in UNIX:

Code:
int creat(const *char path,mode_t mode);

Once the MPU takes the BRK vector, the first task would be to save the registers, and on a 'C02, differentiate BRK from an IRQ. Prior to performing the context switch, the stack pointer (which is actually that of the calling process) must be stored in RAM accessible to the calling process so as to allow a clean return. The next step would be to read the address that was pushed onto the calling process's stack, decrement it and use it to fetch n. With that out of the way, the code needed to switch to kernel mode can be executed. n can then be used to select the jump target within the kernel.

Doing all this would be more difficult with the jump table approach, as each entry point into the kernel would have to run the code to do what I described above. Coding the preamble into a subroutine would require that each kernel call begin with:

Code:
jsr preamble
jmp routine

preamble would have to preserve state for a clean return to the calling process. The problem with the above is calling preamble itself messes with the stack, complicating matters. With BRK entry, the stack doesn't get diddled in that fashion.

Long ago, I used to look at MS-DOS' use of INTx for OS entry as slow and cumbersome (aside from my general disdain for DOS). However, when I became familiar with UNIX and what was going on inside, I started to appreciate why OS access was through software interrupts. In particular, if a new OS function was added it wasn't necessary to recompile all the applications so they would know about the changed jump table. Instead, the OS would use the next unassigned INT number and only applications developed to use that new OS routine would have to know about it. If an older application were subsequently updated to use the new kernel feature, it could be assembled/compiled with the new INT operand table, which would most likely be maintained as an include file.

A point to ponder is a fixed jump table marries the OS developer to a specific range of addresses to avoid breaking compatibility with existing apps. If he has to move the OS for any reason he's got a bit of a problem. If, instead, he uses BRK or COP for OS entry, all he has to do is make sure the BRK or COP vector points to the right location and reassemble the kernel. Apps will never know the difference.

Just a lot of out-loud thinking on my part.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 28, 2010 7:24 am 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
BigDumbDinosaur wrote:
On a system that is not designed to be multitasking, I agree a kernel entry jump table is less cumbersome


Sorry, I have to stop you there -- the Commodore-Amiga is a full-blown, preemptive multitasking environment which continues to utilize jump tables for OS (and non-OS) entry points. In fact, Linux modules work in a manner not entirely dissimilar to how AmigaOS libraries work.

Just because Unix vendors in the 80s couldn't code their way out of a paper bag (I'm specifically referring to statically-linked a.out libraries and fixed-address shared libraries here (yeah, I'm talking to you SunOS and Irix!!)) doesn't mean it can't be done.

(footnote: I still think Unix vendors, including Linux coders, can't really code their way out of a paper bag. The reason is that they continue to uphold 40-year old multitasking concepts when VMS and AmigaOS both showed the world that they can do literally everything with event bits and asynchronous message queues. AmigaOS coders were multithreaded decades before the term became a household word, and wrote high-quality, (hard) real-time applications using these primitives. No, VMS and AmigaOS had to be shot down to the ground though -- we can't have any witchcraft here, NO SIR! No, you're going to have to use (shudder) pthreads, and you'll be effin' THANKFUL!)

(another footnote: I suggest you NOT ask me how I really feel about the state of Unix and Linux today.)

Quote:
With that out of the way, the code needed to switch to kernel mode can be executed.


I disagree -- switching to kernel mode must be a hardware-level operation that occurs immediately upon executing BRK. Otherwise, you couldn't copy buffers between user space and kernel space, AND you continue to rely on the application stack, which could well be corrupted (though, you don't have a choice on the 65xx architecture).

Quote:
Doing all this would be more difficult with the jump table approach, as each entry point into the kernel would have to run the code to do what I described above.


Since there is no concept of a "kernel mode" on a 6502/65816 system without an MMU that distinguishes between processes, this really isn't a flaw.

Quote:
Code:
jsr preamble
jmp routine


Nope.

Code:
functionA:
  .db 0, funcA_id, 96

functionB:
  .db 0, funcB_id, 96


Of course, opcode 96 is (IIRC) RTS.

We know this approach works because of the existence proof known as "libc". Back in the day, when Unix was still running on computers with 32K of RAM or less, the Unix API and standard C library were one and the same. Today, if you examine any implementation of libc for a Unix platform, you'll find that they all basically wrap calls to INT n or SYSENTER.

Quote:
In particular, if a new OS function was added it wasn't necessary to recompile all the applications so they would know about the changed jump table.


I openly invite you to research the inner workings of the Commodore-Amiga operating system some day. AmigaOS has evolved through numerous revisions, 1.0, 1.1, 1.2, 1.3, 1.4 (not released publicly), 2.00, 2.03, 2.04, 2.05, 2.1, 2.2, 3.0, 3.1, 3.5, and the latest 3.9, and provided you didn't touch internal OS data structures, your code for 1.0 will continue to run on 3.9 (although, it'll be damn ugly).

AmigaOS libraries worked by exposing a jump table, addressed off the A6 register (we're talking the 68000 architecture here). So, if you wanted to open a file via the dos.library, for example, you'd use:

Code:
  move.l _DosBase,a6
  jsr _LVOOpenFile(a6)


which in turn dropped the CPU squarely on a JMP instruction to the actual code. Because the Amiga didn't statically locate any binaries ever (you could end up with very different addresses even for many OS-level services from boot to boot), A6 provided the external linkage needed to couple two independently deployed binaries together, while the loader's relocation efforts provided internal fixups for the JMP instructions. The end result was a system that was infinitely expandable (review the Amiga's public domain space, and you'll find out what I mean by infinitely here!), despite relying on jump tables.

So, kindly don't knock jump tables. When implemented correctly, they can work quite well.

Quote:
If he has to move the OS for any reason he's got a bit of a problem.


This is why you put the jump table at the end of ROM space, just beneath the CPU vectors. Your arguments for BRK but against jump tables is mutually inconsistent, for both jump tables and ROM vectors depend on the same environmental characteristics.


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 28, 2010 3:29 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 1043
Location: near Heidelberg, Germany
BigDumbDinosaur wrote:
Doing all this would be more difficult with the jump table approach, as each entry point into the kernel would have to run the code to do what I described above. Coding the preamble into a subroutine would require that each kernel call begin with:

Code:
jsr preamble
jmp routine



This is in fact exactly what I do in my multiasking/multithreading GeckOS.

Quote:
Instead, the OS would use the next unassigned INT number and only applications developed to use that new OS routine would have to know about it. If an older application were subsequently updated to use the new kernel feature, it could be assembled/compiled with the new INT operand table, which would most likely be maintained as an include file.


You can do this with a jump table too, by just extending it at the end. Increases the amount of memory used for it though, but you need to store the address for the operation opcode in the other solution too

Quote:
A point to ponder is a fixed jump table marries the OS developer to a specific range of addresses to avoid breaking compatibility with existing apps. If he has to move the OS for any reason he's got a bit of a problem. If, instead, he uses BRK or COP for OS entry, all he has to do is make sure the BRK or COP vector points to the right location and reassemble the kernel. Apps will never know the difference.


That's why I invented the relocatable file format with late binding :-)

André


Top
 Profile  
Reply with quote  
PostPosted: Thu Jan 28, 2010 3:37 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 1043
Location: near Heidelberg, Germany
fachat wrote:

This is in fact exactly what I do in my multiasking/multithreading GeckOS.



Actually it's something like:

Code:

// application:
       jsr GETC
       ...


// kernel jump table:
         ...
GETC jmp kgetc
         ...


// kernel "module" for character communication:

kgetc
         jsr ENTRY
         ...
         actual code
         ...
         jsr EXIT
         rts

architecture specific module for kernel:

ENTRY ....

EXIT ....



The ENTRY and EXIT routines are different for my selfbuilt computer using an MMU or non-MMU machines like C64 or PET etc.

André

edit: removed bogus quote, code layout


Top
 Profile  
Reply with quote  
PostPosted: Sat Jan 30, 2010 3:35 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8505
Location: Midwestern USA
kc5tja wrote:
Since there is no concept of a "kernel mode" on a 6502/65816 system without an MMU that distinguishes between processes, this really isn't a flaw.

Of course, I would expect an MMU or an equivalent in a PLD to keep kernel space and user space separated.

Quote:
Code:
jsr preamble
jmp routine


Nope.

Code:
functionA:
  .db 0, funcA_id, 96

functionB:
  .db 0, funcB_id, 96

Of course, opcode 96 is (IIRC) RTS.
And, other than relocating the BRK opcode to the kernel, what did you accomplish?

Quote:
So, kindly don't knock jump tables. When implemented correctly, they can work quite well.

I'm not knocking anything. I was thinking out loud. Please try not to be so combative when someone puts forth an idea that appears to be antithetical to how you may see it. :)

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Sat Jan 30, 2010 4:05 am 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
BigDumbDinosaur wrote:
And, other than relocating the BRK opcode to the kernel, what did you accomplish?


Backward compatibility, and an abstraction of the system call mechanism, so that you pay the performance price only when needed.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 22 posts ]  Go to page Previous  1, 2

All times are UTC


Who is online

Users browsing this forum: No registered users and 20 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: