C PROGRAMMING SECTION - TOPICS. JJB TEMPLAR (1) Resident/Reentrant programs. >> 1 Resident/Reentrant programs. This is a discussion of what resident/reentrant programs are. It is entirely my understanding of them, and thus may be completely wrong. I am aware that many people have very strange conceptions of reality when it comes to computers (eg: complaining that C doesn't have screen handling functions, or saying that using a report-generator is "real" programming), and I am no exception. Note that there are two other sorts of "resident" programs that I know of. On the Amiga, there are "Launch and Stay Resident (LSR)" programs, which are the cback type (eg: PopCLI, AskTask, Virus_Checker). On other machines there are "Terminate and Stay Resident (TSR)" programs, which are tricks to emulate multi-tasking, and therefore not useful on the Amiga. Some preliminaries: - A "residentable" program is one that can be made resident. - A program is made up of three sections. These are: 1) code: the actual running code, and sometimes constants. 2) data: initialized data (eg: int flag1 = 1;). 3) bss: uninitialized data (eg: int flag1;). These sections are made up of "hunks" of the appropriate type. While a hunk itself is contiguous in memory, a section is not necessarily so. - There are two ways to reference data and bss sections (in Lattice C programs): 1) as an offset to (a4). a4 points to the __MERGED data and bss sections of a program. That is, all the data and bss hunks are joined together into a large __MERGED section, and are referenced from the code section via a 16-bit offset to (a4). These hunks are "near". 2) as an absolute address. Some hunks are too large to fit into the __MERGED section, or must be in CHIP memory (eg: image data), so these are referenced by an absolute (32-bit) address. These hunks are known as "far". Now on to the discussion... What is a residentable program? An ordinary program is run as follows: 1) load into memory 2) execute 3) unload from memory 4) to run it again, go to step (1). A residentable program is run as follows: 1) load into memory 2) execute 3) to run it again go to step (2). Resident programs are managed by "resident list handlers", which include: WShell, the CBM 1.3 shell, Sharp (ARP Shell), and WBRes. These programs make sure the memory-resident copy is used, and unload it when you want it unloaded. That is, a residentable program does NOT have to be re-loaded from disk (or wherever), in order to run again. It should be obvious to you that if a program uses initialized variable data, the first run of the program may have changed this variable value. So the second run of the program will be different. For example, suppose I had the following program: int flag = 0; void main() { if (!flag) { flag = 1; printf("Pass one!\n"); } else { printf("Pass two!\n"); } } The first run would set the flag variable, so the second run would not have the same result as the first run. This is patently undesirable. So it would seem that a program cannot use initialized data if it wants to be residentable. This is good news for a language like Modula-2, that doesn't formally allow initialized data(!). This is bad news to C programmers, since a lot of nice code is made a lot easier by using initialized variables. Not vital, but VERY convenient. To use initialized variables as well as be residentable, it is necessary to copy the initialized values to a seperate area the code can use, for each invocation. This is made fairly easy by the (a4) convention. A startup module can create a new __MERGED section, copy the program's data section to it, then point a4 to the new __MERGED section. This effectively makes the original data section constant, so multiple runs of the same program have no effect on each other. Initialized data was the main obstacle to residentable programs. The other is self-modifying code. This sort of code is severely frowned upon by most programmers, and is destined to break anyway on the generation of machines using the 68020 chip and higher. There are two types of "self-modifying code": 1) Code that modifies itself! Easy. 2) Constants that the program actually treats as variables. When you define a constant, it is placed into the "code" section of your program. So modifying that constant modifies the "code". Type (1) can be ignored. Lattice C doesn't generate that sort of code, and nobody should. Type (2) however does occur sometimes. The c.o startup module Lattice C provides apparently does this. It is sometimes of no significance, but should be watched out for. It is not significant (yet) if the constant is not initialized. Thus runs following the first are not dependent on the value left in the constant by preceding runs. This WILL become significant when the hardware and/or operating system care about self-modifying code for other reasons. So how do I make my Lattice C (v5) programs residentable? This is actually a doddle. There are a few requirements you must meet: 1) All initialized variables are "near". This is the default on the compiler, and you will only get "far" variables if you specifically declare them as such. 2) All "far" data references are constant. This is the case for image data (compiled with -ad to go into chip memory), for example. This is also the case for amiga.lib absolute references to library bases. 3) You link with cres.o instead of c.o. I used the above process on ty1.3, and get no complaints from WShell's "resi" command, or WShell's resident command usage (or WBRes :-). What is a reentrant program? A reentrant program is a residentable program that can run two or more invocations AT THE SAME TIME. The requirements (beyond those for a residentable program) are: 1) ALL variables must be "near". Since cres does not create copies of "far" variables, multiple invocations would actually use the same far variable, which is catastrophic. 2) There must be ABSOLUTELY NO self-modifying code (of either type). Again, since code is not copied either, at least one of the invocations would be derailed. Unless you want the value to be different for multiple invocations. For example, you could use constant variables as a sort of "shared memory" area to allow multiple invocations to communicate. If these are met, cres.o is again the startup module to link with. And again, ty1.3 is reentrant with no complaints from WShell. Some notes about the bss section: This is the "uninitialized data" section. It costs very little in terms of program size to use bss hunks, since all they require in the program file is enough information to tell the loader their size, their desired location (if any, eg: CHIP or FAST), and any symbolic info required. The loader allocates a new bss section out of free RAM when the program is loaded. The startup module takes care of initializing the bss section to all zeros. Presumably cres takes care of allocating seperate bss sections for multiple invocations of a program running at the same time. And finally, bss hunks are usually merged into the __MERGED data section for reference by the program. And finally: I was extremely impressed by the way Lattice handles this. Very nice indeed. What I had expected to be a "well, maybe..." job of turning ty into a resident AND re-entrant program turned out to be a matter of not quite an hour (tracking down which module dragged in some amiga.lib stuff). Note that ARP has some sort of resident startup code (or function) that does this sort of thing as well. I do not know the details (I don't have access to ARP 1.3 programmer docs), but those ARP fanatics amongst you may want to look into it. This is especially useful for non-Lattice people, who don't have the advantage of cres.o.