System Queues
Volume Number: | | 5
|
Issue Number: | | 1
|
Column Tag: | | Forth Forum
|
System Queues
By Jörg Langowski, MacTutor Editorial Board
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
Looking at system queues
The utility that Im going to describe this month can be used to look at various queues of the Macintosh operating system. I was always curious to know whats going on in the background of my machine while Im working away; thats how this example came to be. At the same time, the program contains another example of popup menus (for which the correct trap call has been added in Mach2.14), and how to use the userData field in the user variable area to display messages in a task window.
A number of queues are maintained by the Mac operating system; the example wont list all of them, but some of the more important ones. Queues are linked lists of information blocks. Each block contains the address of the next list element in its first four bytes, a 16-bit number indicating the queue type in its next 2 bytes, and variable length information thereafter, as indicated below:
Fig.1: general structure of a Macintosh queue
The first element of a queue is called the queue header and the last one the queue tail. The queue tail has a link address of zero, meaning no more elements will follow. One can access the elements of a queue through a pointer to its header which is usually kept in a low memory global variable. For some of the queues the tails location is kept in another global variable for faster access.
The low memory addresses of the queues that our utility will display - vertical blanking queue, volume control block queue, drive queue, file system queue, event queue, deferred task queue and time manager queue - are defined at the beginning of the listing. (Since the address of the TM queue is not given in IM, I dug it out of a disassembly of the _InsTime trap.)
Structure of the program
Well set up a Mach 2 terminal task that contains the usual Apple, File and Edit menus (for desk accessories). The task window will contain a bar display for each queue which gives a visual indication of the size of the queue. The display bars are click-sensitive; when the mouse is clicked on one of them, a popup menu is displayed which shows the name of the queue. When this name is selected, a second window will be opened with information about the queue elements. That second window will disappear on a mouse click.
Inter-task communication in Mach2
I was happily hacking away on the example, got the rectangles drawn in the right places, the popup menus setup (note the slight difference in the interface thats used here - built-in - from my code in MT V4#4), when I got a row of bad bombs trying to write text into a second window from the queue-monitoring task. Just ADDing a new window, then doing a _SetPort and using the Mach2 text I/O routines wouldnt work at all. When it finally dawned on me what was I actually doing? The main task detects a mouse down in the content region of its window and calls the content handler (see listing) to find out which rectangle it has been clicked in, which menu to display, and to output the text in the second window. Now that is a totally trivial piece of code in a single task program like one would write in C or Pascal most of the time. In Mach2, things are different.
The mouse down event is not detected by the task that is supposed to handle it, but by the (in)famous I-O task, present in any Mach2 application. That task calls WaitNextEvent, detects which window the mouse was clicked in, and calls the event handler of the task that owns the window. So while were executing the event handler, were actually in the context of the I-O task! The standard Mach2 console output would be sent to the window associated with the I-O task. Unfortunately, there is no such window - therefore bomb.
The bottom line is that you cant use the Forth text I-O routines from event handlers that are called through the I-O task. You have to rely on the means that Mach2 provides to do inter-task communication and make a terminal task do the text output.
Mach2 text output from event handlers
How is this achieved? The User area of a Mach2 task contains two locations, UserData and UserVector. When a non-zero value is stored in UserData, the routine pointed to by UserVector will be executed. So all we need to do is define a user vector handling routine, and have the content handler of the task store some message in UserData.
The content handler of our main window simply stores the menu ID of the selected popup menu in UserData. The user vector handler finds this menu ID (it gets passed on the stack), and calls the corresponding queue display routine.
The queue display routines use a second window, qInfo. In order to have the output appear in that window and not on top of our bar display, we have to change the task window pointer momentarily; this is done by storing the qInfo window pointer in the user variable taskWindowPointer, doing the output and later restoring the original pointer.
Queues displayed
The utility displays seven queues (and you can easily add more): vertical blanking, volume control block, drive, event, file system, deferred task, and time manager queues, in that order. The display bars are not labeled, but the popup menu will show you the name of the queue when you click on a bar.
When a popup menu has been selected, a queue list is displayed in a second window. The first two columns are the element number and queue type for each list element. The remaining columns vary according to the queue displayed. For the VBL queue, the VBL procedure pointer, the tick count and the phase are listed. The VCB queue gives the name of each mounted volume, its drive number, driver and volume reference numbers, total number of allocation blocks, allocation block size and free blocks. The drive queue will give the drive number, driver reference number, file system ID, total number of blocks, and the status of the drive (locked disk, disk inserted, single or double sided for floppies). The status information is kept in the four bytes preceding the queue element (see IM II-128).
The event queue listing contains the what, message, when, where, and modifiers fields of the event. However, Im sorry to say that in this case the listing window is next to useless, because the list is always empty when it is displayed. Ill let you think of a reason why. The bar display sometimes shows queued events, when the display window is brought to the front from a partially covered position (its activate and update events).
Since I never found any pending asynchronous file system requests, I could not test the display for the FS queue, so that display routine is empty, just like the deferred task display. The time manager queue list shows the procedure pointer and count field for each time manager task.
In my system (MacII, 80MB hard disk, 5 MB memory, Appletalk and file server installed) I found a total of 8 VBL tasks, 2 time manager tasks, 2 mounted volumes (hard disk and Alisashare volume), and, interestingly, 5 different drives. I could make sense of three drives (SCSI disk, floppy drive and Alisashare disk), and can guess that a fourth drive, with the same file system ID as the Alisashare volume, might have something to do with the server. The fifth entry, with a queue type of 3, file system ID of $43C, and driver reference number of zero, doesnt make sense to me. If someone has something to say about that point Id be happier.
Why?
The crucial question - what is this utility good for? Besides satisfying the general curiosity of a system hacker, one important thing that I can imagine it would be helpful in is the detection of - you guessed it - viruses. At least background routines that install VBL or time manager tasks to do their malicious job might be spotted a lot easier when the queue display suddenly starts changing.
I did not add information about working directory control blocks, but you may do so if you like. The working directory queue header pointer is at $372.
Mach2 news
Finally, Palo Alto Shipping has published a trap compiler that allows you to add new traps to the Mach2 system without writing assembly code. The code is freely accessible on the Mach2 GEnie roundtable, but for those of you who dont have access to GEnie, Im including it on the source code disk together with some trap fixes for version 2.14. Sorry we cant print the complete code here because it is too long; also youll have a lot of glue code typed in and debugged before you have finished typing the trap compiler in by hand. Ill nevertheless print a short extract of the description:
This utility allows you to define your own trap glue. Thus as new system traps become available, you will be able to define their high-level interface symbolically (without using assembly language). It will also allow you to define a substitute syntax to the CALL interface, in case you need to modify (fix?) one of the existing traps. Note that in the latter case, you do not actually change the existing CALL, you simply define a new syntax which can be used in place of the CALL sequence.
One might wonder whether it makes good sense to include a 2K utility in an application when all you need is a few trap calls (but, see my comments below). However, for casual use you might consider putting this code in your workspace, along with your commonly used constants, mach words, and other compiler utilities (that way they will always be available during your experimental sessions). If you are working on a serious application, I would suggest the following approach. Place all variables, mach words, and compiler utilities (such as this trap compiler) in a separate segment and when you finish your application use ResEdit to remove that very same segment from your finished application.
This will work because mach words, variables, and compiler utilities (such as this trap compiler) do NOT need to be in memory during the execution of the code which they produce. Generally speaking, any word which only produces in-line code, most immediate words, or any child word which does not reference its parent at run-time can safely be removed from a finished application. If you are really concerned about making the smallest possible applications, then this is a technique which you should always use (as a final step, when you are completely finished with your program). If it seems risky to remove a segment from a finished application, just remember that there is NO WAY you can run code in another segment if that segment has NO jump-table entries (i.e. if you cant get to the code, why include it in your application?)
Mach words, variables, and (most) compiler words dont use or create jump-table entries. This same principle is why you never have to mark mach words or variables as GLOBAL (there is one exception, if you write a CODE word which explicitly states JSR <mach word> or LEA <mach word> then the defined instance of that <mach word> must be in memory at run-time. Thus if <mach word> exists in another segment, it will need to be marked GLOBAL (however, its always ok to say LEA <variable> or MOVE.L <variable>, thus variables never need to be marked as GLOBAL).
Waymen @ PASC
Thanks for that utility and the comments on segmentation.
Till next month.
Listing 1: System queue display
\ mini task to display system queues
\ © 1988 J. Langowski / MacTutor
only forth also assembler also mac
$160 constant vblqhdr
$356 constant vcbqhdr
$308 constant drvqhdr
$14a constant evtqhdr
$360 constant fsqhdr
$d92 constant dtqhdr
$b30 constant timevars
$11c constant utablebase
72 user taskwindowpointer
92 user (type)
108 user taskmenubar
144 user uservector
148 user userdata
152 user content-hook
164 user goaway-hook
168 user update-hook
172 user activate-hook
2 CONSTANT Message
10 CONSTANT Where
14 CONSTANT Modifiers
1 CONSTANT ActivateMask
300 constant appleid
301 constant fileid
302 constant editid
152 CONSTANT WrefCon
create rect1 20 w, 20 w, 90 w, 28 w,
create rect2 20 w, 35 w, 90 w, 43 w,
create rect3 20 w, 50 w, 90 w, 58 w,
create rect4 20 w, 65 w, 90 w, 73 w,
create rect5 20 w, 80 w, 90 w, 88 w,
create rect6 20 w, 95 w, 90 w, 103 w,
create rect7 20 w, 110 w, 90 w, 118 w,
CREATE APPLESTRING 01 C, $14 C,
NEW.MBAR queueBar
NEW.MENU AppleMenu
APPLESTRING AppleMenu TITLE
0 APPLEID AppleMenu BOUNDS
About Queues ...;(- AppleMenu ITEMS
NEW.MENU FileMenu
File FileMenu TITLE
0 FileID FileMenu BOUNDS
Quit FileMenu ITEMS
NEW.MENU EditMenu
Edit EditMenu TITLE
0 EDITID EditMenu BOUNDS
(Undo/Z;(-;(Cut/K;(Copy/C;(Paste/V;(Clear EditMenu ITEMS
NEW.MENU vblmenu
vbl vblmenu TITLE
-1 150 vblmenu BOUNDS
VBL Tasks;(- vblmenu ITEMS
NEW.MENU vcbmenu
vcb vcbmenu TITLE
-1 151 vcbmenu BOUNDS
Vol contrl blks;(- vcbmenu ITEMS
NEW.MENU drvmenu
drv drvmenu TITLE
-1 152 drvmenu BOUNDS
Drives;(- drvmenu ITEMS
NEW.MENU evtmenu
evt evtmenu TITLE
-1 153 evtmenu BOUNDS
Events [???];(- evtmenu ITEMS
NEW.MENU fsmenu
fs fsmenu TITLE
-1 154 fsmenu BOUNDS
File System;(- fsmenu ITEMS
NEW.MENU dtmenu
dt dtmenu TITLE
-1 155 dtmenu BOUNDS
Def Tasks;(- dtmenu ITEMS
NEW.MENU tmmenu
tm tmmenu TITLE
-1 156 tmmenu BOUNDS
Time manager;(- tmmenu ITEMS
NEW.WINDOW SysQueue
System Queues SysQueue TITLE
250 50 350 250 SysQueue BOUNDS
Rounded Visible CloseBox GrowBox SysQueue ITEMS
NEW.WINDOW qInfo
Queue Info qInfo TITLE
50 50 250 500 qInfo BOUNDS
NoGrow Invisible NoCloseBox NoGrowBox qInfo ITEMS
500 2000 terminal queues
CODE unpack
MOVE.L (A6),D0
CLR.L D1
MOVE.W D0,D1
CLR.W D0
SWAP.W D0
MOVE.L D0,(A6)
MOVE.L D1,-(A6)
RTS
END-CODE MACH
: beep ( n )
0 do 1 call sysbeep loop
;
: wait { #ticks | time -- }
call tickcount #ticks + -> time
begin pause
call tickcount time >
until
;
: popup.select { menu pt | point -- menuID item# }
pt -> point
^ point call localtoglobal
menu @ point unpack 1
call popupmenuselect
unpack
;
: do.content { | pt -- }
CALL FrontWindow CALL SetPort
qInfo call HideWindow
RUN-CONTENT
EVENT-RECORD Where + @ -> pt
^ pt CALL GlobalToLocal
0
pt rect1 CALL PtInRect
IF drop vblmenu THEN
pt rect2 CALL PtInRect
IF drop vcbmenu THEN
pt rect3 CALL PtInRect
IF drop drvmenu THEN
pt rect4 CALL PtInRect
IF drop evtmenu THEN
pt rect5 CALL PtInRect
IF drop fsmenu THEN
pt rect6 CALL PtInRect
IF drop dtmenu THEN
pt rect7 CALL PtInRect
IF drop tmmenu THEN
?dup IF ( rectangle was selected)
pt popup.select
IF ( popup selection was made )
( menuID ) userData task-> queues !
THEN
THEN
;
: draw.rects
rect1 call framerect
rect2 call framerect
rect3 call framerect
rect4 call framerect
rect5 call framerect
rect6 call framerect
rect7 call framerect
;
: clr.rects
rect1 call eraserect
rect2 call eraserect
rect3 call eraserect
rect4 call eraserect
rect5 call eraserect
rect6 call eraserect
rect7 call eraserect
;
: blackBar { rect pixels | locBR locTL }
rect ^ locTL 8 cmove
^ locBR w@ ( bottom ) pixels -
^ locTL w!
^ locTL call paintrect
;
: #elems { qhdr | elems -- #.of.queue.elements }
0 -> elems
2 +> qhdr
begin
qhdr @ ?dup while
-> qhdr
1 +> elems
repeat
elems
;
: display.queues { | -- }
clr.rects
draw.rects
rect1 vblqhdr #elems 4* blackBar
rect2 vcbqhdr #elems 4* blackBar
rect3 drvqhdr #elems 4* blackBar
rect4 evtqhdr #elems 4* blackBar
rect5 fsqhdr #elems 4* blackBar
rect6 dtqhdr #elems 4* blackBar
rect7 timevars @ 8 + #elems 4* blackBar
;
: dsp.vbl { | qelemPtr n -- }
cls
qinfo Vertical Blanking Tasks
call SetWTitle
. ----------------------------------------------------------------
cr
. task# qtype ProcPtr Count Phase cr
. ----------------------------------------------------------------
cr
vblqhdr 2+ -> qelemptr
1 -> n
begin
qelemptr @ ?dup while
-> qelemptr
n 3 .r 3 spaces
hex
qelemptr 4 + w@ 5 .r space
qelemptr 6 + @ 8 .r space
qelemptr 10 + w@ 5 .r space
qelemptr 12 + w@ 5 .r cr
decimal
1 +> n
repeat
;
: dsp.vcb { | qelemPtr n -- }
cls
qinfo Volume Control Blocks
call SetWTitle
. --------------------------------------------------------------------------------------------------------------------------------------------------
cr
. vcb# qtype Volume name Drive dRef# vRef# #blks blksz free cr
. --------------------------------------------------------------------------------------------------------------------------------------------------
cr
vcbqhdr 2+ -> qelemptr
1 -> n
begin
qelemptr @ ?dup while
-> qelemptr
n 3 .r 3 spaces
hex
qelemptr 4 + w@ 5 .r space
qelemptr 44 + count dup rot swap type
27 swap - spaces
qelemptr 72 + w@ 5 .r space
qelemptr 74 + w@ 5 .r space
qelemptr 78 + w@ 5 .r space
qelemptr 26 + w@ 5 .r space
qelemptr 28 + @ 5 .r space
qelemptr 42 + w@ 4 .r cr
decimal
1 +> n
repeat
;
: dsp.drv { | qelemPtr n -- }
cls
qinfo Drives
call SetWTitle
. ------------------------------------------------------------------------------------------
cr
. drv# qtype Drive dRef# FSID #blks l dd ss cr
. ------------------------------------------------------------------------------------------
cr
drvqhdr 2+ -> qelemptr
1 -> n
begin
qelemptr @ ?dup while
-> qelemptr
n 3 .r 3 spaces
hex
qelemptr 4 + w@ 5 .r space
qelemptr 6 + w@ 5 .r space
qelemptr 8 + w@ 5 .r space
qelemptr 10 + w@ 4 .r space
qelemptr 12 + w@
qelemptr 4 + w@ 1 = IF
qelemptr 14 + w@ 65536 * +
THEN
8 .r space
qelemptr 4- c@ $80 AND
IF ascii y emit ELSE ascii n emit THEN space
qelemptr 3- c@ 2 .r 2 spaces
qelemptr 1- c@ $80 AND
IF ascii n emit ELSE ascii y emit THEN cr
decimal
1 +> n
repeat
;
: dsp.evt { | qelemPtr n -- }
cls
qinfo Queued Events
call SetWTitle
. ------------------------------------------------------------------------------------------------
cr
. drv# qtype What Message When Where Mods cr
. ------------------------------------------------------------------------------------------------
cr
evtqhdr 2+ -> qelemptr
1 -> n
begin
qelemptr @ ?dup while
-> qelemptr
n 3 .r 3 spaces
hex
qelemptr 4 + w@ 5 .r space
qelemptr 6 + w@ 5 .r space
qelemptr 8 + @ 8 .r space
qelemptr 12 + @ 8 .r space
qelemptr 16 + @ 8 .r space
qelemptr 20 + w@ 4 .r cr
decimal
1 +> n
repeat
;
: dsp.fs
cls
qinfo File System Requests
call SetWTitle
;
: dsp.dt
cls
qinfo Deferred Tasks
call SetWTitle
;
: dsp.tm { | qelemPtr n -- }
cls
qinfo Time manager
call SetWTitle
. ---------------------------------------------------- cr
. task# qtype ProcPtr Count cr
. ---------------------------------------------------- cr
timeVars @ 10 + -> qelemptr
1 -> n
begin
qelemptr @ ?dup while
-> qelemptr
n 3 .r 3 spaces
hex
qelemptr 4 + w@ 5 .r space
qelemptr 6 + @ 8 .r space
qelemptr 10 + w@ 5 .r cr
decimal
1 +> n
repeat
;
: do.user
qInfo dup call showwindow call selectwindow
qInfo taskwindowpointer !
( menuID ) CASE
150 OF dsp.vbl ENDOF
151 OF dsp.vcb ENDOF
152 OF dsp.drv ENDOF
153 OF dsp.evt ENDOF
154 OF dsp.fs ENDOF
155 OF dsp.dt ENDOF
156 OF dsp.tm ENDOF
ENDCASE
SysQueue taskwindowpointer !
;
: do.update { | pt -- }
sysQueue call setport
draw.rects
run-update
;
: do.activate
event-record modifiers + w@
1 AND \ Activate event?
IF
call DrawMenuBar
ELSE
THEN
;
: do.close
bye
;
: INIT-MBAR
queueBar ADD
queueBar APPLEMENU ADD
APPLEMENU @ ascii DRVR CALL ADDRESMENU
queueBar FileMenu ADD
queueBar EditMenu ADD
queueBar vblmenu add
queueBar vcbmenu add
queueBar drvmenu add
queueBar evtmenu add
queueBar fsmenu add
queueBar dtmenu add
queueBar tmmenu add
;
: DO-APPLE { item# | [ 32 lallot ] daName -- }
item# 1 =
IF3 beep
ELSE AppleMenu @
item# ^ DAName CALL GetItem
^ DAName CALL OpenDeskAcc DROP
THEN
;
: do-file
drop bye
;
: MBAR-HANDLER ( item# menuID - )
CASE
APPLEID OF DO-APPLE ENDOF
FILEID OF DO-FILE ENDOF
drop
ENDCASE
0 CALL HILITEMENU
;
: go.queue { | mb -- }
activate
[] do.content content-hook !
[] do.update update-hook !
[] mbar-handler menu-vector !
[] do.activate activate-hook !
[] do.close goaway-hook !
[] do.user uservector !
begin
pause
sysQueue call setport
display.queues
60 wait
again
;
: start
SysQueue add
QInfo add
SysQueue queues build
SysQueue WRefCon + @
QInfo WRefCon + !
SysQueue dup call selectwindow call setport
init-mbar
queueBar queues mbar>task
queues go.queue
;