Al hablar de FAST, me entraron ganas de recordar viejos tiempos, así que un poco como con Bell/beep en ensamblador, decidí implementar algo sencillito que accediera al hardware de bajo nivel.

Lo más tedioso de ensamblador, es que no tienes ninguna biblioteca que tenga implementadas conversiones de números para mostrarlos en pantalla (binario a ASCII), o como en este caso de BCD (Binary Coded Decimal) a binario. En eso FAST Compiler, me iba a ayudar bastante.

En el grupo de desarrollo de FreeDOS, ha habido recientemente cierto interés, en portar aplicaciones UNIX a DOS, algo que implica una gran inversión de tiempo para familiarizarse con la implementación original, y que no me interesaba. Pero dentro del repositorio, encontré Uptime escrito por Mark Aitchison en Turbo Pascal, que en su última versión 7.02, quedó abandonado allá por 1998. Este programa, realiza una función similar al conocido uptime de UNIX, es decir, mostrar la cantidad de tiempo transcurrido, desde que se inició el sistema.

El principal problema, es que el programarse en Pascal, hace que una cosa tan sencilla como esta, de apenas 110 lineas de código, genere un compilado .EXE de enorme tamaño en comparación: 49.746 bytes.

El segundo inconveniente, es que no me gusta el enfoque que sigue. Como DOS, no no mantiene el tiempo durante el cual el sistema lleva activo, la aproximación que sigue es guardar en un archivo la fecha en que uptime.exe se ejecutó por primera vez, y asumir que ese fue el momento de arranque inicial del sistema. A partir de ahí, simplemente resta el tiempo actual, del que guardó en el archivo.

Empecé a programarlo, sin saber muy bien como lo resolvería, sabía que quería usar FAST, y que no quería acceder al área de memoria reservada por la BIOS, porque ya lo había hecho en Beep/Bell en ensamblador. Así que decidí hacerlo, accediendo a la CMOS, o más correctamente la RAM CMOS o memoria BIOS no volátil. O sea, un pequeño espacio de memoria que existe desde los primeros PC, y que guarda información que poderse actualizar, pero a la vez, ser persistente aunque se apague el ordenador. Ahí se guardan configuraciones importantes del hardware, pero también, la fecha y hora actuales, o sea el RTC (Real Time Clock). Por eso las placas base, llevan una pila botón, que se encarga de mantener esa información, aunque no haya suministro de corriente.

El acceso a la CMOS, lo controla un Motorola MC146818 o compatible, que gestiona un mínimo de 64 bytes de información no volátil, y que es accesible mediante los puertos de entrada/salida del PC. Esto es, usando las instrucciones IN/OUT. Algunos son de sólo lectura, pero la mayoría se pueden también escribir.

El MC146818, está conectado al puerto 70h (0x70), en donde espera que le mandemos un byte, con el registro que queremos manipular. Por ejemplo el 0, se asocia a un byte que almacena los segundos de la fecha/hora actuales. Una vez seleccionado el registro, basta leer del puerto 71h para obtener su valor (IN), o escribir en ese mismo puerto 71h (OUT) para modificarlo.

Si leemos los registros 0 (segundos), 2 (minutos) y 4 (horas) consecutivamente, tendremos la hora completa. Podríamos acceder también al 6 (día de la semana), 7 (día del mes), 8 (mes), o 9 (año), para obtener la fecha completa. El engorro, es que estos valores, se devuelven en BCD, o sea que 10 segundos, se almacena como 16, o sea 0F, y tenemos que transformarlos.

Entonces se me ocurrió, que podría utilizar los registros de alarma, que se guarda en la CMOS. Puede que os sorprenda, pero aunque dejó de usarse hace muchísimo, llegue a ver un IBM PC Jr en donde se usaba la alarma. El caso es que están sin utilidad, y podría guardar allí, la hora en que se arrancó por primera vez uptime. Así, no hacía falta pasar por un archivo en disco, que es mucho menos elegante e ineficiente como optó por hacer Aitchison.

El resto es sencillo, calculamos la resta entre el tiempo actual, y el tiempo inicial, y ese es precisamente l uptime. El resultado, es un programa unas 20.000 veces más pequeño que el UPTIME 7.02 de Mark.

Queda trabajo por hacer, por ejemplo, su salida es algo como: System uptime is 49s. Y aunque es lo suficientemente “inteligente” como para mostrar cosas como:
System uptime is 1h:15m:19s o System uptime is 5m:32s, no me he puesto a hacer que reproduzca la típica salida por pantalla de UNIX:
08:11:22 up 1 year, 2 hours, 34 min, 12 sec, 1 users, load average: 0.28, 0.45, 0.38

Las razones de no hacerlo, son en primer lugar filosóficas. ¿Qué carga de CPU pondría? ¿1.00? ¿0.00? ¿Debería complicarme haciendo que se obtenga, cuando se ejecute desde Desqview o TASKMGR?

La otra razón, es técnica. Usando los registros de alarma, sólo puedo guardar horas, minutos, y segundos. Es decir, el uptime máximo será de un día. No podré mostrar más, ni meses, ni años. Incluso menos, porque como necesito almacenar todos los segundos transcurridos en una variable de 16 bits, estoy limitado a 65.535 segundos, o sea, algo más de 18 horas.

Pronto me topé con dificultades, y es que FAST 2.92, tiene un bug al calcular el módulo con valores de 32 bits (mod). Es la función mod32 fastfunc.asm, donde por ejemplo 7200 mod 3600, da 46800, en vez de cero. El problema no ocurre con valores de 16 bits, ni ocurría en FAST 2.67. Sin embargo, compilar con FAST 2.67 no es posible, ya que al ser shareware, estaba limitado a un máximo de 50 compilaciones.

He solventado el bug, omitiendo el uso de mod, así que inyecto código máquina que usa div, para obtener el resto de la división (módulo). No es trivial, porque FAST no ofrece demasiadas posibilidades de conversión entre números de 16 y 32 bits, así que el hack de usar la pila, se las trae.

Otro reto ha sido que esta distribución de FAST, no incluye documentación alguna, así que aunque empecé analizando su código fuente, me di cuenta que era poco práctico, así que opté por revisar la documentación incluida en la 2.67 para guiarme. Así que puede que haya funciones que ya estaban disponibles en FAST, y que no he aprovechado. Como bonus, indicaros que la versión liberada de FAST, la 2.92, no incluye la documentación, y he sido incapaz de encontrarla, así que asumo que se perdió durante el tiempo. Por ello, os he preparado un paquete completo, que incluye los binarios de FAST 2.92 (1997), y la documentación y archivos que faltaban de FAST 2.67 (1990). Lo tienes aquí (344 Kb. en formato ZIP).

Aunque podéis descargar tanto los ejecutables como el código fuente completo en la página oficial y en Sourceforge, podéis descargarlo directamente desde aquí (3 Kb. en formato ZIP). Para aquellos que tengáis curiosidad de la pinta que tiene el código FAST, lo pongo completo a continuación:

;Directives
#errors off
#long
#para
#trace off
 
;Functions
function bcd_to_bin (piByte)
{
	piByte = ((piByte and 0f0h) / 16) * 10 + (piByte and 0fh)
	return(piByte)
}
 
 
;Global variables
unsigned iSecond, iMinute, iHour
unsigned iSecondA, iMinuteA, iHourA
var32 lElapsed
 
 
if (peekb(82h) = '-' or peekb(82h) = '/') and (lcase(peekb(83h)) = 'h') then
{
	;Show copyright
	print dos cr; lf; "UPTIME R1.10             Copyright (c) 2017 by Javier Gutierrez Chamorro (Guti)"; cr; lf; "Show DOS system uptime"; cr; lf
	print dos "UPTIME [-h]"; cr; lf
        print dos "Examples:"
        print dos "	UPTIME -h"
        print dos "	Show this help screen"; cr; lf
        print dos "More information at:"; cr; lf
        print dos "	http://nikkhokkho.sourceforge.net/static.php?page=UPTIME"
        print dos "Press ENTER to continue..."
        ;key
	terminate
}
 
;Get RTC years if range is not valid, we assume there is not CMOS available
out 70h, 7
iSecond = in 71h
if (iSecond = 0) or (iSecond > 89h) then
{
	print dos "Error: CMOS is not available."
	terminate
}
 
;Get RTC seconds
out 70h, 0
iSecond = in 71h
 
;Get RTC minutes
out 70h, 2
iMinute = in 71h
 
;Get RTC hours
out 70h, 4
iHour = in 71h
 
 
;Correct to 24 hour mode if in 12 hour mode
if iHour > 81 then
{
	iHour -= 69
}
 
;Get alarm seconds
out 70h, 1
iSecondA = in 71h
 
;Get alarm minutes
out 70h, 3
iMinuteA = in 71h
 
;Get alarm hours
out 70h, 5
iHourA = in 71h
 
;If invalid alarm, then it is first execution
if (iHourA = 0) and (iMinuteA = 0) and (iSecondA = 0) or ((iHourA > 92h) and (iMinuteA > 59h) and (iSecondA > 59h)) then
{
	iHourA = iHour
	iMinuteA = iMinute
	iSecondA = iSecond
 
	;Set alarm seconds
	out 70h, 1
	out 71h, iSecondA
 
	;Set alarm minutes
	out 70h, 3
	out 71h, iMinuteA
 
	;Set alarm hours
	out 70h, 5
	out 71h, iHourA
}
 
 
iSecond = bcd_to_bin(iSecond)
iMinute = bcd_to_bin(iMinute)
iHour = bcd_to_bin(iHour)
iSecondA = bcd_to_bin(iSecondA)
iMinuteA = bcd_to_bin(iMinuteA)
iHourA = bcd_to_bin(iHourA)
 
;lElapsed = (bcd_to_bin(iSecond) + (bcd_to_bin(iMinute) * 60) + (bcd_to_bin(iHour) * 3600)) - (bcd_to_bin(iSecondA) + (bcd_to_bin(iMinuteA) * 60) + (bcd_to_bin(iHourA) * 3600))
;lElapsed = imul(iSecond, 1) + imul(iMinute, 60) + imul(iHour, 3600) - imul(iSecondA, 1) - imul(iMinuteA, 60) - imul(iHourA, 3600)
lElapsed = iSecond + (iMinute * 60) + (iHour * 3600) - iSecondA - (iMinuteA * 60) - (iHourA * 3600)
 
var32 lTemp
lTemp = lElapsed / 3600
iHourA = low lTemp
 
;lElapsed = lElapsed mod 3600
;mod32 is buggy in fastfunc.asm from FAST 2.92. For instance 7200 mod 3600 returns 46800 
reg bx = 3600
reg dx = high lElapsed
reg ax = low lElapsed
inline 66h, 0f7h, 0fbh	;idiv bx
push reg dx
pop iTemp16
lElapsed = iTemp16
 
lTemp = lElapsed / 60
iMinuteA = low lTemp
 
;lElapsed = lElapsed mod 60
reg bx = 60
reg dx = high lElapsed
reg ax = low lElapsed
inline 66h, 0f7h, 0fbh	;idiv bx
push reg dx
pop iTemp16
lElapsed = iTemp16
 
lTemp = lElapsed
iSecondA = low lTemp
 
if iHourA > 0 then
{
	print dos iHourA; " hour, ";
}
if (iHourA > 0) or (iMinuteA > 0) then
{
	print dos iMinuteA; " minute, ";
}
print dos iSecondA; " second, "; "1 users, load average: 0.00, 0.00, 0.00"