fullmer@dover.uucp (Glen Fullmer) (03/13/89)
I have a friend that has ported a generic graphics package to the Amiga. He is uses the Aztec C compiler, and I use the Lattice compiler. This means that I cannot link my graphics program to his Aztec C graphics object library. So he thought he would fix that and create a graphics Amiga-shared library that anyone can call at run time. That was fine. After a number of weeks trying to track down all that has been written about Amiga-shared libraries (which is precious little) and "ripping off" as much public-domain code that he could find, has written a single-user-at-a-time graphics library. I, being as helpful as usual, suggested that a single-user-at-a-time graphics library is not very useful, especially if one wants to write a generic widowing system with his library (which I do). I suggested that he rewrite his graphics library so that it is re-executable and reentrant (being the helpful person that I am). Given that history, we have a number of questions: 1. Why didn't the developers of the C compilers agree on a common linkable format? 2. How does one make a generic package, like his graphics package, both reexecutable and reentrant? Does it require specific register allocation via assembler programming? Or is there an easier way? A different global hook that can be used to hang environments on? 3. How does one restore the environment, for example, after an interrupt? 4. Is making a package that must retain context both over the life of a call and the life of the session different than "pure" residentable code? If so, how are they different, and what are the coding differences? 5. How did CBM do this for their libraries? 6. Why isn't it simpler? Granted, a lot of this is probably very naive for some of you guys, but where does one learn it? References? Thanks, in advance, for your support. -- _____ _ "For a successful technology, reality must take precedence" {____/ // "over public relations, for Nature cannot be fooled." \ // _ __Richard P. Feynman, Appendix F of Shuttle Disaster Report {____/ </_</_/ / <_ {sun!sunburn | cadillac} !dover!fullmer
jimm@amiga.UUCP (Jim Mackraz) (03/14/89)
In article <913@dover.uucp> fullmer@dover.UUCP (Glen Fullmer) writes:
)1. Why didn't the developers of the C compilers agree on a common linkable
) format?
The culprit was Aztec, but at the time their linking speed was so much
quicker that nobody minded. They also had different register conventions,
which will also haunt you. Read the register conventions in both
the RKM (pertains also to Lattice) and the Aztec manuals.
)2. How does one make a generic package, like his graphics package, both
) reexecutable and reentrant? Does it require specific register allocation
) via assembler programming? Or is there an easier way? A different
) global hook that can be used to hang environments on?
Write it as an Exec library, in any language, respecting the register
parameter passing interface. Each language then requires an interface
technique on the calling side, normally a link library. New lattice
can use some #pragma files instead. Create a .fd file, and then either
find tools to convert to link code or pragmas, or hand tool them. The
technique is traditionally assembler stubs. Examine the source to
Aztec's link libraries.
You will also need to do a library-side interface to get the parameters
out of registers, again compiler specific, but you only do this once,
for the compiler you implement the library with. This is typically
done in assembler.
The key point is that the interface favors neither compiler: it is
for assembly language programming. It is common practice for all
languages to translate in and out of that interface.
)3. How does one restore the environment, for example, after an interrupt?
I don't follow. Unless you are using interrupts as part of the package,
the system will restore you without your ever knowing. All registers must
be saved/restored by an interrupt, since you could be in the middle of
anything. If you want to write interrupts in C, you must have an
assembler interface that saves/restores all parameters you might
change. See the Guru's Guide on Interrupts.
)4. Is making a package that must retain context both over the life of a
) call and the life of the session different than "pure" residentable
) code? If so, how are they different, and what are the coding
) differences?
Most system libraries don't have a notion of "session," that is, there is
typically no preserved state. To do this, you would just introduce
a "handle" that is returned by InitSession() and passed to every pertinent
call (including EndSession()). I guess you could say the OpenWindow() is
doing just that. Think also about the model of a RastPort: it isn't
session-based, but it is a handle. It has an Init() (although no Create()),
and no End().
)5. How did CBM do this for their libraries?
Pretty much as I describe, except we have tools to go from .fd to
the lattice/amiga style interface links in amiga.lib. Our C functions
have assembler stubs pulling parameters out of registers. Non-conforming
vendors (such as Aztec and the Modula folks) have their own stubs, created
from the .fd's, which their applications link to for library interfaces.
)6. Why isn't it simpler?
Actually, the stuff you are asking about is pretty "mechanical" once
you gather the pieces. What is nowhere near as straightforward is
the traditional part of being re-entrant, which must be approached with
EXTREME CAUTION.
You can get bit in the butt by all sorts of things, many of which
show no problem during development time. Hanging something off a
list that is shared, toggling a bit in a shared flags word, asking
most C compilers to return a structure, changing a string (overwriting
the null terminator), all this stuff can bite your butt.
Implementing a library is a hurdle, but reentrant code is a science.
I've heard some people argue that the technology of writing libraries
is best left a little arcane, so as to keep it out of the hands of
the naive. I don't subscribe to this point of view, but I sympathize
with the intent. Writing most shared code is HARD, and the bugs
can be very subtle.
)Granted, a lot of this is probably very naive for some of you guys, but
)where does one learn it? References?
The best source is examples. I have one, elib, on the fish disks somewhere.
It is written in Aztec, small model, and includes demonstrations of link
routines for Aztec. It is not hard to add links for lattice callers.
I am also doing one up using all the new high-powered Lattice 5.0X stuff.
jimm
--
Jim Mackraz, I and I Computing "Like you said when we crawled down
{cbmvax,well,oliveb}!amiga!jimm from the trees: We're in transition."
- Gang of Four
Opinions are my own. Comments are not to be taken as Commodore official policy.
shf@well.UUCP (Stuart H. Ferguson) (03/15/89)
+-- fullmer@dover.UUCP (Glen Fullmer) writes: | I have a friend that has ported a generic graphics package to the Amiga. Great! | So he thought he would fix that and create a graphics Amiga-shared library | that anyone can call at run time. Wonderful! | 2. How does one make a generic package, like his graphics package, both | reexecutable and reentrant? Does it require specific register allocation | via assembler programming? Or is there an easier way? A different | global hook that can be used to hang environments on? Gee. It's really not as hard as all this. The basic "trick" (if you can call it that) is to organize the library interface so that it has no "global" variables. All the context and state information get stored in structures that the client program (the one who opened the library) owns. It gets these strucutres either by calling functions in the library to allocate (and free) them, or by allocating it itself and calling a library function to initialize it. Intuition uses the first strategy, and the graphics library generally uses the second. One you have all the state info in a big packet, the client just calls the library and passes it this structure. The library itself is just a collection of code that operate on these "handles" or "objects." It isn't really so much a "trick" as it is a basic design feature of an Amiga shared library. Since it sounds like the ported graphics library maintains state in global "session" variables, it may need re-designing to make it fit the Amiga style of shared library. Now, it turns out that there is a place to store "global" variables in a shared library -- the library base structure. A shared library can have a normal set of "global" variables if they are initialized at startup and never change, such as a pointer to SysBase. But if values may change during the use of the library, they need to be locked with semaphores to prevent clashes when multiple tasks are calling the same functions in the library. This is why you must call LockIBase() before looking at the window lists and other structures located in IntuitionBase. Locking protocols are no fun. | 6. Why isn't it simpler? Aw, it's not that hard, really. | Granted, a lot of this is probably very naive for some of you guys, but | where does one learn it? References? Look at Jimm's "elib" on Fish disk 87 for how to make one. Look at how the calls to Intuition and other libraries are organized to see how they maintain context for each client task. Once you understand the basics its pretty straightforward. Good luck! -- Stuart Ferguson (shf@well.UUCP) Action by HAVOC
jesup@cbmvax.UUCP (Randell Jesup) (03/15/89)
In article <913@dover.uucp> fullmer@dover.UUCP (Glen Fullmer) writes: >that anyone can call at run time. That was fine. After a number of weeks >trying to track down all that has been written about Amiga-shared libraries >(which is precious little) and "ripping off" as much public-domain code that >he could find, has written a single-user-at-a-time graphics library. I, There is an example library in the RKMs, and I think available in source code form somewhere. There are libraries (source) on fish disks too. >being as helpful as usual, suggested that a single-user-at-a-time graphics >library is not very useful, especially if one wants to write a generic >widowing system with his library (which I do). I suggested that he rewrite >his graphics library so that it is re-executable and reentrant (being the >helpful person that I am). Given that history, we have a number of questions: Most libraries should be reentrant. >1. Why didn't the developers of the C compilers agree on a common linkable > format? Ask them. Lattice supports the Amiga Object File Format, Manx (I think) preferred to use the same format they had used on other machines. I think most of the Modula writers use amiga object files, but I'm not sure. >2. How does one make a generic package, like his graphics package, both > reexecutable and reentrant? Does it require specific register allocation > via assembler programming? Or is there an easier way? A different > global hook that can be used to hang environments on? The only real trick is to set up the global data pointer correctly, and to use locking of shared global structures. Most libraries have global data in the librarybase, which you always get a pointer to at entry (in a6). You don't have to though. If you do a geta4() (check manx docs) at each entrypoint, then you can access your globals. Be careful with library routines that may use globals (stdio is right out). Use Semaphores for locking access to structures in the base when needed (sometimes Forbid()/Permit() is reasonable, but please don't use it unless needed). >3. How does one restore the environment, for example, after an interrupt? Are you writing interrupt handlers?? I think they're well documented; I highly advise NOT writing interrupt handlers in anything but ASM. >4. Is making a package that must retain context both over the life of a > call and the life of the session different than "pure" residentable > code? If so, how are they different, and what are the coding > differences? Your question isn't clear. If by "context" you mean data, then yes, many calls returns pointers (cookies) you have to pass back when doing other calls (FileHandles, for example). "Pure" code merely means there are no hard references to globals, and thus can be invoked multiple times safely. Most programs do this by either not having any globals or never modifying any globals. Some (ex: lattice with cres.o) do it by allocating the data segments, and accessing them via a base pointer. -- Randell Jesup, Commodore Engineering {uunet|rutgers|allegra}!cbmvax!jesup
papa@pollux.usc.edu (Marco Papa) (03/15/89)
In article <6279@cbmvax.UUCP> jesup@cbmvax.UUCP (Randell Jesup) writes: >In article <913@dover.uucp> fullmer@dover.UUCP (Glen Fullmer) writes: >>3. How does one restore the environment, for example, after an interrupt? > > Are you writing interrupt handlers?? I think they're well documented; >I highly advise NOT writing interrupt handlers in anything but ASM. On this one I disagree. Carl Sassenrath clearly shows that it is not really a big deal to write the interrupt handler in C (a least in MANX). You just have to know which registers have to be saved and what register does what (so you can pass the values on the stack, Matt taught me this one). MANX provides the proper entry/exit routines (int_start, int_end). I don't know about Lattice, but I don't see how more difficult it could be. The Guru's Guide is definitely very helpful for the adventurous. I wish the contents of the Guide was part of the CBM docs. -- Marco Papa 'Doc' -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= uucp:...!pollux!papa BIX:papa ARPAnet:pollux!papa@oberon.usc.edu "There's Alpha, Beta, Gamma and Diga!" -- Leo Schwab [quoting Rick Unland] -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
451061@UOTTAWA.BITNET (Valentin Pepelea) (03/15/89)
Glen Fullmer <fullmer@dover.uucp> wites in Message-ID: <913@dover.uucp> > 1. Why didn't the developers of the C compilers agree on a common linkable > format? There is a common linkable object format, that is the one which Lattice C and most assemblers use. Manx s Aztec C 4.0 apparently too will use the standard format. > 2. How does one make a generic package, like his graphics package, both > reexecutable and reentrant? Does it require specific register allocation > via assembler programming? Or is there an easier way? A different > global hook that can be used to hang environments on? This is as simple as specifying a switch on the command line for the Lattice compiler. Basically, all data has to be allocated dynamically. > 3. How does one restore the environment, for example, after an interrupt? The CPU automatically saves the current context on the supervisor stack when an interrupt occurs. To restore the context, exit the interrupt processing routine with an RTE. (return from exception) > 4. Is making a package that must retain context both over the life of a > call and the life of the session different than "pure" residentable > code? If so, how are they different, and what are the coding > differences? Yes. To retain the context over a call simply save to current registers on the user stack with the MOVEM A2-A6/D2-D7,-(A7) instruction, and restore it upon return from the call with a MOVEM (A7)+,A2-A6/D2-D7. > 5. How did CBM do this for their libraries? They used the Green Hills C compiler. All you have to do is use the Green Lettuce C compiler. > 6. Why isn't it simpler? It can t possibly simpler than this! > Granted, a lot of this is probably very naive for some of you guys, but > where does one learn it? References? Read your Lattice compiler manual, dammit. Other than that, get your hands on the RKMs. The third edition if possible. > Richard Valentin _________________________________________________________________________ "An operating system without Name: Valentin Pepelea virtual memory is an operating Phone: (613) 233-1821 system without virtue." Bitnet: 451061@uottawa Usenet: Look at the header - Ancient Inca Proverb Planet: Ontario!Canada!Earth
cmcmanis%pepper@Sun.COM (Chuck McManis) (03/16/89)
In article <913@dover.uucp> fullmer@dover.UUCP (Glen Fullmer) writes: >1. Why didn't the developers of the C compilers agree on a common linkable > format? Because they are different companies and have/had different priorities. Commodore/Amiga defined the "standard" object file format and documented it in the developers documents. Lattice, being the "official" C compiler for the Amiga used it. Aztec on the other hand already had an object module format that their code generators produced and it was simpler to retain that and only wack the linker to produce Amiga standard executables. This is a classic "Time to Market" trade-off. Anyway, that was the history. >2. How does one make a generic package, like his graphics package, both > reexecutable and reentrant? Does it require specific register allocation > via assembler programming? Or is there an easier way? A different > global hook that can be used to hang environments on? See the excellent Article by Carolyn Scheppner(sp?) in Amiga Mail on how to make your code re-entrant. If you don't get Amiga Mail, see if you can get back issues from CBM directly. (CATS, 1200 Wilson Dr, Westchester PA, 19380) Generally, reexecutable requires only that you don't use uninitialized variables or assume your globals are initialized to zeros, reentrant means that you don't store state in global variables, and you arbitrate access to those global variables that you have. >3. How does one restore the environment, for example, after an interrupt? What environment? If you context is on the stack (local variables) then you just return. If you change things that affect other processes using your code at interrupt time, then you have a bug. (Generally, during exception processing of any type one saves any values that your don't "own" like register contents, and then restore them before exit.) >4. Is making a package that must retain context both over the life of a > call and the life of the session different than "pure" residentable > code? If so, how are they different, and what are the coding > differences? Generally "life of a call" context is stored in local variables on the stack. "Life of a session" is handled in one of two ways, the first is to create a "session handle" when you first access the library. Do an OpenLibrary() followed by a NewSession() call. The library may copy a default context into the handle and then this handle is passed with all calls to the graphics library. In the case of the Amiga graphics library the "session handle" is the RastPort structure. The other method is to create a context in the task structure itself. This is much more difficult but certainly possible. Optional info : Generally, if you create a session handle that is simply a structure such as the RastPort in graphics.library, it has the advantage of speed because the handle is the data. Unfortunately, it also has the problem of compatibility later because changes to this structure will directly impact any program that "looks inside" of it. A common way around this problem is to return an index rather than the actual structure to the program. This makes the handle opaque and allows the graphics library to play around with the actual structure without breaking anyones code. >5. How did CBM do this for their libraries? Well see the RastPort discussion above. >6. Why isn't it simpler? Programming on real computers is always harder than programming on microcomputers. The flexibility and usefulness of a multitasking kernel puts certain restrictions on what you can do but opens up the capabilities of what you can accomplish. Anything can be simplified by making assumptions about what will and will not go on, but those same assumptions limit everything else as well. If you make the assumption that everyone using the graphics library will have a 2 bitplane, 640 X 200 pixel screen open, then all sorts of stuff becomes easier. You always know where to clip lines, or how many color registers you will be using etc. But that limits the other programmer who wants to use the same library with a 704 X 440 screen. >Granted, a lot of this is probably very naive for some of you guys, but >where does one learn it? References? Generally global concepts such as the differences between reentrant and reexecutable are taught in Universities. In terms of schools I am familiar with, 2 year programs will teach sort of a mechanical programming style. Using canned algorithims to solve specific problems. Whereas 4 year schools will tend to teach concepts and how to devise an algorithim. Good system design is something that comes from practice. [Writing this graphics library should be some good experience!] Whenever you write something, no matter how long you have been at it, you should go back and look at it a year later. At that time all of it's flaws and deficiencies will be glaringly apparent. Learn from that, take note of areas you didn't consider. Then add those questions to the repetoire of the ones you ask yourself when starting a new design. It's a never ending quest but after while the questions will be quite esoteric. [Example : What if I wanted to port this to a 12bit machine with only 4010 terminals for output, how would that affect the design?] The answers need not be completely relevent but they should point out weaknesses in the design. --Chuck McManis uucp: {anywhere}!sun!cmcmanis BIX: cmcmanis ARPAnet: cmcmanis@sun.com These opinions are my own and no one elses, but you knew that didn't you.
fullmer@dover.azsps.mot.com (Glen Fullmer) (03/23/89)
In article <3623@amiga.UUCP> jimm@cloyd.UUCP (Jim Mackraz) writes: >)3. How does one restore the environment, for example, after an interrupt? > >I don't follow. Unless you are using interrupts as part of the package, >the system will restore you without your ever knowing. All registers must >... >)4. Is making a package that must retain context both over the life of a >) call and the life of the session different than "pure" residentable >) code? If so, how are they different, and what are the coding >) differences? > >Most system libraries don't have a notion of "session," that is, there is >typically no preserved state. To do this, you would just introduce >a "handle" that is returned by InitSession() and passed to every pertinent >call (including EndSession()). I guess you could say the OpenWindow() is >doing just that. Think also about the model of a RastPort: it isn't >session-based, but it is a handle. It has an Init() (although no Create()), >and no End(). >Jim Mackraz, I and I Computing "Like you said when we crawled down >{cbmvax,well,oliveb}!amiga!jimm from the trees: We're in transition." > - Gang of Four I want to thank all those who responded to my original inquiry about Amiga run-time libraries. It was very helpful and enlightening. Howard Anderson, whose generic graphics system originally elicited the questions, also appreciated the comments. There was some misunderstanding concerning my reference to interrupts. No, he is not using interrupts as part of the package. The essential question was: How does one maintain session context (reentrancy) in an Amiga run-time library when multiple sessions use a set of changing globals? There was a couple of suggestions: (1) To require the user to create a session context pointer (like Raster-Pointer in Intuition) or (2) to maintain a session context table within the library. He had some additional comments about his preference to (2) and questions about methods to achieve it. Any other methods that I missed? As it is more convenient for me to post news than he, I am including his comments and questions: OK, let's try a concrete example. I have a graphics system that is a collection of library routines that can be called by the user. The user must call "gstart" first. He must call "gend" when finished using the library. There is a function called "gdraw" that the user can call to draw a line. There is a function called "gset" that allows one to change modes, such as the drawing color, that will be used for subsequent "gdraw" calls. There are of course a lot of other callable functions in the library. I don't want the user to have to bother with setting up a graphics structure for the graphics system. I don't want the user to have to bother with passing some structure pointer that is irrelevant to doing graphics. Since the graphics system library is the object that NEEDS the structure for saving mode data, it should do its own allocation, pointer-finding, and deallocation of its task-dependent global/persistent storage. The purpose of putting a generic graphics system up on the Amiga is to make it easier for the user to use. So the problem on the Amiga is how to set up a library with task-dependent global/persistent storage that, after a time-share interrupt or other interrupt, remembers where its particular task-dependent global/persistent storage area is when it again receives control from the scheduler. I so far have placed all of the global/persistent storage items in one structure. I can use MemAlloc to allocate storage for the area. The problem is how to preserve that pointer through a time-share interrupt (or any other sort of asynchronous interrupt that takes control away from me, runs something else, then returns to me.) During an interrupt, 70 bytes are pushed onto the stack representing all of the data registers, address registers, status register, and the program counter. When you find yourself back in your code after an interrupt, those 70 bytes are all you really know. (You wouldn't even know where the stack was unless one of the registers was dedicated to holding a pointer to it.) Clearly, the best way to save a pointer to a task-dependent global/persistent storage area is to DEDICATE one of the registers, say A3, to always holding the pointer to the task-dependent global/persistent storage area. This is how Multics achieved what I consider to be true reentrancy - reentrancy with per process global/persistent data - and spoiled me beyond words... (See page 68 of "Timesharing System Design Concepts", Richard Watson, McGraw-Hill, 1970. The Multics operating system, by convention, dedicates a hardware register called a "link pointer" to the "linkage information", i.e., the task-dependent storage. This is always restored after an interrupt so the task ALWAYS knows where its own storage is. All programs are shared. New storage for task-dependent data of a called function is always allocated whenever that function is first called by a new process. On Multics, the operating system takes care of all reentrancy/data mechanics.) If I had written the graphics system in assembly language, I could have dedicated a register to point to the task-dependent storage. This register would have to be reserved and untouched in the user program that calls the library function of course. There does not appear to be a way to do that if the calling program is written in C. My best guess at present is that to fake reentrancy with per-process global/persistent data, for a program written in C, where the shared code is responsible for allocating and deallocating its own global/persistent storage automatically, is to use FindTask with a null argument to get a pointer to the current task, then use tc_UserData as a storage location for the pointer to the task-dependent global/persistent storage area -- IF the Amiga designers meant for tc_UserData to be used by ME. Did they? My documentation says "per task data" but doesn't say WHOSE. (I see that it is NULL from within a CLI task.) Now even if I have that, I don't exactly like it. My "gstart" routine would have to call Forbid(), then FindTask so that the pointer could be stored in tc_UserData. Then I would have to store the pointer in a local automatic variable for further use within "gstart" then I'd have to call Permit(). That would be OK but EACH routine in the graphics library, subsequently called by the user program, would have to call FindTask to locate tc_UserData and store the pointer in a local automatic variable so that graphics mode data could be referenced through that structure pointer. What is the overhead for a FindTask? Now assuming the Commodore/Amiga people really don't want me mucking around with tc_UserData, I could define a 2xn vector in static storage that would contain the address of the current task block as determined by a FindTask call paired with the address of the corresponding global/persistent storage area. Then EACH routine in the graphics library would have to call FindTask, to get the task block pointer, then search the vector for that pointer which would yield the index of the corresponding address of global/persistent storage so that it could be placed in a local automatic variable. I don't like the idea of having to search that vector even though I know some great hash algorithms. So I really don't like any of the alternatives very well. Am I missing something? I figured out how to link to a library and pass floating point variables. I know how to DO reentrant code with per-process global/persistent data. I'm just not sure if there is an efficient way to do it in C on the Amiga. I definitely don't like the idea of the user having to pass a pointer to each routine he calls as is done with RPort. All of this began of course with the fact that the Manx and Lattice compilers cannot link each other's object segments. I had originally intended to create a library of object segments for linking in with the user program. Most of the larger machines I've worked with such as the Philco 2000, IBM 360/85, 3094-Q, Honeywell 6080, Multics and even Sun and Apollo have standard linkers on the system provided as part of the system. (At least I've always thought they were provided as part of the system.) On smaller machines I've worked on such as the Apple II and C-64, the linkers appear to have been specified by third-party software suppliers. The shared library method however seems to be a better answer than a link library if I can get it to work the way I want it to. I like the Amiga a lot. Don't misconstrue any of this as criticism of the design. Commodore/Amiga did a rather brilliant job. I am just trying to understand the Amiga conventions while attempting to implement some software whose interface I want to conform to some of my conventions. Is there an easier way to do what I want to do? _____ _ "For a successful technology, reality must take precedence" {____/ // "over public relations, for Nature cannot be fooled." \ // _ __Richard P. Feynman, Appendix F of Shuttle Disaster Report {____/ </_</_/ / <_ {sun!sunburn | cadillac} !dover!fullmer --
utoddl@ecsvax.UUCP (Todd M. Lewis) (03/23/89)
I have not tried this, but if you just want to save the programmer some typing, leaving out one parameter in your function calls (from the Amiga perspective) you could do something like: #define glibcall(a,b,c) glibcal(RportEquiv,a,b,c) Your user would have to make an initial call to set up this context variable and one more at the end to reclaim system resources consumed by the first one, but there is _MUCH_ less overhead this way than having each routine in your library do a FindTask() etc. It is just syntactic sugar, but it might make it taste more like what you want. Note, however, that Amiga programmers have gotten quite used to the pass-me-your-context-when-you-call-me way of working with libraries. It may not be such a big win after all. Especially when someone eventually has multiple contexts within one program. _____ | Todd M. Lewis Disclaimer: If you want my employer's ||\/| utoddl@ecsvax.uncecs.edu ideas, you'll have to || || _buy_ them. | || |___ (Never write a program bigger than your screen.)
ditto@cbmvax.UUCP (Michael "Ford" Ditto) (03/24/89)
In article <933@dover.azsps.mot.com> fullmer@dover.UUCP (Glen Fullmer) writes: >The essential >question was: How does one maintain session context (reentrancy) in an >Amiga run-time library when multiple sessions use a set of changing >globals? There was a couple of suggestions: (1) To require the user >to create a session context pointer (like Raster-Pointer in Intuition) >or (2) to maintain a session context table within the library. Since the reason given for prefering (2) was to eliminate the need for a user to carry around this useless (as far as the program can see) pointer, why not solve all the problems mentioned by hiding that detail in the link part of the library (the part that would normally just jsr to the shared library routine). Since this is already done anyway with the library base pointer itself, just make this "context" pointer a "hidden" variable as well. >OK, let's try a concrete example. I have a graphics system that >is a collection of library routines that can be called by the >user. The user must call "gstart" first. He must call "gend" >when finished using the library. There is a function called >"gdraw" that the user can call to draw a line. /* simplified code -- this is linked with the user's program: */ static struct Library *__glibbase; /* library base */ static void *__context; /* "session handle" for this task */ int gstart() { if (!__glibbase) /* open the library */ __glibbase = OpenLibrary("glib.library", 0); if (__context) /* only one session at a time in this task */ return -1; __context = _gstart(); /* _gstart is the "glue" function */ return __context != 0; } void gend() { _gend(__context); /* another glue function */ __context=0; /* we could now gstart() again if desired */ /* perhaps CloseLibrary(...) */ } void gdraw(arg1, arg2) { _gdraw(__context, arg1, arg2); /* another glue function */ } With that method, you get the best of both worlds: + The glib.library itself is effecient. + Users have a convenient interface. + Users can directly call the _functions if they want to have multiple sessions. >So the problem on the Amiga is how to set up a library with >task-dependent global/persistent storage that, after a >time-share interrupt or other interrupt, remembers where its >particular task-dependent global/persistent storage area is when >it again receives control from the scheduler. I so far have >placed all of the global/persistent storage items in one >structure. I can use MemAlloc to allocate storage for the >area. The problem is how to preserve that pointer through a >time-share interrupt (or any other sort of asynchronous >interrupt that takes control away from me, runs something else, >then returns to me.) You're trying real hard to find a way to do things the hard way. :-) Just don't worry about interrupts. You'll never know they happened. If you have the session pointer in an automatic (stack or register) variable, you can access it as you like, even if another task is also running your library. Each task has its own stack and registers, and you don't need to worry about what happens to yours during or after an interrupt or task switch. >During an interrupt, 70 bytes are pushed onto the stack >representing all of the data registers, address registers, >status register, and the program counter. When you find >yourself back in your code after an interrupt, those 70 bytes >are all you really know. Well, those bytes + everything else you ever knew. None of your data will change during an interrupt or task switch unless you change them. > (You wouldn't even know where the >stack was unless one of the registers was dedicated to holding a >pointer to it.) No, you could keep a copy of it in any register you like. There are no "dedicated" registers as far as context switches go. ALL your registers will be saved. And besides, IT DIDN'T MOVE, so you know where it is just as well as you did before the interrupt. > Clearly, the best way to save a pointer to a >task-dependent global/persistent storage area is to DEDICATE one >of the registers, say A3, to always holding the pointer to the >task-dependent global/persistent storage area. You can do this if you like; but nobody but your code will ever know or care. You might as well just put it in an automatic variable, any one will do. Dedicating a register to hold that pointer whenever in your library code is just another way of passing a parameter to all the functions in your library. You still need to load that pointer when your library is called from the user program, and restore the registers when you return. > This is how >Multics achieved what I consider to be true reentrancy - >reentrancy with per process global/persistent data - and spoiled >me beyond words... (See page 68 of "Timesharing System Design >Concepts", Richard Watson, McGraw-Hill, 1970. The Multics >operating system, by convention, dedicates a hardware register >called a "link pointer" to the "linkage information", i.e., the >task-dependent storage. This is always restored after an >interrupt so the task ALWAYS knows where its own storage is. ALL registers are restored after an interrupt (in Exec and any other OS I've used; I really can't imagine that Multics is any different). I think what you are asking for has nothing to do with interrupts; you just want this magic pointer to always be there for you. The way to make this happen is to pass it in as an argument to all your functions. The user doesn't have to do this, you could do it at the entry to the library or in the glue routines that the user links with. [ description of using FindTask(0) to recognize a previous caller and search for its state information ] This would work also, but it seems that you are stuck on the idea of closely tying a "session" to a task, limiting each task to only one active session. Why go to the trouble of "True Reentrancy" without allowing multiple sessions within a task? If you want to provide a convenient interface, that's great, but keep the basic structure as flexible and efficient as possible, and put the convenience as a layer on top. That way when a user comes along who prefers flexibility to convenience, your library won't be in the way. >Is there an easier way to do what I want to do? I think you're trying to do many unnecessary things. It's obvious that you know about the complexities of reentrant programming, but I think you must have learned them on a very strange system. :-) Perhaps it would help to get the source code to a library and see how things are typically done. -- -=] Ford [=- "The number of Unix installations (In Real Life: Mike Ditto) has grown to 10, with more expected." ford@kenobi.commodore.com - The Unix Programmer's Manual, ...!sdcsvax!crash!kenobi!ford 2nd Edition, June, 1972. ditto@cbmvax.commodore.com