Temporizadores de Hardware y Relojes del Sistema
Los temporizadores son componentes de hardware formados por una fuente de reloj y un contador programable. La fuente de reloj, generalmente un oscilador de cristal, genera señales eléctricas periódicas que impulsan el contador a una frecuencia precisa. Estos dispositivos envían señales de interrupción a la CPU de manera rítmica, conocidas como interrupciones de reloj, cuya frecuencia puede ser configurada mediante software.
Tipos de Temporizadores en Computadoras Personales
- Reloj en Tiempo Real (RTC): Alimentado por una batería de respaldo, mantiene la hora del sistema incluso cuando está apagado. En sistemas tipo Unix, el tiempo se representa como un entero largo que cuenta los segundos transcurridos desde la Época Unix (1 de enero de 1970, 00:00:00 UTC).
- Temporizador de Intervalo Programable (PIT): Un chip de temporizador independiente de la CPU que puede configurarse para generar ticks a intervalos de milisegundos.
- Temporizadores Locales en CPUs Multinúcleo: Cada núcleo de procesador posee su propio temporizador local impulsado por el reloj del sistema, fundamental para la planificación en sistemas multiprocesador.
- Temporizadores de Alta Resolución (TSC): El Contador de Marca de Tiempo (Time Stamp Counter) es un registro de 64 bits que incrementa su valor en cada ciclo de reloj de la CPU, permitiendo mediciones de tiempo de alta precisión.
Ciclo de Ejecución de la CPU
El funcionamiento de la CPU se basa en registros clave: el Contador de Programa (PC o IP) que apunta a la siguiente instrucción, el Registro de Estado (SR) que almacena banderas y modos de operación, y el Puntero de Pila (SP). El ciclo de vida de la CPU puede modelarse como un bucle infinito:
while (sistema_encendido) {
1. Obtener (Fetch): Cargar la instrucción apuntada por el PC y avanzar el PC.
2. Decodificar (Decode): Interpretar el código de operación y preparar los operandos.
3. Ejecutar (Execute): Realizar la operación con los operandos y almacenar resultados.
4. Verificar Interrupciones: Comprobar si hay señales de interrupción pendientes y gestionarlas.
}
El manejo de interrupciones y excepciones es responsabilidad exclusiva del núcleo del sistema operativo, permaneciendo oculto para los procesos en espacio de usuario.
Clasificación de Interrupciones
Una interrupción altera temporalmente el flujo de ejecución de la CPU para atender un evento específico, ya sea interno o externo, para luego retornar al proceso original. Se clasifican en cuatro categorías principales:
- Interrupción (Interrupt): Evento asíncrono generado por hardware externo.
- Fallo (Fault): Excepción sincrónica que puede ser corregida, permitiendo reanudar la instrucción que la causó.
- Trampa (Trap): Excepción sincrónica intencional, utilizada para llamadas al sistema o puntos de depuración.
- Abortar (Abort): Excepción grave que no permite continuar la ejecución del proceso.
Implementación Práctica en C: Llamadas al Sistema de Tiempo
Obtención de Teimpo de Alta Precisión
Para medir el tiempo con precisión de microsegundos, se utiliza la llamada al sistema gettimeofday. A continuación, se muestra una implementación correcta que evita errores comunes de inclusión de cabeceras y conversiones de tipos innecesarias:
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
int main(void) {
struct timeval marca_tiempo;
if (gettimeofday(&marca_tiempo, NULL) == -1) {
perror("Error al obtener el tiempo");
return 1;
}
printf("Segundos: %ld\n", marca_tiempo.tv_sec);
printf("Microsegundos: %ld\n", marca_tiempo.tv_usec);
time_t tiempo_crudo = marca_tiempo.tv_sec;
printf("Fecha y hora: %s", ctime(&tiempo_crudo));
return 0;
}
Medición de Rendimiento de Bucles
La función time() es útil para medir intervalos de tiempo más largos, como el tiempo de ejecución de un bloque de código. En este ejemplo, calculamos la duración de un bucle de procesamiento:
#include <stdio.h>
#include <time.h>
#define ITERACIONES 500000000
int main(void) {
time_t inicio, fin;
long diferencia;
volatile long acumulador = 0;
inicio = time(NULL);
printf("Inicio de la prueba: %ld\n", inicio);
for (long i = 0; i < ITERACIONES; i++) {
acumulador += i;
}
fin = time(NULL);
diferencia = fin - inicio;
printf("Fin de la prueba: %ld\n", fin);
printf("Tiempo total transcurrido: %ld segundos\n", diferencia);
return 0;
}
Configuración de Temporizadores de Intervalo
Los temporizadores de intervalo permiten enviar señales periódicas al proceso. Utilizando setitimer con ITIMER_VIRTUAL, el temporizador solo cuenta cuando el proceso está ejecutándose en modo usuario. El siguiente código configura una alarma que se dispara cada segundo y se cancela tras varias ocurrencias:
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
int disparos_restantes = 5;
void manejador_alarma(int senal) {
printf("Alarma virtual disparada. Senal: %d. Faltan %d disparos.\n",
senal, disparos_restantes);
disparos_restantes--;
if (disparos_restantes <= 0) {
printf("Deteniendo el temporizador virtual.\n");
struct itimerval detener = {{0, 0}, {0, 0}};
setitimer(ITIMER_VIRTUAL, &detener, NULL);
}
}
int main(void) {
struct sigaction accion;
accion.sa_handler = manejador_alarma;
sigemptyset(&accion.sa_mask);
accion.sa_flags = 0;
sigaction(SIGVTALRM, &accion, NULL);
struct itimerval config_temporizador;
config_temporizador.it_value.tv_sec = 1;
config_temporizador.it_value.tv_usec = 0;
config_temporizador.it_interval.tv_sec = 1;
config_temporizador.it_interval.tv_usec = 0;
if (setitimer(ITIMER_VIRTUAL, &config_temporizador, NULL) == -1) {
perror("Error al configurar el temporizador");
return 1;
}
printf("Temporizador iniciado. Simulando carga de trabajo...\n");
while (disparos_restantes > 0) {
volatile int x = 0;
for (int i = 0; i < 100000000; i++) {
x += i;
}
}
printf("Proceso finalizado.\n");
return 0;
}