Alarm mechanism

Arquivo:

Solução

O exercício proposto era remover o busy waiting no método Alarm::delay e fazer com que o handler do alarme tenha tempo máximo de execução.

Idle waiting

Para que não houvesse busy waiting no método Alarm::delay nós adicionamos um semáforo no alarme e o método Alarm::wait() que chama Semaphore::p().

class Alarm
{
...
public:
    void wait() { 
      _semaphore.p();
    };
...
private:
    Semaphore _semaphore;
...
}
Alarm::Alarm(const Microseconds & time, const Handler & handler, int times)
    : _ticks((time + period() / 2) / period()), _handler(handler),
      _times(times), _link(this), _semaphore(0)
{
    db<Alarm>(TRC) << "Alarm(t=" << time << ",h=" << (void *)handler
		   << ",x=" << times << ")\n";
    if(_ticks)
	_requests.insert(&_link);
    else
	handler();
}

Quando alguem invoca o Alarm::delay(), um novo alarme é configurado pra disparar dentro do tempo determinado para o delay (recebendo um dummy handler), e depois a thread em execução chama o wait no alarme criado.

void dummy_handler() {}

void Alarm::delay(const Microseconds & time)
{
  db<Alarm>(TRC) << "delay(t=" << time << ")\n";

  Alarm alarm(time, dummy_handler);
  alarm.wait();
}

Quando o alarme é disparado, o handler é chamado e agora o semaphore também é liberado.

void Alarm::timer_handler(void)
{
  static Tick next;
  static Handler handler;
  static Semaphore * semaphore;

  _elapsed++;
    
  if(Traits::visible) {
    Display display;
    int lin, col;
    display.position(&lin, &col);
    display.position(0, 79);
    display.putc(_elapsed);
    display.position(lin, col);
  }

  if(_master_ticks) {
    if(!(_elapsed % _master_ticks))
      _master();
  }

  if(next)
    next--;
  if(!next) {
    if(handler) {
      new Thread(&exec_handler, handler);
      semaphore->v();
    }
    if(_requests.empty())
      handler = 0;
    else {
      Queue::Element * e = _requests.remove();
      Alarm * alarm = e->object();
      next = alarm->_ticks;
      handler = alarm->_handler;
      semaphore = &(alarm->_semaphore);
      if(alarm->_times != -1)
	alarm->_times--;
      if(alarm->_times) {
	e->rank(alarm->_ticks);
	_requests.insert(e);
      }
    }
  }
}

Limitação do tempo do handler

Para evitar que o handler atrapalhe o tratamento do alarme, ao invés de invocar o método handler diretamente ele é executado por uma nova thread. A nova thread executa um método que executa o handler e retorna sempre zero.

int exec_handler(Alarm::Handler handler) {
  handler();
  return 0;
}

void Alarm::timer_handler(void)
{
  static Tick next;
  static Handler handler;
  static Semaphore * semaphore;

  _elapsed++;
    
  if(Traits::visible) {
    Display display;
    int lin, col;
    display.position(&lin, &col);
    display.position(0, 79);
    display.putc(_elapsed);
    display.position(lin, col);
  }

  if(_master_ticks) {
    if(!(_elapsed % _master_ticks))
      _master();
  }

  if(next)
    next--;
  if(!next) {
    if(handler) {
      new Thread(&exec_handler, handler);
      semaphore->v();
    }
    if(_requests.empty())
      handler = 0;
    else {
      Queue::Element * e = _requests.remove();
      Alarm * alarm = e->object();
      next = alarm->_ticks;
      handler = alarm->_handler;
      semaphore = &(alarm->_semaphore);
      if(alarm->_times != -1)
	alarm->_times--;
      if(alarm->_times) {
	e->rank(alarm->_ticks);
	_requests.insert(e);
      }
    }
  }
}