[comp.sys.atari.st] Preloading revised

leo@philmds.UUCP (Leo de Wit) (05/11/89)

For those who might be interested, here's a demo program that preloads
some programs (whose pathnames are supplied on the command line), then
executes them. It uses the Pexec variants 3, 4 and 5 to do so. Compiled
with Lattice C.
Enjoy!
       Leo.

---->8---->8---->8---->8---->8---->8---->8---->8---->8---->8---->8---->8

/* preload.c */
/* Demo and routines for preloading programs */
/* Author: L.J.M. de Wit - 05/11/89 */
/* This source and its use are PD */

#include <stdio.h>
#include <osbind.h>

#define MAXLOADED 16
#define DEFMNEED 0x1200       /* default memory reservation for heap + stack */
#define ARGSTRLEN 122

#define FAIL 1
#define OK 0

typedef struct img_id {
   char name[64];
   long basepag;
} img_id;

int _mneed = 20000;   /* This program is Mshrinked to this size by Lattice C */

img_id load_img[MAXLOADED];
int nloaded = 0;

/* Main is a demo: all args are taken to be paths of executables; they're
 * preloaded, then executed 10 times, then unloaded.
 */
main(argc,argv)
int argc;
char **argv;
{
   int i,j;

   for (i = 1; i < argc; i++) {
      if (preload(argv[i],0) == FAIL) {
         exit(1);
      }
   }
   printf("Preloaded all\n");
   for (j = 0; j < 10; j++) {
      for (i = 1; i < argc; i++) {
         printf("Executing \"%s\" for the %dth time\n",argv[i],j+1);
         execute(argv[i],"");
      }
   }
   for (i = 1; i < argc; i++) {
      if (unload(argv[i]) == FAIL) {
         exit(1);
      }
   }
}

int preload(progname,memneed)             /* Preload an executable */
char *progname;
long memneed;
{
   long basaddr;
   int i, fd;

   if (memneed == 0) {
      memneed = DEFMNEED;                 /* Default mem for heap and stack */
   }

   for (i = 0; i < MAXLOADED; i++) {
      if (load_img[i].basepag == 0L) {    /* Found an unused slot */
         break;
      }
   }
   if (i == MAXLOADED) {                  /* All slots in use */
      fprintf(stderr,"Max no. of programs (%d) loaded\n",MAXLOADED);
      return FAIL;
   }
   if ((fd = Fopen(progname,0)) < 0) {    /* Progname must exist as file */
      fprintf(stderr,"Cannot open %s\n",progname);
      return FAIL;
   }
   Fclose(fd);
   basaddr = Pexec(3,progname,"",(char *)0);
   if (basaddr == -66) {                  /* Program file in wrong format */
      Fclose(fd);                         /* This fixes a GEMDOS bug */
      fprintf(stderr,"%s not in TOS program format\n",progname);
      return FAIL;
   }
   if (basaddr < 0) {                     /* Any other error */
      fprintf(stderr,"%s failed with error code %d\n",progname,basaddr);
      return FAIL;
   }
   strcpy(load_img[i].name,progname);
   load_img[i].basepag = basaddr;

   /* Now add to memneed the program's own requirements */
   memneed += 0x100 +                     /* Base page   */
             ((long *)basaddr)[3] +       /* Text length */
             ((long *)basaddr)[5] +       /* Data length */
             ((long *)basaddr)[7] +       /* Bss  length */
             1;                           /* For even adjustment */
   memneed &= ~1;                         /* Make it even */
   Mshrink(basaddr,memneed);              /* Now shrink to required size */

   return OK;
}

int unload(progname)                      /* Unload an executable */
char *progname;
{
   int i;

   for (i = 0; i < MAXLOADED; i++) {
      if ((load_img[i].basepag != 0L)
         && !strcmp(load_img[i].name,progname)) {  /* Found it */
         Mfree(load_img[i].basepag);      /* Free it */
         load_img[i].basepag = 0L;        /* Mark slot unused */
         return OK;
      }
   }
   fprintf(stderr,"%s: command was not loaded\n",progname);
   return FAIL;
}

int execute(cmd,tail)                     /* Execute a preloaded program */
char *cmd, *tail;                         /* Prog. path & argstring */
{
   int err_co, i;
   long bp, newbp;
   char newtail[128];
   int v;

   for (i = 0; i < MAXLOADED; i++) {
      if ((load_img[i].basepag != 0L)
         && !strcmp(cmd,load_img[i].name)) { /* Found it */
         break;
      }
   }

   if (i == MAXLOADED) {                  /* Not found */
      fprintf(stderr,"%s: command was not loaded\n",cmd);
      return FAIL;
   }
   if (strlen(tail) >= 128) {             /* Tail must not be too long */
      fprintf(stderr,"%s: command tail too long\n",cmd);
      return FAIL;
   }
   bp = load_img[i].basepag;              /* Just for short */
   strcpy(newtail+1,tail);                /* Prepare real tail */
   newtail[0] = (unsigned)strlen(tail);   /* Length byte */
   newbp = (long)Pexec(5,(char *)0,newtail,(char *)0);   /* Create basepage */
   Mshrink(newbp,256);                    /* Shrink to 256 bytes */
   for (v = 1; v < 8; v++) {              /* Copy over lengths and vectors */
      ((long *)newbp)[v] = ((long *)bp)[v];/* from existing basepage */
   }
   err_co = Pexec(4,(char *)0,newbp,(char *)0); /* Now execute it */
   Mfree(((long *)newbp)[11]);            /* Free environment space */
   Mfree(newbp);                          /* and the basepage itself */

   return err_co;
}
---->8---->8---->8---->8---->8---->8---->8---->8---->8---->8---->8---->8

apratt@atari.UUCP (Allan Pratt) (05/12/89)

In article <1028@philmds.UUCP>, leo@philmds.UUCP (Leo de Wit) writes:
> For those who might be interested, here's a demo program that preloads
> some programs (whose pathnames are supplied on the command line), then
> executes them. It uses the Pexec variants 3, 4 and 5 to do so. Compiled
> with Lattice C.
> Enjoy!
>        Leo.

The program Leo posted should work fine.  It uses Pexec 3 (load/nogo)
once per program you're loading, but then uses Pexec 5 (create basepage)
followed by Pexec 4 (just go) on *that* basepage (after filling it in) 
once per execution of a preloaded program.  This is fine; nothing
wrong with it at all.  Excellent solution.

Except for one thing: some programs expect their basepage to be $100
bytes below their text base.  In fact, virtually all programs make that
assumption to one extent to another, but most of them use it to
determine how to Mshrink, and being off by $100 bytes won't matter. 

And another thing: a program which assumes any initial state for a
variable in the data or BSS segment will lose, because the second time
you start it the variable will have a leftover value from the previous
execution.  You can fix this problem by saving a copy of the initial
data segment and copying it into the process before starting it again,
and clearing the BSS (just the declared part; you don't usually have to
clear the whole heap).  If a program modifies its text segment, this'll
lose, but hell, at that point it'll be quicker just to load it off a
RAMdisk.

Very cool idea.  I might try to add some builtins to Gulam this way.

============================================
Opinions expressed above do not necessarily	-- Allan Pratt, Atari Corp.
reflect those of Atari Corp. or anyone else.	  ...ames!atari!apratt

leo@philmds.UUCP (Leo de Wit) (05/15/89)

In article <1494@atari.UUCP> apratt@atari.UUCP (Allan Pratt) writes:
     [about the preload example]
|Except for one thing: some programs expect their basepage to be $100
|bytes below their text base.  In fact, virtually all programs make that
|assumption to one extent to another, but most of them use it to
|determine how to Mshrink, and being off by $100 bytes won't matter. 

In this case they will just use for the Mshrink the address of the old
basepage that was created with Pexec(3) (load/nogo) in front of the
text segment.  I think this probably won't present a problem (at worst
the Mshrink fails, if the parent process already has set the child's
space to a smaller size).

|And another thing: a program which assumes any initial state for a
|variable in the data or BSS segment will lose, because the second time
|you start it the variable will have a leftover value from the previous
|execution.  You can fix this problem by saving a copy of the initial
|data segment and copying it into the process before starting it again,
|and clearing the BSS (just the declared part; you don't usually have to
|clear the whole heap).  If a program modifies its text segment, this'll
|lose, but hell, at that point it'll be quicker just to load it off a
|RAMdisk.

Alas, some compilers place the data segment in the text segment, when
producing 'large model' code. If a data segment exceeds 64K, it can't
be addressed (in a simple way) using 'address register indirect with
displacement' mode a la 40(a4). So they produce absolute code (for 
Lattice this is even the default), and to make sure the data segment
is relocated correctly (it could contain pointers, for instance) it
is added to the text segment.
What I found to be a good strategy for programs to be preloaded if you
plan to make use of that mechanism (but your mileage may vary):
Explicitly initialize all static and global variables that are being
written to and must have an initial value (most of the time there
aren't that many). If you're programming in C, you could call at
the top of main() for each module that goes in the link a function
that does the initialization of the globals and statics of that file.

About the RAMdisk: an advantage of preloading is that you need only
one copy of the program in memory (with 1M of memory this can still
be a big win). Another advantage is that relocation is not needed
anymore (only Pexec(0) and Pexec(3) relocate). Yet another example
is that you have more control over the image before you run it (you
could perhaps even set up a real argv on the user's stack); even 
things like pipes and multiprocessing come to mind (becoming lyrical
here 8-).

|Very cool idea.  I might try to add some builtins to Gulam this way.

Thanks for the compliment!

    Leo.

neil@cs.hw.ac.uk (Neil Forsyth) (05/15/89)

In article <1494@atari.UUCP> apratt@atari.UUCP (Allan Pratt) writes:
>Very cool idea.  I might try to add some builtins to Gulam this way.

What? Your hacking up Gulam!
May I ask what other things you're putting in there and may I suggest a
builtin RAMdisk that gets trashed when you exit.

Will Netland ever see your improved Gulam?

>============================================
>Opinions expressed above do not necessarily	-- Allan Pratt, Atari Corp.
>reflect those of Atari Corp. or anyone else.	  ...ames!atari!apratt

 _____________________________________________________________________________
/ DISCLAIMER: Unless otherwise stated, the above comments are entirely my own \
! "So your father was a woman" - "No No ROMAN (crack) Argh!" - Monty Python   !
!                                                                             !
! Neil Forsyth                           JANET:  neil@uk.ac.hw.cs             !
! Dept. of Computer Science              ARPA:   neil@cs.hw.ac.uk             !
! Heriot-Watt University                 UUCP:   ..!ukc!cs.hw.ac.uk!neil      !
! Edinburgh                                                                   !
! Scotland                                                                    !
\_____________________________________________________________________________/

leo@philmds.UUCP (Leo de Wit) (05/18/89)

In article <1028@philmds.UUCP> leo@philmds.UUCP (I) wrote:
    [a sample program demonstrating preloading]

As it stands, there is still a problem: the second vector of the
basepage should be updated to match the new Mshrinked program top. This
address is currently used by Pexec(0,...) and Pexec(4,...) to determine
where to build a stack. For Pexec(0,...) this is not a problem, 'cause
the system allocates a (maximal sized & contiguous?) space for the
program, inits the appropriate vectors on the basepage and goes off.
Startup code that Mshrinks also adjusts the stack pointer to point at
the end of the new space. However, if Pexec(4,...) is used and the
preloaded program has been Mshrinked by its parent, the stack must be
guaranteed to be set up properly beforehand. One line of code at the
end of the function preload() (the one marked with /*** ***/) will do
this:

   memneed &= ~1;                         /* Make it even */
   Mshrink(basaddr,memneed);              /* Now shrink to required size */
   ((long *)basaddr)[1] = basaddr + memneed; /*** Adjust new program top ***/

   return OK;
}

[The funny thing is that this line WAS present in my original code - a
shell -; since I could not explain it, and the program appeared to work
without it, I decided to remove it. Note also that only in rare cases
there are problems to be expected: for instance, if a preloaded program
starts another program its own stack will be corrupted. This should be
solved using the above code.]

I will look into supplying some more code (like clearing the BSS,
making copies of the data segment etc.), which could make the concept
of preloading even more userful.

    Leo.

dvlsan@zeus.cs.umu.se (05/22/89)

Please bear with me if this has been discussed before,
but is there anyone who could tell me what the basepage
contains when my program takes control. I haven't been able
to find this info anywhere, so all help is appreciated.

/Stefan
+------------------------------------------------------------+
|  DISCLAIMER: Did I write this? I must be out of my mind... |
+------------------------------------------------------------+