miércoles, 5 de marzo de 2008

Unix


Características Generales:
Es un
sistema operativo de tiempo compartido, controla los recursos de una computadora y los asigna entre los usuarios. Permite a los usuarios correr sus programas. Controla los dispositivos de periféricos conectados a la máquina.
Posee las siguientes
características:
- Es un sistrema operativo multiusuario, con capacidad de simular multiprocesamiento y procesamiento no interactivo.
- Está escrito en un
lenguaje de alto nivel : C.
- Dispone de un
lenguaje de control programable llamado SHELL.
- Ofrece facilidades para la creación de
programas y sistemas y el ambiente adecuado para las tareas de diseños de software.
- Emplea manejo dinámico de
memoria por intercambio o paginación.
- Tiene capacidad de interconexión de
procesos.
- Permite
comunicación entre procesos.
- Emplea un
sistema jerárquico de archivos, con facilidades de protección de archivos, cuentas y procesos.
- Tiene facilidad para redireccionamiento de Entradas/Salidas.
- Garantiza un alto grado de portabilidad.
El
sistema se basa en un Núcleo llamado Kernel, que reside permanentemente en la memoria, y que atiende a todas las llamadas del sistema, administra el acceso a los archivos y el inicio o la suspención de las tareas de los usuarios.
La comunación con el
sistema UNIX se da mediante un programa de control llamado SHELL. Este es un lenguaje de control, un intérprete, y un lenguaje de programación, cuyas características lo hacen sumamente flexible para las tareas de un centro de cómputo. Como lenguaje de programación abarca los siguientes aspectos:
- Ofrece las
estructuras de control normales: secuenciación, iteración condicional, selección y otras.
- Paso de parámetros.
- Sustitución textual de
variables y Cadenas.
-
Comunicación bidireccional entre órdenes de shell.
El shell permite modificar en forma
dinámica las características con que se ejecutan los programas en UNIX:
Las entradas y salidas pueden ser redireccionadas o redirigidas hacia
archivos, procesos y dispositivos;
Es posible interconectar
procesos entre sí.
Diferentes usuarios pueden "ver" versiones distintas del
sistema operativo debido a la capacidad del shell para configurar diversos ambientes de ejecución. Por ejemplo, se puede hacer que un usuario entre directamente a su sección, ejecute un programa en particular y salga automáticamente del sistema al terminar de usarlo.
El núcleo del Sistema Operativo
El núcleo del
sistema operativo Unix (llamado Kernel) es un programa escrito casi en su totalidad en lenguaje C, con excepción de una parte del manejo de interrupciones, expresada en el lenguaje ensamblador del procesador en el que opera.
Las
funciones del núcleo son permitir la existencia de un ambiente en el que sea posible atender a varios usuarios y múltiples tareas en forma concurrente, repartiendo al procesador entre todos ellos, e intentando mantener en grado óptimo la atención individual.
El Kernel opera como asignador de
recursos para cualquier proceso que necesite hacer uso de las facilidades de cómputo. Es el componente central de Unix y tiene las siguientes funciones:
- Creación de procesos, asignación de tiempos de atención y sincronización.
- Asignación de la atención del
procesador a los procesos que lo requieren.
-
Administración de espacio en el sistema de archivos, que incluye: acceso, protección y administración de usuarios; comunicación entre usuarios v entre procesos, y manipulación de E/S y administración de periféricos.
-
Supervisión de la transmisión de datos entre la memoria principal y los dispositivos periféricos.
El Kernel reside siempre en
la memoria central y tiene el control sobre la computadora, por lo que ningún otro proceso puede interrumpirlo; sólo pueden llamarlo para que proporcione algún servicio de los ya mencionados. Un proceso llama al Kernel mediante módulos especiales conocidos como llamadas al sistema.
El Kernel consta de dos artes principales: la sección de control de procesos y la de control de dispositivos. La primera asigna
recursos, programas, procesos y atiende sus requerimientos de servicio; la segunda, supervisa la transferencia de datos entre la memoria principal y los dispositivos periféricos. En términos generales, cada vez que algún usuario oprime una tecla de una terminal, o que se debe leer o escribir información del disco magnético, se interrumpe al procesador central y el núcleo se encarga de efectuar la operación de transferencia.

Cuando se inicia la operación de
la computadora, debe cargarse en la memoria una copia del núcleo, que reside en e] disco magnético (operación denominada bootstrap). Para ello, se deben inicializar algunas interfaces básicas de hardware; entre ellas, el reloj que proporciona interrupciones periódicas. El Kernel también prepara algunas estructuras de datos que abarcan una sección de almacenamiento temporal para transferencia de información entre terminales y procesos, una sección para almacenamiento de descriptores de archivos y una variable que indica la cantidad de memoria principal.
A continuación, el Kernel inicializa un
proceso especial, llamado proceso 0. En general, los procesos se crean mediante una llamada a una rutina del sistema (fork), que funciona por un mecanismo de duplicación de procesos. Sin embargo, esto no es suficiente para crear el primero de ellos, por lo que el Kernel asigna una estructura de datos y establece apuntadores a una sección especial de la memoria, llamada tabla de procesos, que contendrá los descriptores de cada uno de los procesos existentes en el sistema.
Después de haber creado el proceso 0, se hace una copia del mismo, con lo que se crea el proceso 1; éste muy pronto se encargará de "dar vida" al sistema completo, mediante la activación de otros procesos que también forman parte del núcleo. Es decir, se inicia una cadena de activaciones de procesos, entre los cuales destaca el conocido como despachador, o scheduler, que es el responsable de decidir cuál proceso se ejecutará y cuáles van a entrar o salir de la
memoria central. A partir de ese momento se conoce el número 1 como proceso de inicialización del sistema, init.
El proceso init es el responsable de establecer la
estructura de procesos en Unix. Normalmente, es capaz de crear al menos dos estructuras distintas de procesos: el modo monousuario y el multiusuario. Comienza activando el intérprete del lenguaje de control (Shell) en la terminal principal, o consola, del sistema y proporcionándole privilegios de "superusuario". En la modalidad de un solo usuario la consola permite iniciar una primera sesión, con privilegios especiales, e impide que las otras líneas de comunicación acepten iniciar sesiones nuevas. Esta modalidad se usa con frecuencia para revisar y reparar sistemas de archivos, realizar pruebas de funciones básicas del sistema y para otras actividades que requieren uso exclusivo de la computadora.
Init crea otro proceso, que espera pacientemente a que alguien entre en sesión en alguna línea de
comunicación. Cuando esto sucede, realiza ajustes en el protocolo de la línea y ejecuta el programa login, que se encarga de atender inicialmente a los nuevos usuarios. Si la clave del usuario, y la contraseña proporcionadas son las correctas, entonces entra en operación el programa Shell, que en lo sucesivo se encargará de la atención normal del usuario que se dio de alta en esa terminal.
A partir de ese momento el responsable de atender al usuario en esa terminal es el intérprete Shell.
Cuando se desea terminar la sesión hay que desconectarse de Shell (y, por lo tanto, de Unix), mediante una secuencia especial de teclas (usualmente. <> - D). A partir de ese momento la terminal queda disponible para atender a un nuevo usuario.

Administración de Archivos y Directorios
El sistema de archivos de Unix; esta basado en un
modelo arborescente y recursivo, en el cual los nodos pueden ser tanto archivos como directorios, y estos últimos pueden contener a su vez directorios o subdirectorios. Debido a esta filosofía, se maneja al sistema con muy pocas órdenes, que permiten una gran gama de posibilidades. Todo archivo de Unix está controlado por múltiples niveles de protección, que especifican los permisos de acceso al mismo. La diferencia que existe entre un archivo de datos, un programa, un manejador de entrada/salida o una instrucción ejecutable se refleja en estos parámetros, de modo que el sistema operativo adquiere características de coherencia y elegancia que lo distinguen.
La raíz del sistema de archivos (conocida como root ) se denota con el símbolo "/", y de ahí se desprende un conjunto de directorios que contienen todos los archivos del sistema de cómputo. Cada directorio, a su vez, funciona como la subraíz de un nuevo árbol que depende de él y que también puede estar formado por directorios o subdirectorios y archivos. Un
archivo siempre ocupará el nivel más bajo dentro del árbol, porque de un archivo no pueden depender otros; si así fuera, sería un directorio. Es decir, los archivos son como las hojas del árbol.
Se define en forma unívoca el nombre de todo
archivo (o directorio) mediante lo que se conoce como su trayectoria (path name): es decir, el conjunto completo de directorios, a partir de root (/), por los que hay que pasar para poder llegar al directorio o archivo deseado. Cada nombre se separa de los otros con el símbolo /, aunque tan sólo el primero de ellos se refiere a la raíz.

Manejo de archivos y de
información
Como ya se describió, la
estructura básica del sistema de archivos es jerárquica, lo que significa que los archivos están almacenados en varios niveles. Se puede tener acceso a cualquier archivo mediante su trayectoria, que especifica su posición absoluta en la jerarquía, y los usuarios pueden cambiar su directorio actual a la posición deseada. Existe también un mecanismo de protección para evitar accesos no autorizados. Los directorios contienen información para cada archivo, que consiste en su nombre y en un número que el Kernel utiliza para manejar la estructura interna del sistema de archivos, conocido como el nodo-i. Hay un nodo-i para cada archivo, que contiene información de su directorio en el disco, su longitud, los modos y las fechas de acceso, el autor, etc. Existe, además, una tabla de descriptores de archivo, que es una estructura de datos residente en el disco magnético, a la que se tiene acceso mediante el sistema mencionado de E/S por bloques.
El control del espacio libre en el disco se mantiene mediante una lista ligada de bloques disponibles. Cada bloque contiene la
dirección en disco del siguiente bloque en la cadena. El espacio restante contiene las direcciones de grupos de bloques del disco que se encuentren libres. De esta forma, con una operación de E/S, el sistema obtiene un conjunto de bloques libres y un apuntador para conseguir más.
Las
operaciones de E/S en archivos se llevan a cabo con la ayuda de la correspondiente entrada del nodo-i en la tabla de archivos del sistema. El usuario normalmente desconoce los nodos-i porque las referencias se hacen por el nombre simbólico de la trayectoria. Los procesos emplean internamente funciones primitivas (llamadas al sistema) para tener acceso a los archivos; las más comunes son open, creat, read, write, seek, close y unlink, aunque sólo son empleadas por los programadores, no por los usuarios finales del sistema.
Toda esta
estructura física se maneja "desde afuera" mediante la filosofía jerárquica de archivos y directorios ya mencionada, y en forma totalmente transparente para el usuario. Además, desde el punto de vista del sistema operativo, un archivo es muy parecido a un dispositivo.
Las ventajas de tratar a los dispositivos de E/S en forma similar a los archivos normales son múltiples: un archivo y un dispositivo de E/S se tornan muy parecidos; los nombres de los archivos y de los dispositivos tienen la misma sintaxis y significado, así que a un programa que espera un nombre de archivo como parámetro puede dársele un nombre de dispositivo (con esto se logra interacción rápida y fácil entre procesos de alto nivel).
El sistema Unix ofrece varios niveles de protección para el sistema de archivos, que consisten en asignar a cada archivo el número único de identificación de su dueño, junto con nueve bits de protección, que especifican permisos de
lectura, escritura y ejecución para el propietario, para otros miembros de su grupo (definido por el administrador del sistema) y para el resto de los usuarios. Antes de cualquier acceso se verifica su validez consultando estos bits, que residen en el nodo-i de todo archivo. Además, existen otros tres bits que se emplean para manejos especiales, relacionados con la clave del superusuario.
Otra
característica de Unix es que no requiere que el conjunto de sistemas de archivos resida en un mismo dispositivo.
Es posible definir uno o varios
sistemas "desmontables", que residen físicamente en diversas unidades de disco. Existe una orden (mkfs) que permite crear un sistema de archivos adicional, y una llamada al sistema (mount) con la que se añade (y otra con la que se desmonta) uno de ellos al sistema de archivos global.
EI control de las
impresoras de una computadora que funciona con el sistema operativo Unix consiste en un subsistema (SPOOL) que se encarga de coordinar los pedidos de impresión de múltiples usuarios. Existe un proceso de Kernel que en forma periódica revise las colas de servicio de las impresoras para detectar la existencia de pedidos e iniciar entonces las tareas de impresión. Este tipo de procesos, que son activados en forma periódica por el núcleo del sistema operativo, reciben en Unix el nombre de daemons (duendes), tal vez porque se despiertan y aparecen sin previo aviso. Otros se encargan de activar procesos en tiempos previamente determinados por el usuario, o de escribir periódicamente los contenidos de los buffers de memoria en el disco magnético.
Procesos. Manejo del Procesador
En Unix se ejecutan
programas en un medio llamado "proceso de usuario". Cuando se requiere una función del Kernel, el proceso de usuario hace una llamada especial al sistema y entonces el control pasa temporalmente al núcleo. Para esto se requiere de un conjunto de elementos de uso interno, que se mencionan a continuación.
Se conoce como
imagen a una especie de fotografía del ambiente de ejecución de un proceso, que incluye una descripción de la memoria, valores de registros generales, status de archivos abiertos, el directorio actual, etcétera. Una imagen es el estado actual de una computadora virtual, dedicada a un proceso en particular.
Un proceso se define como la ejecución de una
imagen. Mientras el procesador ejecuta un proceso, la imagen debe residir en la memoria principal; durante la ejecución de otros procesos permanece primera en la memoria principal a menús que la aparición de un proceso activo de mayor prioridad la obligue a ser copiada al disco, como ya se dijo.
Un proceso puede encontrarse en uno de varios estados: en ejecución; listo para ejecutar, o en espera.
Cuando se invoca una función del sistema, el proceso de usuario llama al Kernel como subrutina. Hay un
cambio de ambientes y, como resultado, se tiene un proceso del sistema. Estos dos procesos son dos fases del mismo original, que nunca se ejecutan en forma simultánea.
Existe una tabla de procesos que contiene una entrada por cada uno de ellos con los
datos que requiere el sistema:
identificación, direcciones de los segmentos que emplea en la memoria, información que necesita el scheduler y otros. la entrada de la tabla de procesos se asigna cuando se crea el proceso y se libera cuando éste termina.
Para crear un proceso se requiere la inicialización de una entrada en la tabla, así como la creación de segmentos de
texto y de datos. Además, es necesario modificar la tabla cuando cambia el estado del proceso o cuando recibe un mensaje de otro (para sincronización, por ejemplo). Cuando un proceso termina, su entrada en la tabla se libera y queda otro disponible para que otro nuevo la utilice.
En el sistema operativo Unix los procesos pueden comunicarse internamente entre sí, mediante el envío de mensajes o señales. El mecanismo conocido como interconexión (pipe) crea un canal entre dos procesos mediante una llamada a una rutina del Kernel, y se emplea tanto para pasar datos unidireccionalmente entre las
imágenes de ambos, como para sincronizarlos, ya que si un proceso intenta escribir en un pipe ocupado, debe esperar a que el receptor lea los datos pendientes. Lo mismo ocurre en el caso de una lectura de datos inexistentes: el proceso que intenta leer debe esperar a que el proceso productor deposite los datos en el canal de intercomunicación.
Entre las diferentes llamadas al sistema para el manejo de procesos que existen en Unix están las siguientes, algunas de las cuales ya han sido mencionadas: fork (sacar una copia a un proceso); exec (cambiar la
identidad de un proceso); kill (enviar una señal a un proceso); signal (especificar la acción por ejecutar cuando se recibe una señal de otro proceso), y exit (terminar un proceso).
Dentro de las tareas del manejo del procesador destaca la asignación
dinámica (scheduling), que en Unix resuelve el scheduler mediante un mecanismo de prioridades. Cada proceso tiene asignada una prioridad; las prioridades de los procesos de usuario son menores que la más pequeña de un proceso del sistema.
El "
motor" que mantiene en movimiento un esquema de multiprogramación es, por un lado, el conjunto de interrupciones que genera el desempeño de los procesos y, por otro, los constantes recordatorios que hace el reloj del procesador para indicar que se terminó la fracción de tiempo dedicada a cada proceso.
En el sistema Unix, las interrupciones son causadas por lo que se conoce como
eventos, entre los cuales se consideran: la ejecución de una tarea de entrada/salida; la terminación de los procesos dependientes de otro; la terminación de la fracción de tiempo asignada a un proceso, y la recepción de una señal desde otro proceso.
En un sistema de
tiempo compartido se divide el tiempo en un determinado número de intervalos o fracciones y se asigna cada una de ellas a un proceso. Además Unix toma en consideración que hay procesos en espera de una operación de E/S y que ya no pueden aprovechar su fracción. Para asegurar una distribución adecuada del procesador entre los procesos se calculan dinámicamente las prioridades de estos últimos, con el fin de determinar cuál será el proceso que se ejecutará cuando se suspenda el proceso activo actual.
Manejo De Memoria
Dependiendo de
la computadora en la que se ejecute, Unix utiliza dos técnicas de manejo de memoria: swapping y memoria virtual.
Lo estándar en Unix es un sistema de intercambio de segmentos de un proceso entre memoria principal y memoria secundaria, llamado swapping lo que significa que se debe mover la
imagen de un proceso al disco si éste excede la capacidad de la memoria principal, y copiar el proceso completo a memoria secundaria. Es decir, durante su ejecución, los procesos son cambiados de y hacia memoria secundaria conforme se requiera.
Si un proceso necesita crecer, pide más memoria al sistema operativo y se le da una nueva sección, lo suficientemente grande para acomodarlo. Entonces, se copia el contenido de la sección usada al área nueva, se libera la sección antigua y se actualizan las tablas de descriptores de procesos. Si no hay suficiente memoria en el momento de la expansión, el proceso se bloquea temporalmente y se le asigna espacio en memoria secundaria. Se copia a disco y, posteriormente, cuando se tiene el espacio adecuado - lo cual sucede normalmente en algunos segundos - se devuelve a memoria principal.
Está claro que el proceso que se encarga de los intercambios entre memoria y disco (llamado swapper) debe ser especial y jamás podrá perder su posición privilegiada en la memoria central. El Kernel se encarga de que nadie intente siquiera interrumpir este proceso, del cual dependen todos los demás. Este es el proceso 0 mencionado antes. Cuando se decide traer a la memoria principal un proceso en
estado de "listo para ejecutar", se le asigna memoria y se copian allí sus segmentos. Entonces, el proceso cargado compite por el procesador con todos los demás procesos cargados. Si no hay suficiente memoria, el proceso de intercambio examine la tabla de procesos para determinar cuál puede ser interrumpido y llevado al disco.
Hay una pregunta que surge entonces es ¿cuál de los posibles procesos que están cargados será desactivado y cambiado a memoria secundaria? Los procesos que se eligen primero son aquellos que están esperando
operaciones lentas (E/S), o que llevan cierto tiempo sin haberse movido al disco. La idea es tratar de repartir en forma equitativa las oportunidades de ejecución entre todos los procesos, tomando en cuenta sus historias recientes y sus patrones de ejecución.
Otra pregunta es ¿cuál de todos los procesos que están en el disco será traído a memoria principal?. La decisión se toma con base en el tiempo de residencia en memoria secundaria. El proceso más antiguo es el que se llama primero, con una pequeña penalización para los grandes.
Cuando Unix opera en máquinas más grandes, suele disponer de manejo de memoria de paginación por
demanda. En algunos sistemas el tamaño de la página en Unix es de 512 bytes; en otros, de 1024. Para reemplazo se usa un algoritmo que mantiene en memoria las páginas empleadas más recientemente.
Un sistema de paginación por
demanda ofrece muchas ventajas en cuanto a flexibilidad y agilidad en la atención concurrente de múltiples procesos y proporciona, además, memoria virtual, es decir, la capacidad de trabajar con procesos mayores que el de la memoria central. Estos esquemas son bastante complejos y requieren del apoyo de hardware especializado.
Manejo de entradas y salidas
El sistema de entrada/salida se divide en dos sistemas complementarios: el estructurado por bloques y el estructurado por caracteres. El primero se usa para manejar cintas y discos magnéticos, y emplea bloques de tamaño fijo (512 o 1024 bytes) para leer o escribir. El segundo se utiliza para atender a las terminales, líneas de comunicación e
impresoras, y funciona byte por byte.
En general, el sistema Unix emplea programas especiales (escritos en C) conocidos como manejadores (drivers) para atender a cada
familia de dispositivos de E/S. Los procesos se comunican con los dispositivos mediante llamadas a su manejador. Además, desde el punto de vista de los procesos, los manejadores aparecen como si fueran archivos en los que se lee o escribe; con esto se logra gran homogeneidad y elegancia en el diseño.
Cada dispositivo se
estructura internamente mediante descriptores llamados número mayor, número menor y clase (de bloque o de caracteres). Para cada clase hay un conjunto de entradas, en una tabla, que aporta a los manejadores de los dispositivos. El número mayor se usa para asignar manejador, correspondiente a una familia de dispositivos; el menor pasa al manejador como un argumento, y éste lo emplea para tener acceso a uno de varios dispositivos físicos semejantes.
Las rutinas que el sistema emplea para ejecutar
operaciones de E/S están diseñadas para eliminar las diferencias entre los dispositivos y los tipos de acceso. No existe distinción entre acceso aleatorio y secuencial, ni hay un tamaño de registro lógico impuesto por el sistema. El tamaño de un archivo ordinario está determinado por el número de bytes escritos en él; no es necesario predeterminar el tamaño de un archivo.
El sistema mantiene una lista de áreas de
almacenamiento temporal (buffers), asignadas a los dispositivos de bloques. El Kernel usa estos buffers con el objeto de reducir el tráfico de E/S. Cuando un programa solicita una transferencia, se busca primero en los buffers internos para ver si el bloque que se requiere ya se encuentra en la memoria principal (como resultado de una operación de lectura anterior). Si es así, entonces no será necesario realizar la operación física de entrada o salida.
Existe todo un mecanismo de manipulación interna de buffers (y otro de manejo de listas de bytes), necesario para controlar el flujo de datos entre los dispositivos de bloques (y de caracteres) y los programas que los requieren.
Por último, y debido a que los manejadores de los dispositivos son programas escritos en
lenguaje C, es relativamente fácil reconfigurar el sistema para ampliar o eliminar dispositivos de E/S en la computadora, así como para incluir tipos nuevos.
Lenguaje de control del sistema operativo
Entre los rasgos distintivos de Unix está
el lenguaje de control que emplea, llamado Shell. Es importante analizar dos funciones más de Shell, llamadas redireccionamiento e Interconexión.
Asociado con cada proceso hay un conjunto de descriptores de archivo numerados 0, I y 2, que se utilizan para todas las transacciones entre los procesos y el sistema operativo. El descriptor de archivo 0 se conoce como la entrada estándar; el descriptor de archivo 1, como la salida estándar, y el descriptor 2, como el error estándar. En general, todos están asociados con la terminal de vídeo, pero, debido a que inicialmente son establecidos por Shell, es posible reasignarlos.
Una parte de la orden que comience con el símbolo ? se considera como el nombre del archivo que será abierto por Shell y que se asociará con la entrada estándar; en su ausencia, la entrada estándar se asigna a la terminal. En forma similar, un archivo cuyo nombre está precedido por el símbolo > recibe la salida estándar de las
operaciones.
Cuando Shell interpreta la orden
califica <> resulta
llama a ejecución al programa califica (que ya debe estar compilado y listo para ejecutar) y detecta la existencia de un archivo que toma el lugar de la entrada estándar y de otro que reemplaza a la salida estándar. Después, pasa como datos de
lectura los contenidos del archivo examen recién abierto (que debe existir previamente) al programa ejecutable. Conforme el programa produce datos como salida, éstos se guardan en el archivo resulta que Shell crea en ese momento.
En la
teoría de lenguajes formales desempeñan un importante papel las gramáticas llamadas de tipo 3 (también conocidas como regulares), que tienen múltiples aplicaciones en el manejo de lenguajes. Existen unas construcciones gramaticales conocidas como expresiones regulares, con las que se puede hacer referencia a un conjunto ilimitado de nombres con estructura lexicográfica similar; esto lo aprovecha Shell para dar al usuario facilidades expresivas adicionales en el manejo de los nombres de los archivos. Así, por ejemplo, el nombre carta * se refiere a todos los archivos que comiencen con el prefijo carta* y que sean seguidos por cualquier subcadena, incluyendo la cadena vacía; por ello, si se incluye el nombre carta* en alguna orden, Shell la aplicará a los archivos carta, carta1, carta2 y cualquier otro que cumpla con esa especificación abreviada. En general, en lugares donde se emplea un nombre o una trayectoria, Shell permite utilizar una expresión regular que sirve como abreviatura para toda una familia de ellos, y automáticamente repite el pedido de atención para los componentes. Existen además otros caracteres especiales que Shell reconoce y emplea para el manejo de expresiones regulares, lo que proporciona al lenguaje de control de Unix mayor potencia y capacidad expresiva.
En Unix existe también la posibilidad de ejecutar programas sin tener que atenderlos en forma interactiva, sino simulando paralelismo (es decir, atender de manera concurrente varios procesos de un mismo usuario). Esto se logra agregando el símbolo & al final de la línea en la que se escribe la orden de ejecución. Como resultado, Shell no espera que el proceso "hijo'' termine de ejecutar (como haría normalmente), sino que regresa a atender al usuario inmediatamente después de haber creado el proceso asincrónico, simulando en esta forma el procesamiento por lotes ( batch ) Para cada uno de estos procesos Shell proporciona, además, el número de identificación, por lo que si fuera necesario el usuario podría cancelarlo posteriormente, o averiguar el avance de la ejecución.
La comunicación interna entre procesos (es decir, el envío de mensajes con los que los diversos procesos se sincronizan y coordinan) ocurre mediante el mecanismo de interconexiones (pipes) ya mencionado, que conecta la salida estándar de un programa a la entrada estándar de otro, como si fuera un conducto con dos extremos, cada uno de los cuales está conectado a su vez a un proceso distinto. Desde Shell puede emplearse este mecanismo con el símbolo en la línea donde se escribe la orden de ejecución.
Así en el ejemplo:
(califica <> lista) &
se emplean las características de interconexión, redireccionamiento y asincronía de procesos para lograr resultados difíciles de obtener en otros
sistemas operativos. Aquí se pide que, en forma asincrónica (es decir, dejando que la terminal siga disponible para atender otras tareas del mismo usuario), se ejecute el programa califica para que lea los datos que requiere del archivo tareas; al terminar, se conectará con el proceso sort (es decir, pasará los resultados intermedios) para que continúe el procesamiento y se arreglen los resultados en orden alfabético; al final de todo esto, los resultados quedarán en el archivo lista.
Con esta otra orden, por ejemplo, se busca obtener todos los renglones que contengan las palabras "
contrato" o "empleado" en los archivos en disco cuyos nombres comiencen con la letra "E" (lo cual se denota mediante una expresión regular). Para lograrlo, se hace uso de una función llamada egrep, especial para el manejo de patrones y combinaciones de expresiones regulares dentro de los archivos:
egrep-n '
contrato' 'empleado' E *
Los resultados aparecen así:
Emple1: 5: en caso de que un empleado decide hacer uso de la facilidad,
Emple1:7: y el
contrato así lo considere las obligaciones de la
Emple2:9: Cláusula II: El
contrato colectivo de trabajo
Emple2:15: Fracción llI: El empleado tendrá derecho, de acuerdo con lo
El tercer renglón, por ejemplo,
muestra el noveno renglón del archivo Emple2, que contiene una de las palabras buscadas.
Como Unix fue diseñado para servir de entorno en las labores de
diseño y producción de programas, ofrece - además de su filosofía misma - un rico conjunto de herramientas para la creación de sistemas complejos, entre las que destaca el subsistema make. Este último ofrece una especie de lenguaje muy sencillo, con el cual el programador describe las relaciones estructurales entre los módulos que configuran un sistema completo, para que de ahí en adelante make se encargue de mantener el sistema siempre al día. Es decir, si se modifica algún módulo, se reemplaza o se añade otro, las compilaciones individuales, así como las cargas y ligas a que haya lugar, serán realizadas en forma automática, por esta herramienta. Con una sola orden, entonces, es posible efectuar decenas de compilaciones y ligas predefinidas entre módulos, y asegurarse de que en todo momento se tiene la última versión de un sistema, ya que también se lleva cuenta automática de las fechas de creación, modificación y compilación de los diversos módulos. De esta manera, se convierte en una herramienta casi indispensable al desarrollar aplicaciones que requieren decenas de programas que interactúan entre sí o que mantienen relaciones jerárquicas.
Otras
herramientas interesantes son ar, diseñado para crear y mantener bibliotecas de programas (que serán luego utilizadas por otros programas para efectuar las funciones ya definidas sin tener que duplicar el código); awk, un lenguaje para reconocimiento de patrones y expresiones regulares (es decir, generadas por una gramática regular o de tipo 3), útil para extraer información de archivos en forma selectiva; lex, un generador de analizadores lexicográfico, y yacc, un compilador de compiladores. Estos dos últimos se emplean como herramientas en la creación de compiladores y procesadores de lenguajes.
La lista complete de funciones, órdenes de subsistemas que forman parte de las utilerías del sistema operativo Unix es realmente grande, e incluye más de un centenar, que se pueden agrupar en los siguientes rubros:
Compiladores de
compiladores.
Ejecución de programas.
Facilidades de
comunicaciones.
Funciones para control de status.
Funciones para control de usuarios.
Funciones para impresión.
Herramientas de
desarrollo de programación.
Lenguaje C, funciones y
bibliotecas asociados.
Macroprocesamiento.
Manejo de directorios y archivos.
Manejo de gráficas.
Manejo de información.
Manejo de terminales.
Mantenimiento y respaldos.
Otros lenguajes algorítmicos integrados.
Preparación de
documentos.

jueves, 14 de febrero de 2008

Lenguajes Scripts

Es un lenguaje de programación creado por Netscape con el objetivo de integrarse a HTML y facilitar la creación de paginas interactivas sin necesidad de utilizar scripts de CGI o Java.
El código de un programa de Java Script se integra directamente al código de un documento HTML y no necesita ser compilado, es el propio navegador el encargado de traducir dicho código. Es importante hacer referencia que Java Script no es el lenguaje de programación Java.
Para definir un código en Java Script se tienen las siguientes etiquetas :
Los códigos de Java Script se pueden colocar en la cabecera o en el cuerpo del documento, es importante tener presente que Java Script es un lenguaje de programación Case Sensitive, es decir distingue las letras mayúsculas de las minúsculas, por lo tanto las variables: Resultado. Resultado, RESULTADO serán vistas por Java Script como diferentes.

Las llaves { … } se emplean para definir fragmentos de código o bloques. El punto y coma es empleado para separar sentencias que se encuentran en una sola línea o también para indicar el final de una sentencia que ocupa varias líneas.

Los lenguajes de scripting tienen la característica que se ejecutan mediante un intérprete del lenguaje, en tiempo real. Esto quiere decir que tienes el código fuente, lo corres con el intérprete y te genera la salida. Los "otros" se diferencian en que deben ser compilados para que se cree un archivo binario que pueda ser entendido por la máquina (el binario contiene la información en lenguaje de máquina, que el computador entiende directamente). Además, debe ser compilado para cada arquitectura y sistema operativo; no existe un binario universal que corra en todos, como si ocurre en el lenguaje de scripting (tienes el interprete y corres el script en cada sistema operativo, mientras tengas el intérprete)Existe un término intermedio que es lo que usa java y .NET: un bytecode, que es código compilado intermedio, no para máquina que luego se carga en una máquina virtual. Por eso las clases de java pueden correr en cualquier computadora que tenga el intérprete de java, y además están "pseudo compilados"Sobre los paradigmas no hay diferencia ya que existen lenguajes de scripting orientados a objetos, como python, ruby, perl, etc. (si, python es lenguaje de scripting aunque tengo entendido que te permite generar un bytecode)XML no es un lenguaje de programación; es un standar para el traspaso de información entre diferentes actores participantes. Piensalo como un lenguaje universal de contenidos, cosa que diferentes lenguajes, sistemas operativos, etc., puedan interactuar.

leguajes Imperactivos

Lenguajes imperativos
En ciencias de la computación se llama lenguajes imperativos a aquellos en los cuales se le ordena a la computadora cómo realizar una tarea siguiendo una serie de pasos o instrucciones, p. ej.
Paso 1, solicitar número.
Paso 2, multiplicar número por dos.
Paso 3, imprimir resultado de la operación.
Paso 4, etc,

Algunos ejemplos de lenguajes imperativos son: BASIC, C, C++, Java, C# y Perl.
Los lenguajes convencionales basados en el modelo computacional de Von Newman se llaman imperativos, basados en instrucciones o, simplemente, lenguajes Von Newman; Basic, Pascal, Fortran y Lenguaje C fueron los lenguajes imperativos de mayor influencia cuando aparecieron.
Lenguajes imperativos

1. FORTRAN
COBOL
ALGOL 60 --------
-------------------------------------------------------------------------

BASIC ALGOL 68 Pascal PL/I SIMULA BCPL
------------------ ---------------------------------------
-----------------
CLU Modula Smalltalk 72 C
-----------------------------
Ada Smalltalk 80 -----------
Object Pascal ----------------
Eiffel Smalltalk V Objective C C++
------------------------------------------

OOPL/I OOCOBOL
IMPERATIVOS O PROCEDURALES, almacenan los datos en variables sobre los que realizan operaciones
Proceso compilaron: C++
Programación funcional
Los lenguajes funcionales se basan en el concepto de función, por tanto, el objeto básico y fundamental que manejamos son las funciones, que se pueden considerar las principales estructuras de control en este tipo de lenguajes.
La base fundamental para comprender este tipo de lenguajes consiste en ver los programas y aplicaciones expresados a través de funciones y aplicación entre funciones.
Las características fundamentales de estos lenguajes se exponen a continuación:
Utilización de funciones sobre elementos de primer orden.
Utilización de funciones polimórficas, es decir, aquéllas cuyo tipo devuelto depende del tipo de los argumentos.
Utilización de funciones de orden superior, es decir, aquellas funciones que aceptan en su lista de argumentos otras funciones.
Evaluación perezosa (o lazy), es decir, las expresiones no se evalúan hasta que no se necesitan los resultados. Es justamente, lo contrario de la evaluación eager(ansiosa), donde la evaluación de los argumentos se realiza antes de aplicar la función. La evaluación perezosa nos proporciona la ventaja de trabajar con listas potencialmente infinitas.
Existencia de un sistema de tipos fuerte
Uso del Patter Matching: comprobación de patrones.
Vistas las características fundamentales de los lenguajes funcionales, estos pueden clasificarse en dos tipos:
Puros: aquellos en los que sólo podemos aplicar funciones. Estas son las únicas estructuras de control. Posee evaluación perezosa.
Impuros: aquellos que poseen evaluación ansiosa.
2. LISP
---------------------------

LOOPS Plasma INTERLISP Scheme
FUNCIONALES, utilizan funciones para la descripción de los procedimientos.

Programación lógica
La Programación Lógica es un Paradigma de Programación basado en la Lógica.
Los programas construidos un lenguaje lógico están construidos únicamente por expresiones lógicas, es decir, que son ciertas o falsas, en oposición a un expresión interrogativa (una pregunta) o expresiones imperativas (una orden).
Un ejemplo de lenguaje lógico es Prolog (Programación lógica).
Programación orientada a objetos
La programación Orientada a Objetos (POO) es una metodología de diseño de software y un paradigma de programación que define los programas en términos de "clases de objetos", objetos que son entidades que combinan estado (es decir, datos) y comportamiento (esto es, procedimientos o métodos). La programación orientada a objetos expresa un programa como un conjunto de estos objetos, que se comunican entre ellos para realizar tareas. Esto difiere de los lenguajes procedurales tradicionales, en los que los datos y los procedimientos estan separados y sin relación. Estos métodos están pensados para hacer los programas y módulos más fáciles de escribir, mantener y reutilizar.
La programación orientada a objetos (OOP, por las siglas inglesas de Object-Oriented Programming) es una nueva forma de programar que proliferó a partir de los años ochenta y trata de encontrar solución a estos problemas utilizando los siguientes conceptos:
Objetos: entidades complejas provistas de datos (propiedades, atributos) y comportamiento (funcionalidad, programas, métodos).
Corresponden a los objetos reales del mundo que nos rodea.
Clases: conjuntos de objetos que comparten propiedades y comportamiento.
Herencia: las clases no están aisladas, sino que se relacionan entre sí, formando una jerarquía de clasificación. Los objetos heredan las propiedades y el comportamiento de todas las clases a las que pertenecen.
Encapsulamiento: cada objeto está aislado del exterior, es un módulo natural, y la aplicación entera se reduce a un agregado o rompecabezas de objetos. El aislamiento protege a los datos asociados a un objeto contra su modificación por quien no tenga derecho a acceder a ellos, eliminando efectos secundarios e interacciones.
Polimorfismo: programas diferentes, asociados a objetos distintos, pueden compartir el mismo nombre, aunque el significado del programa varíe según el objeto al que se aplica.
La programación orientada a objetos introduce nuevos conceptos, que a veces no son más que nombres nuevos aplicados a conceptos antiguos, ya conocidos. Entre ellos destacan los siguientes:
Método: es un programa asociado a un objeto (o a una clase de objetos), cuya ejecución se desencadena mediante un "mensaje".
Mensaje: una comunicación dirigida a un objeto, que le ordena que ejecute uno de sus métodos con ciertos parámetros.
Propiedad, atributo o variable: datos asociados a un objeto o a una clase de objetos.
Los LENGUAJES ORIENTADOS A OBJETOS, encapsulan los datos junto a los procediéndoos que los procesan.

Lenguaje Orientado a objeto

Se le llama así a cualquier lenguaje de programación que implemente los conceptos definidos por la programación orientada a objetos.
Cabe notar que los conceptos definidos en la
programación orientada a objetos no son una condición sino que son para definir que un lenguaje es orientado a objetos. Existen conceptos que pueden estar ausentes en un lenguaje dado y sin embargo, no invalidar su definición como lenguaje orientado a objetos.
Quizás las condiciones mínimas necesarias las provee el
formalismo que modeliza mejor las propiedades de un sistema orientado a objetos: los tipos de datos abstractos.
Siguiendo esa idea, cualquier lenguaje que permita la definición de
tipos de datos , de operaciones nuevas sobre esos tipos de datos, y de instanciar el tipo de datos podría ser considerado orientado a objetos.
Esta definición concuerda incluso con ciertos ejemplos prácticos, que no son considerados dentro de la
programación orientada a objetos, pero que podrían serlo. Por ejemplo, la programación de interfaces gráficas de usuario para los sistemas X-Window utilizando infraestructuras de funciones y APIs como Motif, Xview y Xlib, son realizadas usualmente en lenguaje C, pero organizando el código en una manera que "parecen objetos" (los Widgets).
Ejemplo de Lenguaje orientado a objeto:

martes, 12 de febrero de 2008

Algoritmo Recursivo

Un algoritmo recursivo es un algoritmo que se define en términos de sí mismo. Son implementados en forma de subrutinas (funciones, procedimientos, subprogramas, etc) de tal forma que dentro de un subrutina recursiva hay una o más llamadas a sí misma.
Algunos ejemplos de recurrencia:
En un texto: Para saber qué es la recurrencia, primero hay que saber qué es la recurrencia.
En un acrónimo: ¿Qué es GNU? -> GNU No es Unix
¿Qué es PHP? -> PHP: Hipertext Preprocessor
En matemáticas: f(x) = x * f(x-1)
En un algoritmo: FUNCIÓN Factorial(n)
INICIO
SI (n<2) ENTONCES
Factorial = 1;
SINO
Factorial = n * Factorial(n-1);
FIN-SI
FIN
Generalmente, si la primera llamada al subprograma se plantea sobre un problema de tamaño u orden N, cada nueva ejecución recurrente del mismo se planteará sobre problemas, de igual naturaleza que el original, pero de un tamaño menor que N. De esta forma, al ir reduciendo progresivamente la complejidad del problema a resolver, llegará un momento en que su resolución sea más o menos trivial (o, al menos, suficientemente manejable como para resolverlo de forma no recursiva). En esa situación diremos que estamos ante un caso base de la recursividad.
Las claves para construir un subprograma recurrente son:
Cada llamada recurrente se debería definir sobre un problema de menor complejidad (algo más fácil de resolver).
Ha de existir al menos un caso base para evitar que la recurrencia sea infinita.
Es frecuente que los algoritmos recurrentes sean más ineficientes en tiempo que los iterativos aunque suelen ser mucho más breves en espacio.

Recursividad indirecta [editar]
Cuando en una subrutina hay llamadas a ella misma se habla de recursividad directa, en contraposición, cuando se tienen varias subrutinas y estas se llaman unas a otras formando ciclos se dice que la recursión es indirecta.Subrutina_A --> Subrutina_B --> Subrutina_A
Subrutina_A --> Subrutina_B --> Subrutina_C --> Subrutina_D --> Subrutina_A

Recursividad

RecursividadEl concepto de recursividad va ligado al de repetición. Son recursivos aquellos algoritmos que, estando encapsulados dentro de una función, son llamados desde ella misma una y otra vez, en contraposición a los algoritmos iterativos, que hacen uso de bucles while, do-while, for, etc.
Algo es recursivo si se define en términos de sí mismo (cuando para definirse hace mención a sí mismo). Para que una definición recursiva sea válida, la referencia a sí misma debe ser relativamente más sencilla que el caso considerado.
Ejemplo: definición de nº natural:
-> el N º 0 es natural-> El Nº n es natural si n-1 lo es.
En un algoritmo recursivo distinguimos como mínimo 2 partes:
a). Caso trivial, base o de fin de recursión:
Es un caso donde el problema puede resolverse sin tener que hacer uso de una nueva llamada a sí mismo. Evita la continuación indefinida de las partes recursivas.
b). Parte puramente recursiva:
Relaciona el resultado del algoritmo con resultados de casos más simples. Se hacen nuevas llamadas a la función, pero están más próximas al caso base.
EJEMPLO
ITERATIVO:
int Factorial( int n )
{
int i, res=1;
for(i=1; i<=n; i++ )
res = res*i;
return(res);
}
RECURSIVO:
int Factorial( int n )
{
if(n==0) return(1);
return(n*Factorial(n-1));
}
TIPOS DE RECURSIÓN
Recursividad simple: Aquella en cuya definición sólo aparece una llamada recursiva. Se puede transformar con facilidad en algoritmos iterativos.
Recursividad múltiple: Se da cuando hay más de una llamada a sí misma dentro del cuerpo de la función, resultando más dificil de hacer de forma iterativa.
int Fib( int n ) /* ej: Fibonacci */
{
if(n<=1) return(1);
return(Fib(n-1) + Fib(n-2));
}
Recursividad anidada: En algunos de los arg. de la llamada recursiva hay una nueva llamada a sí misma.
int Ack( int n, int m ) /* ej: Ackerman */
{
if(n==0 ) return(m+1);
else if(m==0) return(Ack(n-1,1));
return(Ack(n-1, Ack(n,m-1)));
}
Recursividad cruzada o indirecta: Son algoritmos donde una función provoca una llamada a sí misma de forma indirecta, a través de otras funciones.
Ej: Par o Impar:
int par( int nump )
{
if(nump==0) return(1);
return( impar(nump-1));
}
int impar( int numi )
{
if(numi==0) return(0);
return( par(numi-1));
}
LA PILA DE RECURSIÓNLa memoria del ordenador se divide (de manera lógica, no física) en varios segmentos (4):
Segmento de código: Parte de la memoria donde se guardan las instrucciones del programa en cod. Máquina.Segmento de datos: Parte de la memoria destinada a almacenar las variables estáticas.Montículo: Parte de la memoria destinada a las variables dinámicas.Pila del programa: Parte destinada a las variables locales y parámetros de la función que está siendo ejecutada.
Llamada a una función:
Se reserva espacio en la pila para los parámetros de la función y sus variables locales.
Se guarda en la pila la dirección de la línea de código desde donde se ha llamado a la función.
Se almacenan los parámetros de la función y sus valores en la pila.
Al terminar la función, se libera la memoria asignada en la pila y se vuelve a la instruc. Actual. Llamada a una función recursiva:
En el caso recursivo, cada llamada genera un nuevo ejemplar de la función con sus correspondientes objetos locales:
La función se ejecutará normalmente hasta la llamada a sí misma. En ese momento se crean en la pila nuevos parámetros y variables locales.
El nuevo ejemplar de función comieza a ejecutarse.
Se crean más copias hasta llegar a los casos bases, donde se resuelve directamente el valor, y se va saliendo liberando memoria hasta llegar a la primera llamada (última en cerrarse)
EJERCICIOSa). Torres de Hanoi: Problema de solución recursiva, consiste en mover todos los discos (de diferentes tamaños) de una aguja a otra, usando una aguja auxiliar, y sabiendo que un disco no puede estar sobre otro menor que éste.
__
[___]
[_____]
[ ]
-------------------------------------
A B C
/* Solucion:
1- Mover n-1 discos de A a B
2- Mover 1 disco de A a C
3- Mover n-1 discos de B a C
*/
void Hanoi( n, inicial, aux, final )
{
if( n>0 )
{
Hanoi(n-1, inicial, final, aux );
printf("Mover %d de %c a %c", n, inicial, final );
Hanoi(n-1, aux, inicial, final );
}
}
b). Calcular x elevado a n de forma recursiva:
float xelevn( float base, int exp )
{
if(exp == 0 ) return(1);
return( base*xelevn(base,exp-1));
}
c). Multiplicar 2 nºs con sumas sucesivas recurs:
int multi( int a, int b )
{
if(b == 0 ) return(0);
return( a + multi(a, b-1));
}
d). ¿Qué hace este programa?:
void cosa( char *cad, int i)
{
if( cad[i] != '\0' )
{
cosa(cad,i+1);
printf("%c", cad[i] );
}
}

miércoles, 6 de febrero de 2008

Vectores

En programación, un vector, array, arreglo o alineación es un conjunto o agrupación de variables del mismo tipo cuyo acceso se realiza por índices.
Los vectores o arreglos (array en inglés) de dos o más dimensiones se denominan con frecuencia matrices, y pueden tener tantas dimensiones como se desee; Aunque lo correcto es llamarlo arreglo (de memoria) ya que una variable de más de dos dimensiones, no cumple con las características matemáticas de una matriz numérica.
Desde el punto de vista del programa, un arreglo (matriz, array ó vector) es una zona de almacenamiento contiguo, que contiene una serie de elementos del mismo tipo, los elementos de la matriz. Desde el punto de vista lógico podemos considerarlas como un conjunto de elementos ordenados en fila. Así pues, en principio todas las matrices son de una dimensión, la dimensión principal, pero veremos que los elementos de esta fila pueden ser a su vez arreglos (un proceso que puede ser recursivo), lo que nos permite hablar de la existencia de arreglos multidimensionales, aunque las más fáciles de "ver" o imaginar son las de dos y tres dimensiones.
Puede afirmarse que las matrices son un recurso de programación simple y socorrido; en realidad pueden considerarse como las "estructuras" de datos más simples que cabe imaginar (todos los elementos del mismo tipo). Presentan la ventaja de que sus elementos son rápidamente accesibles, en especial si utiliza punteros en vez de subíndices, pero presentan una notable limitación: son de tamaño fijo; es preciso definir su tamaño desde el principio y no pueden ser fácilmente incrementadas o disminuidas sino mediante complejos procesos de copia.
Estas estructuras de datos son adecuadas para situaciones en las que el acceso a los datos se realice de forma aleatoria e impredecible. Por el contrario, si los elementos pueden estar ordenados y se va a utilizar acceso secuencial sería más adecuado utilizar una
lista.
Vectores dinámicos
Dependiendo del tipo de vector y del
lenguaje de programación que se utilice, este vector puede tener una cantidad fija o variable de datos. En el segundo caso, se los denomina vectores dinámicos. Los vectores dinámicos sacan su espacio del denominado heap del programa. Un uso incorrecto de los vectores dinámicos puede conducir a una fuga de memoria, por eso, se aconseja siempre en estos lenguajes liberar la memoria utilizada.
Lenguajes más modernos y de más alto nivel, cuentan con un mecanismo denominado
recolector de basura (como es el caso de Java) que permiten que el programa decida si debe liberar el espacio basándose en si se va a utilizar en el futuro o no un determinado objeto.
En el caso del lenguaje C, la forma de crear vectores estáticos es igual que en c++, pero para vectores dinámicos se utilizan la instrucciones malloc y realloc, acompañadas por free (para liberar la memoria utilizada).
En lenguajes compilados y en la mayoría de máquinas virtuales, la representación de arreglos suele ser en una dimensión, es decir: un conjunto consecutivo de celdas de memoria (como si fueran un gran vector). El compilador (o traductor a byte-code) realizará las conversiones pertinentes para transformar un acceso a una matriz en un acceso a un vector.