LITERATURE REVIEW
2.5. DECISION CONTEXT
2.7.10. Summative remarks for small and micro accommodation enterprise performance
La función EXEC del DOS (4Bh) es el pilar que sustenta la ejecución de programas desde dentro de otros programas, así como la carga de subrutinas de un mismo programa desde disco (overlays). Si no existiera la función EXEC, el proceso sería arduo: habría que reservar memoria, cargar el fichero ejecutable en memoria, relocalizarlo si es de tipo EXE, crear su PSP y demás áreas de datos (entorno, etc)... por fortuna, la función EXEC se ocupa de todo ello. Además, esta función posee una característica no documentada hasta el DOS 5.0 (sí ha sido documentada desde dicha versión), que es la posibilidad de cargar un programa sin ejecutarlo, lo cual puede ser interesante de cara a la creación de depuradores de código.
Para llamar a la función EXEC para cargar y ejecutar un programa se pone un 0 en AL. Hay que apuntar DS:DX a la dirección del nombre del programa (una cadena ASCIIZ, esto es, terminada por cero) que puede incluir la ruta de directorios y debe incluir la extensión. También hay que apuntar en ES:BX a una estructura de datos (bloque de parámetros) que se interpreta de la siguiente forma:
offset 0: Segmento donde está el entorno a copiar para crear el del programa cargado. A 0 si es el del programa padre. Los programas hijos siempre accederán a una copia y no al original.
offset 2: Doble palabra que apunta a los parámetros del programa a ejecutar (los que ese programa admite, por sí solo, en la línea de comandos). Tiene el mismo formato que el contenido de PSP:80h.
offset 6: Doble palabra que apunta al primer FCB a copiar en el proceso hijo. offset 10: Doble palabra que apunta al segundo FCB a copiar en el proceso hijo. offset 14: Si se carga sin ejecutar, devuelve el SS:SP inicial del subprograma. offset 18: Si se carga sin ejecutar, devuelve el CS:IP inicial del subprograma.
El subprograma cargado hereda los ficheros abiertos del programa padre. Antes de llamar a esta función, el ordenador debe tener suficiente memoria libre. Cuando se ejecuta un programa COM ordinario, toda la memoria del sistema está asignada al mismo (el mayor bloque en realidad, lo que en la práctica significa toda la memoria). Por tanto, un programa COM que desee cargar otros programas debe primero rebajar la memoria que el DOS le ha asignado y quedarse sólo con la que necesita. Con los programas EXE, la cantidad de memoria que les asigna el DOS inicialmente depende del compilador y las opciones de compilación; en ensamblador suele ser también toda la memoria, por lo que es deber de éste liberar la que no necesita. Para ello, se calcula cuanta memoria necesita el programa y se llama a la función del sistema para modificar el tamaño del bloque de memoria del propio programa (función 4Ah del DOS, pasando en ES la dirección del PSP).
En los programas COM, la pila está apuntando al final del segmento (SP está próximo a 0FFFEh). Por ello, si el programa va a ocupar menos de 64 Kb, será preciso mover SP más abajo para que no se salga del futuro bloque de memoria del programa. Si no se toma esta precaución, SP apuntará dentro del siguiente bloque de memoria, que es más que probablemente el que utilizará EXEC, con lo que el ordenador debería colgarse a no ser que haya mucha suerte.
Tras llamar a la función EXEC, en teoría todos los registros son destruidos, según la documentación oficial, incluidos SS:SP. Esto significa que antes de llamar a EXEC deben apilarse los registros que no se desee alterar y guardar en un par de variables SS y SP. Tras llamar a EXEC, inmediatamente a continuación y antes de hacer nada se deben recargar SS y SP, para proceder después a recuperar de la pila los demás registros. Este comportamiento de EXEC parece romper la tónica habitual de comportamiento del DOS. Sin embargo, lo cierto es que esto sólo sucedía en el DOS 2.X: aunque Microsoft no lo diga oficialmente, las versiones posteriores del
sistema sólo corrompen DX y BX al llamar a EXEC.
El siguiente programa de ejemplo, de tipo COM, realiza todas las tareas necesarias para cargar otro programa. Como ejemplo, he decidido cargar el COMMAND.COM, aunque el programa a ejecutar podría ser cualquier otro; la ventaja de COMMAND es que crea una nueva sesión de intérprete de comandos y permite comprobar con comodidad qué ha sucedido con la memoria.
; ******************************************************************** ; * * ; * SHELL.ASM 1.0 - Demostración de carga de subprograma. * ; * * ; ******************************************************************** TAMTOT EQU 1024 ; este programa y su pila caben en 1 Kb. shell SEGMENT
ASSUME CS:shell, DS:shell ORG 100h
inicio:
MOV SP,TAMTOT ; redefinir la pila MOV AH,4Ah
MOV BX,TAMTOT/16
INT 21h ; redimensionar bloque memoria LEA DX,hola_txt
MOV AH,9
INT 21h ; mensaje de bienvenida LEA BX,exec_info MOV WORD PTR [BX],0 MOV WORD PTR [BX+2],80h ; PSP MOV WORD PTR [BX+4],CS MOV WORD PTR [BX+6],5Ch ; FCB 0 MOV WORD PTR [BX+8],CS
MOV WORD PTR [BX+0Ah],6Ch ; FCB 1 MOV WORD PTR [BX+0Ch],CS LEA DX,nombre
MOV AX,4B00h
INT 21h ; cargar y ejecutar programa PUSH CS
POP DS ; DS = CS LEA DX,adios_txt
MOV AH,9
INT 21h ; mensaje de despedida MOV AX,4C00h
INT 21h ; terminar
nombre DB "C:\DOS\COMMAND.COM",0 ; programa a ejecutar exec_info DB 22 DUP (0)
hola_txt DB 13,10
DB "Estás dentro de SHELL.COM ...",13,10,"$" adios_txt DB 13,10
DB "... Acabas de abandonar SHELL.COM",13,10,"$" shell ENDS
END inicio
Al ejecutar el programa anterior, y suponiendo que el ordenador tenga el COMMAND.COM en C:\DOS (es más cómodo que andar buscando la variable de entorno COMSPEC), se puede generar una sesión de trabajo como la que se muestra a continuación, en la que la utilidad MAPAMEM permite verificar la estructura de la memoria tras la ejecución de SHELL.COM:
C:\COMPILER\86\AREA>shell
Estás dentro de SHELL.COM ...
Microsoft(R) MS-DOS(R) Versión 5.00
(C)Copyright Microsoft Corp 1981-1991.
C:\COMPILER\86\AREA>mapamem
MAPAMEM 2.2
- Información sobre la memoria del sistema.
Tipo Ubicación Tamaño PID Propietario --- --- --- --- --- Sistema 0000-003F 1.024 Interrupciones Sistema 0040-004F 256 Datos del BIOS Sistema 0050-0B59 45.216 Sistema Operat. Sistema 0B5B-0CF1 6.512 0008
Programa 0CF3-0E1C 4.768 0CF3 COMMAND Libre 0E1E-0E21 64 0000 <Nadie>
Entorno 0E23-0E52 768 0CF3 COMMAND Entorno 0E54-0E6D 416 0E6F SHELL Programa 0E6F-0EAE 1.024 0E6F SHELL Datos 0EB0-0EC8 400 0ECA COMMAND Programa 0ECA-0F72 2.704 0ECA COMMAND Entorno 0F74-0F8B 384 0ECA COMMAND Entorno 0F8D-0FA5 400 0FA7 MAPAMEM Programa 0FA7-0FFA 1.344 0FA7 MAPAMEM Libre 0FFC-9FFE 589.872 0000 <Nadie> Sistema A000-D800 229.392 0008 Sistema D802-E159 38.272 0008 Libre E15B-E179 496 0000 <Nadie> Programa E17B-E187 208 E17B DOSVER Programa E189-E5B7 17.136 E189 BUFFERS Programa E5B9-E617 1.520 E5B9 FILES Programa E619-E663 1.200 E619 LASTDRIV Programa E665-E712 2.784 E665 NLSFUNC Programa E714-E885 5.920 E714 GRAPHICS Programa E887-EA09 6.192 E887 SHARE Programa EA0B-EB0D 4.144 EA0B DOSKEY Programa EB0F-ECB8 6.816 EB0F PRINT Programa ECBA-ED17 1.504 ECBA RCLOCK Programa ED19-ED39 528 ED19 DISKLED Programa ED3B-F1C7 18.640 ED3B DATAPLUS Programa F1C9-F230 1.664 F1C9 HBREAK Programa F232-F255 576 F232 ANSIUP Programa F257-F25C 96 F257 TDSK Datos F25E-F65D 16.384 F257 TDSK Libre F65F-F6FF 2.576 0000 <Nadie> C:\COMPILER\86\AREA>exit
... Acabas de abandonar SHELL.COM
C:\COMPILER\86\AREA>_
La subfunción EXEC para cargar un programa sin ejecutarlo se selecciona con AL=1; ES:BX apunta al bloque de parámetros que se definió para el caso normal de carga+ejecución. Esta subfunción asigna el PID, no obstante, al PSP del subprograma cargado.
La subfunción de EXEC para cargar un overlay o recubrimiento, se llama con los mismos valores en los registros que la anterior, exceptuando AL (que ahora vale 3). Sin embargo el bloque de parámetros apuntado por ES:BX es ahora mucho más sencillo:
Offset 0: Segmento donde cargar el overlay (la memoria ha de asignarla el programa principal).
Offset 2: Factor de reubicación, si se trata de un fichero EXE (normalmente el mismo valor que el anterior, si el subprograma va a correr en el mismo segmento en que es cargado).
El overlay puede haber sido ensamblado, por ejemplo, con un desplazamiento relativo nulo (ORG 0) de manera que para llamarlo hay que hacer un CALL FAR al segmento donde ha sido cargado, con un offset 0. Claro que también se puede calcular la distancia que hay entre el segmento del programa principal y el del overlay, multiplicarlo por 16 y utilizarlo como offset en la llamada al mismo segmento del programa principal. Sin embargo, esto requiere que el overlay sea ensamblado con cierto offset ... a calcular. Quienes proponen este segundo método -que los hay- andaban ese día más bien despistados. En general, la programación con overlays es compleja, y más aún si los overlays constan de varios segmentos internos.
función 4Dh del DOS (Obtener código de retorno), que devuelve en AH: 0 (terminación normal), 1 (programa abortado por Ctrl-Break), 2 (terminación por error crítico) ó 3 (terminación residente). Al llamar a la función 4Dh, se borra la información que devuelve (sólo funciona la primera llamada). En AL se devuelve el valor que retorna el programa que finaliza (valor de ERRORLEVEL).
9.2. - FILTROS.
El DOS es un sistema operativo que soporta el redireccionamiento. Las posibilidades son, sin embargo, muy limitadas. La razón es la ineficiencia del sistema en las operaciones de entrada y salida, que obliga a las aplicaciones a hacer accesos directos al hardware. Por ejemplo: con el comando interno CTTY, a través de un puerto serie es factible poner a un PC como servidor remoto de otro. Esto permite operar en la línea de comandos desde el terminal remoto ubicado a varios metros de distancia. Sin embargo, nada más ejecutar un programa, el teclado del PC con el emulador de terminal dejará de funcionar y será preciso utilizar ¡el del propio servidor!: la razón es que muy pocos programas usan el DOS para leer el teclado; no digamos para escribir en la pantalla...
Sin embargo, aún en la actualidad muchos usuarios de PC trabajan en la línea de comandos, donde sí es posible, como se ha mencionado, utilizar el DOS como un sistema con dispositivos de entrada y salida estándar que soportan el redireccionamiento. El redireccionamiento bajo DOS es empleado sobre todo para procesar ficheros de texto.
Un filtro es un programa normal que lee datos de la entrada estándar (por defecto, el teclado), los procesa de alguna manera y los deposita en la salida estándar (por defecto, la pantalla). Tanto la entrada como la salida estándar, popularmente conocidas como STDIN y STDOUT, respectivamente, así como la salida estándar para errores (STDERR) son dispositivos permanentemente abiertos en el DOS. Tienen asociados un handle de control, como cualquier fichero: 0 para STDIN (denominado CON), 1 para STDOUT (también conocido por CON), 2 para STDERR (también CON), 3 para la salida serie (denominada AUX) y 4 para la impresora (conocida por PRN).
Por tanto, un filtro normal debe limitarse a leer, con las funciones de manejo de ficheros ordinarias, información procedente del handle 0; tras procesarla debe escribirla en el handle 1. Si se produce un error en el proceso, o hay una salida de log que no deba mezclarse con la salida deseada por el usuario, se puede escribir el mensaje en el handle 2. El redireccionamiento y el sistema de ficheros por handle fue incluido a partir del DOS 2.0 (en versiones anteriores no hay siquiera subdirectorios).
Cuando se ejecuta una orden del tipo COMANDO | FILTRO, el intérprete de comandos cierra la salida estándar y crea un fichero auxiliar (de nombre extraño); a continuación abre ese fichero para salida: como al cerrar la salida estándar se había liberado el handle 1, ese handle será asignado al nuevo fichero. Esto significa que toda la salida de COMANDO no irá a la pantalla (CON) sino al fichero auxiliar. Cuando se acabe de ejecutar COMANDO, el intérprete de mandatos cerrará el fichero auxiliar y volverá a abrir la salida estándar, restaurando el sistema al estado normal. Pero la cosa no queda ahí, evidentemente: a continuación se cierra la entrada estándar y se abre como entrada el fichero auxiliar recién creado, que pasará a ser el nuevo dispositivo de entrada por defecto. Seguidamente, se carga y ejecuta FILTRO, que tomará los datos del fichero auxiliar en lugar del teclado. Al final, el fichero auxiliar es cerrado y borrado, abriéndose y restaurándose la entrada por defecto normal. Si se ejecuta DIR | SORT, aparte del directorio ordenado aparecerán dos extraños ficheros con 0 bytes (este era su tamaño cuando se ejecutó DIR): el DOS crea dos ficheros auxiliares para sustituir la entrada y salida estándar, aunque en este ejemplo sólo se emplee uno de ellos. Actuarán los dos si se utilizan filtros encadenados que obliguen a redireccionar simultáneamente tanto la entrada como la salida a ficheros auxiliares, en una orden del tipo DIR | SORT | MORE. A partir del DOS 5.0, si está definida la variable de entorno TEMP los ficheros auxiliares se crean donde ésta indica y no en el directorio activo, por lo que a simple vista podrían no verse dichos ficheros.
Cuando se utilizan los redirectores habituales ('<', '>', '<<' y '>>') suceden procesos similares, todos ellos desencadenados por COMMAND.COM, con objeto de alterar la salida y entrada por defecto para trabajar con
un fichero en su lugar. Por tanto, los filtros son programas que no tienen que preocuparse de cual es la entrada o salida; su codificación es extremadamente sencilla y puede realizarse en cualquier lenguaje de alto o bajo nivel. El siguiente programa en C estándar, NULL.C, es un filtro nulo que no realiza tarea alguna: se limita a enviar todo lo que recibe (por tanto, DIR es lo mismo que DIR | NULL):
#include <stdio.h> void main() { int c;
do putchar(c=getchar()); while (c!=EOF); }
El siguiente filtro, algo más útil, transforma en minúsculas todo lo que pasa por él, teniendo cuidado con los caracteres españoles (Ñ, Ü, Ç, etc.). Lee bloques de medio Kbyte de una sola vez para reducir el número de llamadas al DOS y ganar velocidad. Si se ejecuta sin más (sin emplear '|' ni '<' ni ningún símbolo de redireccionamiento o filtro) se limita a leer líneas del teclado y a reescribirlas en minúsculas, hasta que se acaba la entrada estándar (teclear Ctrl-Z y Return al final). ; ******************************************************************** ; * *
; * MIN.ASM 1.0 - Filtro para poner en minúsculas ASCII Español. * ; * * ; ******************************************************************** segmento SEGMENT
ASSUME CS:segmento, DS:segmento STDIN EQU 0
STDOUT EQU 1 ORG 100h inicio:
CALL lee_entrada ; leer de STDIN JCXZ fin_filtro ; en CX, bytes leídos PUSHF
CALL pon_minusculas
CALL escribe_salida ; escribir en STDOUT POPF
JNC inicio
fin_filtro: MOV AX,4C00h ; CF = 1 si fin de fichero INT 21h lee_entrada PROC LEA DX,buffer MOV CX,512 MOV BX,STDIN MOV AH,3Fh INT 21h ; leer MOV CX,AX RET lee_entrada ENDP escribe_salida PROC LEA DX,buffer MOV BX,STDOUT MOV AH,40h INT 21h ; escribir RET escribe_salida ENDP pon_minusculas PROC PUSH CX LEA BX,buffer procesa_car: MOV AL,[BX] CMP AL,'A' JB car_ok CMP AL,128 JAE car8 CMP AL,'Z' JA car_ok OR AL,32 car_ok: MOV [BX],AL INC BX LOOP procesa_car POP CX RET car8: MOV AH,'ñ' CMP AL,'Ñ' JE trad_ok MOV AH,'ç' CMP AL,'Ç' JE trad_ok MOV AH,'ü' CMP AL,'Ü' JE trad_ok MOV AH,'é' CMP AL,'É' JE trad_ok MOV AH,AL trad_ok: MOV AL,AH JMP car_ok pon_minusculas ENDP
segmento ENDS END inicio