Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to run a program at a lower clock speed in C

Tags:

c++

c

I need to emulate the workings of a piece of hardware (NOT for video games).

This component runs at 1 Ghz, while my PC's run at 2.5 and 2.7 Ghz.

So I am trying to tell the pc to run this particular process at a lower speed.

I have tried timers, but it won't do: when dealing with small intervals of time the process won't keep track of time with accuracy (I'd need to keep track of milliseconds and it can't be done neatly) Also to calculate time intervals you would loose some CPU time

Keep in mind that I am not outsourcing to the community, i am working on my own but maybe you guys could help brainstorming :)

like image 509
Wing Avatar asked Oct 30 '22 20:10

Wing


1 Answers

Premise

From what I understand from your question & comments, you need to run a program at a 12.5 Hz CPU. I can think of single stepping through the instructions, much like a debugger, but instead of waiting for you to single step the instructions, it executes each instruction at every time delay (much like what you said you tried). So, if this premise is wrong, please tell me and I'll delete my answer, cause it's based on it.

The Idea

If your clock count is 80ms, that means you can execute at least one instruction per 80ms. Unfortunately the sleep function will only take unsigned int arguments in seconds, so that's not gonna work. However, there's the syscall nanosleep, which lets you adjust a sleep in nanoseconds.

So, to convert that milliseconds to nano, you multiply it by 106, which will give you 80000000 nanoseconds of sleep time. As you already mentioned, there will be some time waste from the calling of functions and the emulator time, but I think that's the price you gotta pay for an emulator(and you can always tinker with the times to make finer adjustments). So, the nanosleep is:

#include <time.h>

int nanosleep(const struct timespec *req, struct timespec *rem);

struct timespec {
       time_t tv_sec;        /* seconds */
       long   tv_nsec;       /* nanoseconds */
};

The other one is the Linux syscall ptrace

#include <sys/ptrace.h>

long ptrace(enum __ptrace_request request, pid_t pid,
               void *addr, void *data);

This function will let you do all sorts of things with the traced process, and I suggest you read the manual. It's very informing. That syscall is the base function of debugging software, and you can read a tutorial on How Debuggers Work here.

In fact, my idea came from that tutorial (I read it a couple of days ago), and I'll modify that code a little bit to do the emulator, so I also suggest reading the tutorial.


Code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <time.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>

#define MILLI       80
#define HZ      ((double)1000/(double)MILLI)

/* milli to nano */
#define m2n(a)      (a*1000*1000) 

void run_target(char *prog)
{
    printf("Emulating %.2lf Hz to proccess %s...\n\n", HZ, prog);

    if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
        perror("ptrace");
        return ;
    }

    execl(prog, prog, (char*)NULL);
}

void run_emulator(pid_t child)
{
    int wait_status;
    struct timespec req;
    unsigned long int count = 1;

    /* set up the emulation speed */
    req.tv_sec = 0;
    req.tv_nsec = m2n(MILLI);

    /* wait for stop on first instruction */
    wait(&wait_status);
    while (WIFSTOPPED(wait_status)) {
        /* this loop will repeat at every instruction, so it executes the
         * instruction and sleeps for the amount of time needed to 
         * emulate the wanted speed.
         */
        if (ptrace(PTRACE_SINGLESTEP, child, 0, 0) < 0) {
            perror("ptrace");
            return ;
        }
        wait(&wait_status);

        /* this does the sleep */
        nanosleep(&req, NULL);
    }
}

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

    if (argc < 2) {
        fprintf(stderr, "Usage: %s [prog_name]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    child = fork();

    if (!child)
        run_target(argv[1]);
    else if (child > 0)
        run_emulator(child);
    else {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    return 0;
}

To make a quick test, I wrote this simple fat(5) calculator in Assembly, which has 65 instructions (on my machine, of course):

 .section .data
 .section .text
 .globl _start
 .globl factorial
_start:

 pushq  $5
 call   factorial
 movq   %rax, %rdi
 movq   $0x3c, %rax
 syscall

 .type factorial, @function
factorial:
 pushq  %rbp        
 movq   %rsp, %rbp  
 movq   16(%rbp), %rax  
 cmpq   $1, %rax        
 je     end_factorial   
 decq   %rax
 pushq  %rax        
 call   factorial
 movq   16(%rbp), %rbx  
 imulq  %rbx, %rax

end_factorial:
 movq   %rbp, %rsp  
 popq   %rbp
 ret

To assemble, link, run and see the result:

$ as -o fat.o fat.s
$ ld -o fat fat.o
$ ./fat 
$ echo $?
120
$

So, it works and computes the factorial of 5. So, if I'm right on the math, 65 instructions will take 65/12.5 seconds to run on a 12.5Hz CPU, right? 65/12.5 = 5.2.

$ time ./lower ./fat
Emulating 12.50 Hz to proccess ./fat...

Returned: 30720

real    0m5.211s
user    0m0.000s
sys 0m0.008s

Manual References

  • wait
  • ptrace
  • nanosleep
like image 124
Enzo Ferber Avatar answered Nov 08 '22 10:11

Enzo Ferber