Basically, tail recursion is what you have when a function calls itself, but doesn't need to do anything after that, other than possibly return the result of the recursive call.  It's a special case of recursion...not in how it works, but in the fact that it is easy to turn into a loop instead.  For compilers/interpreters that don't optimize tail recursion, that can mean the difference between working correctly and overflowing the call stack.
For example:
void countdown(int t) {
    if (t <= 1) return;
    printf("%d\n", t);
    countdown(t - 1);
}
This is tail-recursive, since nothing else needs to happen after countdown calls itself.
Contrast with something like
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}
This is not tail-recursive.  The difference is that the function needs to regain control after it calls itself, so it can multiply the result by n.
(Bonus extra point though: you can turn this into a tail-recursive function using a technique called "continuation-passing style".  Basically, in this case, you'd pass the result along with the other parameters.)
int factorial(int n, int product = 1) {
    if (n <= 1) return product;
    return factorial(n - 1, product * n);
}
But i digress.
Anyway, compilers that remove tail recursion basically just tweak the current call frame so that the variables are what they'd have been for a call, and then jump back to the beginning of the function.  Your teacher probably isn't going to like that very much, for a few good reasons.  But let's start there.
selection_sort(int a, int b){
  tail_call:
    if(a == b+1){
      //do nothing
    }else{
      i = minIndex(arr, a, b);
      if(i != a)
          swap(arr, i, a);
      // tweak the variables so the next iteration sees (a+1, b)
      a += 1;
      goto tail_call;
    }
}
While we're rearranging stuff, empty conditional blocks are rather hideous.  This can also be rewritten as
selection_sort(int a, int b){
  tail_call:
    if(a != b+1){
      i = minIndex(arr, a, b);
      if(i != a)
          swap(arr, i, a);
      // tweak the variables so the next iteration sees (a+1, b)
      a += 1;
      goto tail_call;
    }
}
Now, you've probably been told that goto is the devil.  :)  (That's overstated, but it shouldn't be used where there's a better option.)  So refactor to get rid of it.
How do we get rid of it?  We find a control structure that does what the goto was trying to do.  And in this case, a while loop fits the bill quite nicely.  Absent scope differences in condition, this code:
loop:
    if (condition) {
        ...magic...
        goto loop;
    }
does exactly the same thing as this:
while (condition) {
    ...magic...
}
to the point where many compilers will even generate the exact same code for both.
So let's clean up the code with a while loop.
selection_sort(int a, int b){
    while (a != b+1) {
        int i = minIndex(arr, a, b);
        if(i != a)
            swap(arr, i, a);
        a += 1;
    }
}