Sunteți pe pagina 1din 44

INDICE

• 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.

HISTORIA DE LOS SISTEMAS


OPERATIVOS
Cuando aparecieron los primeros ordenadores, la programación de estos era hecha
íntegramente en código máquina, lo cual resultaba una tarea extremadamente pesada: cada
vez que se escribía un nuevo programa, además de escribir el algoritmo adecuado era
preciso añadir todo el código necesario para que el ordenador pudiese leer datos desde una
cinta perforada, imprimir en un teletipo, etc. Dado que, en general, todas estas rutinas eran
exactamente iguales para todos los programas que se hacían, pronto los programadores de
aquellas máquinas aprendieron a organizarlas en bibliotecas de rutinas. Cada vez que
había que escribir un nuevo programa, solo tenían que ir a su libreta y copiar las rutinas de
Entrada/Salida que necesitaban, lo cual les simplificaba un poco el trabajo. Otro sistema era
el que la propia empresa que fabricaba el ordenador incluyese un paquete de fichas
perforadas con dichas rutinas, de modo que el programador solo tenía que coger las que le
interesasen y añadirlas estratégicamente en su paquete.

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.

La idea consistía en tener varios programas simultáneamente en memoria, si bien, como el


ordenador es único, solo uno estaría activo en cada momento. Cuando dicho programa hace
una operación de Entrada/Salida en la que se pierde tiempo de CPU (como leer una tarjeta,
por ejemplo), mientras la mecánica realiza la operación de búsqueda y lectura, el ordenador
conmuta al siguiente programa que tiene en memoria, el cual continuará ejecutándose hasta
que haga una operación de Entrada/Salida. Cuando se llega al último, se vuelve a empezar.
Para ese momento, la operación de Entrada/Salida pedida por el primer programa ya estará
lista, con lo que podrá seguir su ejecución. De esta forma, hemos aprovechado un tiempo
que antes perdíamos.

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

• Menor fragmentación de ficheros: HPFS busca primero una zona en donde el


archivo entre completo, con lo que la fragmentación de ficheros es prácticamente
inexistente. De hecho, IBM recomienda desfragmentar los discos duros una vez al
año, y solo a los paranoicos.
• Mayor capacidad: HPFS admite discos duros de hasta 512 gigabytes de capacidad,
manteniendo el tamaño del cluster (unidad mínima de información almacenable) en
512 bytes o un sector. En FAT, el tamaño mínimo de cluster para un disco duro es
2048 bytes, y para discos mayores aumenta (un disco duro de 1 giga tiene un
tamaño de cluster de 32Ks).
• Soporte para nombres largos: permite nombres de hasta 256 caracteres.
• Mayor seguridad: si al grabar en un sector se detecta un error, se marca
automáticamente como defectuoso y se graba en otra parte.
• Mayor velocidad en el acceso, gracias a la estructura jerarquica de directorios, que
optimiza el acceso a disco.

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.

En Noviembre de 1996 se hizo la presentación de Merlín, nombre clave de OS/2 4.0, y


que, en contra de lo que mucha gente piensa, no tiene nada que ver con el mítico mago de
la corte del rey Arturo, sino con un pájaro parecido a un águila (siguiendo la nueva filosofía
de IBM de nombrar sus creaciones con nombres de aves). Merlín trae todo lo que ofrecía
OS/2 3.0, pero lo amplia con un conjunto extra de características, como son:

• Un soporte todavía mayor de hardware.


• Mayor simplicidad de instalación.
• Librerias OpenDoc (compatibles con OLE 2.0, pero más potentes).
• Librerias OpenGL, que permiten aprovechar las capacidades 3D de las tarjetas que
soporten este estandar.
• API de desarrollo Open32, que permiten recompilar con suma facilidad las
aplicaciones escritas para Windows'95 y Windows NT, de forma que aprovechen al
máximo los recursos de OS/2.
• Un Bonus Pack ampliado, incluyendo una nueva versión del IBMWorks basada en
OpenDoc, y las utilidades LotusNotes.
• Un Kernel aún más optimizado.
• Escritorio mejorado, ofreciendo una orientación a objeto aún mayor.
• Un extenso soporte de conectividad, superior a la versión Connect de Warp 3.0, lo
que le convierte en el cliente de red universal, pudiendo conectarse a casi cualquier
servidor (no solo Warp Server, sino Windows NT server, Novell, etc).
• HPFS mejorado: mayor capacidad por disco y seguridad.
• Sesiones DOS reales (el micro se conmuta realmente a modo real, y todo el
contenido de la RAM se guarda en disco, quedando el Sistema Operativo y el resto
de las utilidades congelados, pudiendo rearrancar en cualquier momento. Es util
para juegos o programas de DOS muy exigentes, que se niegan a funcionar en una
sesión DOS virtual).
• La característica estrella de cara al marketing: el VoiceType. Se trata de un software
reconocedor de voz, capaz de funcionar con cualquier tarjeta de sonido, y que
permite al usuario trabajar exclusivamente mediante el dictado de comandos. Este
sistema, al contrario que otros disponibles hasta el momento, realmente reconoce el
habla de forma continua, de modo que no solo se puede usar para navegar por el
escritorio y controlar programas, sino que sirve perfectamente para dictar cualquier
tipo de texto, como articulos, cartas, etc. sin tocar una sola tecla. Se trata, por tanto,
de un avance de los que serán, sin duda, los sistemas operativos del futuro.

LLAMADA AL API DE OS/2


OS/2 es un S.O. pensado para poder ser programado tanto desde lenguajes de alto nivel (C,
Pascal, Basic) como de bajo nivel (Assembler). Para facilitar la llamada a funciones desde
los primeros, todos los parámetros se pasan por medio de la pila de máquina, y solo
devuelve un código de error en el registro AX. Para facilitar las llamadas en el segundo,
éstas no se implementan como saltos a supervisor, sino como FARCALLs, de modo que
para un programador de código máquina no habrá diferencia entre ejecutar una llamada a
una función del sistema o llamar a una función propia.

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.

Funciones de acceso a ficheros y al hardware.

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.

Implementación del sistema de ficheros de OS/2

En este gráfico vemos como está construido el acceso a los dispositivos en OS/2.

En el esquema, vemos que la aplicación se comunica con el Sistema de Ficheros de OS/2,


que forma parte del núcleo, y éste determina a quien tiene que enviar o de quien debe leer
los datos. Si el programa quiere acceder a la pantalla, el Sistema de Ficheros envía los
caracteres al Subsistema de Video (VIO), que es la parte encargada de controlar la totalidad
de accesos a la pantalla. Lo mismo si se trata del teclado, el ratón o un fichero de disco.

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 SISTEMA DE ARCHIVOS DE OS/2


La parte básica de la conexión entre el hardware del ordenador y el programador se
encuentra en el Sistema de Archivos. Es él quien se encarga de darnos acceso a los
ficheros del disco, al teclado y a la pantalla en modo texto. La ventaja que tiene esto es que
nos abstrae totalmente de las características del hardware, presentándonos todo como una
ristra de caracteres.

El acceso a un fichero sigue una serie de pasos absolutamente necesarios: primero es


preciso abrir el archivo, de modo que el programa obtenga control sobre él; una vez hecho
esto, podemos realizar cualquier operación de lectura y/o escritura sobre él, y finalizar
cerrando el archivo, de modo que lo liberamos y puede ser accedido por otro programa.

Apertura y cierre de ficheros

La primera operación necesaria antes de poder acceder a un archivo es identificarlo


mediante una llamada al Sistema Operativo. A esta operación se le denomina apertura del
archivo. Le enviamos como parámetros el nombre del archivo a abrir y una serie de
opciones, y nos devolverá un identificador de archivo (en adelante se denominará Handle)
que usaremos para referirnos a él cuando queramos leer o escribir. El identificador es
simplemente un número entero. Existe una única excepción en este caso: los archivos
estandar no es necesario que sean abiertos, pues ya tienen asignado un identificador
(STDIN=0, STDOUT=1, STDERR=2).

Las opciones de apertura especifican la forma en que el programa va a acceder a dicho


fichero. Por ejemplo, puede querer acceder solo en modo lectura, o bien puede especificar
que si dicho fichero no existe, devuelva un error, o bien que lo cree. O bien, si existe, puede
pedir que lo sobreescriba. Etc.

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

Lectura y escritura de datos

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 acceso al fichero

• READ_ONLY: solo se va a leer del fichero.


• WRITE_ONLY: solo se va a escribir en el fichero.
• READ_WRITE: se va a leer y escribir (es el valor por omisión).

Modos de compartición

• DENY_ALL: OS/2 rechaza cualquier petición posterior de abrir ese archivo.


• DENY_READ: OS/2 rechaza cualquier petición de abrir ese archivo para lectura.
• DENY_WRITE: OS/2 rechaza cualquier petición de abrir ese archivo para escritura.
• DENY_NONE: OS/2 permite cualquier operación con ese archivo.

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 nombres de los archivos de que disponemos son:

• COM1-COM4: maneja los puertos serie del ordenador.


• CLOCK$: el reloj del sistema.
• CON: gestiona la consola (en lectura es el teclado, en escritura, la pantalla).
• SCREEN$: maneja la pantalla.
• KBD$: maneja el teclado.
• LPT1-LPT3: maneja los puertos pararelo (normalmente la impresora).
• NUL: dispositivo nulo. Todo lo que se envíe a él simplemente es ignorado.
• POINTER$: gestiona el dispositivo de puntero (el ratón).

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.

Funciones del DOS

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:

• El símbolo ? sustituye a cualquier caracter situado en esa posicion. Así, la búsqueda


de program?.exe devolverá los nombres de los ficheros que empiecen por
program, a continuación tengan un caracter cualquiera, y por último, terminen con
.exe, como por ejemplo, programa.exe, o programz.exe; pero no devolverá
programad.exe ni programa.
• El símbolo * sustituye a cualquier cadena situada desde esa posición en adelante.
Así, la búsqueda de prog* devolverá cualquier fichero que empiece por prog, tenga
lo que tenga después. Por ejemplo, devolverá programa, progs...
DosFindFirst
DosFindNext
DosFindClose

Redirección de entradas y salidas

Además de estos ficheros de dispositivo, existen también tres 'archivos' especiales: la


entrada estandar (STDIN), la salida estandar (STDOUT), y la salida de error estandar
(STDERR). Por defecto, STDIN está asociada al teclado, y STDOUT y STDERR a la
pantalla; sin embargo, pueden ser redireccionadas desde la linea de comandos a un fichero
o incluso a otro programa por medio de cauces o Pipes. Por ejemplo, si tenemos un
programa UPCASE.EXE que lee de la entrada estandar un caracter, lo convierte en
mayusculas y lo escribe en la salida estandar, y hacemos desde la linea de comandos un

C:\>UPCASE.EXE <entrada.txt >salida.txt

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

C:\>UPCASE.EXE <entrada.txt |MORE.EXE

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

Para permitir la colocación de programas en múltiples directorios y simplificar algunas


opciones de configuración, OS/2 facilita las variables de entorno. Se trata de una serie de
variables que se definen bien en la línea de comandos, bien en el CONFIG.SYS, por medio
de la sentencia SET. Por ejemplo, la línea

SET mi_variable=C:\OS2UTIL\MIPROGRAMA

asigna a la variable de entorno mi_variable el valor C:\OS2UTIL\MIPROGRAMA. De este


modo, el usuario puede especificar el directorio en donde estarán diversas partes de un
programa, o bien diversas opciones para éste. Por ejemplo, el compilador EMX requiere de
algunas variables de este tipo para saber donde están las librerias, documentación, etc. El
propio OS/2 hace uso de esta facilidad para implementar variables como el PATH de datos,
etc.

DosScanEnv

Otras funciones DOS

Otras funciones pertenecientes a este grupo son las siguientes:

DosResetBuffer
DosShutdown
DosBeep

LOS SUBSISTEMAS EN OS/2


En principio, el uso del sistema de ficheros para Entrada/Salida con el teclado y la pantalla
debería ser suficiente, pero por desgracia no es así. Su uso es relativamente lento, por lo
que solo parece útil utilizarlo cuando necesitamos poder redireccionar la entrada o la salida
del programa, o cuando la velocidad no es importante.

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.

Estrictamente hablando, siempre usamos los subsistemas. El sistema de ficheros, cuando


sabe que una salida es para la pantalla, envía los datos al subsistema de video (VIO). Sin
embargo, antes tiene que comprobar para quien es la salida, con lo que pierde algo de
rendimiento. Así mismo, la posibilidad de redireccionar entradas y salidas es otra opción
que ralentiza el sistema. En muchos casos, la pérdida de velocidad es totalmente
inapreciable, pero en algunos programas (juegos, aplicaciones a pantalla completa,...) el uso
del sistema de ficheros puede ser totalmente contraproducente para el rendimiento. Usando
el subsistema directamente nos ahorramos pasos intermedios, a costa de perder la
posibilidad de redirección y de entrada/salida generalizada para ficheros y dispositivos.

En OS/2 disponemos de los siguientes subsistemas:

• 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

Salida por TTY virtual

En un extremo se encuentra el primer servicio que ofrece el subsistema VIO, que es el de


salida TTY. Este servicio es casi idéntico a la salida de caracteres por medio del sistema de
archivos. De hecho, éste, cuando comprueba que lo que el programa envía va dirigido a la
pantalla, usa este servicio para realizar la función.

¿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

Salida de cadenas de caracteres

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:

Parpadeo activado Intensidad activada


Bit Significado Bit Significado
7 Parpadeo del caracter 7 Intensidad del fondo
6 Rojo del fondo 6 Rojo del fondo
5 Verde del fondo 5 Verde del fondo
4 Azul del fondo 4 Azul del fondo
3 Intensidad de la tinta 3 Intensidad de la tinta
2 Rojo de la tinta 2 Rojo de la tinta
1 Verde de la tinta 1 Verde de la tinta
0 Azul de la tinta 0 Azul de la tinta

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:

0 Negro 1 Azul 2 Verde 3 Celeste


4 Rojo 5 Magenta 6 Amarillo 7 Blanco

El bit de intensidad se limita a hacer estos colores más o menos brillantes.


Estos servicios orientados a caracter siguen siendo independientes del hardware utilizado
de modo que no es necesario saber como se trabaja a nivel físico con ellos. Por otra parte, el
propio OS/2 optimiza las transferencias para cada uno de ellos, de modo que se consigue la
mayor velocidad posible, y se eliminan ciertos problemas inherentes a algunos sistemas
gráficos (por ejemplo, en las tarjetas CGA sincroniza automáticamente la escritura con el
retrazado vertical, de modo que se evita la aparición de nieve en la pantalla).

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

El subsistema VIO ofrece, además, la posibilidad de realizar scroll de ventanas en modo


texto. Con este conjunto de funciones, podemos desplazar parte o toda la pantalla en
cualquiera de las cuatro direcciones posibles. La razón de incluirlas es que resulta mucho
más rápido que hacer una rutina que lea cada posición del buffer de video y la reescriba en
el lugar adecuado, aparte de que se trata de una función muy común en casi cualquier
programa.

VioScrollDn
VioScrollLf
VioScrollRt
VioScrollUp

Definición y movimiento del cursor

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.

Al contrario que en MS-DOS, cuando escribimos en la pantalla de OS/2 la posición del


cursor no se cambia. Esto se hace así para ganar tiempo. Normalmente el cursor solo se usa
cuando hay que introducir datos por teclado, y el resto de las veces se suele hacer
desaparecer de la pantalla. Esta es la razón de que halla un conjunto de funciones para
situar el cursor. De esta manera se gana en velocidad.

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

Acceso directo al buffer real de video

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.

Para que un programa pueda acceder directamente a la memoria real de video, es


absolutamente necesario que se encuentre en primer plano. Esto es así porque si escribiese
algo en pantalla cuando estuviese en background, machacaría la imagen de la aplicación
que se encontrase en ese momento en primer plano.

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.

El hecho de obtener un selector no significa que dispongamos de acceso todavía a la


pantalla. De hecho, si intentásemos escribir o leer algo en ese momento y la aplicación no
se encontrase en primer plano, OS/2 la cerraría inmediatamente, dando un Fallo de
Protección General (el cual ya sabemos que no es tan fatal como el de Windows, pues en
OS/2 solo afecta a la aplicación que lo ha provocado, dejando intacto al Sistema Operativo
y al resto de los programas).

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.

Para implementarlo, es necesario crear un nuevo thread en el que se ejecute la llamada


VioSavRedrawWait. Esta llamada bloqueará el thread hasta que el usuario pulse
CTRL+ESC, momento en que OS/2, antes de conmutar de tarea, despertará a dicho thread
indicándole que debe almacenar el contenido de la pantalla. Cuando el thread termine, debe
volver a ejecutar la llamada, con lo que OS/2 sabrá que ha finalizado. El thread se quedará
dormido de nuevo, y solo será despertado cuando el usuario vuelva a conmutar a primer
plano el programa. Entonces OS/2 le indicará que debe repintar la pantalla.

VioSavRedrawWait

La inclusión de este sistema de acceso puede parecer innecesaria, a la vista de la potencia


del acceso al LVB; la razón de haberla implementado fue que, cuando salió OS/2, no
llevaba todavía el Presentation Manager, el gestor de ventanas, sino que era un Sistema
Operativo en modo texto, por lo que se incluyó este sistema para poder acceder en modo
gráfico a la pantalla, dado que VIO no ofrece ninguna facilidad como el trazado de puntos o
líneas. Actualmente, al disponer de un completo (y complejo) gestor de ventanas, este
método puede parecer inutil, sin embargo, para juegos puede ser muy útil, pues permite
acceder a pantalla completa en modos como 320x200 en 256 colores, lo que permite una
alta velocidad de refresco, así como una gran facilidad de manejo. Hay que señalar que el
acceso directo a la memoria de vídeo solo se puede hacer estando en una sesión de pantalla
completa; no funcionará en una sesión de ventana.

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

Acceso a nivel de cadenas de caracteres

La primera posibilidad que ofrece el subsistema de teclado es leer caracteres, lo cual se


puede hacer de dos formas: por cadenas, o caracter a caracter.
La lectura por cadenas es exactamente igual que el uso de los servicios del sistema de
archivos. De hecho, es el servicio que emplea la función DosRead cuando tiene que leer del
teclado. Sin embargo, no necesita un indicativo de dispositivo abierto, ni participa en el
redireccionamiento de E/S. Además, funciona de modo diferente según el modo de teclado
en que se encuentre: ASCII, BINARIO o ECO.

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

El controlador de ratón puede encargarse él mismo de pintar el cursor en pantalla (solo en


sesiones de texto), o bien relegar dicha acción al programa. Así mismo, puede devolver las
coordenadas bien en coordenadas de pantalla, bien en Mickeys, que son unidades naturales
del ratón.

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

ACCESO A LAS FUNCIONES DE 16


BITS
EL THUNKING
Como se comentó en la historia de OS/2, cuando éste fue diseñado era un Sistema
Operativo de 16 bits, pensado para funcionar en máquinas equipadas con un 286. Cuando
se realizó la primera versión de 32 bits, IBM decidió (de forma bastante desafortunada),
excluir algunas llamadas de la antigua API, por considerarlas obsoletas. Este conjunto esta
formado principalmente por todas las llamadas a los subsistemas. Dicen las malas lenguas
que IBM decidió que el futuro eran los programas preparados para el Presentation Manager,
por lo que no tenía sentido pasar el trabajo de incluirlas.

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

GESTION DE MEMORIA EN EL MODO


PROTEGIDO DEL 286
El microprocesador 286 incluye dos modos de funcionamiento: el modo real, en el que se
comporta exactamente igual que un 8086, y el modo protegido, en el que surgen
capacidades nuevas, como memoria virtual, proteccion de segmentos, etc.

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.

GESTION DE MEMORIA EN EL MODO


PROTEGIDO DEL 386
Cuando se creó el microprocesador 386, al ser un micro de 32 bits se varió la estructura del
modo protegido para darle una mayor potencia, aparte de permitirle direccionar los
4GBytes que permite su bus de direcciones de 32 bits.

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.

Un thread se puede considerar como la agrupación de un trozo de programa junto con el


conjunto de registros del procesador que utiliza y una pila de máquina. El conjunto de los
registros y de la pila de cada thread se denomina contexto. Como sabemos, en un Sistema
Operativo multitarea, la CPU se reparte entre cada programa a ejecutar. Para ser más
precisos, el S.O. reparte la CPU entre todos los threads a ejecutar en cada momento (pues
un programa puede contener varios threads), simplemente adueñandose de esta y saltando
al siguiente. Sin embargo, esta conmutación no se puede hacer de cualquier manera. Cada
vez que el S.O. se adueña de la CPU para cedersela a otro thread, los registros y la pila (o
sea, el contexto del hilo) contienen unos valores determinados. Por eso, el S.O. guarda
todos esos datos en cada cambio, de modo que al volver a conmutar al thread inicial, pueda
restaurar el contexto inicial. No olvidemos que OS/2 es un S.O. con multitarea preemptiva,
lo que significa que la CPU puede ser arrebatada en cualquier momento. Esto significa que
un thread no puede saber cuando se le va a arrebatar la CPU, por lo que no puede guardar
los registros ni la pila de forma 'voluntaria'.

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.

Un programa consta, al menos, de un proceso, y un proceso, al menos, de un thread.


Cuando un programa tiene varios procesos, lo normal es que cada uno ejecute un código
distinto, los cuales se encuentran en ficheros ejecutables separados. Dos procesos solo
pueden compartir una zona de memoria si esta es definida expresamente como tal. Así
mismo, es en este caso cuando los sistemas de sincronización a la hora de compartir
memoria (de los que hablaremos más adelante) se vuelven especialmente necesarios e
importantes.

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.

En OS/2 existen cuatro niveles de prioridad, en orden de mayor a menor:

• 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

Vemos que un detalle importante a tener en cuenta en el diseño de un programa es el


consumo de CPU que hace. Pero ¿como podemos hacer que nuestro programa consuma
poca CPU? La respuesta es: no realizar nunca una espera activa.

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.

ESTRUCTURA DEL OS/2


(Selector de Programas, Sesiones, Procesos
y Threads)
La estructura de OS/2 a nivel de multitarea se centra en los tres conceptos dados
anteriormente: Threads y Procesos, gestionados por el núcleo, y Sesiones, gestionadas por
los subsistemas y por el Selector de programas.

El selector de programas es la parte de OS/2 que se encarga de conmutar la pantalla,


teclado y ratón físicos hacia los buffers lógicos de cada sesión. Para ello, incluye un API
propio, que permite a una sesión hacer pasar a primer plano a cualquier sesión hija.

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.

De todo esto deducimos varias cosas

• El Shell del sistema es un programa más, que se ejecuta en una sesión


independiente, por lo que nada impide que escribamos el nuestro propio.
• Un Shell nunca debe arrancar ningún programa en su misma sesión, pues eso
implicaría que el pulsar Ctrl+Esc no nos devolvería al Shell, sino al programa que
estamos ejecutando en su sesión.
• Dado que el Shell es el padre de todas las sesiones que se arrancan, puede conmutar
a cualquiera de ellas sin usar ningún truco especial, simplemente usando el API del
selector de programas. Por esto mismo, tiene acceso a todas las sesiones de la lista
de tareas de OS/2, y también puede matar a cualquiera de ellas.

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.

Las funciones de asignación y desasignación dinámica de memoria son extremadamente


útiles en programas en los que no sepamos la cantidad de esta que vamos a necesitar. Un
ejemplo puede ser una zona de memoria para descomprimir imágenes en un visualizador:
dado que cada una puede medir cualquier longitud, reservar una cantidad excesiva de forma
estática hace al programa lento, y reservar poca puede impedir visualizar imágenes grandes.
La solución es comprobar el tamaño de la imagen y reservar una zona de memoria
dinámica acorde con este, liberandola al terminar.

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

La asignación de memoria es una parte muy importante en cualquier sistema operativo. Da


a los programas la capacidad de adaptarse dinámicamente a las necesidades de cada
momento. Sin embargo, hay un coste relativamente elevado en la asignación de memoria
dinámica. En cada petición es preciso buscar una zona libre, cambiar los descriptores de
segmento, y cargar los registros de nuevo. Todo esto lleva un tiempo muy elevado, debido a
la propia arquitectura del microprocesador. Lo mismo ocurre al liberar una zona. De aquí se
saca una conclusión clara: utiliza toda la memoria que necesites, pero evita asignar y
desasignar constantemente bloques. La solución consiste en asignar un bloque de tamaño
moderado y dentro de él subasignar bloques de tamaño más pequeño, aumentando el
tamaño del bloque físico sólo cuando se necesita más espacio.

Para ayudar en esto, OS/2 ofrece un grupo completo de subasignación de memoria.

DosSubAllocMem
DosSubFreeMem
DosSubSetMem
DosSubUnsetMem

La primera acción consiste en reservar un bloque de memoria con DosAllocMem. Este


bloque de memoria es necesario prepararlo para subasignación con DosSubSetMem. Una
vez hecho esto, podemos asignar y desasignar pequeños bloques con DosSubAllocMem y
DosSubFreeMem como si usásemos las llamadas de siempre, pero con la diferencia de que
será notablemente más rápido. Sin embargo, no debemos olvidar que se trata de un solo
bloque de memoria subdividido, por lo que aunque tengamos subasignado un bloque,
podemos salirnos de él por accidente sin que el sistema dé un error (siempre, claro está, que
no nos salgamos de la memoria asignada).
Debemos ver las funciones de subasignación de memoria simplemente como una ayuda en
la gestión de ésta. Para cualquier aplicación es exactamente lo mismo dividir nosotros
mismos un bloque de memoria de forma lógica que usar estas funciones. La ventaja
consiste en que nos ahorramos programar un grupo de funciones que compruebe cuanto
espacio nos queda libre y en donde y actualice las estructuras. Se trata de una ganancia en
comodidad. Una aplicación clara de estas funciones se verá cuando expliquemos las colas
de mensajes, pues en ellas se hace uso intensivo de pequeños bloques de memoria que se
crean y liberan constantemente.

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.

Se empieza por inicializar la posición de memoria a 1 (o al valor correspondiente si ese


recurso concreto admite más de un acceso simultáneo). Esto se hace en el inicio del
programa principal.

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

Los semáforos se identifican con un nombre, el cual tiene la forma \SEM32\un_nombre.


Un_nombre es el identificador del semáforo. Existe la posibilidad de crear un semáforo sin
nombre, en cuyo caso se puede especificar si es un semáforo compartido o no (por otros
procesos).

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.

Un temporizador asíncrono necesita de un semáforo, pues lo necesita para notificar al


thread que se ha acabado el intervalo de tiempo. La idea es la siguiente: el thread inicializa
el temporizador, el cual activa el semáforo y empieza a contar, pero devuelve el control al
programa. Este sigue trabajando (imprimiría el caracter o lo que tuviese que hacer), y
cuando termine, espera a que el semáforo se active (post) (que será hecho por el
temporizador cuando expire). De este modo, no importa cuanto dure el proceso que haga el
thread: siempre esperará un segundo (o el tiempo elegido, redondeado al ciclo del
conmutador de tareas).

DosAsyncTimer

Como decíamos, la temporización no es precisa por estar redondeada a 32 milisegundos.


Esto puede ser problemático cuando se encadenan temporizaciones. En el ejemplo anterior,
si programamos un temporizador para cada segundo, la ejecución de la instrucción cada vez
ya lleva algo de tiempo, y además, los redondeos se van sumando cada vez más, de modo
que podemos llegar a tener un desfase entre el ritmo que debería llevar y el que lleva
realmente. Para eso, existe otro temporizador asíncrono que borra un semáforo no una vez,
sino constantemente, a cada paso del ciclo. Es este el que deberímos usar en nuestro
ejemplo. Este temporizador, como es cíclico (está contando constantemente intervalos del
tiempo que queramos, y no una sola vez como hacía el primero) puede tener en cuenta el
redondeo del conmutador de tareas, y minimizar su efecto.

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.

También es posible buscar en la cola un elemento concreto y examinarlo, pero sin


eliminarlo de la cola. Además podemos saber el número de elementos que contiene 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:

• En primer lugar, necesitamos definir una serie de bloques de memoria compartidos,


dando acceso a ellos al proceso dueño de la cola, si es necesario.
• En segundo lugar, almacenamos el primer mensaje en uno de los bloques, y lo
escribimos en la cola dando como puntero del mensaje el que apunta a dicho
bloque.
• El proceso dueño, cuando reciba el mensaje de la cola, recibirá únicamente el
puntero a éste, accediendo realmente en el bloque de memoria compartida.
• Cuando se ha terminado con la cola, se libera ésta y los bloques de memoria
compartida.
Vemos, por tanto, que la cola no transmite realmente los mensajes, sino simplemente el
orden en que se debe acceder a ellos, pues estos están siempre accesibles a todos los
procesos por estar en memoria compartida.

Dado que la asignación de bloques de memoria es un proceso relativamente lento, la mejor


solución consiste en subasignar bloques de memoria, uno para cada mensaje, dentro de un
bloque mayor de memoria compartida.

S-ar putea să vă placă și