next up previous contents
Next: Example Implementation for Visual Up: No Title Previous: Genetic Algorithm Implementation

 

Example Implementation for ptrace

The system call ptrace allows a parent process the control over one of its child processes which must be created be the system call fork. The synopsis of the call is:

	int ptrace (int request, int pid, int addr, int data);

The child process is usually executed normally, until it receives a signal. It then stops and informs the parent process about its actual state by the system call wait. The controlling process may then read or write the child process' memory by using ptrace. The parent can also force the child to either resume or stop execution. However, the most interesting request for timing analysis is PTRACE_SINGLESTEP. This command sets the trace-bit within the child's processor-state-word and enables the parent process to trace the child's machine instructions. In this case, the child stops after every machine instruction execution and sends a signal to its parent. An example program for tracing machine instructions could be implemented as follows:

#include <stdlib.h>
#incldue <iostream.h>
#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>

void main (void) {

  long long counter = 1; // machine instruction counter
  int  wait_val;         // child's return value
  int  pid;              // child's process id

  switch (pid=fork()) {
    case -1: perror("fork"); break;
    case 0:     // child process starts
             ptrace(0,0,0,0);
                // must be called in order to allow the
                // control over the child process
             execl("./testprog","testprog",NULL);
                // executes the testprogram and causes
                // the child to stop and send a signal 
                // to the parent, the parent can now
                // switch to PTRACE_SINGLESTEP
             break;
                // child process ends
    default:    // parent process starts
             wait(&wait_val);
                // parent waits for child to stop (execl)
             if (ptrace(PTRACE_SINGLESTEP,pid,0,0) != 0)
               perror("ptrace");
                // switch to singlestep tracing and 
                // release child
             wait(&wait_val);
                // wait for child to stop at next instruction
             while (wait_val == 1407) {
               counter++;
               if (ptrace(PTRACE_SINGLESTEP,pid,0,0) != 0) 
                 perror("ptrace");
               wait(&wait_val);
             }
               // continue to stop, wait and release until
               // the child is finished; wait_val != 1407
               // Low=0177L and High=05 (SIGTRAP)
    } // end of switch
    cout << "Counter: `` << counter << endl;
} // end of main

In order to provide an efficient machine instruction counting, the test program should be linked statically, like: c++ -static testprog.cpp. Hence, setting up and dynamically linking the run-time libraries, which is ``computationally expensive'', is avoided for each run of the test program.

A Genetic Algorithm must execute the fitness function many times. Hereby, the test program's parameters must be passed into the fitness function and the resulting fitness must be passed back. The latter does not create any particular problems, as the machine instructions are counted within the GA procedure (parent process). But passing parameters into a process in order to call the test program (function within the process) requires some additional installations in the form of Inter Process Communication (IPC). A simple methodology for IPC is provided by pipes.

A Unix pipe is a data stream which can be accessed as a file. Consequently, the calling GA process can open a pipe and write its data (parameter) into it. The called process inherits all of the parent's file descriptors and is able to read the data from the pipe. The called process may finally execute the actual test program. The GA fitness function can be implemented as follows:

double fitness (int size, 
                unsigned char* parameter_string,
                char* exename) {
  int pid;           // child's process id
  int pfd[2];        // pipe descriptor
  char str_size[10]; // parameter 1 for executable
  char str_pfd[10];  // parameter 2 for executable

  if (pipe(pfd) == -1) perror("pipe"); // create pipe
  
  switch (pid=fork()) { // create new process
    case 0: {           // child process
              ostrstream os1 (str_size,10); // stream on string
              ostrstream os2 (str_pfd,10);  // for strcat
              if (close(pfd[1]) == -1) perror("close");
              os1 << size;
              os2 << pfd[0];
              ptrace(0,0,0,0);
              execlp(exename,exename,str_size,str_pfd,NULL);
              break;
            }
    default:            // parent process
              if (close(pfd[0]) == -1) 
                perror("close");
              if (write(pfd[1],parameter_string,size) == -1) 
                perror("write");
              wait(&wait_val);
              if ptrace(PTRACE_SINGLESTEP,pid,0,0) != 0)
                perror("ptrace");
              wait(&wait_val);
              while (wait_val == 1407) {
                counter++;
                if (ptrace(PTRACE_SINGLESTEP,pid,0,0) != 0)
                  perror("ptrace");
                wait(&wait_val);
              }
              return((double)counter);
    } // end of switch
} // end of fitness

The test program must access the pipe which was created by the parent process beforehand in order to retrieve the test procedure's input parameter string. It must finally execute the actual test procedure. The source code can be implemented as shown below:

int main (int argc, char* argv[]) {

  int size;              // size of the parameter string
  int pfd[2];            // pipe descriptor
  unsigned char* buffer; // buffer for pipe input

  istrstream is1 (argv[1],10);
  istrstream is2 (argv[2],10);

  is1 >> size;
  is2 >> pfd;

  buffer = new unsigned char [size];
  // no open needed, just read the pipe
  switch (read(pfd,buffer,size) {
    case -1: perror("read"); break;
    case 0:  perror("EOF"); break;
    default: // call actual test procedure with
             // the parameter string converted
             // to the type of the test function
             test_func (*(TYPE*)&buffer);
  }
  delete buffer;
}


next up previous contents
Next: Example Implementation for Visual Up: No Title Previous: Genetic Algorithm Implementation

Commander
Thu Oct 22 15:09:09 GMT 1998