How to call a C function from random places inside

2019-07-22 20:08发布

问题:

Can anyone tell me how to insert a function call (say Yield() ) at random places inside a C function, so that each time the code is run, Yield() gets called from different parts of the code ?

I am faced with such a requirement as I'm using 2 threads in a cooperative threading environment , where unless the running thread yields the processor explicitly , the other (waiting) thread cannot start running. I don't want to place the Yield() call at a single point , since that makes the thread sequence deterministic. Without rewiring the entire environment (from cooperative to pre-emptive) , this is the only solution I can think of in which Thread_1() makes the Yield() call at random places inside it, allowing Thread_2() to take over.

Any insights into a different solution achieving the same end-goals is also welcome!

回答1:

A BFI solution is called for, I think

I think you will have to solve this the obvious way. You will need to make a wrapper for Yield() that makes a "random" decision on whether to call the real thing.

If you are not concerned with execution speed then I would make it a real C function, and if you are I might suggest a preprocessor macro.

So, something like:

#define Yield0() ((random() & 0xf) == 0 && Yield())

Choose the mask for the percentage chance of a call you want. For 0xf, and if random() has good low-order bit randomness, then you would see 1 Yield() in 16 calls. If you can use an MT or other high quality random number generator, the low order bits will be directly useful, otherwise you might want to random() >> 3 & ...

And you will just need to put Yield0() calls everywhere.



回答2:

I'd define a function something like:

void maybe_yield() { 
    if (rand() & 0x10)
        yield();
}

Then sprinkle calls to maybe_yield() throughout your code. Depending on how often you want yield to be called, you can change 0x10 to a constant with more bits set to get yield() called more often. Other than that, be sure to call srand() with a value that changes from one run to the next to get different sequences on different runs.



回答3:

Option A: Why not call yield() when the thread gets stuck? Better yet, why not encapsulate that in every operation which potentially could get stuck:

int disk_read (...)
{
    begin_io ();
    while (!io_completed  &&  !timed_out())
         yield();
    if (timed_out())
        // etc.
     ...
}

Option B: Usually—with cooperative yielding—when the other thread is not ready to run, yield() is a no-op. Therefore, put it everywhere:

void thread1 (...)
{
    yield();
    do_something_a();
    yield();
    do_something_b();
    yield();
    do_something_c();
    ...
}

Option C: Trust that processors are plenty fast and waiting for things occurs often enough that minimal yields() work just fine:

void thread1 (...)
{
    init();
    while (...)
    {
        do_heavy_crunching();
        yield();
        do_something_else();
    }
}

In hundreds of real-world applications, Option C works just fine. The determinism usually helps, not hurts.



回答4:

Actually, when you're running in a co-operatively threaded environment, you really do want determinism.

But, if you're hell-bent on doing it, you just need to make it random.

#include <stdlib.h>
// And make sure you seed the generator with srand() somewhere.
#define YIELD_CHANCE 15

#define yield Yield
#ifdef YIELD_CHANCE
    #if YIELD_CHANCE > 0
        #if YIELD_CHANCE <= 100
            #undef yield
            void yield(void) {
                if (rand() < (RAND_MAX / (100/YIELD_CHANCE)))
                    Yield();
                }
        #endif
    #endif
#endif

then change your Yield calls to yield and, depending on what value YIELD_CHANCE is set to at compile time, you'll get deterministic or non-deterministic behavior.

If it doesn't exist or is outside the range 1 through 100, yield will yield all the time. If it's within the range, then it will call the Yield function randomly, based on the probability you give it.



回答5:

You say you don't want a preprocessor, but it makes it so much easier.

   #!/usr/bin/perl
   chomp(my $n =<stdin>);
   open (my $f, '<', $n);
   while (my $l = <$f>) {
        print $l;
        if ($l =~ /^[\s][^\.]/) {
            $r=rand();
           if ( int($r*5) == 1 ) {
                print "\tcall Yield\n";
            }
        }
    }

This perl script(my first ever) will read a filename from stdin and insert a call randomly into gcc -S generated assembly which can then be compiled easily. It might not work as is for your compiler/arch, but regexes can do almost anything.

A nice addition would be to add a yield always before jump instructions for your processor. This saves you the sprinkling. Finally before jumps you could be using a wrapper function that calls random().