home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / GLOBEN.ZIP / GLOBENV.DOC < prev    next >
Text File  |  1987-10-16  |  24KB  |  504 lines

  1.                 THE GLOBAL ENVIRONMENT PACKAGE
  2.                      (version of 10/16/87)
  3.  
  4. The Global Environment Package extends the OS/2 system with a minor but
  5. useful feature.  It also serves as a demonstration of how OS/2 can be
  6. extended using a Dynamic Link Library.
  7.  
  8. ==============  Necessary Claims & Disclaimers to Affront You
  9.  
  10. The stuff described here is incomplete work in progress and will probably
  11. cause problems of all kinds up to and including mildew.  If you use it as
  12. anything other than an object of scorn, you do so at your own risk.
  13.  
  14. The work described is copyright 1987 by David E. Cortesi (CIS 72155,450;
  15. 415 Cambridge Ave. #18, Palo Alto, CA 94306).  All rights reserved.
  16. Propogate it without retaining this notice, or sell it without asking
  17. permission, and I will wreak such vengeance on you as the law permits.
  18.  
  19. ==============  Functional Summary to Impress You
  20.  
  21. The Global Environment (GEv) is a store of named variables that is
  22. maintained as a global resource, accessible from any program in the system.
  23. Each variable in the GEv has a name which is an ASCIIZ string.  A variable
  24. may also have a value, which is a string of bytes of any format.  A large
  25. number of variables may be defined.  The aggregate size of names and values
  26. may grow to approximately 64KB, although the initial GEv space is only
  27. about 2KB.
  28.  
  29. The GEv is maintained by service routines that provide the following
  30. six functions:
  31.  
  32.         - enter a name
  33.         - assign a new value to a name
  34.         - find out if a name exists and get the size of its value
  35.         - get a copy of a name's value
  36.         - delete a name (and its value if any)
  37.         - get the next name lexically greater than a given string
  38.  
  39. Multiple processes may use these functions concurrently.  OS/2 facilities
  40. are used to synchronize access among processes.  Multiple processes can
  41. interrogate the GEv concurrently.  If a process wants to update the GEv,
  42. it is automatically suspended until any current processes have finished;
  43. then it gets exclusive access to do its work and others are suspended until
  44. it is done.
  45.  
  46. ==============  Package Contents to Dazzle You
  47.  
  48. The Package consists of a Dynamic Link Library (DLL) and three supporting
  49. programs.
  50.  
  51. The dynamically-linked ("dynalink") code that creates and maintains the GEv
  52. is defined in GLOBENV.ASM.  The assembled, linked object is in GLOBENV.DLL.
  53. The file GLOBENV.LIB defines the dynalink code to the programs that will
  54. call on it.
  55.  
  56. The GLOBAL command gives the operator a command interface to the GEv by
  57. which names and values may be displayed, or names may be defined or given
  58. new values or deleted.
  59.  
  60. The GLOBLOAD command defines GEv names and assigns values in bulk, working
  61. from a text file of assignment statements.  This command may be named by a
  62. "run=" statement in the OS/2 configuration file in order to create and
  63. initialize the GEv.
  64.  
  65. The GLOB2SET command displays selected global variables and their values
  66. using the syntax of the SET command.  If its output is redirected into a
  67. file of type .cmd and that file executed, the result is to copy variables
  68. from the global environment into the local environment of a command shell.
  69.  
  70. =============   ARC Contents to Confuse You
  71.  
  72. The files in GLOBENV.ARC are as follows:
  73.  
  74.         GLOBENV.DOC     this file
  75.  
  76.         GLOBENV.ASM     dynalink code
  77.         GLOBENV.DEF     link definition file
  78.         GLOBENV.DLL     dynamic link library
  79.         GLOBENV.LIB     object library for linking client programs
  80.  
  81.         GLOBENV.H       header file defining service functions
  82.  
  83.         GLOBAL.C        global command
  84.         GLOBAL.EXE      compiled, linked with CodeView options
  85.  
  86.         GLOBLOAD.C      globload command
  87.         GLOBLOAD.EXE
  88.  
  89.         GLOB2SET.C      glob2set command
  90.         GLOB2SET.EXE
  91.  
  92. ==============  Installation to Snow You
  93.  
  94. To make the GEv accessible, the dynalink library GLOBENV.DLL must be copied
  95. into the directory defined by the "libpath=" statement in the config.sys
  96. file.  (If there is no "libpath=" statement, it must be in the root
  97. directory of the boot disk.)
  98.  
  99. The three .EXE files from the archive must be located in some directory on
  100. the normal execution path for protect-mode programs.
  101.  
  102. OS/2 loads dynalink code from disk the first time it loads a program that
  103. uses the dynalink package, and keeps the code in storage so long as any
  104. program that uses it is active.  The GLOBENV.DLL code creates the GEv when
  105. it is first loaded and the GEv will persist as long as the code is in
  106. storage.
  107.  
  108. Therefore, in order to initialize the GEv and keep it in storage, some
  109. program that uses GLOBENV.DLL must be in storage at all times.  The
  110. GLOBLOAD command is the preferred program.  There are three ways to make it
  111. active at all times.
  112.  
  113. For testing purposes GLOBLOAD may be started manually:
  114.  
  115.                 globload -z -t
  116.  
  117. The program will start and never end.  Switch to another OS/2 command line
  118. to do other work.  The program can be ended with control-Break, but then
  119. the GEv will vanish along with any variables loaded into it.
  120.  
  121. Again for testing purposes GLOBLOAD may be started and detached manually:
  122.  
  123.                 detach globload -z -t
  124.  
  125. This has the same effect as the first command, except that the program
  126. can't be terminated, and the command line is free for other commands.
  127.  
  128. Finally, the program can be started during bootstrap by placing the
  129. following line in the config.sys file:
  130.  
  131.                 run=\globload.exe -z -t
  132.  
  133. The entire program filename ("globload.exe") must be specified.
  134. Furthermore, the entire file path must be specified (there isn't any
  135. execution "path" variable defined when the config file is being processed).
  136. In the example the path is shown as simply \, which is correct only if
  137. GLOBLOAD.EXE is located in the root directory of the boot disk.
  138.  
  139. Any of these three invocations may also name a file of assignment
  140. statements to be loaded into the GEv.  If the standard load of variables
  141. was defined in \GEV\STDLOAD.DAT, the config.sys line would read
  142.  
  143.                 run=\globload.exe -z -t \gev\stdload.dat
  144.  
  145. Once GLOBLOAD has been started from the config.sys file, the Global
  146. Environment code will always be active.  Two results follow.
  147.  
  148. First, the GLOBENV.DLL file is always open.  Attempts to delete, rename, or
  149. copy over it will fail with a "sharing error" message.  In order to replace
  150. it, you must edit the config.sys file to insert the word "rem" in front of
  151. the "run=" statement, and reboot.  Then the GEv will not be available and
  152. the DLL can be replaced.
  153.  
  154. Similarly, the GLOBLOAD.EXE file named in the "run=" statement will also be
  155. perpetually open and can't be replaced.  Furthermore, although you can
  156. execute the GLOBLOAD command as normal, you will always be executing the
  157. in-storage copy of it.  You won't be loading it from disk even though you
  158. are invoking it from a command line.  This can cause great puzzlement when
  159. you make a small change in the program and try to test the change...
  160.  
  161. ==============  Recompile and Relink Methods to Tempt You
  162.  
  163. If it is necessary to recreate any of these files, use the following
  164. commands.
  165.  
  166. To recreate GLOBENV.DLL:
  167.  
  168.                 masm globenv;
  169.                 link globenv,,nul,doscalls,globenv.def
  170.  
  171. The file DOSCALLS.LIB must be available on the LIB= path.  Be aware that if
  172. the dynalink code is active, that is, if GLOBLOAD with the -z option has
  173. been started in the config.sys file as described above, then the copy of
  174. GLOBENV.DLL in the "libpath=" directory can't be erased or rewritten.
  175.  
  176. To recreate GLOBENV.LIB:
  177.  
  178.                 implib globenv.lib globenv.def
  179.  
  180. Note that the .LIB file is constructed only from the .DEF file.  There is
  181. no dependency on the .DLL file and no need to recreate the .LIB file when
  182. the .DLL is changed.
  183.  
  184. To recreate any of the command files:
  185.  
  186.                 cl -Zi [name].c /link globenv
  187.  
  188. The GLOBENV.H file must be available in the current directory or the
  189. directory named by the INCLUDE environment variable.  The programs are
  190. small-model with no special features or requirements except for needing
  191. GLOBENV.LIB to satisfy their external references.  The -Zi option is needed
  192. only to debug with CodeView.
  193.  
  194. The GLOBLOAD.EXE file that was named in the config.sys file (or a DETACH
  195. command) cannot be replaced.  If a new version is created in a different
  196. library, you can't test it (no, not even if you give an explicit path)
  197. because OS/2 always executes the copy in storage.  You can test the new
  198. version by renaming it.
  199.  
  200. ==============  The Exported Functions to Tantalize You
  201.  
  202. Here are the declarations and functional specifications of the dynalink
  203. entry points.  They are effectively part of the OS/2 system interface, once
  204. the dynalink code has been loaded.  These are what the GLOBAL, GLOBLOAD and
  205. GLOB2SET commands use.  Any program may use them and there are no other
  206. interfaces.
  207.  
  208. First the return values of all functions:
  209.  
  210.         0 = done as requested
  211.         1 = name not found
  212.         2 = name already exists
  213.         3 = no space for name a/o value
  214.         4 = returned value had to be truncated
  215.  
  216. These will become clearer below.
  217.  
  218.         extern int far pascal GLBENTER(char far * thename);
  219.  
  220. The null-terminated character string is entered as a name to the GEv.
  221. There is no restriction as to length or contents of the name.  Names ought
  222. to be printable and keyboardable but there's no check on that.  Returns
  223. either 0, 2 or 3.
  224.  
  225.         extern int far pascal GLBDELETE(char far * thename);
  226.  
  227. The null-terminated string is looked up as a name; if found, it and any
  228. value it might have are deleted from the GEv.  There's no concept of
  229. ownership; any process may enter a name and any process may delete it.
  230. Returns either 0 or 1.
  231.  
  232.         extern int far pascal GLBQUERY(char far * thename
  233.                                       ,unsigned far * thesize);
  234.  
  235. The null-terminated string is looked up; if found, the size of its value
  236. (which may range from zero to something less than 65K) is set in the second
  237. parameter.  Returns either 0 or 1.
  238.  
  239.         extern int far pascal GLBSTORE(char far * thename
  240.                                       ,void far * thevalue
  241.                                       ,unsigned thelen);
  242.  
  243. The null-terminated string given as the first parameter is looked up.  If
  244. it exists as a name its present value (if any) is discarded and the value
  245. given in the second parameter is copied to the GEv and assigned to the
  246. name.  The new value is NOT null-delimited; its length is given by the
  247. third parameter.  The GLOBAL and other commands assume that values are
  248. printable but that doesn't have to be the case.  Returns either 0, 1 or 3.
  249.  
  250.         extern int far pascal GLBFETCH( char far * thename
  251.                                       , void far * valbuff
  252.                                       , unsigned far * thesize);
  253.  
  254. The first parameter specifies a null-terminated string supposed to be a
  255. name.  The third parameter specifies a maximum value-size that can be
  256. placed in the buffer specified by the second parameter.  The name is looked
  257. up; if it exists in the GEv, its present value is copied to the second
  258. parameter for a size not exceeding the count in the third parameter.  The
  259. number of bytes copied is returned in the third parameter.  Returns either
  260. 0, 1 or 4.
  261.  
  262.         extern int far pascal GLBNEXT ( char far * thename
  263.                                       , int strmax
  264.                                       , char far * strbuff
  265.                                       , int far * thesize);
  266.  
  267. This function is used to step through the names in lexical order.  The
  268. first parameter specifies a null-terminated name or name-prefix.  The third parameter specifies a
  269. buffer to receive the next higher name in sequence, and the second
  270. parameter gives the maximum size of that string buffer.  The fourth
  271. parameter receives the value size of the returned name, as for GLBQUERY.
  272.  
  273. The NEXT function finds the existing name next higher in ASCII collating
  274. sequence than the first-parameter string (just a null will do for finding
  275. the lowest name).  That name string is returned in the third parameter and
  276. the size of its value is returned in the fourth.  The first and third
  277. parameters may be identical.
  278.  
  279. Returns either 0, 1 (no higher name exists) or 3 (next name won't fit in
  280. the size specified by the second parameter).
  281.  
  282. ==============  Tracing Under CodeView -- Do You Dare?
  283.  
  284. The workings of the dynalink code can be inspected by tracing any of the
  285. three command files under CodeView.  So long as the dynalink code has been
  286. activated separately (using GLOBLOAD with the -z option), you can trace
  287. right into it with CodeView, and can examine or patch the GEv space.
  288.  
  289. Note that CodeView as distributed with the OS/2 SDK cannot trace a program
  290. that calls upon a DLL that is NOT in storage at the time CodeView starts
  291. up.  If the GLOBENV.DLL code is not active when you begin CodeView on one
  292. of the commands, the initial program registers and descriptors will be
  293. garbage and an attempt to trace will cause an instant segmentation error,
  294. with CodeView (bless its little pointed head) saying the program terminated
  295. normally.  But if the DLL code is already active, CodeView can handle it
  296. fine.
  297.  
  298. However you must remain aware of the nature of code and data that can be
  299. shared from many programs.
  300.  
  301. When any process enters the dynalink code one of two counters, RdrCount or
  302. WtrCount, is incremented depending on the nature of the function called.
  303. The same counter is decremented on exit.  If it is not decremented -- that
  304. is, if you trace into a dynalink routine and then Quit CodeView or reset
  305. the IP so that the dynalink exit code isn't executed -- then a booby trap
  306. is set for the next program to call the dynalink code.  Depending on what
  307. function went unfinished, either all functions or only "writer" functions
  308. will enter an endless wait.  They're waiting for that counter to decrement,
  309. and it won't.
  310.  
  311. There are two ways to avoid this problem: (1) If you trace INTO a GEv
  312. function, be sure to trace all the way OUT again, to where it returns to
  313. its caller.  (2) Don't Q)uit out of CodeView until you have seen the
  314. message "Program terminated normally."  This means that the program has
  315. been terminated by OS/2, which means that the GEv Exit Routine will have
  316. run and (probably) cleaned up the abandoned semaphores.
  317.  
  318. Strangely enough, if you set a breakpoint in the dynalink code, the
  319. breakpoint cannot be tripped except by the program being debugged.
  320. Commands running running asynchronously in other screen groups run right
  321. through it with no trouble.
  322.  
  323. =============== Reading the Code, or, You Are In a Sunlit Meadow and
  324.                 In Front of You is a Small Building...
  325.  
  326. The GLOBENV.ASM file is rather large, but a lot of its bulk is comments.
  327. The actual code is pretty straightforward, once you understand its
  328. objectives.  Furthermore it breaks up into separate chunks that perhaps
  329. should be separate source modules.  They can be read as separate modules,
  330. at any rate.
  331.  
  332. The following discussion is by chunks in the order the chunks appear in the
  333. file.  Use a search on the string "subttl" to locate them, as each chunk
  334. starts with
  335.  
  336.         subttl sub-title
  337.         page
  338.  
  339. followed by a boxed comment.  Read this summary first for orientation.
  340.  
  341. ** subttl Global Data Area
  342.  
  343. There are two segments, one code and one data.  Both are loaded from disk
  344. when a client program is first loaded.  They are assigned entries in the
  345. Local Descriptor Table of that client, and will have exactly those same
  346. selector numbers in all client programs.  (Can you see why this has to be?)
  347.  
  348. Only a single copy of the data segment is created no matter how many
  349. clients are active.  Its selector appears in every client's address space,
  350. and any number of processes can have concurrent, write access to it.
  351. Obviously updates to it have to be synchronized.  The first thing in the
  352. segment is three semaphores and two counters used for this purpose.
  353.  
  354. Most of the segment is a pool of space for names, values, and for two
  355. arrays of pointers to them.  This space is managed dynamically.  As loaded
  356. there's 2K of it, but the segment can be enlarged after loading using
  357. DosReallocSeg.
  358.  
  359. ** subttl Entry and Setup Formalities
  360.  
  361. The first time a DLL is loaded, OS/2 looks to see if a program entry point
  362. was defined when it was linked.  If so, that entry is called.  (Users of
  363. Modula-2 and of IBM Pascal "Units" will find this concept quite familiar.)
  364. The code of this entry point is in this section.
  365.  
  366. ** subttl Exit List Items
  367.  
  368. This version of the package contains a major enhancement in reliability:
  369. it sets up an OS/2 Exit Routine for every client process.  When a process
  370. that has called on a GEv function terminates for any reason, the exit proc
  371. defined here gets control (even under CodeView -- you can set a break at
  372. the top of ExitRoutine and see).  If when termination began the client
  373. process was in a reader or writer function, the code here tries to clean up
  374. so that other processes can keep running.  Works really slick, too.
  375.  
  376. ** subttl Procedures for Mutual Exclusion
  377.  
  378. The purpose of these routines is discussed in the boxed comment, and
  379. pseudo-code is given there.  Here's a summary of the OS/2 facilities used.
  380. A semaphore is just a doubleword in storage.  If it's zero, it's CLEAR and
  381. can be CLAIMED by any thread.  If it's nonzero it can't be claimed but it
  382. can be WAITED on; that is, the thread may suspend itself until such time as
  383. the semaphore becomes clear.
  384.  
  385. DosSemSet forces a semaphore to a nonzero state.  DosSemClear clears one
  386. and incidentally releases any threads that were waiting on it.  DosSemWait
  387. doesn't return until the specified semaphore is clear, which may be right
  388. away or a long time from now.  It also takes a time limit, but in this code
  389. that's given as -1, meaning forever.
  390.  
  391. DosSemRequest takes a semaphore and waits until it is clear, and then
  392. claims it.  It doesn't return until the semaphore has been claimed, which
  393. may be right away if it's clear, or a long time from now.
  394.  
  395. The RdrGate semaphore is used to block readers from coming in while a
  396. writer is busy.  They wait on it; writers set or clear it.  The WtrWait
  397. semaphore is used to block writers while readers are working.  Writers set
  398. it and wait on it; the last reader to leave clears it.  This covers most
  399. cases but there are holes; that's why there's a while-loop in the writer
  400. logic.
  401.  
  402. ** subttl Heap Space Management
  403.  
  404. The middle of the data segment is kept as a pool of "objects." A
  405. name-string is an object, and a value string is an object.  Since values
  406. can be replaced and names can be deleted, objects can be discarded; then
  407. they become "garbage" and can be "collected" for reuse if that's desirable.
  408.  
  409. At the top (highest addresses) of the segment there's an array of words
  410. that contain offsets of objects.  A given object is addressed by only one
  411. word, no more.  The only legitimate way to address an object is to find the
  412. word that anchors it, and to pick up its offset from there.  It is ILLEGAL
  413. to let the offset of an object be known outside this code, or to retain one
  414. beyond the time of a single call.  The reason is that objects can be moved
  415. around during any update.  The offset in the anchor word will be updated
  416. when the object is moved, but offsets stored elsewhere will no longer be
  417. correct.
  418.  
  419. An object is at least 3 words long.  The first word is its total length in
  420. bytes.  The second is the index of its anchor, so the object points to the
  421. word that points to the object.  If this word is zero, the object has no
  422. anchor and is garbage.  The third and following words are the contents of
  423. the object.
  424.  
  425. The first routine in this section is GColl, the garbage collector.  Its
  426. pseudo-code is given; it just sweeps over all objects and compacts the ones
  427. that are in use down over the garbage ones (if any).
  428.  
  429. The second routine is Xtend; it makes more space in the segment by
  430. extending it with DosReallocSeg.  It also has to move the array of words at
  431. the end of the segment out, to the new end of segment.
  432.  
  433. The third routine, GetSpace, finds where to put an object of a given size.
  434. It has several strategies, mainly aimed at avoiding too-frequent calls on
  435. GColl and Xtend.  GetSpace is only called from MakeObj.
  436.  
  437. FreeObj follows, to make garbage of an object.  Then comes MakeObj to make
  438. one using GetSpace.
  439.  
  440. ** subttl Descriptor Array Management
  441.  
  442. There's a long discussion in the boxed comment.  To summarize: the GEv
  443. contains an unknown number of variables.  For each variable we have to
  444. record (1) the anchor of its name-object; (2) the anchor of its
  445. value-object; (3) the length of its value-object (the length word in the
  446. object itself is rounded up to a word multiple).  So we need a three-column
  447. table, one row per variable.  One row describes one variable and is called
  448. a descriptor in the comments.
  449.  
  450. Rather than code in a maximum number of variables, I chose to make the
  451. table variable in size.  Each time a new name is defined, the table has to
  452. be extended.  In order to have this extensible table and also an extensible
  453. pool of objects, I put the array at the top of the segment and let it grow
  454. downward like a stalactite.  Therefore the words in it are addressed
  455. BACKWARD, that is, by SUBTRACTING an offset from SegLimit, the size of the
  456. segment.  This addressing is independent of the segment size, so it doesn't
  457. change when Xtend is called.
  458.  
  459. In the comments these backward offsets are called "backoffs."  The control
  460. word in an object is the backoff of the word that contains the object's
  461. offset.  Cute, huh?
  462.  
  463. There's a fourth column associated with the table.  The names are kept
  464. sorted in ascii order.  Rather than moving the descriptors around to keep
  465. them in sequence (which would have entailed changing the control words in
  466. the objects, too, every time the descriptors moved), I set up a fourth list
  467. which contains the backoffs of the (first words of the) descriptors in
  468. sorted order.  It's easy (tho not fast) to insert a new name into this
  469. list with a dumb insertion sort.
  470.  
  471. The GetDesc routine finds a free descriptor.  If there's an empty one (from
  472. a deletion) it uses that; otherwise it has to grow the array downward.  If
  473. there isn't room on top of the pool of objects, it has to make room by
  474. calling either GColl or Xtend.
  475.  
  476. ** subttl String Operations
  477.  
  478. Hand-coded versions of strlen, strcpy, strcmp.  These use string
  479. instructions but preserve registers.
  480.  
  481. ** subttl Name-Search Routines
  482.  
  483. Routines to search the ordered list of names, discussed in their boxed
  484. comments.  Of interest only for the use of backoffs in addressing.
  485.  
  486. ** subttl the --- function
  487.  
  488. The last 6 sections are the actual functions exported to client routines.
  489. These call upon the inner routines listed above to interrogate or modify
  490. the common segment.
  491.  
  492. Notice how each routine relies on its caller's stack segment, but not on
  493. its caller's data segment.  Register DS is saved and then loaded with the
  494. segment of the common segment.  On exit, register DS is restored.  It is
  495. this little bit of logic that is missing in C and Pascal library routines
  496. and causes the so-called "DS=SS" problems.
  497.  
  498. ** subttl Workspace Integrity Check
  499.  
  500. If the Exit Routine is entered for a process that died while updating the
  501. GEv, this routine is called to validate the GEv and, if it has been
  502. corrupted, to clear it to an empty but usable state.
  503.  
  504.