Documente Academic
Documente Profesional
Documente Cultură
• Historia.
o Historia de los Sistemas Operativos multitarea.
o Historia de OS/2.
• Acceso al API de OS/2
• Funciones de acceso a ficheros y al hardware.
o Estructura de OS/2 (sistema de ficheros y subsistemas).
o El sistema de ficheros de OS/2.
o Los subsistemas de OS/2.
El subsistema de video (API VIO).
El subsistema de teclado (API KBD).
Modos del teclado.
El subsistema de ratón (API MOU).
Acceso a las funciones de 16 bits. El THUNKING.
Gestion de memoria en el modo protegido del 286
Gestion de memoria en el modo protegido del 386
• Multitarea.
o Concepto de Thread.
o Concepto de Proceso.
o Concepto de Sesión.
o Estructura de OS/2 (Selector de programas, sesiones, procesos y threads).
• Gestion de memoria.
• Funciones de sincronización entre Threads y Procesos.
o Concepto de semáforo.
o Servicios de temporización.
o Comunicación entre procesos
Semáforos y memoria compartida.
Cauces.
Colas.
• Apendice A
o Llamadas DOSxxx del sistema de ficheros
o Llamadas DOSxxx para multitarea
o Llamadas DOSxxx para gestión de sesiones
o Llamadas DOSxxx para gestión de memoria
o Llamadas DOSxxx para semáforos
o Llamadas DOSxxx para temporizadores
o Llamadas DOSxxx para cauces (pipes)
o Llamadas DOSxxx para colas
o Llamadas VIOxxx para acceso a la pantalla
o Llamadas KBDxxx para acceso al teclado
o Llamadas MOUxxx para acceso al ratón
• Apendice B: códigos de error de OS/2
• Historia.
El siguiente paso fue generalizar este conjunto de rutinas. La idea era incluir juntas todas
las rutinas necesarias para acceder al hardware, y hacerlas accesibles a cualquier
programador en forma de llamadas a subrutina. De este modo, cada vez que se hacía un
programa no era preciso incluir en él todas esas rutinas. Había nacido el Sistema
Operativo.
Los primeros 'Sistemas Operativos' (si es que podían recibir ese nombre) no eran más que
un conjunto de subrutinas que ayudaban al programador, ofreciéndole servicios básicos
como lectura de caracteres desde un teletipo, escritura en tambor, disco, etc.
Sin embargo, pronto se vio la necesidad de un nuevo enfoque. Los ordenadores eran caros,
y su mantenimiento también, por lo que solo algunas grandes empresas y universidades
podían disponer de ellos. Para amortizarlos, se alquilaba tiempo de proceso, de modo que
se podía ir allí con un programa, ejecutarlo, y pagar por el tiempo que le llevase hacerlo al
ordenador.
El primer problema que había era que se tardaba mucho en cargar cada programa. Cuando
el ordenador terminaba de ejecutar uno, el operador tenía que insertar el siguiente, lo cual
era una tarea bastante pesada y larga. Durante el tiempo que se hacía esto, el ordenador
estaba totalmente inactivo, se estaba desperdiciando tiempo. Fue entonces cuando surgieron
los sistemas de proceso por lotes (batch).
En los sistemas de proceso por lotes, los programas se almacenan en una cinta, todos
seguidos. Cada vez que el ordenador terminaba de ejecutar un programa, leía el siguiente de
dicha cinta. Si llegaba alguien con un programa, este se añadía a continuación del último
que hubiese, mientras el ordenador iba ejecutando el que ya tenía en memoria. El coste de
mantenimiento del nuevo sistema era ligeramente superior, pues había que añadir al sistema
el lector de cintas en donde se escribían los programas; pero como podía ejecutar muchos
más programas en el mismo tiempo (porque no se perdía parte de este en cargar los
programas), el precio de alquiler bajó drásticamente, permitiendo que más gente tuviese
acceso a los ordenadores.
Es en los sistemas de proceso por lotes donde empieza a aparecer realmente lo que hoy
conocemos como Sistema Operativo: un programa principal que es el que va cargando los
distintos programas desde la cinta, les cede el control de la CPU pero ofreciéndoles una
serie de servicios de Entrada/Salida, y cuando terminan, recupera el control del ordenador.
Pronto los programadores se dieron cuenta de que había aún más tiempos muertos de los
que parecía: si un ordenador tenía que leer algo de una cinta o una tarjeta con datos, como
el sistema era mecánico tardaba mucho tiempo en encontrarlo (mucho tiempo de cara al
ordenador, claro. Una décima de segundo es algo muy relativo). Así mismo, se empezaban
a usar terminales para dar entrada de datos en tiempo real al ordenador, y el tiempo que éste
esperaba a que el usuario pulsase cada tecla también era una eternidad. Entonces se empezó
a pensar en los Sistemas Operativos multitarea.
Finalmente, llegamos a los sistemas distribuidos. Generalmente, lo que tenemos es una red
de ordenadores (por ejemplo, en una universidad es normal tener una gran cantidad de
PC's) conectados entre sí. Si nos fijamos, normalmente solo unos cuantos están en uso en
cada momento, y el resto simplemente se dedican a ejecutar un bonito salvapantallas, o
peor aún, a realizar la estética función de caro pisapapeles. En otras palabras, estamos
desperdiciando una vez más tiempo de proceso. Para solucionarlo, se han ideado los
Sistemas Operativos distribuidos. Estos sistemas operativos son el siguiente paso en la
evolución de los Sistemas Operativos multitarea.
La idea consiste en que en los Sistemas Operativos multitarea los programas suelen estar
divididos en varios Threads o hilos de ejecución. Cada uno de estos Threads se puede ver
como un programa absolutamente independiente de los demás, si bien, trabajando todos en
conjunto forman el programa completo. Dado que cada Thread es independiente de los
demás, nada impediría, en principio, que se ejecutase cada uno en un procesador diferente.
Esa es la base de los Sistemas Operativos distribuidos: cada nuevo Thread es ejecutado en
una máquina distinta, de modo que la potencia de cálculo se halla distribuida por toda la
red. De esta forma, los equipos que estaban inactivos son aprovechados al máximo. Por
desgracia, esta idea resulta extremadamente compleja de implementar, por lo que esta clase
de Sistemas Operativos se encuentran todavía poco extendidos.
HISTORIA DE OS/2
OS/2 son las siglas de "Sistema operativo de segunda generación". La idea de OS/2 surgió
entre IBM y Microsoft a mediados de los 80, en un intento de hacer un sucesor de MS-
DOS, el cual ya empezaba a acusar el paso del tiempo y resultaba claramente
desaprovechador de los recursos de las máquinas de la época (basadas en el Intel 286).
OS/2 1.0 salió en abril de 1987 y era un sistema operativo de 16 bits, pues estaba pensado
para trabajar sobre el microprocesador 286. Sin embargo, aprovechaba plenamente el modo
protegido de este ordenador, haciendo uso de sus capacidades para protección de memoria,
gestión de multitarea, etc. El resultado fue un S.O. estable, rápido y muy potente.
OS/2 ya tenía incorporada desde esa primera versión la multitarea real. Se podían ejecutar
varias sesiones simultáneamente, en cada una de ellas se podían tener múltiples programas,
y cada uno de ellos podía tener múltiples threads en ejecución. Se trataba de una multitarea
jerárquica, con cuatro niveles de prioridad: Crítico (útil para programas que requieran
atención casi constante por parte de la CPU, como un módem), Primer plano
(correspondiente al programa que tiene acceso a la pantalla, teclado y ratón), Medio
(programas lanzados por el usuario que se ejecutan en BackGround) y Desocupado (tareas
de poca importancia o lentas, como el Spooler de impresión). Dentro de cada nivel (a
excepción del de Primer plano), existen 32 niveles de prioridad, los cuales son asignados
dinámicamente a cada programa por el S.O. en función del porcentaje de uso de la CPU, de
los puertos de E/S, etc.
OS/2, además, permitía memoria virtual, con lo que se podían ejecutar programas más
largos que lo que la memoria física instalada permitiría en principio (los requerimientos de
aquella versión eran un 286 con 2 megas de memoria). Por otro lado, incluía la
característica de compartición de código: al cargar dos veces un mismo programa, el código
de este no se duplicaba en memoria, sino que el mismo código era ejecutado por dos
Threads diferentes. Esto permitía ahorrar mucha memoria.
Esta versión de OS/2 era íntegramente en modo texto. Si bien el Sistema Operativo daba la
posibildad de usar los modos gráficos de la tarjeta del ordenador, no incluía ningún API que
ayudase en ello, recayendo todo el trabajo de diseño de rutinas de puntos, líneas, etc en el
programador de la aplicación. Esto no era realmente tan problemático, pues era lo que se
hacía (y se hace) en el mundo del MS-DOS. Sin embargo, se hechaba en falta un entorno
gráfico como Windows.
En la versión 1.1, aparecida en octubre de 1988, llegó por fin el Presentation Manager, un
gestor de modo gráfico, junto con la primera versión de Work Place Shell. Ambos
formaban un entorno gráfico muy parecido al aún no comercializado Windows 3.0.
También hizo su aparición el formato de ficheros HPFS (High Performance File System).
Este sistema de ficheros complementaba al clásico FAT, que era el usado por MS-DOS y
por OS/2 1.0; sin embargo, ofrecía una gran cantidad de ventajas, tales como
El gran problema de OS/2 es que seguia siendo un S.O. de 16 bits, con lo que no
aprovechaba plenamente las capacidades de los 386 de la época, que empezaron a
extenderse con más velocidad de la esperada. Según una revista del sector, Microsoft
sugirió hacer una versión de 32 bits (que obligaría a ejecutarla en ordenadores 386 o
superiores), pero IBM insistió en perfeccionar la de 16 bits. Sobre quien dijo cada cosa
realmente solo se puede especular. Lo único que se sabe a ciencia cierta es que la versión
de OS/2 de 32 bits presentada por MicroSoft en 1990 era casi igual que la versión 1.3, con
la única diferencia de que el kernel era de 32 bits. IBM, por su parte, quería un escritorio
orientado a objetos, y no el clásico shell de OS/2 1.x (el cual MicroSoft copiaría para su
Windows 3.0). Puestas así las cosas, finalmente se rompió el acuerdo entre ambos.
OS/2 2.0, la primera versión de OS/2 de 32 bits, iba a salir inicialmente a finales de 1990;
pero al no contar con la ayuda de Microsoft, IBM no fue capaz de sacarlo hasta 1992,
dandole a Windows 3.0 el tiempo suficiente para asentarse en el mercado.
OS/2 2.0 tenía todas las ventajas de los anteriores OS/2, unido al nuevo núcleo de 32 bits.
No se trataba, por tanto, de un retoque de la versión de 16 bits, sino un sistema operativo
prácticamente nuevo que aprovechaba al máximo las capacidades del modo protegido del
microprocesador 386. Sin embargo, iba más allá que Windows, pues al contrario que éste,
ofrecía compatibilidad garantizada con todas las aplicaciones de 16 bits anteriores, gracias
a la inclusión del API original de 16 bits junto con el nuevo de 32, y además sin perdida de
prestaciones. Así mismo, ofrecía también compatibilidad con Windows 2.x y 3.0, junto con
una compatibilidad con MS-DOS muy mejorada, gracias al modo V86 que incorporan los
micros 386 y del que carecía el 286: en OS/2 1.x la compatibilidad DOS era muy limitada,
quedando reducida a una sola tarea y realizando un cambio entre modo real y modo
protegido del microprocesador, además de consumir de manera permanente 640 Ks de
memoria. Aparte, la emulación no era todo lo buena que cabía esperar. Todos estos
problemas desaparecieron en la versión 2.0, pudiendo tener varias sesiones DOS totalmente
independientes entre sí, con una compatibilidad cercana al 100% y beneficiándose de las
capacidades de Crash Protection del OS/2, que impiden que un programa pueda colapsar el
sistema entero.
Por otro lado, el Work Place Shell (el shell de trabajo gráfico, de ahora en adelante WPS)
fue muy mejorado, resultando un shell totalmente orientado a objetos, con acceso directo a
los ficheros, carpetas dentro de carpetas, ficheros sombra (conocidos como alias en los
sistemas UNIX) y un escritorio de verdad. A su lado, el shell de Windows 3.0 quedaba a la
altura del betún.
IBM consiguió vender OS/2 2.0 en grandes cantidades; sin embargo, no consiguio su
autentico despegue, en parte por culpa de la falta de apoyo por parte de las empresas de
software. El API del Presentation Manager, aunque similar al de Windows, tenía muchas
diferencias, con lo que las empresas tuvieron que elegir entre uno u otro, ante la
imposibilidad de muchas de ellas de dividir su talento entre ambos sistemas.
A principios de 1994 aparece OS/2 Warp, nombre comercial de la versión 3.0 de OS/2. En
ella surgen nuevos elementos: un kit completo de multimedia (mejora del que traía la
versión 2.1) y el Bonus Pak, un kit de aplicaciones que permite ponerse a trabajar con el
ordenador nada más instalar el Sistema Operativo, pues contiene elementos como un Kit de
conexión a Internet completo, el paquete integrado IBM Works (formado por un procesador
de textos, hoja de cálculo, base de datos y graficos de empresa, junto con el PIM, que añade
más funcionalidades aprovechando las capacidades drag&drop del WPShell), soft de
terminal, soft de captura y tratamiento de video, etc. Así mismo, la cantidad de hardware
soportado fue ampliado de manera considerable, soportando casi cualquier dispositivo
existente en el mercado: CD-Roms, impresoras, tarjetas de sonido, soporte PCMCIA,
tarjetas de video, tarjetas de captura de video, tarjetas SCSI, etc. Los requisitos mínimos de
esta versión seguían siendo un 386sx a 16MHz con 4 megas de RAM, los mismos que
Windows 3.11, y podía ejecutar programas DOS, OS/2 16bits, OS/2 32 bits, Windows 2.x y
Windows 3.x (incluia además el API Win32s, con lo que se podían ejecutar incluso
programas Windows de 32bits).
IBM se metió en una campaña publicitaria a nivel mundial para promocionar esta nueva
versión, la cual, sin embargo, no dio los resultados esperados por ser demasiado light (todos
recordaremos aquel anuncio de las monjitas en el convento, cuando una le dice a otra que
ya tiene el nuevo OS/2 Warp, contando las nuevas características de este sistema
operativo). A pesar de eso, OS/2 es ampliamente utilizado en múltiples empresas, bancos
sobre todo, en donde su estabilidad es la mayor garantia (los propios cajeros automáticos
funcionaban inicialmente con OS/2 1.0, si bien actualmente usan OS/2 Warp).
Poco después sale al mercado una revisión de Warp, denominada Warp Connect, la cual
añade un kit completo de conexión a redes, soportando prácticamente cualquier estandar de
red, incluyendo Novell Netware, TCP/IP, etc. junto con soporte para SLIP y PPP.
Hay tres tipos de datos que se pueden pasar a una función de OS/2: un byte (8 bits), una
doble palabra (32 bits) o un puntero (32 bits), y retorna un valor de 16 bits en el
acumulador (AX). Sin embargo, para facilitar la lectura de los programas, en la libreria se
han definido diversos tipos que ayudan en la programación, como por ejemplo, todas las
estructuras de datos necesarias en muchos de los programas. Estos tipos están definidos EN
MAYUSCULAS, y serán usados en la definición de cada llamada en este curso. En el caso
de llamadas al API de 32 bits (llamadas DOS y WIN), no hay peligro en cambiar el tipo
predefinido por un INT, CHAR... pero en las llamadas al API de 16 bits puede ser
problematico, pues se puede devolver un entero de 16 bits, de 32, etc. En estos casos (que,
por fortuna, son muy pocos) conviene utilizar los tipos definidos en la libreria,
escribiendolos, por tanto, en MAYUSCULAS.
Todas las definiciones de las funciones y estructuras están definidas en el fichero OS2.H.
Sin embargo, no es suficiente con añadir un #include <os2.h> al principio del programa,
sino que debemos definir primero qué partes queremos que se incluyan, mediante una serie
de sentencias #define. Existen varias importantes, pero en general, suele bastar con añadir
un #define "INCL_BASE", la cual se encarga de definir automáticamente INCL_DOS
(llamadas DOSxxx) INCL_DOSERRORS (códigos de error) e INCL_SUB (llamadas a
subsistemas), la cual simplemente define INCL_KBD, INCL_VIO e INCL_MOU. Con
ésto es suficiente para realizar casi cualquier programa de OS/2.
Dado que solo hay un código que OS/2 devuelve, y éste está reservado para el código de
error, cuando OS/2 tiene que entregar algún dato, necesita que le demos como parámetro un
puntero a una zona de memoria donde pueda dejarlo. Trabajando con C, basta con que le
demos un puntero a una variable del tipo adecuado. Así, si OS/2 tiene que devolvernos, por
ejemplo, el número de bytes que se han leido de un fichero, debemos darle entre los
parámetros de la función un puntero a una variable donde podrá almacenar ese resultado.
En la descripción de las funciones, cuando una variable es de tipo puntero lleva antepuesta
una p, además de estar indicado en la descripción de los parámetros. Por supuesto, es
posible definir la variable como tal en vez de como un puntero, y usar el símbolo & al pasar
el parámetro a la función.
ESTRUCTURA DE OS/2
(Sistema de ficheros y Subsistemas)
Vamos a empezar por ver como acceder al disco, pantalla y teclado en OS/2. Si bien es
cierto que todos los lenguajes de programación ofrecen instrucciones estandar (y sobre
todo, portables) para acceder a estos dispositivos, el uso de las funciones específicas que
ofrece OS/2 nos permite conseguir mejores rendimientos. Un ejemplo es la lectura de datos
desde un fichero. Si usamos las funciones estandar de C, por ejemplo, leeremos datos de un
en un byte; dado que, al compilar, estamos haciendo una llamada al sistema en sí, en
principio sería lo mismo usar estas funciones que las de OS/2; sin embargo, OS/2 permite
leer multiples bytes de una sola vez. La ventaja es que para cada lectura hay que llamar al
núcleo de OS/2, lo cual consume mucho tiempo. Si tenemos que hacer 100 llamadas
estandar para leer 100 bytes, será un proceso bastante lento. Pero si usamos una llamada de
OS/2 para leer los 100 bytes de una sola vez, al haber una sola transición programa-
nucleo-programa, la lectura será notablemente más rápida. Es imposible acelerar esa
transición, pues no es problema de OS/2, sino de la propia arquitectura Intel.
En este gráfico vemos como está construido el acceso a los dispositivos en OS/2.
La razón de usar el Sistema de Ficheros para acceder tanto a los archivos de disco como a
los dispositivos, es que permite que el programa no se tenga que preocupar de la estructura
de estos: cualquier entrada o salida será tratada como un conjunto de caracteres en serie.
Esta independencia de estructura es cómoda para algunas aplicaciones, pero en otras puede
ser mejor poder acceder directamente a cada dispositivo, normalmente por razones de
velocidad. Esa es la razón de que OS/2 permita a las aplicaciones acceder directamente a
los Subsistemas de Video (VIO), teclado (KBD) y ratón (MOU). A través de estos,
podemos por ejemplo hacer funciones de Scroll en la pantalla, conocer la posición del
ratón, etc.
Podemos comparar los Subsistemas de OS/2 con la BIOS del ordenador, si bien estos son
más completos que aquella en muchas funciones, y sobre todo mucho más rápidos.
El cierre del fichero se hace al final del acceso, cuando ya no vamos a leer o escribir nada
más en él. Esto permite su liberación, de modo que cualquier otro programa puede acceder
a él con total libertad.
DosOpen
DosClose
Una vez que hemos abierto un fichero, podemos acceder a él. Los ficheros aparecen como
un flujo de caracteres en serie, y el caracter actual está referenciado por un puntero que se
incrementa automáticamente después de cada operación. Esto significa que cuando
hacemos una lectura de un fichero, recibiremos el caracter siguiente al último leido. Y cada
vez que escribamos un caracter, lo haremos a continuación del último escrito. Existe sin
embargo la posibilidad de cambiar la posición de dicho puntero, de modo que podemos leer
y escribir de forma aleatoria. Esta opción, sin embargo, solo funciona con ficheros de disco,
y no con dispositivos como el teclado, pantalla, etc.
DosRead
DosWrite
Compartición de ficheros
Dado que OS/2 es un Sistema Operativo multitarea, puede ocurrir que dos (o más)
programas intenten acceder a la vez a un mismo fichero de disco. Para evitar interferencias,
OS/2 ofrece una serie de opciones de compartición de archivos, que se especifican en el
momento de abrirlos. Estas son:
Modos de compartición
Los modos de compartición permiten elegir la forma en que otros programas acceden al
fichero que tenemos abierto. Por ejemplo, si tenemos un programa que lee del fichero
prueba.txt y lo envía a la impresora, lo lógico es que lo abra como READ_ONLY, pues no
va a escribir nada en él. Por otro lado, no puede permitir que otro programa modifique el
fichero, pero no le importa que pueda leerlo, por lo que como modo de acceso asigna
READ_ONLY. De esta forma, si otro programa intenta acceder al mismo fichero, OS/2
solo le dará acceso si intenta acceder en modo READ_ONLY; si intenta abrirlo como
WRITE_ONLY o READ_WRITE, OS/2 le devolverá un error.
Gestión de dispositivos
A través del sistema de ficheros podemos acceder también a los dispositivos físicos que
forman el ordenador. Existen normalmente una serie de archivos que identifican a cada
periférico y que nos permiten enviar y recibir datos hacia y desde ellos.
Los dispositivos cuyo nombre acaba en $ no son accesibles directamente por los programas,
y es preciso usar funciones específicas para acceder a ellos. Esto da una mayor riqueza y
control sobre ellos.
OS/2 ofrece una gran colección de llamadas para gestionar ficheros en el sistema de
archivos. Estas llamadas permiten copiar un fichero a otro, renombrarlo, borrarlo, cambiar
de directorio, etc.
DosCopy
DosMove
DosDelete
DosForceDelete
DosCreateDir
DosDeleteDir
DosQueryCurrentDir
DosQueryCurrentDisk
DosSetCurrentDir
DosSetDefaultDisk
Busqueda de ficheros
En muchas ocasiones un programa necesita conocer todos los ficheros que coinciden con
un patrón determinado. Es el caso, por ejemplo, de querer mostrar una lista de ficheros que
acaben en AL, o que tengan la letra F en el tercer lugar, etc. Para ello, OS/2 facilita una
serie de funciones que permiten realizar ésto. En ellas se debe especificar un nombre de
fichero, sabiendo que:
no se quedará esperando a que pulsemos una tecla, ni mostrará en pantalla lo que haya
convertido. Lo que hará será leer caracteres del fichero entrada.txt, y todo lo que salga lo
escribirá en el fichero salida.txt. Hemos redireccionado la entrada al fichero entrada.txt y la
salida al fichero salida.txt. También podemos direccionar la salida de un programa a la
entrada de otro programa distinto, con el caracter |. Así, si hiciesemos
nos saldría el texto contenido en entrada.txt por pantalla, y cada vez que se llene ésta,
esperará a que pulsemos una tecla. La salida de UPCASE.EXE ha sido tomada como
entrada de MORE.EXE.
Para poder hacer ésto en nuestros programas, simplemente necesitamos leer las órdenes a
través de la entrada estandar (indicando 0 como handle en DosRead) y enviar los resultados
y errores por la salida estandar y la salida de error estandar (indicando respectivamente 1 y
2 como handle en DosWrite).
Variables de entorno
SET mi_variable=C:\OS2UTIL\MIPROGRAMA
DosScanEnv
DosResetBuffer
DosShutdown
DosBeep
Para mejorar y simplificar el acceso a los tres dispositivos principales de entrada de datos,
OS/2 permite el acceso directo a los subsistemas de Video, Teclado y Ratón, de modo que
se puede mejorar notablemente la velocidad de los programas. El acceso a estas funciones
va desde el mismo procedimiento que en el acceso a través del sistema de archivos (una
ristra de caracteres) hasta el acceso directo al hardware del sistema. Entre ambos extremos
hay un amplio abanico de posibilidades. De entre todas ellas, se deberá escoger la que
mejor se adapte a las necesidades de velocidad y flexibilidad del programa.
• Subsistema de Video
• Subsistema de Teclado
• Subsistema de Puntero (Ratón)
EL SUBSISTEMA DE VIDEO
El subsistema de vídeo (VIO) es el encargado de gestionar la comunicación entre los
programas y la pantalla. Es, sin duda, el subsistema más complejo de los tres, y el que
ofrece, por tanto, mayores posibilidades.
Dado que puede haber varios programas ejecutandose a la vez en el sistema, pero solo uno
puede acceder a la vez a la pantalla (normalmente el programa que se encuentra en primer
plano o foreground), es necesario virtualizar ésta por medio de un buffer de pantalla propio
de cada programa: el LVB (Logic Video Buffer, buffer de vídeo virtual). Cuando una
aplicación quiere escribir en pantalla y se encuentra en segundo plano (background), su
salida se escribe en dicho LVB. En el momento en que el usuario conmuta dicho programa
a primer plano, el LVB se copia tal cual en la memoria de pantalla, y el resto de las
escrituras van a ésta directamente. Si se vuelve a conmutar dicho programa a segundo
plano, OS/2 copia lo que hubiese en pantalla en ese momento al LVB. De este modo, el
programa nunca sabe ni le preocupa cuando está en primer o en segundo plano.
FUNCIONES VIO
¿Cual es la diferencia entre uno y otro, entonces? Las diferencias son dos: la primera es que
el uso del subsistema VIO es una opción más rápida que el sistema de ficheros; la segunda
es que si usamos el subsistema, no podremos redireccionar la salida a un fichero, o a otro
dispositivo de salida. Siempre irá a la pantalla.
El servicio TTY admite los caracteres de control estandar del ASCII, y también puede
soportar ANSI, si éste es activado mediante la llamada correspondiente.
VioWrtTTY
VioGetAnsi
VioSetAnsi
VioGetMode
VioSetMode
VioGetState
VioSetState
Los siguientes servicios se encargan del tratamiento de la pantalla a más bajo nivel. Con
ellos podemos imprimir largas cadenas de caracteres con atributos y leer los caracteres que
hay en determinadas posiciones de la pantalla. También podemos repetir un caracter o una
pareja caracter-atributo un número determinado de veces.
Los atributos son bytes que definen el color de tinta y de fondo para cada caracter, así como
otras características como el parpadeo. Están compuestos por un byte, el cual se divide en
dos nibbles (grupos de 4 bits). El nibble de menor peso determina el color de la tinta del
caracter, y el de mayor peso el color de fondo y, según se encuentre activo o no, el
parpadeo del caracter. La distribución es como sigue:
Los bits de color activan directamente cada una de las componentes del monitor, de modo
que éstas se suman directamente, dando lugar a los siguientes colores:
Tanto cuando hacemos una lectura como una escritura, si excedemos el fin de una línea se
seguirá leyendo en la siguiente, y si llegamos al final de la pantalla no se seguirá leyendo ni
imprimiendo.
VioWrtCellStr
VioWrtCharStr
VioWrtCharStrAtt
VioWrtNAttr
VioWrtNCell
VioWrtNChar
VioReadCellStr
VioReadCharStr
Funciones de Scroll
VioScrollDn
VioScrollLf
VioScrollRt
VioScrollUp
El cursor (el cuadradito parpadeante) es totalmente definible por el usuario en las sesiones
de texto y gráficos de OS/2. Podemos definir tanto su tamaño como su posición dentro del
caracter.
VioGetCurPos
VioSetCurPos
VioGetCurType
VioSetCurType
Acceso al LVB
Cuando se necesite alta velocidad, se puede pedir acceso directo al buffer virtual de video
asociado con la aplicación. Al hacerlo, OS/2 devuelve un puntero a la zona de memoria en
donde está situado, con lo que podremos escribir en él como si se tratase de la pantalla
física. Una vez que hemos terminado, debemos enviar una orden de retrazado, que hará que
OS/2 copie el LVB a la pantalla física (siempre que la aplicación se encuentre en primer
plano). Esto significa que los cambios que hagamos en el LVB no son visibles hasta que
nosotros queramos.
Usar esta opción implica que perdemos el aislamiento entre el hardware y nosotros: dado
que el LVB no es más que una copia del buffer real de pantalla, es necesario que nuestro
programa conozca la geometría y la forma de almacenamiento de los datos en ésta.
VioGetBuf
VioShowBuf
En el extremo opuesto se encuentran las funciones de acceso directo al video. Con ellas,
OS/2 da acceso directo a la memoria de pantalla. Sin embargo, esto que en el DOS de
siempre es la opción más normal y común, puede resultar catastrófico en un Sistema
Operativo multitarea como OS/2; no se hundiría el suelo bajo nuestros pies, pero la pantalla
podría ser alterada en un momento poco oportuno... si OS/2 no tomase las debidas
precauciones.
Lo primero que hay que hacer es pedir la dirección física de la memoria de vídeo. OS/2
devuelve un selector (o varios) a dicha zona de memoria (ver modos de direccionamiento
del 286). Estos selectores deben ser convertidos a punteros antes de poder trabajar con
ellos. Pueden ser varios pues cada selector no puede apuntar a un bloque de memoria mayor
de 64Ks, el cual es también, casualmente, el tamaño de cada bloque de memoria de las
tarjetas de vídeo actuales. Esto ayuda a simplificar el acceso, pues en modos como
640x480x16colores no necesitaremos cambiar de bancos; OS/2 nos devuelve un selector
que apunta a cada uno de ellos, con lo que solo tenemos que acceder normalmente como si
fuese memoria lineal, y el Sistema Operativo conmutará de uno a otro automáticamente.
Cada vez que queramos acceder a la memoria física de video, debemos bloquear el acceso
al buffer. Si el programa estaba en primer plano, OS/2 devolverá un valor afirmativo al
retornar de la llamada, y bloqueará el selector de programas. Esto significa que el usuario
no podrá conmutar la sesión actual a segundo plano hasta que ésta termine el acceso. Sin
embargo, el resto de las aplicaciones siguen funcionando en segundo plano, sin verse
afectadas por este hecho. Por supuesto, esto puede ser peligroso, y OS/2 toma algunas
precauciones: si el sistema está bloqueado y el usuario hace una conmutación de tarea, si el
programa no desbloquea el conmutador antes de un cierto tiempo definido por el sistema,
queda congelado y se realiza la conmutación. Por eso es recomendable desbloquear cada x
tiempo el selector de programas y volverlo a bloquear.
Si por el contrario el programa se encontraba en segundo plano, hay dos opciones: OS/2
puede retornar un código de error al programa, con lo que este sabrá que no tiene acceso al
buffer y puede seguir trabajando en otra cosa, o bien OS/2 congelará al programa hasta que
el usuario lo pase a primer plano, momento en que lo despertará indicandole que tiene
acceso al buffer real.
Una vez que OS/2 ha devuelto un resultado afirmativo, el programa tiene acceso total a la
memoria de video. Cuando haya terminado, tiene que proceder a desbloquear la pantalla, de
modo que OS/2 pueda desbloquear el selector de programas y devolver el sistema a la
normalidad.
VioGetPhysBuf
VioScrLock
VioScrUnLock
Existe una dificultad adicional a la hora de trabajar con acceso directo a la pantalla. Se trata
de que OS/2, al conmutar de una tarea a otra, solo guarda el contenido de la pantalla si ésta
se encontraba en modo texto (esto se cumple para OS/2 1.x. En Warp 4, sin embargo, SI
conserva el contenido de la pantalla, pero no se si se cumple también para OS/2 2.x o 3.x).
Si estabamos trabajando en modo gráfico, el contenido se perderá. Para evitarlo, OS/2
facilita la posibilidad de crear un thread (este concepto será explicado más adelante, cuando
veamos la multitarea a fondo) que será activado cuando el programa vaya a cambiar de
primer a segundo plano, y viceversa. Esto es así para permitir que un programa pueda
almacenar el contenido de la pantalla en modo gráfico cuando no tiene bloqueado el acceso
a la memoria de video. Es el thread SavRedrawWait.
VioSavRedrawWait
EL SUBSISTEMA DE TECLADO
El subsistema de teclado es el encargado de gestionar el acceso de los programas a este
periférico. Al igual que el subsistema de vídeo, no es necesario usarlo para escribir
programas sencillos de cara al interface de usuario, pues se puede acceder fácilmente a él a
través del sistema de archivos. Es más, el uso del subsistema de teclado no ofrece casi
ninguna ventaja en lo que a velocidad se refiere. Por tanto ¿para qué usarlo? Simplemente
porque el acceso a través del sistema de archivos solo nos permite detectar códigos ASCII,
pero no la pulsación de teclas de funcion, o, por ejemplo, si tenemos pulsada la tecla ALT,
o CTRL, etc. Para este tipo de funciones necesitamos usar los servicios del subsistema de
teclado.
FUNCIONES KBD
Con este sistema, se leen caracteres hasta que el buffer definido esté lleno o hasta que se
pulse el caracter de retorno, que por defecto es el retorno de carro (tecla enter).
KbdStringIn
KbdGetStatus
KbdSetStatus
La lectura por caracteres es un acceso más a bajo nivel. En este caso podemos saber
exactamente qué tecla está pulsada y cual no, y saber si además se encuentran apretadas
teclas como Ctrl, Alt, AltGr, etc. Además, si el informe de cambio está activo, se
notificará no solo cuando se pulse una tecla, sino también cuando se suelte.
KbdCharIn
KbdPeek
Por último, tenemos una función que nos permite vaciar el buffer de teclado, de modo que
todas las pulsaciones hechas hasta el momento que no han sido atendidas serán borradas.
KbdFlushBuffer
MODOS DE TECLADO
Los modos de teclado se definen como:
ASCII
En este modo, que es el modo por defecto, el teclado es sensible al retorno de carro. Esto
significa que cuando el usuario teclee datos, la llamada no retornará hasta que éste pulse
ENTER. Siempre se debe leer de forma síncrona, por lo que la opción NO ESPERA de
KbdStringIn no está soportada. El campo de longitud se utiliza para determinar qué
cantidad del buffer de entrada es afectado por las funciones de edicion de línea (como
F3=recupera la ultima cadena, F1=recupera caracter, etc). Si se pone a 0, la edición está
desactivada. En este modo, los códigos de caracteres de dos bytes (DBCS) se retornan de
forma completa.
BINARIO
En este modo, solo se retorna cuando el buffer se llena por completo, y el retorno de carro
no tiene ningún significado especial.
ECO
Si el modo ECO está activo, todo lo que se pulse aparecerá en pantalla automáticamente; en
caso contrario, no lo hará. El modo ECO solo puede estar fijado con el modo ASCII, nunca
con el BINARIO
EL SUBSISTEMA DE RATON
El subsistema de ratón controla todo lo referente a este dispositivo.
Lo primero que hay que hacer para trabajar con el ratón es abrir el dispositivo. Esta acción
crea una cola de eventos para el ratón e inicializa todos los parámetros de la sesión a un
valor conocido.
MouOpen
MouClose
La cola de eventos del ratón almacena todos los sucesos ocurridos con éste dispositivo, de
modo que puedan ser leídos por el programa en el momento adecuado. Un evento es, por
ejemplo, mover el ratón, pulsar un botón, soltarlo... Existe la posibilidad de filtrar
determinados eventos, de modo que al leer la cola, el resultado será exactamente igual que
si no se hubiesen producido.
Cuando se lee la cola, se puede especificar además si la función retornará aún en el caso de
que ésta esté vacía, o bien si debe esperar a que haya algún evento en ella. Además de esto,
podemos conocer en cualquier momento la posición actual del ratón, así como cambiarla
por otra. Estas opciones deben usarse sólo con fines de actualizar alguna variable del
programa; si se pretende que sea el propio programa el que pinte el cursor, es mejor leer las
coordenadas por medio de la cola de eventos.
MouReadEventQue
MouGetEventMask
MouSetEventMask
MouFlushQue
MouGetNumQueEl
MouGetPtrPos
MouSetPtrPos
Por otro lado, es posible definir un área de la pantalla que será especifica de la aplicación,
de modo que el controlador no pintará el cursor cuando éste se encuentre dentro de aquella.
Por defecto, este área ocupa toda la pantalla.
MouDrawPtr
MouRemovePtr
MouGetDevStatus
MouSetDevStatus
Por último, es posible obtener información sobre las características de nuestro ratón.
MouGetNumButtons
MouGetNumMickeys
Sin embargo, IBM tenía claro que quería conservar la compatibilidad con todos los
programas escritos para OS/2 1.x, por lo que tuvo que incluir un API de 16 bits, que
aceptaba los parámetros en el formato del modo protegido del 286, y otra API de 32 bits,
que trabajaba con el formato del modo protegido de los 386 o superiores. Esto significa
que, en principio, no podemos llamar a estas funciones desde nuestros programas de 32
bits, pues la forma en que se pasan los punteros, por ejemplo, es distinta.
Ahora bien; dado que el API 16 y el API 32 son casi iguales, repetir todo el código de una
para la otra suponía ocupar una gran cantidad de memoria y de espacio en disco, por lo que
se realizaron las partes comunes dentro del API 32, y cada vez que un programa de 16 bits
quería acceder a ellas, se realizaba un proceso de recorte (THUNKING), de modo que los
datos eran convertidos al formato adecuado. Si dicho programa quería acceder a una
función no perteneciente al API 32 (una llamada a un subsistema, por ejemplo), podía
hacerlo directamente, pues al pertenecer exclusivamente al API 16, estaba escrita para
trabajar únicamente con el formato del 286.
Lo interesante de esto es que nosotros podemos aprovechar estas rutinas de thunking para
convertir nuestros parámetros al formato de 16 bits, y así llamar al API de los subsistemas.
Para facilitarlo aún más, Robert E. Canup ha escrito una DLL, así como ficheros LIB y H
para trabajar desde C, que hacen ver al compilador que son llamadas de 32 bits, y no de 16.
Esto nos permite trabajar con estas funciones con total comodidad. El fichero lo tienes
disponible simplemente pulsando aqui. Este fichero no lo necesitarás si usas el compilador
EMX, el cual realiza automáticamente el proceso de Thunking.
Sin embargo, ni esta librería ni el EMX realizan el Thunking de forma completa. Cuando se
trata de enviar un puntero entre los parámetros de la función, lo hacen bien, pero no si la
rutina devuelve un puntero.
En el caso de que se nos devuelva un selector, o un selector y un offset, debemos usar una
función que nos convierta estos datos en un puntero.
MAKEP
Por otro lado, en algunas funciones tenemos que pasar un puntero dentro de una estructura
de datos (por ejemplo, cuando queremos cambiar los colores de la paleta con VioSetState).
En estos casos tenemos que poder convertir dichos punteros al formato de selector:offset.
El caso contrario no es preocupante, pues OS/2 puede trabajar directamente con punteros
del tipo selector:offset.
_emx_32to16
La forma de acceder a la memoria en el modo protegido, que es en el que trabaja OS/2 1.x,
es mediante una combinación de dos valores de 16 bits cada uno: un selector y un offset o
desplazamiento. El valor del selector se almacena en un registro del microprocesador, y se
usa como índice para acceder a una tabla de selectores. Conviene señalar que esta tabla se
almacena en la memoria RAM del sistema; esto explica que el hecho de cargar un nuevo
valor en el registro de selector consuma mucho tiempo, pues hay que hacer dos lecturas a
RAM (cada entrada de esta tabla ocupa 4 bytes), aparte de numerosas comprobaciones. En
esta tabla se definen los distintos segmentos accesibles por el programa, y entre estos datos
se incluye la posición base de cada uno. El offset indica el valor a sumar a dicha dirección
base para obtener la dirección del dato.
Dado que la dirección base almacenada en la tabla de selectores viene definida con 24 bits,
podemos situar cada segmento en cualquier punto de la memoria física (el 286 tiene un bus
de direcciones de 24 lineas, luego puede direccionar hasta 16 megas), y recorrerlo con el
valor de offset. Dado que este valor es de 16 bits, el tamaño máximo de cada segmento es
de 64 kilobytes.
En este gráfico se ve el proceso seguido para calcular una dirección en base al valor del
selector y del offset.
Vemos que este sistema nos permite cambiar de sitio cada uno de los segmentos sin que el
programa falle. Esto es porque el programa se refiere a cada segmento a través de un
selector, y no por medio de su dirección base, como ocurre en el modo real. Si para
compactar la memoria, el OS/2 necesita mover el segmento 3 a otra zona, cambiará también
la dirección base en la tabla de selectores. Como el programa se refiere a esa zona como el
'Selector 3', siempre accederá de forma correcta.
También vemos que el tamaño de cada segmento es variable, pudiendo medir desde 1 byte
hasta 64 Kbytes. Si el segmento mide menos que el máximo y se intenta leer o escribir más
allá de sus límites, se produce un error, una excepción, que hace que OS/2 intercepte el
programa y lo termine inmediatamente. Lo mismo si se carga un valor no válido en el
registro de selector.
Este esquema permite además implementar un sistema de memoria virtual. Entre los
muchos datos que se almacenan en la tabla de selectores, uno de ellos es si ese segmento
está presente o no en la memoria física. Si el programa carga en el registro de selector un
valor correspondiente a un segmento no presente, se produce una excepción, la cual salta al
gestor de memoria virtual que lo que hará será pasar a disco un segmento poco usado,
marcandolo como no presente, cargar en esa zona el segmento reclamado por el programa,
marcarlo como presente y anotar su dirección base en la tabla de selectores, y devolver el
control a la aplicación. De este modo se puede trabajar con más memoria que la que tiene
físicamente la máquina.
Este esquema tiene un pequeño problema: dado que los segmentos pueden tener un tamaño
variable, puede ser necesario liberar varios pequeños para hacer sitio a uno grande.
Además, la memoria puede fragmentarse con facilidad, lo que obliga a reorganizarla para
juntar pequeños huecos libres en uno solo grande. Todo esto consume tiempo extra durante
el intercambio a disco. Pensando en esto, en el 386 se mejoró el modo protegido, de modo
que se facilita la gestión de la memoria virtual.
En principio, el modo protegido del 386 funciona igual que el del 286: tenemos un registro
de selector de 16 bits y uno de offset, esta vez de 32 bits, y en la tabla de selectores
disponemos esta vez de 32 bits para indicar la posición base de cada segmento. Sin
embargo, disponemos solamente de 20 bits para indicar la longitud de cada segmento. Esto
nos la limitaría, en principio, a una longitud máxima de 1 mega por segmento. Sin
embargo, los diseñadores de Intel reservaron un bit de granularidad entre los muchos
reservados del registro de estado. Este bit permite conseguir segmentos de hasta 4 GBytes
con solo 20 bits. El truco consiste en que, si el bit está a cero, la unidad de longitud será 1
byte, con lo que solo podremos tener segmentos de hasta 1 mega. En este modo se es
compatible con el 286. Pero si este bit se pone a 1, la unidad será 4 KBytes. Esto significa
que un segmento puede tener una longitud múltiplo de esta: 4, 8, 12, 16, 20, 24, 28, 32, etc
KBytes. En total, podemos disponer de hasta 64 TeraBytes de memoria para cada
programa.
Sin embargo, cuando usamos memoria virtual, este método tiene un inconveniente que ya
comentamos: dado que los segmentos pueden tener un tamaño variable, puede ser necesario
liberar varios pequeños para hacer sitio a uno grande. Además, la memoria puede
fragmentarse con facilidad, lo que obliga a reorganizarla para juntar pequeños huecos libres
en uno solo grande. Todo esto consume tiempo extra durante el intercambio a disco. Para
evitarlo, se incluyó el modo paginado. Si este modo está activo, las direcciones que salen de
la suma del selector y del offset son pasadas por un esquema como el siguiente:
Aquí vemos que el sistema se ha complicado mucho más. Mediante este, podemos dividir
la memoria en bloques de 4KBytes, e intercambiar solo aquellos que nos interese. De esta
forma, si tenemos que cargar un segmento de 24 KBytes, solo liberaremos 24 KBytes de
otro segmento, y no un segmento completo. Esto acelera las transferencias, elimina la
fragmentación (los bloques de 4KBytes que forman un segmento no tienen por qué ser
consecutivas) y evita el crecimiento incontrolado del fichero de Swap. Sin embargo, hace el
sistema algo más lento, pues tiene que hacer más accesos a la RAM (todas estas tablas se
situan en la RAM normal). Para evitarlo, se incluyeron una serie de Buffers que retienen las
paginas de direccion y de tabla mas usadas.
En OS/2 2.0 y posteriores se define un único segmento para cada programa, de 4 GBytes de
longitud (si bien la cantidad de memoria disponible para uso propio es de 'solo' 512
MBytes) y se activa el modo paginado. De esta forma el acceso es más rápido, pues no es
necesario cargar múltiples valores en el registro de selector, y conservamos la capacidad de
memoria virtual. Esta es la razón de que, cada vez que se hace una reserva de memoria (con
DosAllocMem o similar) la cantidad se redondea al múltiplo de 4KBytes superior.
Multitarea.
CONCEPTO DE THREAD
Un Thread (que de una forma un poco 'basta' se puede traducir como hilo) es la unidad
básica de ejecución de OS/2. Cualquier programa que se ejecute consta de, al menos, un
thread.
Dado que la forma en que un programa funciona depende principalmente del contexto, dos
threads distintos pueden compartir el código de ejecución. Esto significa que si queremos
dos threads que hagan las mismas operaciones sobre dos grupos de datos distintos, no
necesitamos duplicar el código en memoria. Dado que conmutamos el contexto cada vez,
aunque el código sea el mismo, los resultados no lo son, pues los registros y la pila son
diferentes. Sin embargo, esto tiene un pequeño problema: las zonas de datos son comunes
para todos los threads de un mismo proceso (esto ocurre en cualquier S.O., no solo en
OS/2). Eso implica que, en estos casos, es necesario que cada thread cree su propia zona de
datos, esto es, usar memoria de asignación dinámica (en C se usa MALLOC para crear una
zona de memoria dinámica, si bien OS/2 también ofrece servicios de este tipo más
potentes).
Por otro lado, debemos recordar que cada thread se ejecuta de forma absolutamente
independiente. De hecho, cada uno trabaja como si tuviese un microprocesador para el solo.
Esto significa que si tenemos una zona de datos compartida entre varios threads de modo
que puedan intercambiar información entre ellos, es necesario usar algún sistema de
sincronización para evitar que uno de ellos acceda a un grupo de datos que pueden estar a
medio actualizar por otro thread. Estos servicios se verán más adelante.
Un thread puede crear otro thread usando una llamada de OS/2. Dado que no existe relación
del tipo padre-hijo entre threads, los nuevos alcanzan los mismos privilegios que sus
hermanos.
DosCreateThread
Por último, un thread acaba cuando vuelve al repartidor de threads. Trabajando en código
máquina, esto implica un FAR RET; trabajando en C, significa ejecutar una instrucción
EXIT o bien llegar al fin del procedimiento MAIN. Cuando acaban todos los threads de un
proceso, OS/2 lo mata, liberando la memoria que ocupaba y los distintos recursos, como
ficheros de disco, etc. Existe otra forma de terminar un programa en OS/2, es mediante una
llamada a DosExit. Esta llamada finaliza el thread actual.
DosExit
Un thread tiene control sobre otros threads, siempre que pertenezcan al mismo PROCESO,
pudiendo terminarlos si es preciso. También puede sincronizarse con ellos si es preciso,
esperando a que alguno en concreto termine su ejecucion, o bien dormirlo hasta que le
interese ponerlo en ejecución de nuevo.
DosKillThread
DosWaitThread
DosSuspendThread
DosResumeThread
CONCEPTO DE PROCESO
Un proceso no es más que un conjunto de threads que ejecutan el mismo código, junto con
las zonas de memoria asociadas a ellos y los ficheros que tienen abiertos.
DosExecPgm
DosKillProcess
Los threads que componen un proceso tienen un cierto control sobre los demás, pudiendo
bloquearse entre sí de forma temporal si alguno lo necesita, entre otras posibilidades.
DosEnterCritSec
DosExitCritSec
Cada vez que un thread se adueña de un recurso del sistema (un fichero o un bloque de
memoria), este pasa a formar parte del proceso al que pertenece dicho thread, y accesible a
todos los threads de éste, pero los threads del resto de los procesos no tienen por qué poder
acceder a estos. Por supuesto, si un proceso acaba o bien si es abortado (bien
voluntariamente por el usuario, bien por un error o trap), OS/2 libera automáticamente
todos los recursos ocupados por éste. Sin embargo, esto no significa que no se deban liberar
estos antes de finalizar los threads que componen el proceso. Es buena práctica hacerlo,
aparte de que el hecho de cerrar los ficheros permite que se actualicen los datos que se han
grabado en ellos.
Si se crean dos procesos a partir del mismo código, OS/2 también procede a realizar
compartición de código, con el consiguiente ahorro de memoria. Sin embargo, este hecho
no implica que los recursos de cada proceso sea compartido con el resto.
En el caso de los procesos, sí existe una relación padre-hijo: si un proceso crea otro, el
primero se denomina padre, y el segundo, hijo. Los hijos de un proceso, así como los hijos
de los hijos, etc, se llaman descendientes.
El proceso padre mantiene un control sobre toda su descendencia, de forma que puede
matar a cualquier proceso perteneciente a ésta.
Cuando se crea un proceso hijo, este hereda los recursos del padre, a menos que estos
fuesen adquiridos por el padre sin derecho a herencia. Esto es: si el padre ha abierto con
derecho a herencia un fichero, sus hijos podrán acceder a él; en caso contrario, no podrán (a
menos que lo vuelvan a abrir ellos mismos, y los privilegios de acceso lo permitan).
Por último, se puede escoger si un proceso hijo determinado se ejecuta de forma síncrona o
asíncrona. En el primer caso, el proceso padre se detiene hasta que el proceso hijo ha
terminado; en el segundo, ambos procesos siguen ejecutándose de forma independiente.
Prioridades de ejecución
En un S.O. multitarea, todos los procesos compiten por tener el máximo tiempo de CPU.
Sin embargo, es obvio que no todos tienen la misma importancia. Por ejemplo, un
programa que controle un MODEM tiene que tener prioridad sobre otro que controle una
impresora, por ejemplo. Por esto existen las prioridades.
• Crítico.
• Primer plano.
• Medio.
• Desocupado (Idle).
En el nivel crítico se ejecutan aplicaciónes que necesiten una atención inmediata por parte
del procesador, como sistemas de comunicaciones o en tiempo real (sonido, video,...).
Dentro de este nivel existen 32 subniveles distintos de prioridad. El subnivel en el que se
situa el programa es escogido por él mismo. Un ejemplo es el selector de programas del
OS/2, el cual corre en el subnivel más bajo (0) del nivel crítico.
Para evitar una sobrecarga del sistema, es conveniente que los programas que se situen en
nivel crítico consuman poco tiempo de CPU.
En el nivel de primer plano se ejecuta la aplicación que se encuentra en primer plano en ese
momento. Solo tiene un subnivel de prioridad.
En el nivel medio se ejecutan los programas normales, como las aplicaciones, juegos, etc.
Tiene 32 subniveles distintos de prioridad. El subnivel en que se situa es escogido por
OS/2, y varia de forma dinámica en función de los accesos de E/S y del tiempo de CPU que
consuma el programa, de forma que los más voraces tendrán menor prioridad que los
menos.
Por último, en el nivel desocupado (o Idle Time) se situan aquellos programas que no
necesitan una atención excesiva de la CPU. Es lo opuesto al nivel crítico. Tiene 32
subniveles de prioridad. El subnivel en el que se situa el programa es escogido por él
mismo. Un ejemplo es el Spooler de Impresora, o bien un salvapantallas.
DosSetPriority
Una espera activa es cuando entramos en un bucle que se limita a comprobar el estado de
una variable, y solo salimos de él cuando cambia a un valor determinado. Un ejemplo es
cuando queremos esperar a que el usuario pulse una tecla. Normalmente hacemos un bucle
que se queda leyendo el teclado constantemente hasta que el usuario realice la pulsación. El
problema es que estamos desperdiciando un tiempo que se podría aprovechar para ejecutar
otras cosas. Para evitarlo, OS/2 nos suele dar la posibilidad de esperar por nosotros. Por
ejemplo, cuando vimos el subsistema de teclado, podíamos pedirle a OS/2 que retornase de
la llamada tanto si había una tecla pulsada como si no, o bien que no devolviese el control
hasta que se pulsase. Pues bien, si le pedimos que haga la segunda opción, estaremos
ahorrando ciclos de CPU. Esto es así porque OS/2 sabe que ese proceso no está listo para
correr, por lo que repartirá el tiempo de CPU unicamente entre el resto de los procesos, y
no volverá a dedicarle atención hasta que el usuario pulse una tecla, momento en que
volverá a darle ciclos de CPU. Vemos que, de esta forma, OS/2 ahorra el tiempo que
consumiríamos si nos dedicásemos a hacer una espera activa, con lo que si ejecutamos
varios programas a la vez tardarán menos que la suma del tiempo que tardarían en
ejecutarse uno a uno, precisamente porque aprovechamos estos tiempos muertos. Por
supuesto, no solo se ahorran éstos, sino también los necesarios para mover los cabezales de
los discos, escribir caracteres en la pantalla, etc.
Si hay dos procesos listos para correr, siempre obtendrá el procesador aquel que tenga
mayor prioridad. Puede parecer que si un proceso siempre tiene cosas que hacer y está
situado en un nivel alto, siempre se quedará con la CPU, dejando al resto de las tareas
bloqueadas. Esta es la razón de que la prioridad de los procesos en el nivel medio sea
dinámica: de esta forma, los procesos que consumen mucha CPU ven rebajada su prioridad,
de modo que todos se ejecutan por igual. Por otro lado, existe un tiempo máximo durante el
cual un proceso puede estar sin recibir ciclos de CPU. Transcurrido este, OS/2 le cambiará
momentaneamente la prioridad, de modo que recibirá un intervalo de tiempo, volviendo a
su prioridad original después. Esto permite que los programas de prioridad desocupada
obtengan siempre ciclos de CPU, aunque haya un proceso de nivel medio que consuma
mucha (por ejemplo, un RayTracer).
CONCEPTO DE SESION
Hasta ahora, cada vez que nos referíamos a un conjunto de Procesos, usábamos el término
programa ; si bien algunas veces el uso fue correcto, en otras no fue así, y deberíamos
haber usado el término sesión.
Una sesión es un conjunto de procesos, junto con una pantalla, teclado y ratón virtuales.
Cada vez que abrimos un programa en modo texto, OS/2 crea para él una sesión,
asignándole un LVB en donde escribir los datos de salida, y una cola de teclado y de ratón,
en donde se almacenarán las pulsaciones y los movimientos hasta que pueda procesarlos. Si
esa sesión se encuentra en primer plano, su VDU, cola de teclado y cola de ratón estarán
conectadas directamente a la pantalla física, al teclado físico y al ratón físico, así hasta que
el usuario conmute a otra sesión usando el selector de programas, el cual es una parte
especial de OS/2 dedicada precisamente a gestionar las sesiones.
Todos los procesos pertenecientes a una sesión acceden al mismo LVB y a la misma cola
de teclado y ratón. Esto no es un problema, sino un efecto buscado, pues si es necesario, es
posible desde una sesión crear otras nuevas y arrancar en ellas procesos, de modo que no
interfieran con el actual. De aquí surje que también existe una jerarquía de sesiones, y
ciertas implicaciones entre ellas. Por ejemplo, una sesión padre puede ser notificada de que
ha terminado una sesión hijo. También una sesión padre puede traer a primer plano a
cualquiera de sus hijos (siempre y cuando ella esté en primer plano), o vincularse a ellos, de
modo que cuando uno sea traido a primer plano, el padre también lo haga (y viceversa). Por
último, un padre puede parar una sesión hijo, pero solo a ella, y no a sus 'nietos', o a ella
misma. Sin embargo, cuando se para una sesión hijo, se paran también todas sus sesiones
descendientes.
DosStartSession
DosStopSession
DosSelectSession
A nivel interno, los conceptos de Thread y Proceso están implementados en el núcleo,
mientras que el de Sesión lo está en los subsistemas (los cuales no pertenecen al núcleo),
pues son ellos los que deciden cuando un conjunto de procesos puede acceder o no a la
pantalla física, o al teclado. Por otra parte, el selector de programas es el encargado de
gestionar cuando se conmuta de uno a otro.
Normalmente, cada vez que arrancamos un programa, se crea una sesión para él; sin
embargo, en algunos casos podemos desear que no se cree una pantalla virtual o una cola
de entrada de teclado, por ejemplo si el programa no tiene que interactuar con el usuario.
En estos casos, se puede arrancar en una sesión 'muda', a la cual no se podrá conmutar
mediante el selector de programas, y que finalizará cuando acabe dicho proceso/procesos.
Para arrancar un programa así desde la línea de comandos de OS/2, se usa la orden
DETACH nombre de programa.
Surje la cuestión de como se arranca la primera sesión, la que nos permitirá arrancar nuevos
programas, etc. Para ello, OS/2 incluye una línea en el CONFIG.SYS que le indica un
proceso que debe arrancar al principio de todo. Este proceso será el Shell del sistema.
El Shell del sistema, en principio, puede ser cualquier programa, de modo que al arrancar
OS/2, se arrancará éste también. Sin embargo, para que sea útil, tiene que permitir arrancar
nuevas sesiones desde él y pasar el control a éstas.
En OS/2 1.0, el Shell del sistema era un simple menú, que contenía los programas básicos
(como una línea de comandos). Pulsando sobre estos, se arrancaba una nueva sesión con
éste, y se le cedía el control. En las versiones posteriores, como Shell se pone el
Presentation Manager. Este es el gestor de ventanas. Sin embargo, el PM no incluye
ningún Shell, por lo que surge una nueva línea en el CONFIG.SYS, que especifica qué
Shell debe arrancar el PM. Este Shell, por defecto, es el Work Place Shell, o WPS, que es
el que nos da la orientación a objetos del escritorio de OS/2; sin embargo, puede ser
cambiado por otro cualquiera. Por ejemplo, poniendo como Shell el CMD.EXE de OS/2,
tendremos una situación parecida a UNIX, en la que arrancamos siempre desde una línea de
comandos.
El selector de programas incorpora dos Hot Keys o Teclas Calientes: Ctrl+Esc y Alt+Esc.
La primera pasa la sesión actual a segundo plano y vuelve a poner el Shell en primer plano.
Esto nos permite arrancar nuevas sesiones sin cerrar la actual. La segunda permite
conmutar de una sesión a otra de forma cíclica, pero sin pasar por la del Shell.
OS/2 es muy atento con el usuario, y por eso tiene especial cuidado con el Shell. Dado que
se trata del principal nexo de unión entre ambos, si se produce un error y el Shell se cierra,
OS/2 abre uno nuevo inmediatamente, sin afectar para nada al resto de las aplicaciones que
estaban corriendo. De esta forma el usuario nunca se queda sin control de la máquina. No
olvidemos que el Shell es un programa más, que no corre en el núcleo, y que por tanto
puede contener errores.
GESTION DE LA MEMORIA
OS/2, como cualquier Sistema Operativo multitarea, mantiene un estricto control sobre la
memoria usada por cada programa. Esto es así porque un programa que sobreescribiese la
memoria de otro lo haría fallar con casi total seguridad. OS/2 hace uso del modo protegido
de los microprocesadores 386 y superiores para asegurar que cada programa tenga acceso
solo a sus bloques de memoria, y no pueda machacar el contendido de otra zona. A la vez,
se encarga de intercambiar zonas de memoria entre la RAM y el disco duro,
implementando así un sistema de memoria virtual eficiente, con lo que puede ejecutar
programas más largos que los que la memoria física del ordenador permitiría.
DosAllocMem
DosFreeMem
Otra función es la de crear zonas de memoria compartida. Se trata de bloques de memoria a
los que pueden acceder dos procesos distintos de forma simultánea, y se usan para
intercambiar grandes cantidades de información de forma rápida, y para compartirla.
Existen dos formas de compartir un bloque de memoria: una es mediante un nombre que
refiera a ese bloque; otra es mediante un puntero que señale a su inicio.
DosAllocSharedMem
En el primer caso, se debe especificar un nombre para el bloque que cumpla el convenio de
nombres de ficheros de OS/2, y además debe empezar por \SHAREMEM\. Por ejemplo,
un nombre válido sería \SHAREMEM\BLOQUE.DAT.
DosGetNamedSharedMem
Si no se especifíca un nombre, la memoria solo podrá ser compartida mediante el
intercambio de un puntero que señale al inicio del bloque. Si ambos procesos conocen este
valor, podrán acceder a dicho bloque pidiendolo o asignandolo. Para comunicarlo, es
necesario usar la comunicación entre procesos, que veremos más adelante.
DosGetSharedMem
DosGiveSharedMem
Subasignación de memoria
DosSubAllocMem
DosSubFreeMem
DosSubSetMem
DosSubUnsetMem
SINCRONIZACION Y
COMUNICACION ENTRE
PROCESOS
Al ser OS/2 un Sistema Operativo multitarea, sus programas se componen de múltiples
partes denominadas threads, las cuales se ejecutan de forma paralela. Debido a esto,
cuando dos o más threads intentan acceder a la vez a un mismo recurso (por ejemplo, una
zona de memoria compartida), el resultado puede ser , en el mejor de los casos,
impredecible. Por eso surgen los sistemas de sincronización entre procesos. Estos permiten
establecer un sincronismo entre dos o mas threads y procesos de una forma consistente y,
sobre todo, fiable y predecible. En OS/2, estos sistemas están formados por los semáforos.
Por otro lado, dos procesos pueden necesitar intercambiar información entre ellos. En un
primer momento, la memoria compartida puede parecer la panacea, pero en muchas
aplicaciones no son el método más eficiente. Por eso surgen otros: los cauces y las colas de
mensajes
CONCEPTO DE SEMAFORO
Un semáforo es una estructura diseñada para sincronizar dos o más threads o procesos, de
modo que su ejecución se realice de forma ordenada y sin conflictos entre ellos.
El por qué no se pueden usar directamente otras estructuras mas clásicas, como por ejemplo
usar una variable común para decidir si se puede o no acceder a un recurso, se debe a que
estamos en un sistema multitarea: hacer esto implicaría realizar una espera activa (un bucle,
comprobando constantemente si la variable está o no a 0, y así saber si podemos seguir
ejecutando o no). Por otro lado, puede ocurrir algo mucho peor: supongamos que un
proceso comprueba la variable, y ve que el recurso está libre, por lo que procedería a
cambiar dicha variable de valor y seguir. Pues bien, si justo después de la comprobacion
pero antes de que cambie el valor se conmuta de tarea (puede pasar, pues el sistema
operativo puede hacerlo en cualquier momento), y el nuevo proceso comprueba la variable,
como todavía no se ha actualizado, creerá que el recurso está libre, e intentará tomarlo,
haciendo que ambos programas fallen. Lo peor del caso es que se tratará de un error
aleatorio: unas veces fallará (cuando se produzca cambio de tarea en ese punto) y otras no.
Para evitarlo, se idearon los semáforos. Un semáforo básico es una estructura formada por
una posición de memoria y dos instrucciones, una para reservarlo y otra para liberarlo. A
esto se le puede añadir una cola de threads para recordar el orden en que se hicieron las
peticiones.
A continuación, cada vez que un thread o un proceso quiera acceder a dicho recurso (por
ejemplo, un fichero), hará primero una petición con la primera de las llamadas disponibles.
Cuando el S.O. ejecuta esa llamada, comprueba el valor que hay en la posición de memoria
del semáforo, y si es distinta de cero, se limita a restarle 1 y devolver el control al
programa; sin embargo, si ya es cero, duerme al proceso que hizo la petición y lo mete en la
cola de procesos, en espera de que el semáforo se ponga a un valor distinto de cero.
Por último, cuando el proceso ha terminado el acceso al recurso, usa la segunda llamada
para liberar el semáforo. Cuando el S.O. la ejecuta, comprueba si la cola del semáforo está
vacia, en cuyo caso se limita a incrementar el valor del semáforo, mientras que si tiene
algún proceso, lo despierta, de modo que vuelve a recibir ciclos de CPU y sigue su
ejecución. Si había varios procesos en espera, se irán poniendo en marcha uno tras otro a
medida que el anterior va liberando el semáforo. Cuando termina el último, el semáforo se
vuelve a poner a 1. Se trata, por tanto, del mismo proceso que seguiríamos con la variable,
pero con la ventaja de que es un mecanismo estandar para todos los procesos, y como es
una operacion atómica (esto es, que durante su ejecución no se admiten cambios de tarea),
no surje el problema de que una conmutación pueda producir errores aleatorios.
Vemos que la primera vez que un proceso usa el semáforo, este tiene valor 1, por lo que
pasa a cero y el proceso puede acceder al recurso. Si durante ese tiempo otro proceso quiere
acceder también, al usar el semáforo, este tiene valor cero, por lo que el S.O. deja de darle
ciclos de CPU. Cuando el primer proceso ha terminado, libera el recurso, con lo que el S.O.
puede comprobar que el segundo proceso está esperando, por lo que le vuelve a dar ciclos.
En este punto, el proceso sigue como si nunca hubiese sido detenido. Este tipo de
semáforos son los de Exclusión mútua, o Mutex.
DosCreateMutexSem
DosOpenMutexSem
DosCloseMutexSem
DosQueryMutexSem
DosReleaseMutexSem
DosRequestMutexSem
Otra utilización de los semáforos es cuando uno o más procesos tienen que esperar a que
otro halla terminado una tarea. Para ello, el primer proceso borra el semáforo y con una
primitiva adecuada se pone a esperar a que el semáforo se active (posted). Mientras, el
segundo proceso va trabajando, y cuando termina lo que tiene que hacer, activa el
semáforo, con lo que el primer proceso vuelve a ponerse en marcha, sin haber
desperdiciado ciclos de CPU. Son semáforos evento. Vemos que puede haber varios
procesos esperando por el mismo semáforo, y el thread que lo activa no tiene por qué saber
cuantos son. Cobran su importáncia cuando el evento no es producido por otro thread, sino
por otras funciones del S.O., como las de sincronización, que veremos más adelante.
DosCreateEventSem
DosOpenEventSem
DosCloseEventSem
DosPostEventSem
DosQueryEventSem
DosResetEventSem
DosWaitEventSem
TEMPORIZADORES EN OS/2
En muchos casos, un programa necesita perder tiempo, esto es, esperar unos segundos sin
hacer nada, o similar. Un sistema muy usado en programas DOS consiste en un bucle de
espera, que tarde justo el tiempo que nos interesa. Sin embargo, este sistema es bastante
imperfecto, pues si el programa funciona bien en un ordenador concreto, en otro que sea
más rápido o más lento no lo hará. La segunda solución que 'encontraron' los
programadores fue usar el timer del ordenador, un integrado que puede ser programado
para que provoque interrupciones cada n milisegundos. Como ese chip es el mismo en
todos los PCs, sin importar su velocidad, permite que los programas funcionen igual en
todos.
En OS/2, sin embargo, no se puede permitir esto, pues puede haber múltiples programas
que necesiten el temporizador a la vez. Para solucionarlo, surgen los temporizadores.
Antes de nada, conviene señalar que los temporizadores de OS/2 (así como los de cualquier
otro S.O. multitarea) no son todo lo precisos que sería deseable. Esto se debe en primer
lugar a que, aunque especifiquemos la temporización al milisegundo, será redondeado al
ciclo superior del conmutador de tareas (en OS/2, normalmente, son 32 milisegundos). Por
otro lado, OS/2 es un Sistema Operativo multitarea por prioridades, por lo que puede
ocurrir que en el momento en que expire un temporizador, haya otro proceso de más
prioridad esperando a ejecutarse, en cuyo caso se producirá otro retardo. Sin embargo,
existen maneras de minimizar estos efectos, y OS/2 las ofrece.
Una primera clasificación de los temporizadores los divide en síncronos y asíncronos. Un
temporizador síncrono se activa y no devuelve el control al thread hasta que este termine.
Es el caso más básico: si queremos hacer una pausa de 1 segundo, llamamos a la función
DosSleep, y el thread correspondiente se dormirá durante el tiempo fijado.
DosSleep
Sin embargo, puede no interesarnos este sistema, sino que durante ese intervalo podemos
necesitar hacer alguna operación y luego dormirnos hasta terminarlo. Por ejemplo,
queremos un programa que, una vez por segundo, imprima un caracter en pantalla. Una
primera idea sería imprimir un caracter, dormir un segundo, imprimir otro, dormir un
segundo, etc. Sin embargo, el hecho de imprimir el caracter consume algo de tiempo, luego
estamos gastando algo más. Por otro lado, ese tiempo es variable, en función de la
velocidad del ordenador y de la tarjeta gráfica. Por eso existen los temporizadores
asíncronos.
DosAsyncTimer
DosStartTimer
DosStopTimer
Por último, tenemos las funciones de fecha y hora, las cuales permiten obtener y cambiar
estos datos. Un detalle importante es que solo existe una fecha y una hora. Esto significa
que si un programa los cambia, lo hace para todos, no solo para él mismo.
DosGetDateTime
DosSetDateTime
Comunicación entre procesos
MEMORIA COMPARTIDA Y
SEMAFOROS
El primer procedimiento para intercambiar datos consiste en combinar un bloque de
memoria compartida con un semáforo que se encargue de evitar que ambos procesos
accedan a la vez al bloque. Cuando uno quiere leer o grabar datos, borra el semáforo,
accede, y lo libera (post). Si ya estaba ocupado el bloque, el proceso es detenido hasta que
el semáforo se libera. Todo lo necesario para trabajar con esto ya se ha visto en las páginas
anteriores.
CAUCES
Otra manera de intercambiar información son los cauces (pipes). En OS/2 existen dos tipos:
con nombre (named pipes) y sin nombre (unnamed pipes).
Los cauces son vistos por el programador como un fichero más, en el que, o bien puede
leer, o bien escribir. La cuestión es que un extremo pertenece a un thread y el otro a otro
distinto, y si uno de ellos escribe en ese fichero (recordemos que el sistema de archivos de
OS/2, al igual que el de cualquier otro S.O., consigue que no sepamos diferenciar cuando
trabajamos con un fichero de disco, con un cauce, o con la pantalla), cuando el otro lea,
encontrará los datos grabados. Podríamos definirlo como una tubería (la cual es otra de las
traducciones que he encontrado en algunos libros) que conecta los dos threads, y que se
maneja con las mismas ordenes que el sistema de ficheros: DosRead, DosWrite y
DosClose.
Cuando creamos una unnamed pipe, el sistema nos devuelve dos handles o indicativos: uno
para lectura y otro para escritura. El thread que creó el cauce debe quedarse con uno, y
enviar el otro al otro thread. Para ello puede usar un pequeño bloque de memoria
compartida, definiendo claramente como acceder a ella. Dado que los demás threads van a
acceder solo en lectura, no haría falta incluir un semáforo.
Los cauces tienen una opción muy interesante (y útil), que consiste en la posibilidad de
duplicar handles. Usando la llamada DosDupHandle, se pueden crear dos handles para un
mismo cauce. Lo interesante es que se puede dar no solo el siguiente número libre, sino el
que se quiera. Haciendo esto, se puede asignar a un cauce el valor 0 y a otro el 1, y redirigir
así la entrada y salida estandar (STDIN y STDOUT) del programa en curso. Esto es
precisamente lo que hace el interprete de comandos de OS/2 para hacer la redirección desde
la línea de comandos.
DosCreatePipe
DosDupHandle
En las versiones 1.x de OS/2 solo existian estos cauces; sin embargo, su potencia quedaba
ligeramente oscurecida por el hecho de tener que usar memoria compartida para pasar un
handle al otro thread. Para evitarlo, se añadieron en OS/2 2.0 y superiores las named pipes,
o cauces con nombre (denominación horrible a mas no poder. Espero sugerencias para una
posible traducción). En estos, al crearlos se les asigna un nombre del tipo
\PIPE\un_nombre. Dentro de un_nombre puede ir cualquier cadena. Ejemplos válidos son
\PIPE\EJEMPLO, \PIPE\CURSO\OS2\EJEMPLO_DE_PIPE. Cuando un thread
cualquiera quiere acceder a ese cauce, solo tiene que referirse a él por dicho indicativo.
Esto resulta mucho más cómodo, pues basta con fijar durante la programación el nombre
que se le va a dar, y no hace falta pasarlo de forma dinámica en cada ejecución. Sin
embargo, además de crearlo, es necesario que el proceso servidor lo haga disponible a los
clientes.
DosCreateNPipe
DosConnectNPipe
DosDisConnectNPipe
Para que un cliente acceda a una named pipe, tiene que usar DosOpen, usando como
nombre de fichero el nombre de la pipe. El segundo parámetro es un puntero a una variable
de tipo HPIPE, que contendrá un handle que podremos usar con DosRead, DosWrite y
DosClose para manejar la pipe. El tercer parámetro contendrá un puntero a un ULONG, en
el cual se almacenará la causa por la que no se ha podido abrir la pipe (es mejor usar los
códigos de error que se devuelven de la forma normal en vez de éste valor). El cuarto
parámetro contiene la longitud del buffer que se reservará. Es conveniente que tenga un
tamaño más bien grande. El resto de parámetros son particularizaciones para las pipes de
los parámetro normales de DosOpen.
Se debe tener cuidado al definir las named pipes. Hay que tener en cuenta que el servidor
tiene ciertos privilegios que el cliente no tiene, como por ejemplo disponer de un semáforo
que le indique cuando hay datos en el cauce. Un cliente tendría que hacer una espera activa
en el caso de querer estar siempre disponible a través de ese cauce. Por otro lado, cualquier
cliente se puede conectar a una pipe, por lo que el servidor no puede saber (en principio)
quien le está reclamando el servicio. Al revés sí ocurre, pues todo cliente sabe quien es el
servidor de un cauce determinado por el nombre de éste.
DosSetNPipeSem
DosQueryNPHState
DosSetNPHState
DosWaitNPipe
DosPeekNPipe
Un detalle importante es que las unnamed pipes son unidireccionales. Esto es, si se quiere
intercambiar datos en ambos sentidos, son necesarios dos cauces, no se puede usar el
mismo. El sentido de la comunicación lo decide el que cree el cauce, al enviar uno u otro
handle. En el caso de named pipes, por el contrario, el creador puede determinar si la
comunicación va del servidor al cliente, al revés, o en ambos sentidos.
COLAS
Los cauces no siempre son la solución ideal. En muchos casos necesitamos poder tratar
elementos separados en vez de una secuencia de bytes, y además puede ser necesario que se
organicen de forma distinta a la clásica FIFO (First In First Out, el primero en entrar es
el primero en salir). Incluso puede ser necesario eliminar elementos antes de que los lean
los destinatarios. Para esto se crearon las colas.
Una cola es similar a un cauce con nombre (named pipe), pero en vez de escribirse bytes en
ella, se escriben secuencias de longitud variable que son tratadas como elementos
independientes. Por ejemplo, si en un cauce introduzco la secuencia Esto es una prueba,
puedo leer solo la primera letra, o las dos primeras, o unas cuantas, pero la frase será tratada
como un conjunto de elementos separados. En una cola, sin embargo, sería tratada siempre
como un solo elemento. Cuando el proceso lee de la cola, leerá la frase completa, y no
podrá leer solo un trozo, o incluso pegarlo a otro elemento introducido depués.
Por otro lado, la ordenación de los elementos en las colas es definible por el usuario. Así,
puede ser FIFO (First In First Out, el primero en entrar es el primero en salir), LIFO (Last
In First Out, el último en entrar es el primero en salir), o bien por prioridades. Un ejemplo
del primer tipo de colas sería una cola de un cine: el primer espectador que llega será el
primero en obtener la entrada, y así sucesivamente. Un ejemplo del segundo tipo de colas
sería una pila de revistas. Cuando pongo una nueva, la añado en la cima, y cuando cojo una,
cojo la que está arriba de todo: la última en llegar es la primera en salir. Por último, un
ejemplo de una cola con prioridad podría ser una recepción oficial. No importa el orden en
que lleguen los dignatarios; estos siempre saldrán en orden descendente de rangos: primero
el presidente invitado, luego el del país, luego ministros, etc. Y si mientras salen, llega
algún rezagado, todos los de rango inferior le cederán su sitio.
Las colas se denominan, como viene siendo habitual, con un nombre propio, que es de la
forma \QUEUES\nombre_de_la_cola, con nombre_de_la_cola un nombre que la define.
Dado que las colas no trabajan con secuencias de bytes individuales sino con mensajes
discretos, no usan las llamadas del sistema de archivos, sino que disponen de su propio
conjunto de instrucciones para leer y escribir en ellas.
DosCreateQueue
DosOpenQueue
DosReadQueue
DosWriteQueue
DosQueryQueue
DosCloseQueue
Solo el proceso que crea la cola tiene la capacidad de eliminarla o de purgar (borrar) sus
elementos, así como de leer de ella; sin embargo, cualquier otro proceso puede tener acceso
a ella en el sentido de escribir. Aún más interesante resulta el hecho de que es posible
saltarse el orden normal de la cola y leer un elemento situado en una posición concreta. Así
mismo, es posible hacer una lectura síncrona, en la cual se espera hasta que aparezca un
elemento (si no habia ninguno) o bien una lectura asíncrona, usando un semáforo, en la cual
se pueden seguir haciendo operaciones y resincronizar cuando se desee, de forma similar a
los temporizadores. Sin embargo, hay algunas restricciones en este caso: el semáforo debe
ser abierto por todos los procesos que llaman a DosWriteQueue para añadir un elemento a
la cola.
DosPeekQueue
DosPurgeQueue
El trabajo con colas es distinto que con los cauces. En los primeros, definimos una ristra de
bytes que deben ser enviados a los procesos. Aquí, sin embargo, nos limitamos a enviar un
puntero, el cual será el que le llegue a los demás en el orden adecuado. Los datos a enviar
son almacenados en zonas de memoria compartidas.
Para verlo más claro, veamos como haríamos para enviar varios mensajes a través de una
cola: