Compatibilidad¶
Hay dos razones para que un programa compilado para Linux no se pueda ejecutar sobre Windows (o incluso sobre otros sistemas tipo UNIX) a pesar de que el hardware soporte sea el mismo, como ocurre en un sistema con arranque dual.
La primera razón es que Windows y Linux representan los ficheros ejecutables con diferentes formatos. Normalmente cada sistema operativo define su propio formato de ejecutable, y un sistema operativo no va a reconocer un ejecutable que no es suyo. Solo para hacerte una idea de lo que esto significa, puedes echar un vistazo rápido esta página sobre formatos de ejecutables en la Wikipedia.
La segunda razón es que printf()
es una función de biblioteca estándar de C, como sleep()
, y está implementada en términos de las llamadas al sistema propias del sistema operativo donde está instalada esa biblioteca. El hecho de que en Windows la sintaxis de printf()
se haya preservado, al contrario que la de sleep()
, es relevante en cuanto a que facilita la vida del programador, pero no evita tener que recompilar.
Es decir, tanto el código de la versión compatible del reloj como el del Hello World presentan compatibilidad fuente, pero no compatibilidad binaria entre Linux y Windows. También se suele decir que el programa es compatible a nivel fuente, pero no es compatible a nivel binario entre ambas plataformas.
En cuanto a la última versión del reloj, que utiliza explícitamente llamadas al sistema Linux, ya has visto que no se puede recompilar sobre Windows. Así pues, las llamadas al sistema imponen mayores restricciones a la compatibilidad fuente de los programas.
Vamos a definir un poco más formalmente el concepto de compatibilidad.
Considera que tenemos un programa P desarrollado para un sistema operativo A y disponemos del código fuente de P, F(P):
- Si P se puede ejecutar directamente en otro sistema operativo B, P presenta compatibilidad binaria entre A y B.
- Si F(P) se puede compilar para B, y se puede ejecutar en B, F(P) presenta compatibilidad fuente entre A y B.
Normalmente, para compatibilidad binaria se requiere el mismo sistema operativo y la misma arquitectura hardware. La compatibilidad fuente se consigue, en principio, usando las bibliotecas estándar del lenguaje de programación y evitando las llamadas al sistema. Sin embargo ya hemos visto que Linux y Windows montan diferentes bibliotecas de C. ¿Qué se puede hacer?
A lo largo de la historia se han definido estándares para ayudar para compatibilidad fuente de aplicaciones (al menos entre sistemas operativos similares), por ejemplo, POSIX para el mundo UNIX. El manual establece si una función cumple un estándar. Si el programador se atiene a funciones que siguen el estándar, el programa tendrá compatibilidad fuente con sistemas que ofrezcan ese estándar. Lo que sucede es que la funcionalidad de los sistemas operativos y de las bibliotecas evoluciona, de forma que no hay un estándar, sino muchos. Para hacerte una idea, echa un vistazo a esta página sobre los estándares POSIX y otros relacionados con el mundo UNIX.
En el caso de nuestro reloj que usa llamadas al sistema, ¿es posible preservar la compatibilidad fuente? Los estándares POSIX se extienden también a las llamadas al sistema, pero difícilmente preservaremos la compatibilidad entre sistemas tan diferentes como Linux y Windows. A decir verdad, con un cierto esfuerzo podríamos solucionarlo usando compilación condicional, ...pero para eso ya tenemos una versión compatible basada en bibliotecas estándar. ¿Qué ventajas tiene entonces usar las llamadas al sistema?
En general, pocas ...mientras nuestro sistema disponga de las bibliotecas adecuadas, algo que en los sistemas empotrados, que suelen verse limitados por restricciones hardware, no siempre es posible. Por otra parte, con las llamadas al sistema se pueden hacer cosas que están restringidas en las funciones de biblioteca, como ilustraremos con el siguiente ejemplo.
Imagina que pretendemos leer de un sensor o dispositivo mientras contamos tiempo, por ejemplo en una aplicación tipo pulsómetro que cuenta pulsaciones en el teclado en un intervalo de tiempo. Para implementarlo, podemos partir de uno de los programas reloj que conocemos. Sin embargo, no parece directo implementar la lectura de teclado, ya que o bien contamos tiempo con sleep()
o bien leemos de teclado. ¿Cómo hacer las dos cosas a la vez? sleep()
se implementa con la pareja de llamadas al sistema alarm
-pause
, según podemos comprobar en la versión del reloj con llamadas al sistema. La llamada pause
es bloqueante, pero alarm()
no lo es. Lo que hace alarm()
es programar la activación de la señal SIGALRM
en el tiempo especificado. Mediante signal()
hemos asociado la función que debe ejecutarse (fnula
en este caso, que no hace nada) cuando se produce la señal.
Prescindiendo de pause()
y programando en la función que trata la señal SIGALRM
la acción asociada al finalizar el intervalo de tiempo, es directo desarrollar un pulsometro.
Utilizando algunas llamadas al sistema, acabamos de aprender una forma de introducir concurrencia en un programa. Sin embargo, terminaremos por encontrar métodos más generales que el de las señales para construir programas realmente concurrentes.