los
lenguajes más primitivos fueron los lenguajes de máquina. Esto, ya que el
hardware se desarrolló antes del software, y además cualquier software
finalmente tiene que expresarse en el lenguaje que maneja el hardware.
La
programación en esos momentos era sumamente tediosa, pues el programador tenía
que "bajarse" al nivel de la máquina y decirle, paso a pasito, cada
punto de la tarea que tenía que realizar. Además, debía expresarlo en forma
numérica; y por supuesto, este proceso era propenso a errores, con lo que la
productividad del programador era muy limitada. Sin embargo, hay que recordar
que en estos momentos, simplemente aún no existía alternativa.
El
primer gran avance que se dio, como ya se comentó, fue la abstracción dada por
el Lenguaje Ensamblador, y con él, el nacimiento de las primeras herramientas
automáticas para generar el código máquina. Esto redujo los errores triviales,
como podía ser el número que correspondía a una operación, que son sumamente
engorrosos y difíciles de detectar, pero fáciles de cometer. Sin embargo, aún
aquí es fácil para el programador perderse y cometer errores de lógica, pues
debe bajar al nivel de la forma en que trabaja el CPU, y entender bien todo lo
que sucede dentro de él.
Con
el desarrollo en los 50s y 60s de algoritmos de más elevado nivel, y el aumento
de poder del hardware, empezaron a entrar al uso de computadoras científicos de
otras ramas; ellos conocían mucho de Física, Química y otras ramas similares,
pero no de Computación, y por supuesto, les era sumamente complicado trabajar
con lenguaje Ensamblador en vez de fórmulas. Así, nació el concepto de Lenguaje
de Alto Nivel, con el primer compilador de FORTRAN (FORmula TRANslation), que,
como su nombre indica, inició como un "simple" esfuerzo de traducir
un lenguaje de fórmulas, al lenguaje ensamblador y por consiguiente al lenguaje
de máquina. A partir de FORTRAN, se han desarrollado innumerables lenguajes,
que siguen el mismo concepto: buscar la mayor abstracción posible, y facilitar
la vida al programador, aumentando la productividad, encargándose los
compiladores o intérpretes de traducir el lenguaje de alto nivel, al lenguaje
de computadora.
Hay
que notar la existencia de lenguajes que combinan características de los de
alto nivel y los de bajo nivel (es decir, Ensamblador). Mi ejemplo favorito es
C: contiene estructuras de programación de alto nivel, y la facilidad de usar
librerías que también son características de alto nivel; sin embargo, fue
diseñado con muy pocas instrucciones, las cuales son sumamente sencillas,
fáciles de traducir al lenguaje de la máquina; y requiere de un entendimiento
apropiado de cómo funciona la máquina, el uso de la memoria, etcétera. Por
ello, muchas personas consideramos a lenguajes como C (que fue diseñado para
hacer sistemas operativos), lenguajes de nivel medio.
El
lenguaje de programación Java, fue diseñado por la compañía Sun Microsystems
Inc, con el propósito de crear un lenguaje que pudiera funcionar en redes computacionales
heterogéneas ( redes de computadoras formadas por más de un tipo de
computadora, ya sean PC, MAC's, estaciones de trabajo, etc.),y que fuera
independiente de la plataforma en la que se vaya a ejecutar. Esto significa que
un programa de Java puede ejecutarse en cualquier máquina o plataforma. El
lenguaje fue diseñado con las siguientes características en mente:
·
Simple. Elimina la
complejidad de los lenguajes como "C" y da paso al contexto de los
lenguajes modernos orientados a
objetos. Orientado a Objetos. La
filosofía de programación orientada a objetos es diferente a la programación
convencional.
·
Familiar. Como la
mayoría de los programadores están acostumbrados a programar en C o en C++, el
sintaxis de Java es muy similar al de estos.
·
Robusto. El sistema
de Java maneja la memoria de la computadora por ti. No te tienes que preocupar
por apuntadores, memoria que no se esté utilizando, etc. Java realiza todo esto
sin necesidad de que uno se lo indique.
·
Seguro. El sistema de
Java tiene ciertas políticas que evitan se puedan codificar virus con este
lenguaje. Existen muchas restricciones, especialmente para los applets, que
limitan lo que se puede y no puede hacer con los recursos críticos de una
computadora.
·
Portable. Como el
código compilado de Java (conocido como byte code) es interpretado, un programa
compilado de Java puede ser utilizado por cualquier computadora que tenga
implementado el interprete de Java.
·
Independiente a la
arquitectura. Al compilar un programa en Java, el código resultante un tipo de código binario conocido como byte
code. Este códido es interpretado por diferentes computadoras de igual manera,
solamente hay que implementar un intérprete para cada plataforma. De esa manera
Java logra ser un lenguaje que no depende de una arquitectura computacional
definida.
·
Multithreaded. Un
lenguaje que soporta multiples threads es un lenguaje que puede ejecutar
diferentes líneas de código al mismo tiempo.
·
Interpretado. Java
corre en máquina virtual, por lo tanto es interpretado.
·
Dinámico. Java no
requiere que compiles todas las clases de un programa para que este funcione.
Si realizas una modificación a una clase Java se encarga de realizar un Dynamic
Bynding o un Dynamic Loading para encontrar las clases.
Java
puede funcionar como una aplicación sola o como un "applet", que es
un pequeño programa hecho en Java. Los applets de Java se pueden
"pegar" a una página de Web (HTML), y con esto puedes tener un
programa que cualquier persona que tenga un browser compatible podrá usar.
Nota:Diferencia entre Java y CGI La
diferencia es esencialmente simple, un CGI se ejecuta en el servidor mientras
que un programa en Java se ejecuta en la máquina del usuario.
Java
funciona de la siguiente manera: El compilador de Java deja el programa en un
Pseudo-código (no es código maquinal) y luego el intérprete de Java ejecuta el
programa (lo que se conoce como el "Java Virtual Machine"). Por eso
Java es multiplataforma, existe un intérprete para cada máquina diferente. Nota:
El código maquinal es el código binario que la computadora entiende y puede
ejecutar.
Para
entender bien como funciona un applet de Java vean el siguiente ejemplo:
1.
Existe un código de
Java en un servidor de Web. (Los códigos de Java se caracterizan por tener la
extensión *.class).
2.
Una persona en
Internet, con un browser compatible con Java, realiza una conección al
servidor.
3.
El servidor envía el
documento HTML y el código en Java (*.class).
4.
En la computadora del
usuario remoto llegan ambos, y la Máquina Virtual de Java, que está en el
browser, transforma el código Java en un código que entienda la máquina local y
se ejecuta el programa dentro de la página de Web.
5.
Si el usuario realiza
otra conexión a otro URL o se sale del browser, el programa se deja de ejecutar
y en la computadora no queda rastro de el.
Ejemplo de tutorial de Java:
En Java hay tres tipos de
comentarios:
// comentarios para una sola línea
/* comentarios de una o
más líneas
*/
/** comentario de documentación, de una o más líneas
*/
Los dos
primeros tipos de comentarios son los que todo programador conoce y se utilizan
del mismo modo. Los comentarios de documentación, colocados inmediatamente
antes de una declaración (de variable o función), indican que ese comentario ha
de ser colocado en la documentación que se genera automáticamente cuando se
utiliza la herramienta de Java, javadoc. Dichos comentarios sirven como
descripción del elemento declarado permitiendo generar una documentación de
nuestras clases escrita al mismo tiempo que se genera el código.
En este tipo de comentario para
documentación, se permite la introducción de algunos tokens o palabras clave,
que harán que la información que les sigue aparezca de forma diferente al resto
en la documentación.
Identificadores
Los identificadores nombran
variables, funciones, clases y objetos; cualquier cosa que el programador
necesite identificar o usar.
En Java, un identificador comienza
con una letra, un subrayado (_) o un símbolo de dólar ($). Los siguientes
caracteres pueden ser letras o dígitos. Se distinguen las mayúsculas de las
minúsculas y no hay longitud máxima.
Serían identificadores válidos:
identificador
nombre_usuario
Nombre_Usuario
_variable_del_sistema
$transaccion
y su uso sería, por ejemplo:
int contador_principal;
char _lista_de_ficheros;
float $cantidad_en_Ptas;
Ejemplo de Unix:
No todo el "árbol" de
directorios está compuesto por directorios de usuario. Existen muchos de ellos
que son de uso general o del propio sistema y con los que habrá que
familiarizarse. Los más importantes son:
/
El raíz, del que "cuelgan" todos.
/bin y /usr/bin
Contienen comandos UNIX ejecutables.
/etc
Es quizá el directorio más importante. Contiene ficheros de datos y
configuración del sistema, el fichero de password, configuración de terminales,
red, etc (de ahí su nombre).
/dev
Ficheros de dispositivos E/S.
/usr/man
Manual
/tmp
Directorio para arreglos temporales. TODOS los usuarios pueden leer y
escribir en él.
C es un lenguaje de programación
diseñado por Dennis Ritchie, de los Laboratorios Bell, y
se instaló en un PDP-11 en 1972; se
diseñó para ser el lenguaje de los Sistemas Operativos
UNIX1. A su vez, UNIX es un Sistema
Operativo desarrollado por Ken Thompson, quién
utilizó el lenguaje ensamblador y un
lenguaje llamado B para producir las versiones originales de UNIX, en 1970. C
se inventó para superar las limitaciones de B.
C es un lenguaje maduro de
propósitos generales que se desarrolló a partir de estas raíces;
su definición aparece en 1978 en el
apéndice ``C Reference Manual'' del libro The C
Programming Language, de Brian W.
Kernighan y Dennis M. Ritchie (Englewood Cliffs,
Nueva Jersey, Prentice-Hall 1978),
pero el estándar recomendable más reciente apareció en
junio de 1983, en el documento de
los Laboratorios Bell titulado The C Programming
Language-Reference Manual, escrito
por Dennis M. Ritchie
Generalizando, un programa en C
consta de tres secciones. La primera sección es donde van todos los
``headers''. Estos ``headers'' son comúnmente los ``#define'' y los
``#include''. Como segunda sección se tienen las ``funciones''. Al igual que
Pascal, en C todas las funciones que se van a ocupar en el programa deben ir
antes que la función principal (main()). Declarando las funciones a ocupar al
principio del programa, se logra que la función principal esté antes que el
resto de las funciones. Ahora, solo se habla de funciones ya que en C no
existen los procedimientos.
Y como última sección se tiene a la
función principal, llamada main. Cuando se ejecuta el programa, lo primero que
se ejecuta es esta función, y de ahí sigue el resto del programa.
Los símbolos { y } indican ``begin''
y ``end'' respectivamente. Si en una función o en un ciclo while, por ejemplo,
su contenido es de solamente una línea, no es necesario usar ``llaves'' ({ }),
en caso contrario es obligación usarlos.
/*Programa ejemplo que despliega el
contenido de "ROL" en pantalla*/
#include <stdio.h>
#define ROL "9274002-1"
despliega_rol() {
printf("Mi rol es : \%s\n", ROL);
}
void main() {
despliega_rol();
}
/* Fin programa */
Pascal
es un lenguaje de programación de alto nivel de propósito general; esto es, se
puede utilizar para escribir programas para fines científicos y comerciales.
El
lenguaje de programación Pascal fue desarrollado por el profesor Niklaus (Nicolás)
Wirth en Zurich, Zuiza, al final de los años 1960s y principios de los 70s.
Wirth diseñó este lenguaje para que fuese un buen primer lenguaje de
programación para personas comenzando a aprender a programar. Pascal tiene un
número relativamente pequeño de conceptos para aprender y dominar. Su diseño
facilita escribir programas usando un estilo que está generalmente aceptado
como práctica estándar de programación buena. Otra de las metas del diseño de
Wirth era la implementación fácil. Él diseñó un lenguaje para el cual fuese
fácil escribir un compilador para un nuevo tipo de computadora.
program
Sorting;
{
Este
programa lee un natural y una secuencia de N caracteres de la entrada estandar; construye un indice para
ordenarlos de menor a mayor e imprime en
la salida la secuencia ordenada.
}
uses
CRT;
Const
Max = 10;
Espacio = ' ';
Enter = chr (13);
type
Indice = 1..Max;
Cantidad= 0..Max;
SecOfChar = record
elems : array [Indice] of
char;
ult : Cantidad;
end;
SecOfInd = record
elems : array [Indice] of
Indice;
ult : Cantidad;
end;
Natural = 0..MaxInt;
function
PosMin (idx: SecOfInd; i: Indice; s: SecOfChar): Cantidad;
{ Devuelve la posicion en el indice
idx del menor caracter en s, para
las posiciones >= i. }
var
j: Indice;
pm: Cantidad;
begin
if i > idx.ult then
pm := 0
else begin
pm := i;
for j := i+1 to idx.ult do
if s.elems[idx.elems[j]] <
s.elems[idx.elems[pm]] then
pm := j;
end;
PosMin := pm;
end;
procedure
Swap (var idx: SecOfInd; i,j: Indice);
{ Intercambia las posiciones i j en
idx. }
var
tmp: Indice;
begin
if (i<=idx.ult) and (j<=idx.ult)
then begin
tmp := idx.elems[i];
idx.elems[i] := idx.elems[j];
idx.elems[j] := tmp;
end;
end;
procedure
InicInds (var idx: SecOfInd; cant: Indice);
{ Construye la secuencia de indices
1,2,3,...,n. Sera el indice
inicial para el ordenamiento de
una secuencia de caracteres
c1,c2,...,cn. }
var
n: Natural;
begin
n := cant;
idx.ult := n;
while n > 0 do begin
idx.elems [n] := n;
n := n-1;
end;
end;
procedure
InicSecChar (var s: SecOfChar);
{ Devuelve la secuencia vacia. }
begin
s.ult := 0;
end;
function
Llena (s: SecOfChar): Boolean;
begin
Llena := s.ult = Max;
end;
{ PRE: not Llena(s) }
procedure
InsCar (var s: SecOfChar; c: char);
{ Inserta el caracter c en la
secuencia s }
begin
s.ult := s.ult + 1;
s.elems [s.ult] := c;
end;
procedure
IndSelSort (s: SecOfChar; var ind: SecOfInd);
{ Construye el indice que ordena la
secuencia s. Ordena el indice
inicial 1,2, ..., n por el metodo
de selection sort }
var
i: Indice;
begin
InicInds (ind, s.ult);
for i := 1 to ind.ult-1 do begin
Swap (ind, i, PosMin (ind, i, s));
end
end;
procedure
WriteSorted (idx: SecOfInd; s: SecOfChar);
{ Imprime en la salida estandar la
secuencia s ordenada segun el
indice idx }
var
i: Indice;
begin
write ('Ordenado: ');
for i := 1 to idx.ult do
write (s.elems[idx.elems[i]],' ');
writeln;
end;
procedure
LeerCar (var c: char; var ok: boolean; sep: Char);
{ Lee de la entrada estandar un
caracter seguido del caracter sep }
var
c1, c2: char;
begin
c := ReadKey; write (c);
c1 := ReadKey; write (c1);
ok := c1 = sep;
end;
procedure
LeerSecOfChar (var s: SecOfChar; cant: Natural; var ok: Boolean);
{ Construye una secuencia de cant
caracteres provistos por el
procedimeinto LeerCar. Si cant
> Max trunca. }
var
bien: Boolean;
i: Natural;
ch, sep: Char;
begin
writeln ('Ingrese ',cant, ' caracteres
separados por blancos. Enter para terminar ');
write (' > ');
InicSecChar (s);
i := 1;
ok := true;
sep := Espacio;
while ok and (i <= cant) and not Llena
(s) do begin
if i = cant then sep := Enter;
LeerCar (ch, bien, sep);
i := i+1;
ok := ok and bien;
if ok then
InsCar (s, ch);
end;
end;
procedure
LeerCant (var n: Natural);
{ Lee de la entrada estandar un
natural <= Max }
begin
repeat
writeln ('Ingrese cantidad de
caracteres (<=',Max,')');
write (' > ');
readln (n);
until n <= Max;
end;
procedure
Continuar (var seguir: Boolean);
var
car: Char;
begin
writeln;
writeln ('Otro ? (s/n)');
write (' > ');
car := ReadKey;
writeln (car);
seguir := car in ['s','S'];
end;
var
cant: Natural;
cars: SecOfChar;
inds: SecOfInd;
seguir, ok: boolean;
begin
repeat
ClrScr;
LeerCant (cant);
LeerSecOfChar (cars, cant, ok);
if ok then begin
IndSelSort (cars, inds);
writeln;
WriteSorted (inds, cars);
end
else begin
writeln;
writeln ('Error en los
datos');
end;
Continuar (seguir);
until not seguir;
end.
Qbasic es un lenguaje de alto nivel,
el cual consiste en instrucciones que los humanos pueden relacionar y
entender. El compilador de Qbasic se
encarga de traducir el mismo a lenguaje de máquina.
Un programa es una secuencia de
instrucciones. El proceso de ejecutar
esas instrucciones se llama correr el programa. Los programas contienen las funciones de entrada, procesamiento y
salida. La persona que resuelve
problemas mediante escribir programas en la computadora se conoce como programador. Después de
analizar el problema y desarrollar un plan para solucionarlo, escribe y prueba
el programa que instruye a la computadora como
llevar a cabo el plan. El
procedimiento que realiza el programador se define como "problem
solving". Pero es necesario
especificar que un programador y un usuario no son lo mismo. Un usuario es
cualquier persona que use el programa.
DIM total AS DOUBLE
DIM number AS DOUBLE
DIM secondNumber AS DOUBLE
DIM more AS STRING
DIM moreNumbers AS STRING
DIM operation AS STRING
total = 0
more = "y"
moreNumbers = "c"
CLS
WHILE more = "y"
INPUT "Enter the first number"; number
total = number
WHILE moreNumbers = "c"
COLOR 14
PRINT "The total is:"; total
COLOR 7
PRINT "Select an operation"
COLOR 2
PRINT "(+)"
COLOR 5
PRINT "(-)"
COLOR 1
PRINT "(x)"
COLOR 4
INPUT "(/)"; operation
COLOR 7
CLS
IF operation = "+" THEN
REM where we do additions
PRINT "Enter the number to Add to";
total
INPUT secondNumber
total =
secondNumber + total
COLOR 14
PRINT "The total is now:"; total
COLOR 7
ELSE
IF operation = "-" THEN
REM subtraction
PRINT "Enter the number to Subtract
from"; total
INPUT secondNumber
total = total - secondNumber
COLOR 14
PRINT "The total is now:"; total
COLOR 7
ELSE
IF operation = "x" THEN
REM multiplication
PRINT "Enter the number to
Multiply"; total; "by"
INPUT secondNumber
total = secondNumber * total
REM * is the multiplication sign in
programs
COLOR 14
PRINT "The total is now:"; total
COLOR 7
ELSE
IF operation = "/" THEN
REM division
PRINT "Enter the number to
Divide"; total; "by"
INPUT secondNumber
IF secondNumber = 0 THEN
COLOR 4
PRINT "You cannot divide by zero"
COLOR 7
ELSE
total = total /
secondNumber
REM / is the division sign in programs
END IF
COLOR 14
PRINT "The total is
now:"; total
COLOR 7
ELSE
PRINT "you must select
an operation"
END IF
END IF
END IF
END IF
INPUT "Do you wish to continue (c) or
start with new numbers
(n)";moreNumbers
CLS
WEND
COLOR 14
PRINT "The grand total is:"; total
COLOR 7
INPUT "Do you wish to make more calculations (y
- n)"; more
moreNumbers = "c"
REM if we don't put "moreNumbers" back to
y, it will always
REM come back to "Do you wish to make more
calculations" and never REM ask
for numbers again
REM (try it)
total = 0
REM if we don't reset the total to 0, it will just
REM keep on adding to the total
WEND
Linux es una implementación del
sistema operativo UNIX (uno más de entre los numerosos clónicos del histórico
Unix), pero con la originalidad de ser gratuito y a la vez muy potente, que
sale muy bien parado (no pocas veces victorioso) al compararlo con las
versiones comerciales para sistemas de mayor envergadura y por tanto
teóricamente superiores. Comenzó como proyecto personal del –entonces
estudiante- Linus Torvalds, quien tomó como punto de partida otro viejo
conocido, el Minix de Andy. S. Tanenbaum (profesor de sistemas operativos que
creó su propio sistema operativo Unix en PCs XT para usarlo en su docencia).
Actualmente Linus lo sigue desarrollando, pero a estas alturas el principal
autor es la red Internet, desde donde una gigantesca familia de programadores y
usuarios aportan diariamente su tiempo aumentando sus prestaciones y dando
información y soporte técnico mútuo. La
versión original -y aun predominante- comenzó para PCs compatibles (Intel 386 y
superiores), existiendo también en desarrollo versiones para prácticamente todo
tipo de plataformas:
PowerPC <http://www.cs.us.es/archive/linuxppc/>,
Sparc <http://www.geog.ubc.ca/sparclinux.html>,
Alpha <http://www.azstarnet.com/~axplinux>,
Mips <http://www.fnet.fr/linux-mips/>,
etc.
De todas ellas la más reciente en
este momento es la versión para PowerMac <http://www.mklinux.org> (el
PowerPC de Apple) basada en el
microkernel Mach 3.0 y de la que ya hay una distribución para desarrolladores
avalada directamente por Apple y OSF pero conservando el espíritu (gratuito, de
libre distribución, etc) de la version original. Un servidor la acaba de probar
hace unos días y se ha llevado una grata sorpresa (aún tendrá muuuchos fallos,
pero para ser una primerísima versión y el poco tiempo que lleva en marcha, ha
avanzado más de lo que me esperaba).
Ejemplo de linux:
Compilar el Kernel
Dado que un diskette sólo almacena
1.44 Megabytes (1440 Kilobytes) de datos, no puedes el mismo kernel que utilizas al diskette. Primero debes conseguir
los fuentes del núcleo y
descomprimirlos en /usr/src/linux. Luego ejecuta la siguiente orden
desde el directorio
/usr/src/linux:
make config
Configura solamente aquello que
realmente necesites. Yo, personalmente, sólo configuro el soporte para "ext2", soporte para la
disquetera (floppy disk), y soporte para "PPP". Tus elecciones
pueden se diferentes en función de lo
que decidas incluir. Ahora introduce el siguiente comando:
make dep; make clean; make zImage
¡make zImage es muy importante! Comprime el kernel
definitivo. Después de que termine la compilación, deberás buscar el nuevo
núcleo en /usr/src/linux/arch/i386/boot bajo el
nombre de zImage.
El sistema de ficheros: No es solamente un conjunto de
ficheros
Ahora hemos de crear el sistema de
ficheros (en inglés: filesystem, fs) para el diskette. En vez de copiar los ficheros tal cual directamente al
diskette, los comprimiremos antes de copiarlos. Esto nos hará un poco más
difícil la faena de modificar todo permanentemente. Primero tecleamos el
siguiente
comando:
dd if=/dev/zero of=[DEVICE] bs=1k count=3000
Donde [DEVICE] es "lugar"
en el disco duro donde vas a guardar el sistema de ficheros descomprimido. Luego, introduce el siguiente
comando y pulsa ENTER, sustituyendo [DEVICE] por el directorio en tu disco duro donde estás guardando el sistema
de ficheros descomprimido:
mke2fs -m 0 [DEVICE]
Si make2fs te pregunta si realmente
quieres hacer esto (Do you really want to do this?), acepta tecleando "y" (yes).
Después tenemos que montar este
sistema de ficheros que hemos creado. Para ello, el núcleo que utilices tiene que permitir "montar
ficheros", en otras palabras, ha de tener habilitada la posibilidad
de "loopback devices". Para
ello has de compilar el núcleo de tu máquina (no el núcleo que hemos creado, sino el de tu propia máquina) con la
opción:
Loopback device support (CONFIG_BLK_DEV_LOOP) [M/n/y/?]
bien como módulo (M) o en el mismo
núcleo (Y). Si lo compilas como módulo (lo más
recomendable) luego tienes que insertar el módulo modprobe loop !No
olvides rearrancar la máquina si has tenido
que recompilar el núcleo!
mount -t ext2 DEVICE /mnt
Si se queja la orden mount puedes
intentar con la siguiente orden:
mount -o loop -t ext2 DEVICE /mnt
Ahora debes copiar todos los
ficheros que necesites en el nuevo sistema de ficheros. Primero, ponte en el directorio /mnt, (cd /mnt), y crea los
siguientes directorios:
/dev
/pro
/etc
/bin
/lib
/mnt
/usr
Ahora crearemos el directorio /dev
tecleando lo siguiente:
cp -dpR /dev /mnt/dev
Si se te acaban los i-nodos del diskette,
puedes ir a /mnt/dev y borrar los archivos de dispositivo que no necesites. Cuando acabes de copiar
los ficheros necesarios para /dev, ves a /etc. Para estar seguro copia todos
los ficheros de /etc a /mnt/etc:
cp -dpR /etc /mnt/etc
Luego copia todo del directorio /lib
en /mnt:
cp -dpR /lib /mnt/lib
Para el directorio /bin, copia sólo
aquello que creas que necesitas en /mnt/bin.
Copiar todo a tu diskette
Ahora hemos de copiar todo en el/los
diskette/s. Para hacer esto, debemos comprimir ahora el sistema de ficheros tecleando las siguientes
ordenes:
cd /
umount /mnt
dd if=[DEVICE] bs=1k | gzip -9 > rootfs.gz
Ahora es importante comprobar el
tamaño del núcleo. Ponte en /usr/src/linux/arch/i386/boot
y teclea "ls -l". Luego divide el tamaño del núcleo entre 1024.
Por ejemplo, si el tamaño es de
250000 bytes, entonces son 245 KB. En adelante, reemplaza [ROOTBEGIN] en las ordenes que aparezca por
el número total de kilobytes que has calculado. Ahora copia el kernel al diskette usando el siguiente comando:
dd if=zImage of=/dev/fd0
Este comando grabará el kernel en el
diskette. Luego introduce el siguiente comando para que el kernel pueda encontrar la raíz del sistema
de ficheros en el diskette.
rdev /dev/fd0 /dev/fd0
Ahora tendrás que hacer un pequeño
cálculo en hexadecimal. Suma 4000 al equivalente en hexadecimal de [ROOTBEGIN]
(que en nuestro ejemplo es F5). Convierte el resultado a decimal y teclea el
siguiente comando, sustituyendo 16629 con el resultado que tú has obtenido:
rdev -r /dev/fd0 16629
Finalmente, teclea lo siguiente para
copiar el sistema de ficheros al diskette:
dd if=/rootfs.gz of=/dev/fd0 bs=1k seek=[ROOTBEGIN]
El sistema de ficheros raíz será
copiado al diskette justo después del kernel. ¡Ya lo tienes! Para el segundo diskette, el proceso es más fácil.
Copia los ficheros que quieras en el diskette. No obstante, para poder usar los ficheros que hay en el
segundo disco, tendrás que entrar lo siguiente después de arrancar con el diskette:
mount /dev/fd0 /usr
Ventajas
y desventajas del Lenguaje Ensamblador
Una
vez que hemos visto la evolución de los lenguajes, cabe preguntarse: ¿En estos
tiempos "modernos", para qué quiero el Lenguaje Ensamblador?
El
proceso de evolución trajo consigo algunas desventajas, que ahora veremos como
las ventajas de usar el Lenguaje Ensamblador, respecto a un lenguaje de alto
nivel:
1.Velocidad
2.Eficiencia
de tamaño
3.Flexibilidad
Por
otro lado, al ser un lenguaje más primitivo, el Ensamblador tiene ciertas
desventajas respecto a los lenguajes de alto nivel:
1.Tiempo
de programación 2.Programas fuente grandes 3.Peligro de afectar recursos
inesperadamente 4.Falta de portabilidad
Velocidad
El
proceso de traducción que realizan los intérpretes, implica un proceso de
cómputo adicional al que el programador quiere realizar. Por ello, nos
encontraremos con que un intérprete es siempre más lento que realizar la misma
acción en Lenguaje Ensamblador, simplemente porque tiene el costo adicional de
estar traduciendo el programa, cada vez que lo ejecutamos.
De
ahí nacieron los compiladores, que son mucho más rápidos que los intérpretes,
pues hacen la traducción una vez y dejan el código objeto, que ya es Lenguaje
de Máquina, y se puede ejecutar muy rápidamente. Aunque el proceso de traducción
es más complejo y costoso que el de ensamblar un programa, normalmente podemos
despreciarlo, contra las ventajas de codificar el programa más rápidamente.
Sin
embargo, la mayor parte de las veces, el código generado por un compilador es
menos eficiente que el código equivalente que un programador escribiría. La
razón es que el compilador no tiene tanta inteligencia, y requiere ser capaz de
crear código genérico, que sirva tanto para un programa como para otro; en
cambio, un programador humano puede aprovechar las características específicas
del problema, reduciendo la generalidad pero al mismo tiempo, no desperdicia
ninguna instrucción, no hace ningún proceso que no sea necesario.
Para
darnos una idea, en una PC, y suponiendo que todos son buenos programadores, un
programa para ordenar una lista tardará cerca de 20 veces más en Visual Basic
(un intérprete), y 2 veces más en C (un compilador), que el equivalente en
Ensamblador.
Por
ello, cuando es crítica la velocidad del programa, Ensamblador se vuelve un
candidato lógico como lenguaje.
Ahora
bien, esto no es un absoluto; un programa bien hecho en C puede ser muchas
veces más rápido que un programa mal hecho en Ensamblador; sigue siendo
sumamente importante la elección apropiada de algoritmos y estructuras de
datos. Por ello, se recomienda buscar optimizar primero estos aspectos, en el
lenguaje que se desee, y solamente usar Ensamblador cuando se requiere más
optimización y no se puede lograr por estos medios.
Tamaño
Por
las mismas razones que vimos en el aspecto de velocidad, los compiladores e
intérpretes generan más código máquina del necesario; por ello, el programa
ejecutable crece. Así, cuando es importante reducir el tamaño del ejecutable,
mejorando el uso de la memoria y teniendo también beneficios en velocidad,
puede convenir usar el lenguaje Ensamblador. Entre los programas que es crítico
el uso mínimo de memoria, tenemos a los virus y manejadores de dispositivos
(drivers). Muchos de ellos, por supuesto, están escritos en lenguaje Ensamblador.
Flexibilidad
Las
razones anteriores son cuestión de grado: podemos hacer las cosas en otro
lenguaje, pero queremos hacerlas más eficientemente. Pero todos los lenguajes
de alto nivel tienen limitantes en el control; al hacer abstracciones, limitan
su propia capacidad. Es decir, existen tareas que la máquina puede hacer, pero
que un lenguaje de alto nivel no permite. Por ejemplo, en Visual Basic no es
posible cambiar la resolución del monitor a medio programa; es una limitante,
impuesta por la abstracción del GUI Windows. En cambio, en ensamblador es
sumamente sencillo, pues tenemos el acceso directo al hardware del monitor.
Resumiendo,
la flexibilidad consiste en reconocer el hecho de que
Todo
lo que puede hacerse con una máquina, puede hacerse en el lenguaje ensamblador
de esta máquina; los lenguajes de alto nivel tienen en una u otra forma
limitantes para explotar al máximo los recursos de la máquina.
Tiempo
de programación
Al
ser de bajo nivel, el Lenguaje Ensamblador requiere más instrucciones para
realizar el mismo proceso, en comparación con un lenguaje de alto nivel. Por
otro lado, requiere de más cuidado por parte del programador, pues es propenso
a que los errores de lógica se reflejen más fuertemente en la ejecución.
Por
todo esto, es más lento el desarrollo de programas comparables en Lenguaje
Ensamblador que en un lenguaje de alto nivel, pues el programador goza de una
menor abstracción.
Programas
fuente grandes
Por
las mismas razones que aumenta el tiempo, crecen los programas fuentes;
simplemente, requerimos más instrucciones primitivas para describir procesos
equivalentes. Esto es una desventaja porque dificulta el mantenimiento de los
programas, y nuevamente reduce la productividad de los programadores.
Peligro
de afectar recursos inesperadamente
Tenemos
la ventaja de que todo lo que se puede hacer en la máquina, se puede hacer con
el Lenguaje Ensamblador (flexibilidad). El problema es que todo error que
podamos cometer, o todo riesgo que podamos tener, podemos tenerlo también en
este Lenguaje. Dicho de otra forma, tener mucho poder es útil pero también es
peligroso.
En
la vida práctica, afortunadamente no ocurre mucho; sin embargo, al programar en
este lenguaje verán que es mucho más común que la máquina se "cuelgue",
"bloquee" o "se le vaya el avión"; y que se reinicialize.
¿Por qué?, porque con este lenguaje es perfectamente posible (y sencillo)
realizar secuencias de instrucciones inválidas, que normalmente no aparecen al
usar un lenguaje de alto nivel.
En
ciertos casos extremos, puede llegarse a sobreescribir información del CMOS de
la máquina (no he visto efectos más riesgosos); pero, si no la conservamos,
esto puede causar que dejemos de "ver" el disco duro, junto con toda
su información.
Falta
de portabilidad
Como
ya se mencionó, existe un lenguaje ensamblador para cada máquina; por ello,
evidentemente no es una selección apropiada de lenguaje cuando deseamos
codificar en una máquina y luego llevar los programas a otros sistemas
operativos o modelos de computadoras. Si bien esto es un problema general a
todos los lenguajes, es mucho más notorio en ensamblador: yo puedo reutilizar
un 90% o más del código que desarrollo en "C", en una PC, al llevarlo
a una RS/6000 con UNIX, y lo mismo si después lo llevo a una Macintosh, siempre
y cuando esté bien hecho y siga los estándares de "C", y los
principios de la programación estructurada. En cambio, si escribimos el
programa en Ensamblador de la PC, por bien que lo desarrollemos y muchos
estándares que sigamos, tendremos prácticamente que reescribir el 100 % del
código al llevarlo a UNIX, y otra vez lo mismo al llevarlo a Mac.
Bibliografía:
Peter Abel.
IBM PC Assembly Language and Programming. Fourth Edition. Prentice Hall. 1997.
http://sunsite.dcc.uchile.cl/webmastercl/mirrors/javatutor/
http://highland.dit.upm.es:8000/UNIX/movs1/2dirsist.html
http://docs.inf.utfsm.cl/pub/DI/labsw/manual_c/node1.html
http://docs.inf.utfsm.cl/pub/DI/labsw/manual_c/node2.html
http://docs.inf.utfsm.cl/pub/DI/labsw/manual_c/node3.html
http://www.fing.edu.uy/~yemurenk/indexsort.html
http://www.cs.us.es/archive/LinuxFocus/Castellano/May1998/article11.html