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.
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);
}
}
}
}
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);
}
}
}
}