This is the mail archive of the guile@cygnus.com mailing list for the guile project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

guile-unexec experiences



I've just completed a fascinating exercise with guile-unexec, and I'd
like to share my experiences so that other users may be able to use
guile in a similar way.

I have a program that loads ~4K lines of scheme code spread over ~15
modules on startup.  The program often takes over a minute to load,
because of the large number of NFS stat calls, etc., involved in
loading this many scheme files.

So I decided to take a look at guile-unexec, and see if I could
integrate it into my program.  After a couple of days of effort, I've
gotten everything working, and the program now starts up in a flash!

Although most of the problems I ran into were easy to resolve thanks
to the excellent notes that come with guile-unexec, there were a
couple of extra gotchas. I will try to summarize all the relevant
information here.

1. The unexelf.c that comes with guile-unexec does not build on
   Solaris without some minor code changes.  See the end of this mail
   for the 'diff -c' patch; I called the patched version of the file
   unexelf.cc.  The function 'brk_addr()' is not actually needed, but
   I left the code in there for some reason.  (Perhaps this part of
   Emacs should be a separately maintained library?)

2. You can use a global variable to determine whether you are starting
   up from a dump.  When creating a dump, set the global variable to 1.

3. libguile must be linked statically for unexec to work.  If you try
   to link libguile dynamically, the dumped executable will also load
   libguile.so at runtime, and you'll lose the effects of loading
   scheme code on startup.

4. The ports that get returned from (current-input-port)
   (current-output-port) and (current-error-port) need to be
   re-initialized after starting up from a dump.  I used code like
   this to do the re-initialization (stolen directly from init.c):

    // fix up the input and output ports
    scm_def_inp = 
        scm_standard_stream_to_port(stdin, 
                                    (isatty (fileno (stdin)) ? "r0" : "r"),
                                    "standard input");
    scm_def_outp = 
        scm_standard_stream_to_port(stdout, "w", "standard output");
    scm_def_errp = 
        scm_standard_stream_to_port(stderr, "w", "standard error");

5. In general, you have to be careful to not capture any references to
   external resources that won't survive the dump.  This can imply
   significant changes in the startup sequence of your program.

6. The extra arguments in the call to unexec don't seem to be of any
   use.  Perhaps these arguments are important on non-ELF platforms
   (?), but on ELF they must not be of any use.  The signature of the
   function is:

     void
     unexec (char *new_name,
             char *old_name,
             unsigned data_start,     // not used anywhere
             unsigned bss_start,      // not used anywhere
             unsigned entry_address); // not used anywhere

7. Linking on Solaris is apparently much easier than on Linux.  The
   guile-unexec Makefile has a line like this:

     $(CC) $(LDFLAGS) -o $@ -nostartfiles pre-crt0.o \
     /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtbegin.o \
     $(TGUILE_OBJS) unexelf.o gmalloc.o $(GUILE_LIBS) -lm -ldl \
     /usr/lib/crtend.o /usr/lib/crtn.o

   I didn't need any of the /usr/lib/crt* business to get things
   working on Solaris.  I don't know what these files are for
   specifically, and a quick run through the man pages on my Linux
   system was not very helpful.

That's all I can think of for now.  Hopefully, I'll get the itch to
produce a version of guile-scsh that does the unexec dance, so that it
can actually be used for writing small scripts.  I guess I would need
to have an implementation of unexec for more platforms than just ELF.

I think that the possibilities for this technology in relation to guile
are fantastic, and it's probably worth some additional effort to make
unexec friendlier to use, and supported on more platforms.

-russ


*** unexelf.cc	Tue Jan 26 10:49:54 1999
--- /home/mcmanr/compile/guile-unexec/unexelf.c	Tue Jan 26 14:28:51 1999
***************
*** 425,435 ****
  #include <sys/mman.h>
  
  #ifndef emacs
-  extern "C" {
- extern int exit(int);
- void unexec (char *new_name, char *old_name,
-              unsigned data_start, unsigned bss_start, unsigned entry_address);
- }
  #define fatal(a, b, c) fprintf (stderr, a, b, c), exit (1)
  #else
  #include <config.h>
--- 425,430 ----
***************
*** 485,491 ****
  /* Round X up to a multiple of Y.  */
  
  int
! round_up (int x, int y)
  {
    int rem = x % y;
    if (rem == 0)
--- 480,487 ----
  /* Round X up to a multiple of Y.  */
  
  int
! round_up (x, y)
!      int x, y;
  {
    int rem = x % y;
    if (rem == 0)
***************
*** 503,540 ****
   *
   */
  
! #include <sys/types.h>
! #include <sys/signal.h>
! #include <sys/fault.h>
! #include <sys/syscall.h>
! #include <sys/procfs.h>
! 
! unsigned int
! brk_addr()
! {
!     static struct prstatus p;
!     static int here_before;
!     
!     if (!here_before) {
!         char filename[256];
!         int fd, retval;
!         here_before++;
!         sprintf(filename, "/proc/%d", (long)getpid());
!         fd = open(filename, O_RDONLY);
!         if (fd < 0) {
!             fatal ("Can't open %s for reading: errno %d\n", filename, errno);
!         }
!         retval = ioctl(fd, PIOCSTATUS, (void*)&p);
!         if (retval < 0) {
!             fatal ("Can't ioctl %s: errno %d\n", filename, errno);
!         }
!     }
!     return((unsigned int)p.pr_brkbase);
! }
  
  void
! unexec (char *new_name, char *old_name,
!         unsigned data_start, unsigned bss_start, unsigned entry_address)
  {
    int new_file, old_file, new_file_size;
    void *orig_brk;
--- 499,512 ----
   *
   */
  
! #if 0
! extern void *___brk_addr;
! #endif
  
  void
! unexec (new_name, old_name, data_start, bss_start, entry_address)
!      char *new_name, *old_name;
!      unsigned data_start, bss_start, entry_address;
  {
    int new_file, old_file, new_file_size;
    void *orig_brk;
***************
*** 763,771 ****
--- 735,750 ----
  	  NEW_SECTION_H (nn).sh_addralign = OLD_SECTION_H (n).sh_addralign;
  
  	  /* Now copy over what we have in the memory now. */
+ #if 0          
+ 	  orig_brk = ___brk_addr;
+ #endif          
+ 	  /* ___brk_addr = 0; */
  	  memcpy (NEW_SECTION_H (nn).sh_offset + new_base,
  		  (caddr_t) OLD_SECTION_H (n).sh_addr,
  		  new_data2_size);
+ #if 0          
+ 	  ___brk_addr = orig_brk;
+ #endif          
  	  nn++;
  	}




--
"Nobody goes to that restaurant anymore: it's too crowded."
             --Yogi Berra