|
| Charles Curley - Software Engineer, Writer | << | < | > | >> + Larger Font | - Smaller Font |
Charles Curley |
A Forth Utility: atexit
| Abstract | The Design | The Implementation |
| A Debugging Tool | Conclusions | Permissions |
| Appendix: The Complete Source | ||
ANSI C has a wonderful facility called atexit(). Pass it a pointer to a function which takes no arguments and returns no results. That function will be executed when the C program exits back to the operating system. It is useful for closeout functions. For example, unhooking vectors, or printing out a nice "Goodbye" message. This article is about implementing such a facility in Forth.
The Linux man page's synopsis for atexit() is as follows:
#include <stdlib.h>
int atexit(void (*function)(void));
Actually, it is about implementing two such facilities. Forth is often used in embedded systems, and so should have a "cold start" capability as well as an exit. The cold start and exit do the same thing in an embedded environment. They both re-initialize everything and re-start the software. In an operating system environment, the cold start functions do different things. The exit function ends the Forth process, and returns execution to the calling process. The cold start function returns the Forth system to the bare kernel, disposing of any compiled additions and re-initializing the Forth environment.
This code was written in fast-Forth on the Atari ST, a 68000 based computer. All data are 32 bits wide, and that fact is hard coded into the source. fast-Forth is my own 68000 BSR/JSR threaded Forth, which I described in Forth Dimensions Volume XIV, nos. 5 and 6, January/February and March/April, 1993. The concepts should be portable to almost any Forth. The words f@ and f! are fast (but word-aligned) versions of @ and !, respectively.
A complete listing is provided at the end of the article.
What we need are two arrays or lists to store the functions to be executed. We need code to add functions to each array, code to execute the arrays in the opposite order, and then some handy tools such as a way to list the contents of each array. With re-usability in mind, we will build generic code, and then apply it to each array.
We start with some housekeeping. It is good Forth practice to save the base for numeric conversion while compiling, and then return it to the previous value. So we start with:
base f@ hex
This will be matched at the end of the source with:
base f!
With good information hiding in mind, the next thing we do is build a vocabulary to hide functions from the casual user (and also speed up dictionary searches).:
vocabulary exits immediate exits definitions
The next thing is to define a constant for the maximum number of functions allowed in each list. The purpose of the constant is to allow easy modification of the code. For example, an embedded system programmer might fine tune the list(s) to hold exactly the number of pointers, or vectors, the application requires.
40 constant outfuncs
The next step is to build two lists, one for functions to be executed on exit, and the other for functions to be executed on a cold start.
create exitfuncs exitfuncs outfuncs 4* dup allot erase create coldfuncs coldfuncs outfuncs 4* dup allot erase
Each line creates a variable, then allocates storage with the word allot. The code then erases the allocated storage. This allows us to consider the lists as zero-terminated (i.e. a "vector" of 0 indicates the end of the list). Zero termination depends on the idea that 0 can never be a valid execution address, a reasonable assumption.
We need the ability to add to a given list. This word will later be used to build words to add to each list. addto, as the stack diagram indicates, takes a cfa (code field address, or execution address) and the address of an list. It scans for the end of the list, and appends the cfa to the list. If the list is full, an error is indicated.
: addto \ cfa array --- | add cfa to array [ outfuncs 4* ] literal bounds do i f@ 0= if i f! 0 then 4 +loop 0b ?error ;
We then switch to the forth vocabulary, and define two public words. These are the words programmers may use to add to the exit and cold execution lists. Each takes the name of a function, gets its cfa, and adds that to the appropriate list.
forth definitions : atexit: \ --- | add following function to exit funcs exits [compile] ' exitfuncs addto ; : tcold: \ --- | add following function to cold funcs exits [compile] ' coldfuncs addto ;
A typical use is to restore the screen pallet colors of the Atari on exit or on a cold start.
atcold: palcold atexit: palcold
The reason to restore the pallet on exit is obvious. The reason to do so on a cold start is a bit more subtle. Part of the process of bringing in the utilities after a cold start is to get the existing pallet stored so that it can be restored later. In order for this to work correctly, the original pallet must be present.
Now that we have the lists, and can stuff them, we need to write the code that walks through each list, executing each address in turn. The general purpose code is hidden from the casual user.
exits definitions : doarray \ addr --- | execute all funcs in array 4- [ outfuncs 4* ] literal bounds swap do i f@ -dup if execute then -4 +loop ;
Given the address of a function list, this code walks through, and executes every non-zero value in it. Note that there is no error checking!
Also, we execute the list in the order opposite to that in which the functions were added, in effect making the list a stack. This will allow applications to clean themselves up in the order opposite to that in which they were compiled.
For example, a programmer might load a debugger, which would take over certain processor vectors. It would also preserve all the processor vectors in case a later application mucked with them. The code to do the restore would be added to the atcold and atexit lists. An application, say a device driver, might then take over a processor vector. Its exit code would restore that one vector. That restoration code has to execute before the debugger's in order to ensure that the system is properly restored at the end of the process.
One thing I did not do here was to add an else clause after the test for a zero value: -dup if execute else leave then. One application of this code is in embedded systems, and it is possible that the extra time scanning the list was worth the few bytes saved by eliminating the extra code. This is code which is not executed very often, and probably is not time critical when it is called. It is a judgment call.
The next two functions are both public. They are the new cold start and exit code, which redefine the previous words. Typically, the kernel words bye and cold are the ones called by these two.
forth definitions : bye \ leave the system, doing list as we go. exits exitfuncs doarray bye stop : cold \ reset the system, doing list as we go. exits coldfuncs doarray cold stop
Stop is a fast-Forth specific compiler directive which ends compilation without adding any exit code to the word being compiled. As execution will never return from either cold or bye, this saves us a few bytes of code.
The code we have seen so far is designed to be added to a Forth application fairly early on. It is the guts of the application code. The next screen shows a handy debugging utility which can be loaded on top of an application. It lets the user print out the contents of each list.
We start this code with some housekeeping. We set the vocabulary, and then remove any temporary code from the dictionary by forgetting the boundary word task. We then re-define it, to re-establish the boundary between temporary and permanent words. Since we are re-defining task before the new code, we are making it temporary also.
forth definitions forget task forth definitions : task ;
As usual, we start with the generic code to walk through a given list. This code lists the contents of the list in the order in which it will be executed.
exits definitions : .array ?cr \ addr --- | show all funcs in array 4- [ outfuncs 4* ] literal bounds swap do i f@ -dup if c>n id. col then -4 +loop ?cr ;
col and c>n are fast-Forth specific words. col is used to format output in nice columns. c>n, when given a code field address, returns the appropriate name field address, ready for printing by id..
This word is used by the two following words to print out the contents of the two lists.
forth definitions : .byes \ show all exit functions exits exitfuncs .array ; : .colds \ show all cold start functions exits coldfuncs .array ;
At first glance this is an application where create ... does> defining words would be very useful. Had the application here been more general, it might have been suitable for a defining word or three. However, with only two lists, there is little scope for defining words, so a simpler approach was selected.
One place this facility has proved very useful is in preserving a history of command lines executed. fast-Forth has a history function similar to that in any good Unix shell or to that provided to Mess-DOS by the Microsoft add-on program doskey. Most Unix shells preserve their command lines by writing the history to disk between the time the user hits return, to initiate execution, and the time the user's process is actually spawned. fast-Forth is designed to be used over a serial link if necessary, so the idea of saving the history every time the user hits the enter key is a bit much. But with the atexit: facility, the history can be preserved from session to session, a useful facility indeed.
This code is released to the public domain. If you use it, please give an appropriate attribution.
The complete source follows:
Scr # 441
0 \ atexit: emulate ANSI C function ( 6 11 93 CRC 20:21 )
1 base f@ hex
2 vocabulary exits immediate exits definitions
3
4 40 constant outfuncs
5
6 create exitfuncs exitfuncs outfuncs 4* dup allot erase
7
8 create coldfuncs coldfuncs outfuncs 4* dup allot erase
9 -->
10
11
12
13
14
15
Scr # 442
0 \ atexit: emulate ANSI C function ( 7 11 93 CRC 20:58 )
1 : addto \ cfa array --- | add cfa to array
2 [ outfuncs 4* ] literal bounds do
3 i f@ 0= if i f! 0 then 4 +loop 0b ?error ;
4
5 forth definitions
6 : atexit: \ --- | add following function to exit funcs
7 exits [compile] ' exitfuncs addto ;
8
9 : atcold: \ --- | add following function to cold funcs
10 exits [compile] ' coldfuncs addto ;
11 -->
12
13
14
15
Scr # 443
0 \ atexit: emulate ANSI C function ( 11 1 96 CRC 10:31 )
1 exits definitions
2 : doarray \ addr --- | execute all funcs in array
3 4- [ outfuncs 4* ] literal bounds swap do
4 i f@ -dup if execute then -4 +loop ;
5
6 forth definitions
7 : bye \ leave the system, doing list as we go.
8 exits exitfuncs doarray bye stop
9
10 : cold \ reset the system, doing list as we go.
11 exits coldfuncs doarray cold stop
12
13 base f!
14
15
fastForth on Atari ST (c) 1985-97 by Charles Curley
Sunday 23 March, 1997 5:59:26
Scr # 444
0 \ atexit: emulate ANSI C function ( 18 11 93 CRC 21:45 )
1 forth definitions forget task
2 forth definitions : task ;
3
4 exits definitions
5 : .array ?cr \ addr --- | show all funcs in array
6 4- [ outfuncs 4* ] literal bounds swap do
7 i f@ -dup if c>n id. col then -4 +loop ?cr ;
8
9 forth definitions
10 : .byes \ show all exit functions
11 exits exitfuncs .array ;
12
13 : .colds \ show all cold start functions
14 exits coldfuncs .array ;
15 editor flush .byes .colds
fastForth on Atari ST (c) 1985-97 by Charles Curley
Sunday 23 March, 1997 5:59:26
|
| << | <
| > | >>
| Welcome
| Software
| Communications
| Classes
| Resume
| Sample Code
| Thomas Jefferson: Patron Saint of the Internet
| Yum Repository Notes
| NFS and Firewalls on Fedora Core
| Netiquette
| NT Emacs Installation
| My .emacs File
| Notes on OpenSSH
| Bare Metal Recovery
| Fn
| Rms
| Dump
| Register
| Atexit
| Graphics Tree Walker
| which.nvidia
| buildiso
| wallpaper2
| gps
| Fedora Gpsdrive RPMs
| Single Source Frames
| Notes
| A Bug Notification
| Helpful Little Paperclip
| Linux on Lenovo R51
| Wyoming Travel
|