Sinclair QL Programación Avanzada
Anterior Siguiente

5. Los TRAPs del Gestor

5.1 Introducción a los TRAPs del gestor

Este capítulo trata de la asignación de los recursos disponibles en el QL. Si no ha leído y comprendido el capítulo de introducción al QDOS (capítulo 3), este es un buen momento para hacerlo.

Los recursos disponibles en el QL se dividen en dos áreas. La primera es la memoria, y la segunda está constituida por las otras piezas de 'hardware' que están disponibles para el QL, incluyendo la CPU 68008. Todos los programas que se ejecuten en el QL necesitan memoria en la que almacenar los datos asociados con la tarea que se ejecuta. Para esto hay una serie de rutinas que asignan memoria a los programas para el uso del programa y/o almacenamiento de datos. Estas rutinas se tratan en la sección 5.2. Hay otro grupo de rutinas para la gestión de los dispositivos 'hardware' que no sean la memoria. Estos dispositivos pueden ser los componentes internos estándar, como el reloj, IPC (controlador inteligente de la periferia), o dispositivos externos como disco duro o puertas paralelas para impresora. El 'hardware' interno se trata con una serie de 'controladores de dispositivos' que se encuentran dentro del QDOS. El 'hardware' externo puede ser conectado fácilmente al sistema por un grupo de vías bien definidas. Este aspecto del gestor del sistema se trata en la sección 5.3.

Generalmente, la mayoría de la gestión de los recursos del sistema se realiza mediante los TRAPs del gestor, a los que se accede mediante el TRAP #1. Se puede realizar, también, una cierta cantidad de manejo de recursos mediante las rutinas de utilidad por vector que se describen en el capítulo 8. Éstas las pueden usar los 'controladores de dispositivos' llamados con el TRAP #2 o el TRAP #3, pero NO se deben usar directamente desde el código del usuario.

5.2 Gestión de memoria en el QL

La gestión de memoria se ocupa generalmente de la protección y asignación de la memoria. En el QL, sólo se encarga de la asignación. En la práctica, esto significa que se puede hacer fallar al sistema entero mediante la introducción de datos en el área de memoria usada como espacio de trabajo del QDOS (en un sistema de 'hardware' más sofisticado no se podría hacer). Ya que pueden existir bloques importantes de programa y datos en cualquier parte de la memoria del QL, es esencial que se use la memoria solamente de la forma requerida por el QDOS. Tenemos gran cantidad de potentes TRAPs para realizar la asignación de memoria. El tipo particular de asignación depende de las demandas exactas del programa y/o datos almacenados en memoria. Las siguientes secciones nos explican los diferentes tipos de asignación de memoria que se pueden usar.

5.2.1 Procedimientos residentes y su memoria

Ya hablamos, en el capítulo 3, de los procedimientos residentes como una forma muy potente de programar. Se sitúan en la parte alta del mapa de memoria (ver figura 3.1). La razón de su potencia reside en que la memoria en la que se sitúan se asigna solamente en el momento de puesta en marcha del QL. Una vez que se ha asignado el área de procedimientos, no se puede borrar ni extender. La razón para esto es que inmediatamente debajo está el área de programas transitorios. Esta área no puede ser reasignada, y por lo tanto no puede moverse, como se requeriría para cambiar el tamaño del área de procedimientos residentes. Solamente en la circunstancia excepcional de que no hubiera procedimientos transitorios, se podría cambiar el área de procedimientos residentes.

5.2.2 Jobs como programas transitorios

Como se explicó en el capítulo 3, el área de memoria de programas transitorios se puede alterar dinámicamente. Se pueden añadir nuevos programas (en cuyo caso se expande el área) o borrar programas viejos (contrayéndose el área). Los programas que se pueden introducir en esta área se suelen llamar jobs.

Durante la vida de un job pueden suceder una gran cantidad de eventos. Algunos de éstos son esenciales e inherentes al hecho de la existencia del job (ej. creación del job), otros son opcionales (como cambiar el job de prioridad o borrarlo).

El área de memoria asignada a un job se puede considerar en dos partes. El código del job ocupa la primera parte de la memoria asignada a él. Los datos usados por el job ocupan el resto, desde la parte superior del código hasta la parte superior del área. Estas áreas se especifican cuando se crea el job inicialmente. A tiempo de creación del job se puede definir éste como una unidad independiente por sí mismo o como una subunidad de otro job. Los jobs que son propiedad de otros jobs serán borrados cuando lo sea su propietario.

Para permitir al usuario seguir el desarrollo de todos estos jobs, a cada una se le asigna un marcador único llamado Job ID. El primer job que se crea es el job 0, y siempre es el intérprete de BASIC. A los jobs posteriores se les asigna su propio número. La numeración de los jobs es totalmente automática, y el número particular de un job depende exclusivamente del número de jobs que haya en el QL cuando se crea. La palabra inferior del 'Job ID' contiene el número de job y se usa como un índice dentro de la tabla de jobs. La palabra superior del 'Job ID' es una etiqueta. Cada nuevo job se distingue con su propia etiqueta, que es uno mayor que la última asignada.

Una vez que se ha creado el job, puede estar en uno de tres estados claramente diferenciados. Puede estar activo, suspendido o inactivo.

Un job activo comparte los recursos de la CPU con otros jobs. Para determinar la cantidad de tiempo de CPU que se debe asignar a un job particular, a cada uno se le asigna una prioridad entre 1 y 127. La mayor es 127 y la menor (para un job activo) es 1. De esta forma, el planificador decide cuál de los jobs activos debe disponer de la CPU por un tiempo determinado.

Un job suspendido es uno que está activo todavía, pero está suspendido mientras espera por entrada, salida u otro job que no está aún preparado. Por lo tanto, el planificador no asigna tiempo de CPU a un job suspendido hasta que el proceso por el que está esperando se ha completado. Entonces se reactiva el job.

Un job inactivo es aquel cuya prioridad ha sido puesta a 0. A este tipo de job no se le asigna tiempo de CPU. Un job inactivo está presente en el área de programas transitorios, y puede ser reactivado en cualquier momento por otro job.

Finalmente, un job puede ser borrado del área de programas transitorios. Hay dos tipos básicos de borrado. El primer tipo sólo permite borrar un job si está inactivo. El otro permite forzar el borrado del job (aunque esté activo). Cuando se borra un job, se borran también todos aquellos de los que es propietario.

5.2.3 Asignación de memoria para programas BASIC

El intérprete de BASIC se inicia como Job 0. Como en los demás jobs, se requiere memoria para almacenar el programa (en este caso un programa BASIC) y los datos. A diferencia de otros jobs, a los que se les asigna una cantidad fija de memoria, al Job 0 se le permite asignar más espacio de memoria por sí mismo. NINGÚN otro job puede expandirse dinámicamente como éste. Existen dos TRAPs del gestor (D0=16 y 17) para expandir y contraer la memoria disponible para el BASIC.

5.2.4 Asignación en el Área Común

Si un job requiere más memoria extra (sobre la que se le asignó cuando fue creado), se le puede asignar en el área común. El espacio en esta área pertenece a este job hasta que es liberada, o el job es borrado. El área común se usa también para almacenar bloques de definición de canal, datos, y almacenamiento de trabajo para el IOSS (subsistema de E/S).

La secuencia de la figura 5.1 ilustra una progresión típica del almacenamiento en el área común. Inicialmente, esta área se encuentra vacía (fig 5.1a). El Job 'A' decide entonces que le gustaría usar parte de esta área, así que le dice al QDOS que le asigne una parte usando MT.ALCHP (TRAP #1 con D0=$18). El área asignada aparece sombreada en la figura 5.1b. Ahora el Job 'B' pide otra parte de esta área. De nuevo, el QDOS aloca algo de memoria a este job, justo encima del área perteneciente al Job 'A' (ver fig 5.1c). Otro job (Job 'C') requiere ahora algo de memoria. Ésta es alocada justo encima de la del Job 'B' (ver fig 5.1d).

Algún tiempo después, el Job 'B' es borrado del sistema. El QDOS conoce exactamente a quién pertenece cada bit del área común, así que sabe que el área asignada al Job 'B' no se va a necesitar más. El área de este job es borrada para que pueda ser usada por otro job (ver fig 5.1e). Otro job (Job 'D') hace una petición de memoria en el área común. El QDOS encuentra la parte que le perteneció al Job 'B' disponible, de modo que se la asigna al Job 'D' (ver fig 5.1f). El Job 'E' hace una petición de un área más grande. El QDOS se da cuenta que el único espacio suficientemente grande está sobre la del Job 'C' por lo que se la asigna al Job 'E'. El Job 'A' ha llegado a un punto en el que necesita más memoria. Hace una nueva petición usando MT.ALCHP. El QDOS busca por el área común y encuentra que puede caber entre la parte alta de 'D' y la baja de 'C'. Consecuentemente el nuevo espacio se asigna ahí.


Figura 5.1 - Uso del área común

Este proceso continúa mientras la máquina permanece encendida. Se puede apreciar que el área común se fragmenta terriblemente si los jobs la asignan continuamente, devolviendo y reasignando espacio. Hay una considerable sobrecarga por cada espacio asignado, ya que el sistema operativo mantiene 16 octetos de información de cada sección (para poder disponer de las áreas de los jobs que son borrados). Un modo más eficiente de asignar área común es tomar la mayor cantidad de memoria que se va a necesitar. El job puede preparar su propia área común en este espacio, sin recargar al sistema operativo.

Hay un gran número de TRAPs y utilidades que ayudan a los programas del usuario a gestionar sus propias áreas.

5.2.5 Asignación del área de usuario

El área común se ha estudiado en la sección 5.2.4. Esta sección nos muestra cómo el sistema operativo puede hacer una gran cantidad de trabajo extra de preparación si se asignan áreas pequeñas de memoria en el área común. El concepto de área de usuario es muy útil. Básicamente involucra al job en la asignación de un área grande de área común o de su propia memoria asignada. Entonces es el job el que se cuida de su propia área común (con alguna ayuda de las utilidades).


Figura 5.2a - Área de usuario nueva (vacía)


Figura 5.2b - Área de usuario (parcialmente usada)

La figura 5.2 muestra cómo se maneja el área de usuario, con la ayuda de MT.ALLOC (TRAP #1, D0=$0C) y MT.LNKFR (TRAP #1, D0=$0D). El área de usuario se prepara inicialmente conectando un área de RAM a un área inexistente (con el apuntador al espacio libre = 0). Esto se ve en la figura 5.2a.

El job asigna espacio por sí mismo dentro del área de usuario; todos los espacios se encadenan juntos en un formato de lista encadenada. A medida que se va asignando y liberando, el área de usuario se va fragmentando. En un cierto momento, habrá muchos espacios libres dentro del área. Para encontrar el que se va a usar a continuación, las rutinas de utilidad recorren la lista para encontrar un espacio de tamaño adecuado, que es asignado. Esto se ve en la figura 5.2b. Tenga en cuenta que el área de usuario se puede extender dentro de otra área de memoria (área de trabajo del job del área común) simplemente enganchándola al final de la lista de espacio libre.

5.3 Gestión del 'hardware'

Así como gestiona el 'hardware' estándar del QL, el QDOS ha sido diseñado para permitir una fácil expansión del sistema.

5.3.1 'Hardware' estándar del QL

Hay cuatro bloques de 'hardware' que tienen relación con TRAPs del gestor. Éstos son la pantalla, el IPC (incluye el teclado y el sonido), las puertas serie y el reloj. El control de pantalla selecciona alta resolución 512x512 modo de cuatro colores, o baja resolución 256x512 modo de ocho colores más modo parpadeo. El control del IPC es más complejo, ya que el 68008 tiene que comunicarse con el segundo procesador 8049 (el IPC). El IPC puede leer información del teclado y hacer sonar el altavoz. El IPC se encarga de todos los parámetros del sonido programable, de forma que el 68008 no tiene que perder tiempo controlándolo. Se puede cambiar la velocidad de transmisión de las puertas serie mediante un TRAP del gestor. Se puede leer el reloj, ponerlo en hora o ajustarlo, usando los TRAPs $13, $14 y $15 del gestor.

5.3.2 Extensiones al QDOS

El QDOS se puede extender añadiéndole rutinas para servir interrupciones y controladores de dispositivos. Este tipo de rutinas adicionales se suele asociar (no necesariamente) con dispositivos 'hardware' que se han añadido al QL estándar. Todas las interrupciones se desarrollan mediante listas encadenadas. Una lista encadenada consiste en una serie de apuntadores. Entra la primera rutina de la lista, a su retorno pasa el control a la siguiente de la lista, y así sucesivamente hasta que todas las rutinas se han ejecutado. Claramente, cuanto más cerca esté una rutina del principio de la lista, mayor prioridad tiene, ya que puede decidir si pasa o no a la siguiente rutina. Ya que las llamadas a rutinas del sistema están realacionadas con las últimas, es posible reemplazar rutinas del sistema con las nuevas creadas para aplicaciones específicas, según se necesiten.

Las cinco listas encadenadas sirven para:

5.4 TRAPs del gestor - Sección de referencia

En esta sección se proporciona toda la información sobre los TRAPs del gestor. Se accede a ellos mediante el TRAP #1, la función concreta se selecciona mediante el contenido del registro D0. La siguiente tabla es una lista de todas las funciones en orden numérico. Para obtener una descripción más completa de cada una, así como una explicación del código de llamada, se debe referir a las secciones posteriores del capítulo.

Sumario de los TRAPs del gestor

D0NombreDescripción
00MT.INFDa información del job actual y sistema
01MT.CJOBCrea job en área de programas transitorios
02MT.JINFProporciona información de un job
04MT.RJOBBorra job de área de programas transitorios
05MT.FRJOBFuerza borrado de job del área de programas transitorios
06MT.FREEBusca el área libre mayor en el espacio de programas transitorios
07MT.TRAPVPone un vector de TRAP para un job
08MT.SUSJBSuspende un job
09 MT.RELJB Libera un job
0AMT.ACTIVActiva un job
0BMT.PRIORCambia la prioridad de un job
0CMT.ALLOCAsigna memoria en el área común
0DMT.LNKFREncadena un espacio libre en el área común
0EMT.ALRESAsigna área de procedimientos residentes
0FMT.RERESLibera área de procedimientos residentes
10MT.DMODEPone y lee el modo de pantalla
11MT.IPCOMManda un comando al IPC
12MT.BAUDPone la velocidad de trasmisión
13MT.RCLCKLee el reloj
14MT.SCLCKPone el reloj
15MT.ACLCKAjusta el reloj
16MT.ALBASAsigna área de programa BASIC
17MT.REBASLibera área de programa BASIC
18MT.ALCHPAsigna área común
19MT.RECHPLibera área común
1AMT.LXINTEncadena rutina de interrupción externa al QDOS
1BMT.RXINTElimina una rutina de interrupción externa
1CMT.LPOLLEncadena una rutina de 'polling' 50/60 Hz al QDOS
1DMT.RPOLLElimina rutina de 'polling' 50/60 Hz
1EMT.LSCHDEncadena tarea de bucle del planificador al QDOS
1FMT.RSCHDElimina tarea de bucle del planificador
20MT.LIODEncadena al QDOS un controlador de dispositivos de E/S
21MT.RIODElimina un controlador de dispositivo E/S
22MT.LDDEncadena un controlador de dispositivos con directorio al QDOS
23MT.RDDElimina un controlador de dispositivos con directorio

MT.INFTRAP #1D0=0

Obtiene información del sistema

Parámetros de llamadaParámetros retornados
D1D1.LJob ID actual
D2D2.Lversión ASCII (n.nn)
D3D3preservado
A0A0apuntador a variables del sistema
A1A1preservado
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Este TRAP nos proporciona información sobre el estado actual del sistema, el Job ID del job actual y su número de versión. También nos proporciona el apuntador a la base de las variables del sistema.

5.4.1 Creación y borrado de jobs

Los jobs se crean en el área de programas transitorios de la memoria del QL. Es esencial que la memoria asignada al job sea suficiente para contener el código ejecutable, una pila y espacio de trabajo. Si se activa un job, la ejecución comenzará en la dirección base, a menos que se le especifique una dirección distinta de comienzo. La dirección de principio se da como dirección absoluta. No es necesario que el job tenga código ejecutable, se puede haber creado para contener un área de trabajo de otro job. Sin embargo, si el job no tiene código ejecutable, no debe ser activado. Si lo activáramos, haríamos fallar al sistema completo.

El área del job se divide en dos partes. El código comienza en la base del job, el resto del espacio sobre el código, hasta lo alto del área del job es espacio de datos. Cuando se activa un job por primera vez, (A6) apunta a la base de su área, (A6,A4) a la parte inferior del espacio de datos, y (A6,A5) a lo alto de su área. Al activar un job de formato estándar, se le puede pasar información en la pila. En lo alto de la pila se coloca una palabra que contiene el número de canales abiertos para el job. Ésta va seguida del número de identificación de canal (palabras largas), para entrada básica, salida y reporte de canales, respectivamente, y finalmente una cadena de comandos del job en formato estándar.

MT.CJOBTRAP #1D0=1

Crea un job en el área de programas transitorios

Parámetros de llamadaParámetros retornados
D1.LJob ID propietarioD1.LJob ID
D2.Llongitud del códigoD2preservado
D3.Llongitud espacio datosD3preservado
A0A0preservado
A1dirección principio ó 0A1preservado
A2A2preservado
A3A3preservado

Error devuelto:
OMno hay memoria
NJno hay espacio en tabla de jobs o D1 no es un job

Descripción:

Este TRAP de creación de job tiene dos efectos. Asigna espacio para el job en el área de programas transitorios, y prepara una entrada de job en las tablas del planificador. Sin embargo, esto no hace que el job se inicie totalmente. La única iniciación que ocurre es que pone dos palabras a ceros en la pila. El Job 'padre' carga normalmente el nuevo job en el área de memoria asignada después de esta llamada al sistema. El apuntador de la pila (en el área de control del job) se pone inicialmente apuntando a las dos palabras a ceros de la pila. Éstas se colocan en la dirección más alta del área de datos del job.

Si el job va a tener algún canal abierto, o se le va a pasar una cadena de comandos, se puede hacer antes de que sea activado. El Job ID propietario, pasado en D1, debe ser cero si el job va a ser independiente. Si el job actual va a ser el propietario del nuevo, D1 debe pasar un valor negativo. En el sistema operativo versión 1.03 y anteriores, A1 debe ser puesto siempre a cero a la entrada.

MT.JINFTRAP #1D0=2

Obtiene información de un job

Parámetros de llamadaParámetros retornados
D1.LJob IDD1.Lsiguiente job en cadena
D2.Ljob más altoD2.Lpropietario del job
D3D3primer octeto: negativo si está suspendido
segundo octeto: prioridad
A0A0dirección base del job
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
NJno existe el job

Descripción:

Este TRAP devuelve el estado actual del job. Se puede usar para comprobar el estado de una cadena completa de jobs. Si se va a usar de esta forma, D2 debe contener siempre el ID del job en lo alto de la cadena. Para poder analizar la cadena completa, se prepara el TRAP con D1 conteniendo el valor devuelto por el TRAP anterior. Cuando se ha alcanzado al último job de la cadena, D1 devuelve cero.

MT.RJOBTRAP #1D0=4

Borra un job del área de programas transitorios

Parámetros de llamadaParámetros retornados
D1.LJob IDD1indefinido
D2D2indefinido
D3.LCódigo de errorD3indefinido
A0A0indefinido
A1A1indefinido
A2A2indefinido
A3A3indefinido

Error devuelto:
NJno existe el job
NCjob no está activo

Descripción:

Este TRAP borra un job y todos sus subsidiarios del área de programas transitorios. Este TRAP borrará solamente jobs inactivos. Los jobs activos pueden ser borrados forzándolos mediante MT.FRJOB D0=5.

Tenga en cuenta que este TRAP no está garantizado como atómico y no puede borrar el Job 0.

MT.FRJOBTRAP #1D0=5

Fuerza borrado de un job del área de programas transitorios

Parámetros de llamadaParámetros retornados
D1.LJob IDD1indefinido
D2D2indefinido
D3.LCódigo de errorD3indefinido
A0A0indefinido
A1A1indefinido
A2A2indefinido
A3A3indefinido

Error devuelto:
NJel job no existe

Descripción:

Este TRAP inactiva una cadena completa de jobs y borra todos los jobs de la cadena. Si D1 es una palabra negativa, se borra el job actual. Si hay un job esperando que se complete el job que se va a borrar, ese job se libera con D0 conteniendo un código de error (ver MT.ACTIV D0=A).

Tenga en cuenta que este TRAP no está garantizado como atómico y por lo tanto no puede borrar el Job 0.

MT.FREETRAP #1D0=6

Busca el mayor espacio libre en el área de transitoria

Parámetros de llamadaParámetros retornados
D1D1.Llongitud del espacio
D2D2indefinido
D3D3indefinido
A0A0indefinido
A1A1indefinido
A2A2indefinido
A3A3indefinido

Error devuelto:
ninguno

Descripción:

Este TRAP busca el mayor espacio libre contiguo que puede ser asignado en el área de programas transitorios. La longitud del espacio es devuelta en el registro D1.

MT.TRAPVTRAP #1D0=7

Pone un vector de TRAP para un job

Parámetros de llamadaParámetros retornados
D1.LJob IDD1.LJob ID
D2D2preservado
D3D3preservado
A0A0base del job
A1apuntador a la tablaA1preservado
A2A2preservado
A3A3preservado

Error devuelto:
OMno hay memoria

Descripción:

A un job le es posible redireccionar TRAPs y vectores de excepción que no usa el QDOS. Esta redirección se define en una tabla (ver abajo). Si el job prepara una tabla de vectores de TRAP para él mismo, se usará automáticamente siempre que se ejecute el job. Un job usará inicialmente la tabla del que lo ha creado, aunque no le pertenezca. Este estado de de cosas persiste hasta que otro job prepare su propia tabla. Las tablas de vectores usadas por otros jobs no son afectadas. Si el Job ID es una palabra negativa, la tabla la preparará el job llamado.

La tabla consiste en una dirección de una palabra larga de longitud por cada TRAP o excepción, en el siguiente orden:

principio$00error de dirección
$04instrucción ilegal
$08división por cero
$0CCHK (comprobar registro con fronteras)
$10TRAPV (TRAP por rebasamiento)
$14violación de privilegio
$18trace
$1Cnivel 7 de interrupción
$20trap #5
$24trap #6
$28trap #7
$2Ctrap #8
$30trap #9
$34trap #10
$38trap #11
$3Ctrap #12
$40trap #13
$44trap #14
$48trap #15
$4Cfin de la tabla

5.4.2 Control de job

Los jobs están siempre en uno de tres estados definidos. Pueden estar activos (compartiendo los recursos de la CPU con otros jobs), suspendidos (esperando por E/S o por otros jobs) o inactivos (ocupando memoria pero sin poder hacer uso de los recursos de la CPU). La única diferencia práctica entre un job inactivo y uno que ha sido suspendido indefinidamente es que el último no puede borrarse con MT.RJOB D0=4.

La información sobre un job, su estado actual, prioridad, propietario, tamaño, etc. está contenida en el área de control del job. Esta área está en la región de memoria de programas transitorios y precede al área asignada para el job. En QDOS v1.03, la base del área de control está $68 octetos antes que el job. Esto es susceptible de cambio en versiones futuras. Para el QDOS v1.03, la organización de memoria es:

dir.long.nombredescripción
$004LEN*Longitud total control job + área job.
$044STARTDirección de principio en activación.
$084OWNER?Job ID del propietario.
$0C4HOLD?Apuntador a un octeto que se limpiará cuando el planificador libere al job (ver MT.SUSJB D0=8).
$102TAG*Etiqueta del job asignada por MT.CJOB.
$121PRIOR?Prioridad acumulada actual. Se incrementa cuando el job está activo pero no ejecutándose. El planificador permite ejecutarse al job con mayor prioridad acumulada.
$131PRINC?Ésta es la prioridad inicial del job. El BASIC activa jobs con prioridad $20.
$142STAT*Estado del job:
 0 no suspendido
 >0 número de cuadros antes de liberarlo
 −1 suspendido (ES o MT.SUSJB)
 −2 esperando que termine otro job
$161RELA6?Bit superior puesto si el siguiente TRAP #2 ó #3 tiene direccionamiento relativo (como lo puso el TRAP #4).
$171WFLAG?Puesto si hay otro job esperando por éste.
$184WJOB?ID del job que espera por éste.
$1C4TRAPV?Apuntador a los vectores de TRAP.
$2032Valores de D0 a D7 salvados.
$4032Valores de A0 a A7 salvados.
$602Valor del registro de estado salvado.
$624Valor del contador de programa salvado.

* significa valor que no debe ser cambiado
? significa valor que puede ser cambiado por un TRAP, o directamente (¡pero con cuidado!)
long longitud en octetos:
1 un octeto
2 una palabra
4 una palabra larga
32 8 palabras largas

MT.SUSJBTRAP #1D0=8

Suspende un job

Parámetros de llamadaParámetros retornados
D1.LJob IDD1.LJob ID
D2D2preservado
D3.Wperiodo de esperaD3preservado
A0A0base área control job
A1dirección indicadoresA1preservado
A2A2preservado
A3A3preservado

Error devuelto:
NJJob ID inválido

Descripción:

Este TRAP suspende un job. El periodo de suspensión puede ser indefinido, o por un periodo de tiempo especificado. El periodo de espera se define como el número de cuadros, por lo que la suspensión puede ser hasta un periodo de 32K cuadros (unos 10 minutos). Si se le especifica el periodo de espera como -1, la suspensión será indefinida. No se puede usar otro valor negativo. Si el Job ID es una palabra negativa, se suspende el job actual. El octeto de indicadores se limpiará cuando se libere el job. Si no hay octeto de indicadores, A1 debe ponerse a cero. Si el job ya estaba suspendido, se quita la suspensión. Todos los jobs son replanificados.

Tenga en cuenta que este TRAP no es totalmente atómico, ya que invoca al planificador.

MT.RELJBTRAP #1D0=9

Libera un job

Parámetros de llamadaParámetros retornados
D1.LJob IDD1.LJob ID
D2D2preservado
D3D3preservado
A0A0base área control job
A1A1preservado
A2A2preservado
A3A3preservado

Error devuelto:
NJjob inválido

Descripción:

Este TRAP hace que se libere un job que se encuentra suspendido. Todos los jobs pueden ser replanificados después de esta llamada.

Tenga en cuenta que este TRAP no es totalmente atómico, ya que invoca al planificador.

MT.ACTIVTRAP #1D0=A

Activa un job

Parámetros de llamadaParámetros retornados
D1.LJob IDD1.LJob ID
D2.Bprioridad (0 a 127)D2preservado
D3.Wespera (-1 ó 0)D3preservado
A0A0base área control job
A1A1preservado
A2A2preservado
A3A3preservado

Error devuelto:
NJno existe el job
NCjob activo

Descripción:

Este TRAP hace que un job determinado en el área de programas transitorios sea activado. La ejecución comenzará desde la dirección de comienzo que fue definida cuando se creó. La actividad de un job se puede controlar también ajustando su nivel de prioridad (MT.PRIOR D0=B). Si la prioridad se pone a cero, el job se pone inactivo.

Si el periodo de espera se pone a cero, la ejecución del job actual continúa, si se pone otro valor, el job será suspendido hasta que el job activo termine. Este TRAP volverá entonces con un código de error de ese job.

Este TRAP no es totalmente atómico ya que invoca al planificador.

MT.PRIORTRAP #1D0=B

Cambia la prioridad de un job

Parámetros de llamadaParámetros retornados
D1.LJob IDD1.LJob ID
D2.Bprioridad (0 a 127)D2preservado
D3D3preservado
A0A0base área control job
A1A1preservado
A2A2preservado
A3A3preservado

Error devuelto:
NJno existe el job

Descripción:

Este TRAP cambia la prioridad de un job. Si D1 es una palabra negativa, cambiará la prioridad del job actual. Si se pone prioridad 0, el job será inactivado. Este job reentra el planificador, de modo que si el job pone su prioridad a 0, se inactivará instantáneamente. Esta llamada no es totalmente atómica, ya que invoca al planificador.

5.4.3 Gestión del área de usuario (ver capítulo 8)

Bajo ciertas circunstancias, la gestión del área de usuario puede ser atómica. Hay dos TRAPs que lo hacen.

El área de usuario es asignada en múltiplos de 8 octetos. El espacio libre se encadena usando dos palabras largas por espacio. La primera palabra larga contiene la longitud del espacio y la segunda el apuntador relativo al siguiente espacio libre. Esta área de usuario es totalmente reubicable, ya que los apuntadores son relativos. Se puede usar toda el área asignada por el código de usuario, asumiendo que en el programa esté previsto mantener control de la longitud de los items dentro del área. Cuando se asigna el área, la primera palabra larga contiene la longitud del área, de modo que pueda ser retenida por el código del usuario si lo desea.

El código del usuario debe mantener un apuntador al espacio libre. En la sección 5.2.5 se explica claramente este punto. El apuntador al espacio libre debe ser relativo (una palabra larga). Si el área no tiene espacio libre, porque se ha llenado o no existe, este apuntador será cero.

Estas áreas se preparan encadenando un área de RAM dentro de un área no existente (con el apuntador al espacio libre = 0). Se pueden expandir encadenándose a otra área de RAM. Para que su uso sea óptimo deberían ser contiguas.

MT.ALLOCTRAP #1D0=C

Asigna espacio en el área común

Parámetros de llamadaParámetros retornados
D1.Llongitud requeridaD1.Llongitud asignada
D2D2indefinido
D3D3indefinido
A0apuntador al apuntador al espacio libreA0base del área asignada
A1A1indefinido
A2A2indefinido
A3A3indefinido
A6dirección baseA6preservado

Error devuelto:
OMno hay suficiente espacio libre

Descripción:

Este TRAP asigna memoria en el área común para usarla con el código de usuario. El TRAP es totalmente atómico. A6 se usa como dirección base para este TRAP, de forma que A0 debe ser relativo a A6.

MT.LNKFRTRAP #1D0=D

Encadena espacio libre (devuelto) en el área común

Parámetros de llamadaParámetros retornados
D1.Llongitud a encadenarD1indefinido
D2D2indefinido
D3D3indefinido
A0base del nuevo espacioA0indefinido
A1apuntador al apuntador al espacio libreA1indefinido
A2A2indefinido
A3A3indefinido
A6dirección baseA6preservado

Error devuelto:
ninguno

Descripción:

Este TRAP encadena un espacio libre de memoria (devuelto) en el área común. A6 se usa como dirección base para esta llamada, así que A0 y A1 deben ser relativas a A6.

MT.ALRESTRAP #1D0=E

Asigna área de procedimientos residentes

Parámetros de llamadaParámetros retornados
D1.L# octetos requeridosD1indefinido
D2D2indefinido
D3D3indefinido
A0A0dirección base del área
A1A1indefinido
A2A2indefinido
A3A3indefinido

Error devuelto:
OMno hay memoria
NCimposible asignar (TRNSP área no está vacía)

Descripción:

El área de procedimientos residentes está situada en lo alto de la memoria, y se asigna normalmente a tiempo de encendido. La razón para ello es que todos los procedimientos transitorios se sitúan debajo de los procedimientos residentes en la memoria. Si se expande el espacio de procedimientos residentes cuando hay procedimientos transitorios, éstos deberían ser reubicables, como normalmente no lo son, esto resulta imposible. Sin embargo, en el caso excepcional de que no haya procedimientos transitorios en la máquina, sería posible extenderla o reducirla. Los TRAPs que se encargan de ello son MT.ALRES D0=E y MT.RERES D0=F. La asignación se hace por un número de octetos. La función RESPR(n) del BASIC puede usarse para asignar octetos a la memoria de procedimientos residentes.

MT.RERESTRAP #1D0=F

Libera área de procedimientos residentes

Parámetros de llamadaParámetros retornados
D1D1indefinido
D2D2indefinido
D3D3indefinido
A0A0indefinido
A1A1indefinido
A2A2indefinido
A3A3indefinido

Error devuelto:
NCimposible liberar (TRNSP no está vacía)

Descripción:

Este TRAP libera memoria de procedimientos residentes para su uso por el sistema. Solamente se puede usar cuando el área de programas transitorios está completamente vacía. Ver también MT.ALRES D0=E para la asignación de memoria de procedimientos residentes.

MT.DMODETRAP #1D0=10

Pone y lee el modo de pantalla

Parámetros de llamadaParámetros retornados
D1.B
-1 lee modo
0 modo 4 colores
8 modo 8 colores
D1.Bmodo pantalla
D2.B
-1 leer pantalla
0 monitor
1 TV 625 líneas
D2.Btipo pantalla
D3D3preservado
A0A0preservado
A1A1preservado
A2A2preservado
A3A3preservado
A4A4indefinido

Error devuelto:
ninguno

Descripción:

Este TRAP pone o lee el modo actual de pantalla. Se suele seleccionar a tiempo de iniciación del sistema, para decirle al QL si está usando un televisor o un monitor. Este TRAP afecta a todas las ventanas, por lo que no se debe usar a menos que la máquina esté completamente vacía, ya que los programas pueden requerir una pantalla diferente a la que le estamos forzando a usar. Si se usa para leer el modo de pantalla, no hay ningún peligro.

5.4.4 Comunicación con el IPC

El IPC (controlador inteligente de periféricos) es un microprocesador de un solo chip 8049. Este segundo procesador se encarga de los datos de entrada del teclado y de los de salida al canal de sonido. El 68008 se comunica con el 8049 mediante medios octetos (nibble).

Los comandos se mandan al IPC mediante los 'nibbles' de comando. Éstos van seguidos por los parámetros del comando, que son una cadena de 'nibbles' y/u octetos. Toda la información devuelta por el IPC (como la lectura de una fila de teclas) viene en formato de octeto.

El comando que se manda al IPC (usando MT.IPCOM) debe almacenarse en memoria en el siguiente formato. Consiste en una cabecera que describe el comando, seguida por el número de parámetros requeridos por el comando, terminando en un octeto que indica si se espera respuesta.

1 octel comando va en los cuatro bits inferiores
1 octcontiene el número de octetos de parámetros
2 octnúmero de bits a mandar de cada octeto de parámetro:
bits 1,0 = cantidad a mandar del primer octeto
bits 3,2 = cantidad a mandar del segundo octeto
etc.
n octoctetos de los parámetros
1 octlongitud de la respuesta codificada en bits 1,0

Se pueden transferir 0, 4 u 8 bits de cada octeto de parámetro. El número de bits que se van a transferir está codificado en una palabra larga. El cádigo de dos bits es como sigue:

00mandar los 4 bits inferiores
01no mandar nada
10mandar los 8 bits del octeto
11no mandar nada

La comunicación con el IPC está completamente desprotegida, por lo que los comandos no deben contener errores. Si hay errores, el QL entero fallará. La comunicación con el IPC es un proceso muy lento, por lo tanto es esencial no hacer excesivo uso de él. Por ejemplo, es más eficiente leer solamente las teclas del cursor (están todas en una misma fila del teclado) que leer el teclado entero. Si se lee todo el teclado regularmente, resultará excesivamente caro en tiempo de proceso.

La mayoría de los comandos del IPC están diseñados para ser usados por el sistema operativo, y no se debe intentar usarlos desde los programas de usuario. Este tipo de intento puede resultar en pérdida de datos o fallos del sistema. Sin embargo, hay tres comandos que han sido diseñados para usarse en programas de aplicación. Éstos son:

comando 9lee el teclado (requiere un parámetro)
4 bits para el número de fila
8 bits para la respuesta
(ver fig. 5.3 con el esquema del teclado)
comando Ainicia la generación de sonido (8 parámetros)
8 bits para tono 1 (1 mayor que en BASIC)
8 bits para tono 2 (1 mayor que en BASIC)
16 bits intervalo entre pasos
16 bits duración sonido
4 bits paso en tonos
4 bits repetición
4 bits paso aleatorio
4 bits ruido
no necesita respuesta
(ver "QL User Guide" para la descripción de los parámetros de sonido)
comando Bcortar sonido - sin parámetros - sin respuesta


Figura 5.3 - Esquema del teclado de la versión inglesa (para la versión española, ver el Apéndice X)

MT.IPCOMTRAP #1D0=11

Manda comandos al IPC

Parámetros de llamadaParámetros retornados
D1D1.Bparámetro devuelto
D2D2preservado
D3D3indefinido
A0A0preservado
A1A1preservado
A2A2preservado
A3apuntador al comandoA3preservado

Error devuelto:
ninguno

Descripción:

Este TRAP manda un comando al IPC. El formato del comando debe ajustarse a lo descrito en las páginas anteriores, ya que no se realiza comprobación de los parámetros. Cualquier fallo en la definición de los comandos puede ser desastroso, desembocando en un fallo de todo el sistema.

MT.BAUDTRAP #1D0=12

Pone la velocidad de transmisión

Parámetros de llamadaParámetros retornados
D1.Wvelocidad transmisiónD1indefinido
D2D2preservado
D3D3preservado
A0A0preservado
A1A1preservado
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Las dos puertas serie del QL operan a la misma velocidad, tanto en transmisión como en recepción. MT.BAUD se usa para seleccionar la velocidad requerida. Las siguientes velocidades son válidas:

75transmisión y recepción
300transmisión y recepción
600transmisión y recepción
1200transmisión y recepción
2400transmisión y recepción
4800transmisión y recepción
9600transmisión; recepción (sólo con más de un bit de parada)
19200sólo transmisión

Tenga en cuenta que la paridad y el número de bits de parada se ponen independientemente de la velocidad. Para más detalles, vea el capítulo 6 (IO.OPEN e IO.CLOSE).

MT.RCLCKTRAP #1D0=13

Lee el reloj

Parámetros de llamadaParámetros retornados
D1D1.Ltiempo en segundos
D2D2indefinido
D3D3preservado
A0A0indefinido
A1A1preservado
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Con este TRAP se puede leer el reloj de tiempo real. El tiempo viene en una palabra larga conteniendo el número de segundos desde 00:00 1 Enero 1961. Esto significa que el QL puede dar la fecha hasta el ¡año 2029! Para convertir el tiempo en segundos a una cadena con la fecha, la hora y/o el día de la semana, se pueden usar las utilidades por vector CN.DATE y CN.DAY.

MT.SCLCKTRAP #1D0=14

Pone en hora el reloj

Parámetros de llamadaParámetros retornados
D1.Ltiempo en segundosD1.Ltiempo en segundos
D2D2indefinido
D3D3indefinido
A0A0indefinido
A1A1preservado
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Este TRAP permite poner la hora y día del reloj de tiempo real.

MT.ACLCKTRAP #1D0=15

Ajusta el reloj

Parámetros de llamadaParámetros retornados
D1.Lajuste en segundosD1.Ltiempo en segundos
D2D2indefinido
D3D3indefinido
A0A0indefinido
A1A1preservado
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Con este TRAP se puede ajustar el reloj. Tenga en cuenta que el poner el reloj en hora lleva su tiempo, por lo tanto si llama a MT.ACLCK con D1=0, el QDOS no intentará ajustar el tiempo.

MT.ALBASTRAP #1D0=16

Asigna área para el programa BASIC

Parámetros de llamadaParámetros retornados
D1.L# octetos requeridosD1.L# octetos asignados
D2D2indefinido
D3D3indefinido
A0A0indefinido
A1A1indefinido
A2A2indefinido
A3A3indefinido
A6dirección baseA6nueva dirección base
A7apuntador pila usuarioA7nuevo apuntador de pila

Error devuelto:
OMno hay memoria

Descripción:

MT.ALBAS asigna más memoria para que la use el intérprete de comandos BASIC. Esta memoria puede usarse para líneas de programa adicionales o para almacenamiento de datos. El intérprete de comandos es un job muy especial, ya que es el único que se le permite expandir su memoria de esta forma. El intérprete de comandos se ejecuta en modo usuario como Job 0.

MT.REBASTRAP #1D0=17

Libera área del programa BASIC

Parámetros de llamadaParámetros retornados
D1.L# octetos a liberarD1.L# octetos liberados
D2D2indefinido
D3D3indefinido
A0A0indefinido
A1A1indefinido
A2A2indefinido
A3A3indefinido
A6dirección baseA6nueva dirección base
A7apuntador pila usuarioA7nuevo apuntador pila

Error devuelto:
ninguno

Descripción:

MT.REBAS libera área de programa de usuario para que la usen otros jobs. El espacio sólo se libera cuando se ejecuta un comando NEW o CLEAR.

MT.ALCHPTRAP #1D0=18

Asigna área común

Parámetros de llamadaParámetros retornados
D1.L# octetos requeridosD1.L# octetos asignados
D2.LJob ID propietarioD2indefinido
D3D3indefinido
A0A0 dirección base del área
A1A1indefinido
A2A2indefinido
A3A3indefinido

Error devuelto:
OMno hay memoria
NJno existe el job

Descripción:

MT.ALCHP permite a un job asignar por sí mismo espacio en el área común. El espacio asignado no debe ser propiedad del job creado. Cuando se borra el job que posee el espacio, todo el espacio que le ha sido asignado es limpiado y puesto a disposición del sistema para que lo pueda asignar a otra aplicación.

MT.RECHPTRAP #1D0=19

Libera área común

Parámetros de llamadaParámetros retornados
D1D1indefinido
D2D2indefinido
D3D3indefinido
A0base del área a liberarA0indefinido
A1A1indefinido
A2A2indefinido
A3A3indefinido

Error devuelto:
ninguno

Descripción:

MT.RECHP libera espacio del área común que se asignó con el TRAP MT.ALCHP.

5.4.5 Extendiendo el sistema operativo

Es posible extender el QDOS añadiéndole rutinas para servicio de interrupciones o controlar dispositivos. Esto se realiza uniendo las nuevas rutinas en una lista encadenada que mantiene el QDOS. El diagrama y la explicación que le sigue nos muestra cómo funciona.


Figura 5.4a - Lista encadenada original

Un ítem en una lista encadenada consiste en un apuntador de unión que contiene la dirección del siguiente elemento en la lista. Con cada apuntador de unión hay asociada una (o varias) direcciones, que apuntan a las rutinas de servicio. La lista se examina por medio del apuntador de unión, que tiene asociada la dirección de la primera rutina de servicio que se ha de llamar (rutina 1 en el diagrama). Cuando devuelve control esta rutina, el QDOS usa el viejo apuntador de unión para encontrar el nuevo, desechándolo después. El nuevo apuntador tiene otra rutina asociada (rutina 2), que es llamada. A su retorno, se repite el proceso completo hasta que se llega al final de la lista.


Figura 5.4b - Desconexión de un item de la lista

En la figura 5.4b se muestra cómo se borra un ítem de la lista. El apuntador de unión del que se va a borrar (el que estaba asociado a la rutina 2) se pone en el apuntador del anterior. Al hacer esto, forzamos a que la lista vaya de la rutina 1 a la 3, perdiendo la rutina 2 como si no hubiera existido nunca. A este proceso se le llama desconectar un ítem de la lista.


Figura 5.4c - Conectando un ítem a la lista

Cuando se va a conectar un nuevo ítem en la lista, el apuntador de unión del ítem anterior debe ponerse apuntando al nuevo. En nuestro ejemplo, el apuntador viejo del ítem 1 se ha puesto apuntando al nuevo (ítem 4), y en el nuevo se pone el apuntador que había antes en el anterior. Esto nos asegura que la lista permanece completa y se puede extender a cualquier tamaño (dentro de los límites de memoria de la máquina).

Hay cinco listas encadenadas en el QL:

  1. servidores de interrupciones externas
  2. servidores de interrupciones 50/60 Hz
  3. tareas de bucle del planificador
  4. controladores de dispositivos
  5. controladores de dispositivos de directorio

Para cada una de éstas, hay un TRAP que conecta una rutina a su lista, y otro que borra una rutina.

Para la lista encadenada de interrupciones, se deben asignar 8 octetos de RAM. Los primeros 4 octetos forman el apuntador de unión (puesto por MT.LXINT, MT.LPOLL y MT.LSCHD). Los otros 4 octetos forman una palabra larga que contiene la dirección de la rutina conectada.

Para los controladores de dispositivos, se han de asignar 16 octetos de RAM. La primera palabra larga es el apuntador de unión, las segunda, tercera y cuarta palabras largas apuntan respectivamente a las rutinas de entrada/salida, abrir y cerrar. Para los controladores de directorio se requieren 40 octetos de RAM como mínimo.

Antes de añadir un elemento a una lista encadenada, es esencial que la RAM en la que se va a conectar esté asignada al job. La memoria asignada debe estar en el área de procedimientos residentes, si hace falta (código controlador de dispositivos en RAM), o alternativamente en el área común (código controlador de dispositivos en ROM). Si se usa el área común, el espacio debe ser propiedad del Job 0. Si es propiedad de otro job y éste es forzado a borrarse antes de que se hayan desconectado las rutinas correspondientes de la lista, se puede garantizar que fallará el sistema operativo.

MT.LXINTTRAP #1D0=1A

Conecta una rutina de servicio de interrupciones al QDOS

Parámetros de llamadaParámetros retornados
D1D1preservado
D2D2preservado
D3D3preservado
A0dirección de la conexiónA0preservado
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Ver sección 5.4.5 para la descripción de lista encadenada.

MT.RXINTTRAP #1D0=1B

Borra una rutina de servicio de interrupciones del QDOS

Parámetros de llamadaParámetros retornados
D1D1preservado
D2D2preservado
D3D3preservado
A0dirección de la conexiónA0preservado
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Ver sección 5.4.5 para la descripción de lista encadenada.

MT.LPOLLTRAP #1D0=1C

Conecta al QDOS una rutina de servicio de 'polling' 50/60 Hz

Parámetros de llamadaParámetros retornados
D1D1preservado
D2D2preservado
D3D3preservado
A0dirección de la conexiónA0preservado
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Ver sección 5.4.5 para la descripción de lista encadenada.

MT.RPOLLTRAP #1D0=1D

Borra del QDOS una rutina de servicio de 'polling' 50/60 Hz

Parámetros de llamadaParámetros retornados
D1D1preservado
D2D2preservado
D3D3preservado
A0dirección de la conexiónA0preservado
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Ver sección 5.4.5 para la descripción de lista encadenada.

MT.LSCHDTRAP #1D0=1E

Conecta al QDOS una tarea de bucle del planificador

Parámetros de llamadaParámetros retornados
D1D1preservado
D2D2preservado
D3D3preservado
A0dirección de la conexiónA0preservado
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Ver sección 5.4.5 para la descripción de lista encadenada.

MT.RSCHDTRAP #1D0=1F

Borra del QDOS una tarea de bucle del planificador

Parámetros de llamadaParámetros retornados
D1D1preservado
D2D2preservado
D3D3preservado
A0dirección de la conexiónA0preservado
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Ver sección 5.4.5 para la descripción de lista encadenada.

MT.LIODTRAP #1D0=20

Conecta al QDOS un controlador de dispositivos E/S

Parámetros de llamadaParámetros retornados
D1D1preservado
D2D2preservado
D3D3preservado
A0dirección de la conexiónA0preservado
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Ver sección 5.4.5 para la descripción de lista encadenada.

MT.RIODTRAP #1D0=21

Borra del QDOS un controlador de dispositivos E/S

Parámetros de llamadaParámetros retornados
D1D1preservado
D2D2preservado
D3D3preservado
A0dirección de la conexiónA0preservado
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Ver sección 5.4.5 para la descripción de lista encadenada.

MT.LDDTRAP #1D0=22

Conecta al QDOS un controlador de dispositivos de directorio

Parámetros de llamadaParámetros retornados
D1D1preservado
D2D2preservado
D3D3preservado
A0dirección de la conexiónA0preservado
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Ver sección 5.4.5 para la descripción de lista encadenada.

MT.RDDTRAP #1D0=23

Borra del QDOS un controlador de dispositivos de directorio

Parámetros de llamadaParámetros retornados
D1D1preservado
D2D2preservado
D3D3preservado
A0dirección de la conexiónA0preservado
A1A1indefinido
A2A2preservado
A3A3preservado

Error devuelto:
ninguno

Descripción:

Ver sección 5.4.5 para la descripción de lista encadenada.


Anterior Tabla de contenidos Siguiente
Experimentando con QDOS Asignación de Entrada/Salida