programacion de software - os

139
Programaci´on de Software de Sistemas Prof.: Sr. Pedro A. Rodr´ ıguez M. 5 de abril de 2015

Upload: raul-opazo

Post on 21-Dec-2015

17 views

Category:

Documents


2 download

DESCRIPTION

Programacion de Software - OS

TRANSCRIPT

Programacion de Software de Sistemas

Prof.: Sr. Pedro A. Rodrıguez M.

5 de abril de 2015

2

Indice general

1. Introduccion. 11

2. El Proceso de Compilacion 132.1. Estado del actual de la Tecnologıa de Procesadores: RISC vs CISC. . . . . . . . . . . . . . 132.2. Software de Sistemas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142.3. Unix y el Lenguaje C. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142.4. El compilador de C. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

2.4.1. El programa cc. Pasos de compilacion. . . . . . . . . . . . . . . . . . . . . . . . . . 152.4.2. Convenciones en los nombres de archivos. . . . . . . . . . . . . . . . . . . . . . . . 162.4.3. Uso del compilador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172.4.4. Resumen de opciones para el compilador. . . . . . . . . . . . . . . . . . . . . . . . 172.4.5. Algunas opciones y ejemplos utiles del compilador. . . . . . . . . . . . . . . . . . . 182.4.6. Opciones de optimizacion en compilacion. . . . . . . . . . . . . . . . . . . . . . . . 192.4.7. El compilador de C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

2.5. Compilacion de Programas compuestos por varios archivos. . . . . . . . . . . . . . . . . . 202.5.1. Compilacion Separada. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

2.6. Creacion y mantencion de bibliotecas con el comando ar. . . . . . . . . . . . . . . . . . . 212.7. Organizacion de los archivos fuentes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

3. El Compilador de Java. 253.1. Introduccion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

3.1.1. El compilador de Java: javac. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253.1.2. El interprete de Java: java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263.1.3. El depurador de Java: jdb. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273.1.4. El desmontador de Java: javap. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273.1.5. Ejemplos: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

4. Estructura de un programa en lenguaje C. 314.1. Introduccion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314.2. Caracterısticas deseables de un programa. . . . . . . . . . . . . . . . . . . . . . . . . . . . 324.3. Caracterısticas de C. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334.4. Aspectos de tipo Lexicos y Sintacticos del lenguaje. . . . . . . . . . . . . . . . . . . . . . . 34

4.4.1. El Conjunto de Caracteres de C. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344.4.2. Identificadores y Palabras Reservadas. . . . . . . . . . . . . . . . . . . . . . . . . . 344.4.3. Tipos de Datos Basicos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

4.5. Declaracion de variables y su equivalente en lenguaje de maquina. . . . . . . . . . . . . . 364.6. Tipos de Almacenamiento. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

4.6.1. Variables automticas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374.6.2. Variables Externas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384.6.3. Variables Estaticas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384.6.4. Variables Register. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424.6.5. Estructura del ejecutable y el Espacio de Direcciones de un Proceso. . . . . . . . . 424.6.6. Constantes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424.6.7. Definicion de variables globales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444.6.8. Expresiones y Operadores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

3

4.6.9. Incremento y Decremento. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464.6.10. Activar las funciones de optimizacion. . . . . . . . . . . . . . . . . . . . . . . . . . 474.6.11. Control de Flujo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474.6.12. Sentencias de Iteracion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

5. Arreglos. 515.1. Introduccion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515.2. Arreglos uni y multi-dimensionales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515.3. Los Arreglos bidimensionales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535.4. Strings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

6. Punteros 576.1. Antecedentes Basicos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 576.2. Opciones de inicializacion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

6.2.1. Inicializacion Estatica. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586.2.2. Inicializacion a traves de memoria dinamica. . . . . . . . . . . . . . . . . . . . . . 596.2.3. Asignacion de punteros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606.2.4. Mas sobre punteros a caracteres y string. . . . . . . . . . . . . . . . . . . . . . . . 606.2.5. Ejemplos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

6.3. Punteros a void. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656.4. Punteros y const. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 666.5. Puntero nulo (”Null pointer”). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 676.6. Puntero a puntero. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 676.7. Puntero a funcion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

7. Funciones. 717.1. Introduccion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 717.2. Declaracion de una funcion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 727.3. Definicion de una funcion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 727.4. Llamada a una funcion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

7.4.1. Para ejecutar una funcion hay que llamarla. . . . . . . . . . . . . . . . . . . . . . . 737.4.2. Pasando Argumentos a Funciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . 737.4.3. Funciones void. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

7.5. Funciones y Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

8. Procesos en Unix 758.1. Creacion de procesos utilizando la funcion fork() . . . . . . . . . . . . . . . . . . . . . . . 758.2. Terminacion de procesos hijos, utilizando la llamada waitpid. . . . . . . . . . . . . . . . . 76

8.2.1. Descripcion de llamadas al sistema wait y waitpid. . . . . . . . . . . . . . . . . . . 788.3. Comandos de la familia exec. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

8.3.1. La funcion execl. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 798.3.2. La funcion execlp. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 798.3.3. La funcion execv. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 808.3.4. La funcion execvp. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

8.4. Creacion de procesos utilizando la funcion vfork. . . . . . . . . . . . . . . . . . . . . . . . 81

9. Comunicacion entre Procesos. 839.1. Tipos de Comunicaciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

9.1.1. Mensajes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 839.1.2. Comunicacion directa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 839.1.3. Comunicacion indirecta. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

9.2. Ejemplos de Mecanismos de Comunicacion en Procesos Unix. . . . . . . . . . . . . . . . . 849.2.1. Pipe’s. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 849.2.2. Sockets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 869.2.3. Pasos o etapas para establecer una conexion con socket. . . . . . . . . . . . . . . . 929.2.4. Algunas estructuras de datos relacionadas con comunicaciones IPC. . . . . . . . . 939.2.5. La funcion select. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

4

10.Sockets en Java. 9910.1. Introduccion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

10.1.1. Entrada y Salida en Sockets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10110.1.2. Resumen de clases importantes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10210.1.3. Ejemplos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

11.El Sistema de Archivos de Unix. 10911.1. Introduccion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10911.2. Los directorios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

11.2.1. El inode. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11011.2.2. Accesando el i-node de un archivo. Utilizacion de la funcion stat(). . . . . . . . . . 111

5

6

Indice de figuras

2.1. Diagrama de bloques funcional del procesador UltraSparc. . . . . . . . . . . . . . . . . . 142.2. El software de sistema en capas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152.3. Etapas del proceso de compilacion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162.4. Compilacion separada de un programa de varios archivos fuentes. . . . . . . . . . . . . . 21

3.1. Java es Multiplataforma. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253.2. Compilacion y ejecucion de un programa Java. . . . . . . . . . . . . . . . . . . . . . . . . 26

4.1. Estructura del archivo ejecutable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424.2. Espacio de direcciones de un proceso. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

9.1. Implementacion de sockets en Unix.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 869.2. Orden de los bytes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 879.3. Servidor simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 949.4. Servidor concurrente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

10.1. Estableciendo la conexion entre el cliente y el servidor. . . . . . . . . . . . . . . . . . . . 10010.2. El servidor crea un nuevo socket para realizar la comunicacion con el nuevo cliente. . . . . 101

11.1. Estructura del sistema de archivos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10911.2. La estructura di node. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11011.3. La Estructura i-node y los bloques fısicos. . . . . . . . . . . . . . . . . . . . . . . . . . . . 11111.4. Tablas de descriptores de archivos y de i-nodes. . . . . . . . . . . . . . . . . . . . . . . . 116

7

8

Indice de cuadros

4.1. Caracteres soportados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344.2. Palabras reservadas de C. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354.3. Otras palabras reservadas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354.4. Tamano de los tipos de datos basicos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354.5. Tipos basicos derivados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364.6. Variables y Tipos de Almacenamiento. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414.7. Caracteres especiales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444.8. Operadores binarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454.9. Descripcion de los Operadores Binarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464.10. Operadores unarios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

6.1. Inicializaciones validas de un puntero. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586.2. Tomando como ejemplo el tipo char. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 596.3. Reserva y Liberacion de memoria. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 596.4. Asignacion de Punteros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606.5. Asignacion de Punteros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656.6. Sintaxis y punteros a const. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 666.7. Contenidos de localidades de memoria. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 676.8. Inicializacion de un puntero a una funcion. . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

9.1. Protocolo que deben seguir cliente y servidor para una comunicacion TCP. . . . . . . . . 929.2. Protocolo que deben seguir cliente y servidor para una comunicacion UDP. . . . . . . . . 93

11.1. La estructura i-node. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

9

10

Capıtulo 1

Introduccion.

El lenguaje de programacion C fue creado a principios de los anos ’70 como un lenguaje deprogramacion e implementacion de sistemas, especialmente para escribir el Kernel del naciente SistemaOperativo UNIX y para la mayorıa de los comandos y programas que corren en el. Derivado del lenguajeB, su padre directo, y de su .abuelo.el lenguaje no tipeado BCPL, desarrollado en el MIT (MassachusettsInstitute of Technology). Posteriormente, C desarrollo estructuras de tipos. Otros abundantes cambiosaparecieron entre los anos 1977 y 1979, cuando se demostro la portabilidad del sistema Unix. En la mitadde los anos ’80, el lenguaje fue estandarizado oficialmente por el comite de la ANSI X3J11, la cual hizoimportantes cambios.

BPL, B y C difieren sintacticamente en muchos detalles, pero en general son similares. Los programasconsisten en una secuencia de declaraciones globales y declaraciones de funciones (procedimientos). Losprocedimientos pueden estar anidados en BCPL, pero no pueden hacer referencia a objetos no estaticosdefinidos en los procedimientos que estan contenidos. B y C evitan estas restricciones imponiendo unaregla mas severa: no existen procedimientos anidados. Cada uno de los lenguajes (excepto las primerasversiones de B) reconocen la compilacion por separado.

En C no se lleva a cabo chequeo de parametros en las funciones y tampoco el de tipos, pero puedeser realizado explıcitamente por el usuario mediante el programa lint.

C trabaja con tipos de datos que son directamente tratables por el hardware de la mayorıa decomputadoras actuales, como son los caracteres, numeros y direcciones. Estos tipos de datos puedenser manipulados por las operaciones aritmeticas que proporcionan las computadoras. No proporcionamecanismos para tratar tipos de datos que no sean los basicos, debiendo ser el programador el que losdesarrolle. Esto permite que el codigo generado sea muy eficiente y de ahı el exito que ha tenido comolenguaje de desarrollo de sistemas. No proporciona otros mecanismos de almacenamiento de datos que nosea el estatico y no proporciona mecanismos de entrada y salida. Esto permite que el lenguaje sea sencillo ylos compiladores de facil implementacion en otros sistemas. Por el contrario, estas carencias se compensanmediante la inclusion de funciones de librerıa para realizar todas estas tareas, que normalmente dependendel sistema operativo.

Durante los anos 1973-1980, se anadieron los tipos unsigned, long, union, y tipos enumerados, y lasestructuras se volvieron casi la primera clase de objetos. El lenguaje provee otros diferentes tipos dedatos, tales como enteros (int), caracter (char), flotantes (float) doble precision (double) y todos losderivados, con el uso de arreglos o estructuras. Otros tipos derivados son los enumerados, punteros,uniones y estructuras.

C provee primitivas estandares para el control de flujo, tales como if y switch para seleccion, y whiley for para iteracion, lo que hace de C un lenguaje estructurado y de proposito general. Las funcionespueden ser definidas de tal forma que retornen un valor. C tambien permite la recursividad. Una funcionque no retoma un valor puede ser declarada como void.

Existe, para C, un conjunto de herramientas que permiten mantener (make) y depurar (gdb, xxgdb)programas. Las funciones que proporciona el sistema (systems calls) estan escritas en C, como tambienla casi totalidad del software que corre bajo los Sistemas Operativos UNIX y Linux.

Otra caracterıstica importante del lenguaje es su portabilidad, lo que permite que los programaspueden ser compilados sin grandes problemas en otras maquinas.

11

12

Capıtulo 2

El Proceso de Compilacion

2.1. Estado del actual de la Tecnologıa de Procesadores: RISCvs CISC.

En los ultimos 40 anos se han distinguido dos grandes tendencias en el diseno y construccion deprocesadores. Se diferencian esencialmente en las caracterısticas de su repertorio de instrucciones.

Los procesadores CISC (Complex Instruction Set Computer) tienen un conjunto de ins-trucciones grande (entre 200 y 300); este juego de instrucciones es mas complejo que el que posee lafamilia de procesadores RISC, con lo que la circuiterıa necesaria para la decodificacion y secuencia-cion tambien aumenta, y la velocidad del proceso disminuye. Por lado, el formato de instruccionesde este tipo de procesadores es variable. Podemos encontrar instrucciones de maquina de diferentetamano. Como tambien su juego de registros es pequeno, entonces hace que el procesador tengaque realizar constantes accesos a memoria. La microprogramacion es una caracterıstica importan-te y esencial de casi todas las arquitecturas CISC. Ejemplos de procesadores CISC: Intel, AMD,Motorola.

Los procesadores RISC (Reduced Instruction Set Computer) tienen caracterısticas opues-tas a los CISC. Su juego de instrucciones es mas reducido (menos de 128), y las instrucciones sonmas sencillas (con lo que se necesitaran ms instrucciones para ejecutar un programa). El formatode instrucciones es fijo (o son pocos formatos, donde las todas las instrucciones tienen el mismotamano) con lo que el control del hardware es mas sencillo y se facilita la puesta de instruccionesen memoria. Por otro lado, los accesos a la memoria son menos frecuentes ya que el procesadorposee un mayor numero de registros. Ejemplos de procesadores RISC: Alpha, Sparc, UltraSparc,Power PC, etc.Debido a que se dispone de un conjunto reducido de instrucciones, estas se pueden implementarpor hardware directamente en la CPU, lo cual elimina el microcodigo y la necesidad de decodificarinstrucciones complejas.

Sin embargo, hoy en dıa se ha producido una hibridacion de estos procesadores, con lo cual losprocesadores CISC modernos tienen algunas caracterısticas de los procesadores RISC, y viceversa.

A continuacion, algunas elementos importantes de cualquier arquitectura y sus caracterısticas:

1. Arquitectura simple?.

2. Que es la arquitectura de un procesador?. La arquitectura define el comportamiento funcionaldel computador, tal como lo ve el programador a traves del lenguaje de maquina (en este caso ellenguaje ensamblador). Establece las distintas formas de representacion interna de la informaciony el set de instrucciones de la maquina. Por tanto, la arquitectura define el que hace el compu-tador, pero mas especıficamente, el procesador. Esto se denomina tambien, arquitectura logica delcomputador.

3. Set de instrucciones simple/compleja.

13

4. Formato de instrucciones homogenea, al menos en su tamano.

5. Amplio set de registros (ventanas de registros): %r0→ %r31 en Sparc; $0→ $31 en MIPS.

6. Cantidad de modos de direccionamiento restringidas.

7. Unidades de Almacenamiento, en general: bit, byte, palabra.

8. Llamadas a procedimientos eficiente. Algunas arquitecturas como Intel, realizan el pasaje de parame-tros a procedimientos a traves del stack. En las arquitecturas RISC, como en el caso de Sparc, losparametros son puestos en algunos registros del procesador ( %o0→ %o7).

9. Accesos a la memoria (Arquitectura de tipo Load/Store).

Las instrucciones LOAD/STORE son las unicas que pueden acceder a los datos en memo-ria. Estas instrucciones pueden acceder a datos de diferente tamano: 1 byte; media palabra(halfword, 2 bytes); una palabra (word, 4 bytes); y una doble palabra (doubleword, 8 bytes).

10. Arquitectura fısica del procesador. (ver figura 2.1).

Figura 2.1: Diagrama de bloques funcional del procesador UltraSparc.

2.2. Software de Sistemas.

En general, el software del sistema tiene como objetivo ocultar la complejidad del hardware y deservir de interfaz, con el proposito de simplificar el trabajo del usuario.

2.3. Unix y el Lenguaje C.

C fue disenado pensando en un lenguaje de bajo nivel. Por que?. Este lenguaje es utilizado porejemplo, para el desarrollo de software que implica generalmente llevar a cabo un control directodel hardware., tal como el Kernel de un sistema operativo y los compiladores. Los lenguajes de altonivel evitan especificaciones y definiciones que implican la manipulacion de registros del procesadory la complejidad de los punteros (Fortran, Cobol, Java, PowerBuilder, etc.).

Otra caracterıstica importantes esta en que los compiladores de C (cc, gcc) generan codigo eficiente.

• cc : + eficiente en compilacion, - eficiente en ejecucion.

14

Figura 2.2: El software de sistema en capas.

• gcc: - eficiente en compilacion, + eficiente en ejecucion.

La optimizacion depende tambien de la arquitectura del procesador. Por ejemplo, el codigo eje-cutable que se genera bajo un arquitectura RISC es aproximadamente el 30 % del codigo que segenera bajo una arquitectura como Intel.

2.4. El compilador de C.

Esta parte es suficiente para conocer el uso basico del compilador de lenguaje C, a traves del programacc. Para comprender este texto no es necesario estar familiarizados con el C, aunque se precisan unasmınimas nociones de este lenguaje. Ademas, deberıan conocer conceptos basicos relacionados con eldesarrollo de programas, como programa fuente, objeto, enlace (linking), etc.

2.4.1. El programa cc. Pasos de compilacion.

Para compilar programas escritos en C, se dispone de un compilador de nombre cc. Este compiladortoma como parametros los archivos fuentes que componen el programa final y, tras una serie de pasos,produce un archivo ejecutable. Si durante la compilacion se produce un error, no se genera el ejecutable.Los pasos de compilacion en UNIX son al menos estos tres:

Preproceso (macros, inclusion de archivos...).

Compilacion a objeto.

Enlace (linking) de objetos y bibliotecas.

El preproceso interpreta las macros creadas con #define y expande los archivos para incluir con#include.El preprocesador produce la entrada para un compilador, y pueden realizar las siguientes funciones:

1. Procesamiento de macros. Un preprocesador puede permitir a un usuario definir macros, que sonabreviaturas de construcciones mas grandes. Por ejemplo la declaracion:

#define TRUE 1#define FALSE !TRUE

15

Figura 2.3: Etapas del proceso de compilacion.

2. #defineMAY OR(X,Y )X > Y

3. Inclusion de archivos: para insertar archivos cabeceras (.h o .hpp) en el texto del programa. Porejemplo que el contenido del archivo string.h reemplace a la declaracion #include < string.h >.

Un archivo una vez preprocesado se compila a codigo maquina, pero no se genera un ejecutable, sinoun archivo objeto. Este paso intermedio es necesario por muchos motivos, entre ellos que las rutinasde biblioteca, como printf, tienen que ser .empotradas”posteriormente para generar un ejecutable, y engeneral debido a que un programa en C puede constar de varios archivos compilados por separado.

Por eso existe un ultimo paso, denominado enlace (linking en ingles), en el cual se recogen todos losarchivos objetos mas las bibliotecas (que tambien residen en archivos) necesarios para producir el archivoejecutable.

2.4.2. Convenciones en los nombres de archivos.

Existen varias clases de archivos: fuentes, objetos, bibliotecas, ejecutables, etc. El compilador de Ces capaz de distinguir el tipo de un archivo en base a sus ultimos caracteres. La siguiente lista muestra

16

las convenciones mas habituales.

.c : archivo fuente en C.

.h : archivo cabecera o header fuente en C (solo util para los #include).

.s : archivo fuente en ensamblador (tambien reconocido).

.o : archivo objeto.

.a : archivo de biblioteca.

El compilador de C solo genera un ejecutable, de nombre a.out, aunque se le puede indicar que tengaotro nombre con la opcion -o nombre de ejecutable.

2.4.3. Uso del compilador.

El programa cc (o el gcc) se invoca desde el shell, admitiendo como argumentos los archivos emplea-dos para construir el ejecutable mas una serie de opciones de compilacion. Las opciones y modalidadesde uso del cc son amplias, por lo que aquı nos limitaremos a exponer las mas comunes y utiles.

Por omision, el cc genera un ejecutable llamado a.out. En la lınea de comandos pueden incluir tantoarchivos fuentes en C como archivos objeto, incluso fuentes en ensamblador o traductor (de hecho el pasode compilacion a objeto suele atravesar una fase intermedia en el cual se genera un archivo en lenguajeensamblador y se invoca al ensamblador del sistema). Los nombres de los archivos pueden aparecer encualquier orden.

En la siguiente seccion se muestra un resumen de opciones; pero antes veremos algunos ejemplos.

Ejemplo 1:

$cc prog.c

Si prog.c es un archivo fuente en C, se compila y se enlaza con las bibliotecas del sistema. Si no habıaerrores sintacticos ni referencias a funciones o variables inexistentes, se genera el ejecutable a.out.

Ejemplo 2:

$gcc− o prog main.c funciones.c prog.o -lm

este es un ejemplo mas complejo donde se anticipa el uso de un par de opciones de compilacion. Laopcion -o prog sirve para generar el ejecutable prog (que es un archivo ejecutable), en lugar de a.out.Para construir el ejecutable se hace uso explıcito de cuatro elementos:

Un fuente main.c (quizas con el programa principal)

Otro fuente, funciones.c (quizas con funciones adicionales)

Un objeto prog.o (tal vez con otras funciones ya compiladas)

La biblioteca matematica estandar (con la opcion -lm).

Con este ejemplo hacemos ver que un programa en C puede estar compuesto de varios modulos enforma de archivos fuentes. Y que si un modulo ya esta compilado, podemos pasar como argumento alcompilador el correspondiente .o para ahorrar tiempo.

2.4.4. Resumen de opciones para el compilador.

Como ya se dijo anteriormente, existe una infinidad de opciones para controlar la ejecucion del cc.Algunas de las opciones mas necesarias o comprensibles son las siguientes:

-Aa compila en C ANSI.

17

-c solo compila, no hace el montaje.

-Idirectorio define directorios para buscar los #includes.

-llib monta la biblioteca lib

-Ldirectorio define directorios para buscar las bibliotecas.

-o archivo establece el nombre del ejecutable .

-O optimiza el codigo.

-On optimiza el codigo hasta el nivel n.

-S genera un fuente .s en ensamblador; no compila.

-v modo verboso: imprime todos los pasos.

2.4.5. Algunas opciones y ejemplos utiles del compilador.

Descrito el modelo basico de compilacion, se daran algunas opciones utiles y algunas veces esenciales.

-E: El compilador se detiene en la etapa de preprocesamiento y el resultado se muestra en la salidaestandar.

Ejemplo: $gcc -E prog.c

-c: Suprime el proceso de linkeado y produce un archivo con extension .o para cada archivo fuentelistado. Despues los archivos objeto pueden ser linkeados con el comando gcc. Por ejemplo:

$gcc arch1.o arch2.o ... -o ejecutable

-lbiblioteca: Enlaza con las bibliotecas objeto. Probablemente la biblioteca mas comunmente usa-da es la biblioteca matematica (math.h). Esta biblioteca debera ligarse explıcitamente si se deseausar las funciones matematicas (y por supuesto no olvidar el archivo header #include ¡math.h¿,en el programa que llama a las funciones), por ejemplo:

$gcc -o prog prog.c -lm

Muchas otras bibliotecas son ligadas de esta forma, indicando el directorio. Cuando se trabaja consockets en Solaris:

$\$ gcc [ flag ... ] file ... -lsocket -lnsl [library ...]$

#include <sys/types.h>

#include <sys/socket.h>

-Ldirectorio: Agrega directorios a la lista de directorios que contienen las rutinas de la bibliotecade objetos. El linker siempre busca las bibliotecas estandares y del sistema en -lib y /usr/lib. Si sequieren linkear bibliotecas personales o instaladas por usted, se tendra que especificar donde estanguardados los archivos, por ejemplo:

$gcc prog.c -L/home/prodrigu/libraries liba.a

18

-Itrayectoria: Agrega un path o ruta a la lista de directorios en los cuales se realizara la busquedade los archivos cabecera o headers (que comienzan con #include) con nombres relativos (es decir,los que no empiezan con diagonal /).El preprocesador por defecto, primero busca los archivos #include en el directorio que contiene elarchivo fuente, y despues en los directorios nombrados con la opcion -I si los hubiera, y finalmen-te, en /usr/include (que corresponde a los headers del sistema). Por lo tanto, si se quiere incluirarchivos headers almacenados en /home/prodrigu/headers, se debe hacer lo siguiente:

$gcc prog.c -I/home/prodrigu/headers

-g: Permite llamar a las opciones de depuracion (debug). Indica al compilador que inserte infor-macion adicional en la tabla de sımbolos (tabla generada durante la compilacion) que es usadopor una variedad de utilitarios de depuracion. Por ejemplo, si se emplea el depurador de GNU, elprograma debera compilarse de la siguiente forma para generar extensiones de GDB:

$gcc -ggdb -o prog prog.c

-D: Define sımbolos como identificadores (-Didentificador) o como valores (-Dsımbolo=valor) enuna forma similar a la directiva del preprocesador #define).

-v: Muestra en la salida estandar de errores los comandos ejecutados en las etapas de compilacion.Las opciones del compilador de C pueden variar segun la version de UNIX que se utilice. Elcompilador de C esta preparado para compilar en modo ANSI, con lo que no hace falta que seescriba la opcion -Aa. Algunas bibliotecas utiles, para la opcion -llib, son:

• c: biblioteca estandar del C. No hace falta incluirla explıcitamente.

• m: biblioteca estandar matematica.

• termcap: contiene rutinas de manejo de terminales.

• curses: contiene rutinas de visualizacion con ventanas, etc.

• l: biblioteca para uso del lex y el yacc

2.4.6. Opciones de optimizacion en compilacion.

Estas opciones controlan varios ordenamientos de optimizacion:

-O: Optimiza el codigo.

-O1: Optimizar. La optimizacion de la compilacion toma algo mas de tiempo, y mucha mas me-moria para grandes funciones. Con la opcion -O, el objetivo del compilador es reducir el costo decompilacion y hacer debugging para producir los resultados esperados. Los comandos son indepen-dientes: si se detiene el programa con un quiebre entre los comandos, se puede entonces asignar unnuevo valor a cualquier variable o cambiar el contador de programa a cualquier otra instrucciondentro de la funcion y conseguir exactamente los resultados esperados desde el codigo fuente.Sin la opcion -O, solo las variables declaradas como register son asignadas en registros.Con la opcion -O, el compilador tata de reducir el tamano del codigo y el tiempo de ejecucion.Cuando se especifica -O, las dos opciones fthread-jumps 2fdefer-pop”son habilitadas. En maquinasque tienen ciertos rangos de retardos, la opcion fdelayed- branch”tambien es habilitada. Para aque-llas maquinas que pueden soportar debugging incluso sin un puntero frame, se habilitada la opcionfomit-frame-pointer”. En algunas otras maquinas algunos flags tambien pueden ser habilitados.

-O2: Optimiza, incluso mas. Son ejecutadas casi todas aquellas optimizaciones soportadas por -O1que no involucren space-spedd tradeoff (espacio-velocidad). Por ejemplo, loop unrolling e inlinig defunciones no son hechas. Comparado a -O, esta opcion incrementa tanto el tiempo de compilacioncomo el rendimiento del codigo generado.

19

-O3: Optimiza aun mas. Hace todo lo que se logra con -O2, agregando -finline-functions.

-O0: No optimiza. Es equivalente a no especificar la opcion -O.

2.4.7. El compilador de C++.

Es muy probable que en la maquina en la que se trabaje exista un compilador de C++.El compilador de la GNU se llama gcc, aunque se puede invocar como gcc o c++. Es capaz de

reconocer tanto fuentes en C como en C++. El gcc reconoce como fuentes en C++ a los archivos conextension .cpp, .hpp o .C (letra C”mayuscula).

Las opciones admitidas por el gcc son mas o menos las mismas que acepta el compilador convencionalde UNIX; aunque siempre es recomendable consultar el manual en lınea.

2.5. Compilacion de Programas compuestos por varios archivos.

En general, el proceso de compilacion se vuelve algo mas complicado cuando un programa esta com-puesto por varios archivos. Por ejemplo, supongamos un programa compuesto por tres archivos: prog.c,que contiene la funcion main() y los archivos func1.c y func2.c. La forma mas simple de compilar elprograma es la siguiente:

$gcc -o prog programa.c func1.c func2.c

En este ejemplo, el compilador gcc se encarga de llamar al preprocesador, de compilar cada uno delos archivos fuente y de llamar al enlazador (linker) para que enlace los archivos objeto resultantes delproceso de compilacion, generando el ejecutable prog.

2.5.1. Compilacion Separada.

Podemos tambien compilar cada archivo .c por separado utilizando la opcion -c. Esta opcion haceque se genere un archivo objeto con el mismo nombre que el archivo fuente, pero con la extension .o.

Una vez que hemos compilado todos los archivos fuente y generado los correspondientes archivosobjeto, podemos enlazarlos llamando al linker para generar el programa ejecutable.

En el ejemplo anterior, este proceso se puede llevar a cabo mediante las siguientes lıneas de comandos:

$gcc -c prog.c

$gcc -c func1.c

$gcc -c func2.c

$gcc -o prog prog.o func1.o func2.o

En la practica, al recibir como argumentos archivos objeto y bibliotecas, el programa gcc llama allinker para que genere el programa ejecutable. El linker de GNU es el programa ld. Si se desea, se puedellamar al linker directamente.

En la siguiente figura se puede ver el esquema de compilacion tıpico de un programa C. Se refleja elproceso de compilacion de un programa compuesto por los siguientes archivos:

prog.c: Contiene la funcion main() y, posiblemente, otras funciones del programa.

func 1.c .. func n.c: Contienen funciones requeridas en el programa.

liba 1.c .. liba n.c y libb 1.c .. libb n.c: Contienen funciones relacionadas y generalizadas para suuso por cualquier programa. Formaran las bibliotecas liba.a y libb.a.

20

Figura 2.4: Compilacion separada de un programa de varios archivos fuentes.

2.6. Creacion y mantencion de bibliotecas con el comando ar.

El utilitario de archivos ar permite, entre otras funciones, mantener las bibliotecas que utilizan losdiferentes compiladores y ademas el montador. Con ar podemos crear un archivo que contenga bibliotecasde funciones, tal como se muestra a continuacion:

Primero, compilar con la opcion -c para crear los archivos objetos:

$gcc -c liba1.c liba2.c liba3.c

Luego, crear la biblioteca con el utilitario ar.

$ar -rcv liba.a liba1.o liba2.o liba3.o < enter >a - liba1.oa - liba2.oa - liba3.o$

donde liba.a es el archivo que contiene nuestras bibliotecas. Las opciones:r: inserta un archivo (con reemplazo) en la biblioteca.c: para crear.v: para verbosidad.

Si queremos ver los archivos que estan contenidos en una biblioteca, entonces utlizamos la opcion -t:$ar -t liba.aliba1.oliba2.oliba3.o

\# Para Sparc/Solaris

MAKELIB= ar -rcv

21

OBJECT=liba1.o liba2.o liba3.o liba4.o liba5.o liba6.o liba7.o

LIBNSYS= liba.a

CINCLUDE= -ggdb -I./include

LDEBUG= -ggdb

all: $(LIBNSYS)

.SUFFIXES:

.SUFFIXES: .o .c .s

.c.o .s.o:

gcc -c $(CINCLUDE) $<

$(LIBNSYS): $(OBJECT) $(MAKELIB)

rm -rf $(LIBNSYS)

sh $(MAKELIB) $@ $(OBJECT)

clean:

rm -f *.o *~

Ahora, utilizamos la biblioteca y compilamos nuestro programa con el comando make.

LFLAGS= -ggdb

LIBNSYS= -L/home/prodrigu/libraries/liba.a

all: ejec

.SUFFIXES:

.SUFFIXES: .o .c .s

.c.o .s.o:

gcc -c $(CFLAGS) $<

ejec: prog1.o prog2.o prog3.o main.o $(LIBNSYS)

gcc $(LFLAGS) prog1.o prog2.o prog3.o main.o -o $@ $(LIBNSYS)

clean:

rm -f *.o *~

2.7. Organizacion de los archivos fuentes.

Los archivos fuentes que componen el programa deben estar organizados en un cierto orden. Normal-mente sera el siguiente:

Una primera parte formada por una serie de constantes simbolicas en lineas #define, una serie delıneas #include para incluir archivos de cabecera y definiciones (typedef) de los tipos de datos quese van a tratar.

La declaracion de variables externas y su inicializacion, si es el caso.

Una serie de funciones.

El orden de los elementos es importante, ya que en el lenguaje C, cualquier objeto debe estar definidoantes de que sea usado. Las funciones deben definirse o declararse antes de que se realice cualquierllamada a ellas. Se nos presentan dos posibilidades:

22

1. Que la definicion de la funcion se encuentre en el mismo archivo en el que se realiza la llamada: Eneste caso:

Bien se situa la definicion de la funcion antes de la llamada,

Tambien se puede incluir una linea de declaracion al principio del archivo con lo que ladefinicion se puede situar en cualquier punto del archivo, incluso despues de las llamadas aesta.

2. Que la definicion de la funcion se encuentre en otro archivo diferente al que contiene la llamada:en este caso es preciso incluir al principio del mismo una lınea de declaracion de la funcion en elarchivo que contiene las llamadas a esta. Normalmente se realiza incluyendo (mediante la directivade preprocesamiento #include) el archivo de cabecera asociado al modulo que contiene la definicionde la funcion.

23

24

Capıtulo 3

El Compilador de Java.

3.1. Introduccion.

Java en realidad, es un lenguaje interpretado, pero previo a la ejecucion de un programa escritoen Java, existe una etapa de compilacion cuyo proposito es traducir los fuentes de Java a un lenguajeintermedio denominado bytecode.

Figura 3.1: Java es Multiplataforma.

Los fuentes de Java son compilados a bytecode, los cuales son ejecutados por por el runtime o maquinavirtual de Java.

3.1.1. El compilador de Java: javac.

Para compilar un programa, usar:

$javac [-g][-O][-debug][-depend][-nowarn][-verbose][-classpath path][-nowrite]

[-deprecation][-d dir][-J<runtime flag>] file.java...

donde, la opcion:

-g:Hace que se genere informacion de depuracion.

-g: nodebug: Suprime la generacion de informacion de numeros de lınea y depuracion de variableslocales.

-O: para optimizar el bytecode.

-debug: verbosidad y se detiene donde encuentra el primer error, e indica las lıneas del programadonde hay errores.

25

Figura 3.2: Compilacion y ejecucion de un programa Java.

-depend: Hace que se recompilen los archivos fuente de los que dependen los archivos que se estancompilando.

-nowarn: deshabilita los mesanjes tipo warning.

-verbose: para verbosidad.

-classpath path: Especifica una classpath alternativa.

-nowrite:No genera los .class.

-deprecation: Especifica aquellos metodos que estan obsoletos.

-d dir: Especifica el directorio de destino de los archivos compilados .class.

-J< runtimeflag >: Pasa option al interprete que ejecuta el compilador.

3.1.2. El interprete de Java: java.

Se utiliza para ejecutar archivos en bytecode (.class).

$java [options] className [aguments]

-elp: imprime mensajes de ayuda.

-ersion: imprime la version de este interprete .

-v -verbose: modo verboso.

-debug: habilita debuggin Java remoto.

-noasyncgc:no permite garbage collection asıncrono.

-verbosegc: imprime mensajes cuando hay garbage collection.

-noclassgc: deshabilita garbage collection

26

-ss< number >: fija el tamano maximo del stack nativo para cualquier thread.

-oss< number >: fija el tamano maximo del stack de Java para cualquier thread.

-ms< number >: fija el tamano inicial del heap de Java.

-mx< number >: fija el tamano maximo del heap de Java.

-classpath <directories separated by colons> : classpath alternativo.

3.1.3. El depurador de Java: jdb.

$jdb [options] [className]

-help: imprime mensaje s de ayuda.

-version: imprime la version del jdb.

-host ¡hostname¿: Especifica el nombre de la maquina o host con la que se va a utilizar el depurador.

-password ¡psswd¿: especifica password que se va a utilizar para unir el depurador a un interpreteque se esta ejecutando en un sistema remoto.

-v -verbose: modo verboso.

-debug: habilita debugging Java remoto.

-noasyncgc: no permite garbage collection asıncrono.

-verbosegc: imprime mensajes cuando hay garbage collection.

-noclassgc: deshabilita garbage collection

-ss< number >: fija el tamano maximo del stack nativo para cualquier thread.

-oss< number >: fija el tamano maximo del stack de Java para cualquier thread.

-ms< number >: fija el tamano inicial del heap de Java.

-mx< number >: fija el tamano maximo del heap de Java.

-classpath <directories separated by colons>: classpath alternativo.

3.1.4. El desmontador de Java: javap.

Se utiliza para recuperar el codigo fuente Java desde los archivos bytecode.

$javap [options] className

-c : Desassembla el codigo (muestra el bytecode).

-classpath ¡directories separated by colons¿: Lista de directorios desde la cual buscar clases.

-l: Imprime numero de lıneas y variables locales.

-public: muestra solo clases y miembros publicos.

-protected: muestra solo clases y miembros definidos como protected.

-package: Muestra miembros y clases de paquetes/protected/public.

-private: Muestra solo clases y miembros privados.

Con la opcion -c, se puede ver la class en bytecode, como un lenguaje assembler. A continuacion semuestra un ejemplo: una clase que se llama Main.java, que posee un metodo, el metodo main() y unavariable de instancia Interfaz de tipo MyFrameInterfaz, y donde Main es publica. Despues de la definicionde la clase en java, se aprecia depues el bytecode de la clase.

27

3.1.5. Ejemplos:

(1) Compiled from Main.java

public synchronized class Main extends java.lang.Object

/* ACC_SUPER bit set */

{

static MyFrameInterfaz Interfaz;

public static void main(java.lang.String[]);

public Main();

}

Method void main(java.lang.String[])

0 new #2 <Class MyFrameInterfaz>

3 dup

4 invokespecial #4 <Method MyFrameInterfaz()>

7 putstatic #6 <Field MyFrameInterfaz Interfaz>

10 return

Method Main()

0 aload_0

1 invokespecial #5 <Method java.lang.Object()>

4 return

(2) Compiled from ListaFifo.java

synchronized class ListaFifo extends Lista

/* ACC_SUPER bit set */

{

ListaFifo();

public void insertar_elemento(Segmento);

}

Method ListaFifo()

0 aload_0

1 invokespecial #5 <Method Lista()>

4 return

Method void insertar_elemento(Segmento)

0 aload_0

1 getfield #8 <Field ElementoGeometrico first>

4 ifnonnull 33

7 aload_0

8 aload_1

9 putfield #8 <Field ElementoGeometrico first>

12 aload_0

13 aload_0

14 getfield #8 <Field ElementoGeometrico first>

17 putfield #9 <Field ElementoGeometrico last>

20 aload_1

21 aload_1

22 aconst_null

23 dup_x1

24 putfield #11 <Field Segmento next>

27 putfield #6 <Field Segmento ant>

30 goto 62

33 aload_0

34 getfield #9 <Field ElementoGeometrico last>

37 aload_1

28

38 putfield #10 <Field ElementoGeometrico next>

41 aload_1

42 aload_0

43 getfield #9 <Field ElementoGeometrico last>

46 checkcast #4 <Class Segmento>

49 putfield #6 <Field Segmento ant>

52 aload_1

53 aconst_null

54 putfield #11 <Field Segmento next>

57 aload_0

58 aload_1

59 putfield #9 <Field ElementoGeometrico last>

62 aload_0

63 dup

64 getfield #7 <Field int cantidad_elementos>

67 iconst_1

68 iadd

69 putfield #7 <Field int cantidad_elementos>

72 return

29

30

Capıtulo 4

Estructura de un programa enlenguaje C.

4.1. Introduccion.

Generalmente, un programa en lenguaje C tiene la siguiente forma o estructura:

Comandos para el preprocesador.

Definiciones de tipo de datos.

Prototipos de funciones (define el nombre de la funcion, el tipo que retorna y los parametros querecibe, aunque pueden aparecer solo los tipos de estos).

Variables globales.

Funciones.

Todo programa en este lenguaje consta de uno o mas modulos que pueden ser funciones o quecada modulo corresponde a un conjunto de funciones, con uno o mas programas fuentes escritos enarchivos separados. Este esquema de definir modulos separados en diferentes archivos fuentes facilitala compilacion de programas mas complejos. Todo programa cuenta con una funcion principal llamadamain, que es donde comienza la ejecucion del programa. El kernel del sistema operativo cuenta con unarutina especial que es la que llama a main cada vez que se ejecuta el programa. El resto de las funcionesdel programa pueden ser accedidos a partir de main, y pueden ubicarse antes o despues de main.

Cada funcion debe contener:

1. Un encabezado de la funcion, que esta compuesta por el nombre de esta, seguido de una listaopcional de argumentos encerrados entre parentesis.

2. Una lista de declaracion de argumentos, si se incluyen estos en el encabezado.

3. Una o mas instrucciones, que conforman el cuerpo de la funcion.

4. Si la funcion retorna un valor de algun tipo dado, entonces la funcion debe terminar retornandoel valor usando la palabra reservada return seguida de un valor o de una variable que contiene elvalor.

function_name (parametros) {

variables locales;

...

instrucciones C;

}

Si el tipo de la funcion se omite, C asume que la funcion retorna un tipo entero. Cuidado, que estopuede ser una fuente de problemas en un programa. Por ejemplo:

31

main() {

printf( "Me gusta C \n" );

exit ( 0 );

}

Algunas observaciones:

C exige punto y coma al final de cada instruccion.

printf es una funcion C estandar llamada desde la funcion main.

\n significa nueva lınea.

La funcion exit() es una llamada al sistema que hace que el programa termine su ejecucion y elproceso que ejecuta ese programa pase a estado zombie. Estrictamente hablando, no es necesariocolocarlo, ya que si esta funcion no se coloca, el programa igual terminara.

Las instrucciones compuestas corresponden a un bloque del programa conformada por dos o masinstrucciones y que se encuentran delimitadas por parentesis rizados o de llaves, { }. Las instruccionescompuestas pueden estar anidadas, es decir, una instruccion compuesta puede contener otras instruc-ciones compuestas tambien delimitadas por parentesis de llaves. Cada instruccion debe terminar con;(punto y coma).

Los comentarios van delimitados por /* y */ y pueden ir en cualquier parte del programa. Los comen-tarios son ignorados por el compilador y por tanto no requieren de analisis y traduccion. Por ejemplo,/* Aquı van los comentarios */. Los comentarios son utiles para mejorar la autodocumentacion de unprograma y explicar lo que este hace.

Los argumentos son elementos que son entregados a las funciones a traves de algun mecanismo depasaje de parametros (por valor, por referencia, por nombre, etc.) y que contienen informacion util paraellas.

Es util utilizar sangrıa o indentacion para lograr mayor orden y legibilidad en el programa, de estaforma es posible reconocer con facilidad los elementos e instrucciones que estan anidados dentro de otrasinstrucciones y reconocer y entender con mas facilidad la logica del programa. Por ejemplo.

int fact(int n) { /*funcion recursiva*/

if (n < 1)

return 1;

else

return

(n*fact(n-1));

}

es mas claro escribir la funcion de esta forma:

int fact(int n) { /*funcion recursiva*/

if (n < 1) return 1;

else

return (n*fact(n-1));

}

donde se utilizaron tabs y espacios en blancos para construir la sangrıa. Es cierto que este caso es trivialporque contiene pocas lıneas de codigo, pero cuando un programa cuenta con cientos o miles de ellas, ladiferencia sı se nota.

4.2. Caracterısticas deseables de un programa.

1. Integridad. Se refiere a la correctitud en los calculos. Esta claro que toda posible extension delprograma no tendra sentido si los calculos no se realizan de forma correcta, pues la integridad deestos es absolutamente necesaria en cualquier programa computacional.

32

2. Claridad Se refiere a la facilidad de lectura del programa. Si un programa esta escrito de formaclara, sera posible para otras personas entender la logica de este. Incluso, es util para el mismoprogramador, ya que con el tiempo la logica del programa puede ser olvidada.

3. Sencillez. La claridad y la correctitud de un programa suelen verse favorecidas si se hacen las cosasde forma tan sencilla como sea posible.

4. Eficiencia. Esta relacionada con la velocidad de ejecucion y la utilizacion eficiente de los recursosdel computador, como por ejemplo la memoria y el procesador.

5. Modularidad. Un programa puede ser subdividido en varias modulos diferentes. Cada modulo puedeser implementado como una funcion independiente de las otras. La modularidad favorece a laclaridad de un programa.

4.3. Caracterısticas de C.

En forma breve, se listan algunas caracterısticas del lenguaje C. Naturalmente se estudiaranmuchos de estos aspectos a traves de este documento.

Tamano pequeno.

Uso extensivo de llamadas a funciones.

Loose tiping o debilemente tipeado, distinto a otros lenguajes como Java.

Bajo nivel (BitWise, con operadores a nivel de bit).

Implementacion de punteros, extensivo uso de punteros a memoria, arreglos, estructuras y funcio-nes.

C ahora se ha transformado en un lenguaje profesional por varias razones:

Posee construcciones de alto nivel.

Puede manejar actividades de bajo nivel.

Produce programas eficientes.

Puede ser compilado en varios computadores (portable).

Su principal defecto es que posee una pobre deteccion de errores. Como un ejemplo extremo, elsiguiente codigo en C (mystery.c) es codigo C valido.

#include <stdio.h>

main(t,_,char *a) {

return!0<t?t<3?main(-79,-13,a+main(-87,1-_,

main(-86, 0, a+1 )+a)):1,t<_?main(t+1,_,a ):

3,main ( -94, -27+t, a)&&t == 2

?_<13 ?main ( 2, _+1, "%s %d %d\n" ):9:16:

t<0?t<-72?main(_,t,"@n’+,#’/*{}w+/w#cdnr/

+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l,+,/n{

n+\,/+#n+,/#;#q#n+,/+k#;*+,/’r :’d*’3,}{w+K

w’K:’+}e#’;dq#’lq#’+d’K#!/\+k#;q#’r}eKK#}

w’r}eKK{nl]’/#;#q#n’){)#}w’){){nl]’/+#n’;d}

rw’ i;#){n\l]!/n{n#’; r{#w’rnc{nl]’/#{l,+’K{

rw’iK{;[{nl]’/w#q#\n’wknw’iwk{KK{nl]!/w{

%’l##w#’i;:{nl]’/*{q #’ld;r’}{nlwb!/*de}’c\;;

{nl’-{}rw]’/+,}##’*}#nc,’,#nw]’/+kd’+e}+;

\#’rdq#w! nr’/ ’)}+}{rl#’{n’ ’)#}’+}##(!!/"):

33

t<-50?_==*a ?putchar(a[31]):

main(-65,_,a+1):

main((*a == ’/’)+t,_,a\+1 ):0<t?main ( 2, 2 ,

"%s"):*a==’/’||main(0,main(-61,*a, "!ek;

dc \i@bK’(q)-[w]*%n+r3#l,{}:\nuwloca-O;

m .vpbks,fxntdCeghiry"),a+1);

}

Este programa compilara y se ejecutara sin problemas, produciendo una salida que tiene bastantesentido.

4.4. Aspectos de tipo Lexicos y Sintacticos del lenguaje.

Presentaremos ahora algunos detalles del lenguaje relacionado con los sımbolos especiales, palabrasreservadas, identificadores, constantes, tipos basicos, expresiones, como tambien sentencias de control deflujo, declaracion de funciones, arreglos punteros y tipos de almacenamiento.

4.4.1. El Conjunto de Caracteres de C.

Cuando se escribe un programa en un lenguaje de alto nivel u orientado al programador, esnecesario escribir las ordenes o instrucciones utilizando sımbolos que combinandolos en cadenas de ca-racteres o strings, podemos representar estas ordenes y subsecuentemente podamos entender la logica queesta detras del programa. Estas cadenas de caracteres son utilizadas para formar palabras que represen-tan comandos (generalmente estas palabras son un subconjunto del vocabulario ingles), identificadores,operaciones, etc., con lo cual el programador u otras personas puedan leer el programa y entenderlo. Ellenguaje C utiliza los caracteres del alfabeto ingles (letras mayusculas de la A a la Z, y las minusculas dela a a la z), los dıgitos del 0 al 9 y caracteres especiales. Por ejemplo, los caracteres especiales permitidospor el lenguaje, se ilustran en el cuadro 4.1.

+ - ∗ \ = %& ] ! ? ∧ ”′ ∼ \ — < >( ) [ ] { }: ; . , b

Cuadro 4.1: Caracteres soportados.

4.4.2. Identificadores y Palabras Reservadas.

Los identificadores se utilizan para nombrar a diversos tipos de elementos de un programa, talescomo: variables de memoria (variables escalares, arreglos, uniones, punteros, etc.), constantes, labels yfunciones. Un identificador consiste en una secuencia de letras o dıgitos comenzando con una letra. Sepueden utilizar letras mayusculas y minusculas (pero en C, las letras mayusculas y minusculas no soniguales). El caracter de subrayado tambien se puede utilizar ( ) y es considerado como una letra. Unidentificador tambien puede comenzar con el caracter de subrayado, aunque esto es poco comun. Existenciertos nombres que son utilizados como palabras claves y no pueden ser utilizados como identificadores,estos nombres constituyen las palabras reservadas del lenguaje y estas se muestran en el cuadro 4.2.

Algunos compiladores incluyen todas o algunas de las siguientes palabras reservadas, y que se mues-tran en el cuadro 4.3.

4.4.3. Tipos de Datos Basicos.

El lenguaje C soporta varios tipos basicos de datos. El tipo permite determinar como se almacenanun objeto y su identificador asociado. Existen tres tipos de enteros: short int, int, long int. Enteros

34

auto do for short typedefbreak double if signed unioncase else int sizeof unsignedchar enum long static void

continue extem register struct volatiledefault float return switch while

Cuadro 4.2: Palabras reservadas de C.

ada far nearasm fortran pascalentry huge

Cuadro 4.3: Otras palabras reservadas.

sin signo son declarados anteponiendo la palabra reservada unsigned.Para tener precision simple con punto flotante se dispone del tipo float y para precision doble el tipo

double.El cuadro 4.4 ilustra los almacenamientos tıpicos en tres miprocesadores modernos distintos (tamano

en bits).

Tipo Intel Sparc MIPSchar 8 8 8int 32 32 32

float 32 32 32double 64 64 64

Cuadro 4.4: Tamano de los tipos de datos basicos.

Con ellos podemos formar combinaciones utilizando otras palabras reservadas denominadas cualifica-dores, para definir otros tipos de datos basicos derivados. Estos cualificadores son los siguientes: short(para definir un entero corto), long (para definir un entero o un flotante largo), signed (con signo),unsigned (sin signo). El cuadro 4.5 muestra un ejemplo de ello.

El tipo booleano no existe en C, generalmente se utiliza el tipo int para suplir esta falta. Tambien sepuede utilizar char, y mejor aun unsigned char. Sin embargo, en compiladores gcc, se ha incluido el tipode datos bool. Para ello se debe incluir el archivo cabecera stdbool.h.

Para declarar variables, se deber realizar de la siguiente forma:

var type lista de variables, separadas por coma;

por ejemplo:

] include <stdbool.h>

int i, j, k;

float x, y, z;

char c;

unsigned char ch;

bool b;

La variable b podra tomar los valores true o false. Una funcion tambien puede retornar un valor detipo bool. Por ejemplo:

35

Tipo Tamano (bits)short o short int 16

long int 16 o 32 (1)unsigned int 16 o 32 (1)

unsigned short int 16signed char 8

unsigned char 8long float 32 o 64 (1)

long double 64 o mas (1)

Cuadro 4.5: Tipos basicos derivados.

bool mayoritarioMC(int *a, int n, int *may)

{

int j;

int i = intuniforme(0,n);

int k = 0;

int x = a[i];

for ( j=0; j < n; j++ ) {

if ( a[j] == x ) k++;

}

*may = x;

return (k > n/2);

}

4.5. Declaracion de variables y su equivalente en lenguaje demaquina.

Para declarar variables en C, se deber realizar de la siguiente forma:

var type lista de variables;

por ejemplo:

char caracter;

int entero;

float flotante;

short int entero_corto;

long int entero_largo;

double float_largo;

unsigned char char_sin_signo;

Como se ve a nivel de lenguaje de maquina? (arquitectura Sparc).

.file "tipos.c"

gcc2_compiled.:

.global myarray

.section ".data"

!Seccin de datos globales inicializados

36

.section ".text"

!Seccion de Codigo

.LLfe2:

!Seccin de datos globales no inicializados

.size main,.LLfe2-main

.common caracter,1,1

.common entero,4,4

.common flotante,4,4

.common entero_corto,2,2

.common entero_largo,4,4

.common float_largo,8,8

.common char_sin_signo,1,1

.ident "GCC: (GNU) 2.95.2 19991024 (release)"

4.6. Tipos de Almacenamiento.

Existen dos formas diferentes de caracterizar a las variables: por su tipo de datos y por su tipo dealmacenamiento.

Tipo de Dato. Se refiere al tipo de informacion representada por una variable (entero, flotante,char, etc.) y la cantidad de bytes que ocupara.

Tipo de Almacenamiento. Se refiere a la permanencia de la variable y a su ambito (scope) dentrodel programa, que corresponde a la parte del programa en la que se reconoce la variable.

Existen cuatro tipos de especificaciones distintas de tipo de almacenamiento en C:

automatica (auto).

externa (extern).

estatica (static).

registro (register).

Las que estn identificadas por las palabras reservadas, especificadas junto a ellas. A veces se puedeestablecer el tipo de almacenamiento asociado a una variable simplemente por la posicin de su declaracionen el programa. En otras situaciones, sin embargo, la palabra reservada que especifica un tipo particularde almacenamiento se tiene que colocar al comienzo de la declaracion de la variable.

Ejemplo: Declaraciones tıpicas de variables que incluyen la especificacion de un tipo de almacenamiento.

extern char e;

static int count=0;

extern float x1, x2;

auto int sum=0;

4.6.1. Variables automticas.

Estas variables se declaran siempre dentro de la funcion y son locales a ella donde han sido declara-das; es decir, su ambito esta confinado a esa funcion. Las variables automaticas definidas en funcionesdiferentes seran independientes unas de otras, incluso si tienen el mismo nombre.

Cualquier variable declarada dentro de una funcion se interpreta como una variable automatica amenos que se incluya dentro de la declaracin un tipo distinto de almacenamiento.

La palabra reservada auto es opcional. Una variable automatica no mantiene su valor cuando setransfiere el control fuera de la funcion en que esta definida.

En tiempo de ejecucion, estas variables se almacenan en el stack del proceso, especıficamente, dentrode un frame del stack.

37

4.6.2. Variables Externas.

Su ambito se extiende al resto del programa desde su punto de definicion. Por tanto, se expanden pordos o mas funciones y frecuentemente por todo el programa. A menudo se les llama variables globales.Se pueden acceder desde cualquier funcion que se encuentre dentro de su ambito y del programa.

Estas proporcionan un mecanismo adecuado de transferencia de informacion entre funciones, sin usarargumentos, especialmente cuando una funcion requiere numerosos datos de entrada.

Tambin son utiles cuando se requiere que una variable sea utilizada en diferentes modulos de unprograma compuesto de varios archivos fuentes.

Al trabajar con variables externas, hay que distinguir entre definiciones de variables externas y de-claraciones de variables externas.

Una definicion de variable externa se escribe de la misma forma que una declaracion de una variablesordinaria. Tiene que aparecer fuera, y normalmente antes de las funciones que acceden a las variablesexternas. Una definicion de variable externa automaticamente reserva el espacio de almacenamiento re-querido en la memoria del computador. La asignacion de valores iniciales puede incluirse, si se deseadentro de una definicion de variable externa.

Una declaracion de una variable externa tiene que comenzar con el especificador de tipo de almace-namiento extern.

El nombre de variable externa y su tipo de datos tienen que coincidir con su correspondiente defini-cion de variable externa que aparece fuera de la funcion.

No se reservara espacio de almacenamiento para variables externas como consecuencia de una decla-racion de variable externa.

Una declaracion de variable externa no puede incluir una asignacion de valores iniciales.Estas son las diferencias importantes entre la definicion de una variable externa y su declaracion.

Ejemplo:

#include <stdio.h>

int val = 100; // variable externa

extern int f(int); //delacraciion de una funcion externa

void sumar() {

int k; // variable automatica

k = val;

k++;

return i;

}

void main() {

printf("%d\n",sumar(a));

}

En este ejemplo se puede observar que la variable val, fue definida como externa o global (fuera decualquier funcion), siendo utilizada tanto en la funcion sumar como main. Por otro lado la variable k esautomatica o local a la funcion sumar, por lo que no puede ser accesada fuera de esta funcion. La funcion

Toda variable o constante debe ser definida o declarada antes de que pueda ser utilizada. Si se vaa hacer referencia a una variable externa antes de su definicion o si esta definida en un archivo fuentediferente al que se esta utilizando, entonces es obligatoria una declaracion extern.

4.6.3. Variables Estaticas.

Las variables estaticas retienen sus valores durante toda la vida del programa. Las variables estaticasse definen dentro de una funcion de la misma forma que las variables automaticas, excepto que ladeclaracion de variables tiene que comenzar con la declaracion del tipo de almacenamiento static.

38

Se pueden utilizar las variables estaticas dentro de una funcion de la misma forma que las otrasvariables. Sin embargo, no pueden ser accedidas fuera de la funcion en que estan definidas.

Se pueden incluir valores iniciales en las declaraciones. En particular:

Los valores iniciales tienen que ser constantes y no expresiones.

Si no tienen valor inicial, se les asignara el valor cero.

Las variables locales llevan implıcito el modificador auto. Estas se crean al inicio de la ejecucion de lafuncion y se destruyen al final. En un programa sera muy ineficiente en terminos de almacenamiento, quese crearan todas las variables al inicio de la ejecucion, aunque en algunos casos puede ser deseable. Esto seconsigue anteponiendo el modificador static a una variable local. Si una funcion necesita una variable queunicamente sea accedida por la misma funcion y que conserve su valor a traves de sucesivas llamadas, esel caso adecuado para que sea declarada local a la funcion con el modificador static. El modificador staticse puede aplicar tambien a variables globales. Una variable global es por defecto accesible desde cualquierfuente del programa. Si, por cualquier motivo, se desea que una de estas variables no sea visible desdeotro fuente se le debe aplicar el modificador static. Lo mismo ocurre con las funciones. Las funcionesdefinidas en un fuente son utilizables desde cualquier otro. En este caso conviene incluir los prototiposde las funciones del otro fuente. Si no se desea que alguna funcion pueda ser llamada desde fuera delfuente en la que esta definida se le debe anteponer el modificador static.

void stat(); /* prototype fn */

main() {

int i;

for (i=0;i<5;++i) stat();

}

void stat() {

int auto_var = 0;

static int static_var = 0;

printf("auto = %d, static = %d n",auto_var, static_var);

++auto_var;

++static_var;

}

La declaracion static externa se usa con mas frecuencia en variables, pero tambien se puede aplicar afunciones. Normalmente los nombres de las funciones son globales, visibles a cualquier parte del programacompleto. Sin embargo si una funcion se declara como static su nombre es invisible fuera del archivo enel que esta declarada.

La declaracion static tambien puede aplicarse a variables internas. Las variables internas static sonlocales a una funcion en particular, tal como lo son las variables automaticas, pero a diferencia de ellasmantienen su existencia en lugar de ir y venir cada vez que se activa la funcion. Esto significa que lasvariables internas static proporcionan almacenamiento privado y permanente dentro de una funcion.

Ejemplo:

#include <stdio.h>

int incr() {

static int i = 1;

printf("%d\n",i);

return ++i;

}

void main() {

while (incr()<=10) ;

}

39

En este ejemplo la variable ı.es declarada como static lo que permite que se inicialice unicamente unavez y cada vez que se llame a la funcion el valor de la variable no regresa a uno.

Las variables declaradas fuera de cualquier funcion y de todos los identificadores de nombres de lafuncion, pueden ser referenciadas desde otros archivos, por tanto, estas variables tienen enlace (linkage)de tipo externo por defecto. La palabra clave o modificador static especifica enlace interno para esosidentificadores.

Ejemplo: Veamos ahora los siguientes programas:

int myarray[50000] = {1,2,3,4};

int main(int arg0, char *arg1[]) {

myarray[0] = 3;

}

y

int myarray[50000];

int main(int arg0, char *arg1[]) {

myarray[0] = 3;

}

Cual de los dos pesa mas?. Veamos el directorio:

drwxr-xr-x 2 prodrigu docentes 512 Apr 25 10:29 .

drwxr-xr-x 3 prodrigu docentes 512 Apr 2 16:16 ..

-rw-r--r-- 1 prodrigu docentes 91 Apr 25 09:25 myarray.c

-rwxr-xr-x 1 prodrigu docentes 206440 Apr 25 09:25 myarray1

-rwxr-xr-x 1 prodrigu docentes 6440 Apr 25 09:25 myarray2

-rw-r--r-- 1 prodrigu docentes 79 Apr 25 09:25 myarray2.c

Como se ve a nivel de lenguaje de maquina?

.file "myarray.c"

gcc2_compiled.:

.global myarray

.section ".data"

.align 4

.type myarray,#object

.size myarray,200000

myarray:

.uaword 1

.uaword 2

.uaword 3

.uaword 4

.skip 199984

.section ".text"

.align 4

.global main

.type main,#function

.proc 04

main:

!#PROLOGUE# 0

save %sp, -112, %sp

!#PROLOGUE# 1

st %i0, [%fp+68]

st %i1, [%fp+72]

40

Objeto Donde se Declara Static Modifies Se aplica static? Clase de Almac. Clase de Enlacevariable Dentro de funcion clase almacenamiento Si static internavariable Dentro de funcion clase almacenamiento No automatic internavariable Fuera de funcion clase enlace Si static internavariable Fuera de funcion clase enlace No static externafuncion Fuera de funcion clase enlace Si static internafuncion Fuera de funcion clase enlace No static externa

Cuadro 4.6: Variables y Tipos de Almacenamiento.

sethi %hi(myarray), %o1

or %o1, %lo(myarray), %o0

mov 3, %o1

st %o1, [%o0]

.LL2:

ret

restore

.LL2:

ret

restore

.LLfe1:

.size main,.LLfe1-main

.ident "GCC: (GNU) 2.95.2 19991024 (release)"

y

.file "myarray2.c"

gcc2_compiled.:

.section ".text"

.align 4

.global main

.type main,#function

.proc 04

main:

!#PROLOGUE# 0

save %sp, -112, %sp

!#PROLOGUE# 1

st %i0, [%fp+68]

st %i1, [%fp+72]

sethi %hi(myarray), %o1

or %o1, %lo(myarray), %o0

mov 3, %o1

st %o1, [%o0]

.LL2:

ret

restore

.LLfe1:

.size main,.LLfe1-main

.common myarray,200000,4

.ident "GCC: (GNU) 2.95.2 19991024 (release)"

41

4.6.4. Variables Register.

En vez de utilizar la memoria del computador, es preferible que ciertas variables sean almacena-das en algn registro del procesador durante la ejecucion del programa. Esto se hace con propositos deeficiencia para reducir los accesos a memoria (que suelen ser caros en tiempo de ejecucion) y reducir eltiempo de ejecucion. Por ejemplo, una variable de local a una funcion de tipo int (entero) que se usacomo variable de iteracion en algun loop (for, while) es preferible declararla con como register, dadoque se usara con mucha frecuencia.

Normalmente, solo a las variables automaticas (locales a las funciones) se les esta permitido utilizareste tipo de almacenamiento.

Ejemplo.

void f() {

register int k; /* El compilador asignara un registro de la CPU para */

/* mantener esta variable. */

...

...

}

4.6.5. Estructura del ejecutable y el Espacio de Direcciones de un Proceso.

Figura 4.1: Estructura del archivo ejecutable.

4.6.6. Constantes.

Una constante entera es una secuencia de dıgitos. Existen constantes de tipo octales (Onnn),hexadecimales (0xnnn o 0Xnnn) y decimales. Para constantes muy grandes se usa almacenamiento long,

42

Figura 4.2: Espacio de direcciones de un proceso.

como por ejemplo OL es el long 0.ANSI C, que define el estandar de C, permite declarar constantes. Cuando se declara una constante

se hace igual cuando se declara una variable excepto que su valor no puede ser modificado.La palabra reservada const permite declarar una constante, tal como se muestra a continuacion:

const int c = 10; / ∗ tambien puede ir primero el tipo int antes que const ∗ /

Otro detalle importante, por ejemplo, es si declaramos la siguiente constante:

const int c=10;

const int k;

/*y en el main hacemos lo siguiente:*/

int main(int argc, char *argv[]) {

k=20;

printf("La constante k = %i\n",k);

c=c+1;

printf("La constante c = %i\n",c);

printf("\n");

}

El compilador emitira el siguiente mensaje:

[prodrigu@nico]#gcc -o const const.c

const.c: In function ‘main’:

const.c:11: warning: assignment of read-only

variable ‘k’

const.c:12: warning: assignment of read-only

variable ‘c’

y en tiempo de ejecucion:

La constante k = 20

Segmentation fault (core dumped)

43

Caracter EscapeNewline \n

Tab horizontal \tTab vertical \vBackspace \b

Return \rFornfeed \fBackslash \\

Comilla simple \’Nulo \0

Cuadro 4.7: Caracteres especiales.

Existe ademas un conjunto de caracteres especiales a los que se antepone un y son los que muestranen en el cuadro 4.7.

Cualquier caracter de la forma \ddd se considera un octal. Si un \esta antepuesto a cualquier caracterque no se menciono antes sera ignorado. Ademas los caracteres \permiten incluir comillas dobles en losstrings. El caracter de fin de string ′\0′ se agrega al final de un string (caracter de fin de string) paratodas las funciones que operan con este tipo de datos.

4.6.7. Definicion de variables globales.

Las variables globales son definidas antes de la funcion main, de la siguiente forma:

short number,sum;

int bignumber,bigsum;

char letter;

main() {

...

}

tambien es posible pre-inicializar las variables globales en la definicion, utilizando el operador = (deasignacion, en Pascal es :=), de la siguiente forma:

float sum = 0.0;

int bigsum = 0;

char letter = ‘A’;

main() {

...

}

Esto es lo mismo que:

float sum;

int bigsum;

char letter;

main() {

sum=0.0;

bigsum=0;

letter=‘A’;

}

44

... pero es mas eficiente, por que?...

C tambien permite multiples comandos de asignacion utilizando el operador de asignacion =, por ejemplo:

a=b=c=d=0;

lo cual es lo mismo, pero mas eficiente que:

a = 0;

b = 0;

c = 0;

d = 0;

Este tipo de asignacion es solo posible si todas las variables son del mismo tipo.Podemos definir nuevos tipos de datos, utilizando la declaracion typedef . Por ejemplo.

typedef float real;

typedef char letter;

declaracion de variables:

real sum = 0.0;

letter nextletter;

4.6.8. Expresiones y Operadores.

En C una expresion esta formada por constantes, identificadores, subındices de variables, estructuraso uniones, y llamadas a funciones combinadas usando operadores unarios y binarios.

Los operadores binarios tienen asociatividad por la izquierda, a menos que se explicite lo contrariousando parentesis.

Los operadores binarios pueden ser agrupados bajo las siguientes categorıas, expuesto en el cuadro4.8.

Operador SımboloOperadores Aritmeticos +, -, ∗, /, %Operadores Relacionales ¡, ≤, >,≥

Operadores de Comparacion ==, !=Operadores Bitwise &, |,∧, � , �Operadores Logicos &&, ‖

Operador Coma (exp 1, exp2 )Operador de Asignacion =Operador Condicional (exp ? exp1 : exp2)

Otros Operadores (,) ,[, ], .

Cuadro 4.8: Operadores binarios.

Si tenemos la siguiente operacion:

a operador b

Si el operador es, tal como se aprecia en la tabla 4.9.La operacion de shift a la izquierda es equivalente a multiplicar por 4. El operador coma permite

agrupar instrucciones como en la siguiente llamada la funcion: f(a, (t = 3, t + 2),c), donde el segundoparametro tomara el valor 5. El operador ? permite realizar una seleccion, si la expresion exp es verdaderase realiza exp1 y si es falsa exp2.

En C el valor cero (0) es falso y un valor distinto de cero (0) es verdadero. En el cuadro 4.10 sedescribe los operadores unarios:Operador permite poner en cero los bits que estan en uno y viceversa.

45

+ obtener suma de a y b- obtener resta de a y b∗ obtener producto de a y b/ obtener division de a y b% obtener resto de division entre a y b¿ sera verdadera si a es mayor a b≥ sera verdadera si a es mayor o igual a b≤ sera verdadera si a es menor o igual a b¡ sera verdadera si a es menor a b

== sera verdadera si a es igual a b!= sera verdadera si a es distinto a b& and de bits| or inclusivo de bits∧ or exclusivo de bits� desplaza b veces los bits de a a la izquierda� desplaza b veces los bits de a a la derecha

&& sera verdadera si a y b lo son‖ sera verdadera si a o b lo son= asignara valor de b a variable a.

Cuadro 4.9: Descripcion de los Operadores Binarios.

4.6.9. Incremento y Decremento.

C contiene dos operadores muy utiles que no existen generalmente en otros lenguajes de computadora.Son el incremento y el decremento, ++ y –. el operador ++ anade 1 a su operando y – le resta 1.

++x; /* x++; o x=x+1 */

--x; /* x--; o x=x-1 */

Sin embargo existe una diferencia cuando se utilizan estos operadores en una expresion. El siguienteejemplo lo ilustra.

x=10; x=10;

y=++x; y=x++;

pone a y a 11 y toma el valor 10

La mayorıa de los compiladores de C generan un codigo objeto muy rapido y eficiente para lasoperaciones de incremento y decremento, siendo mejor que el que se obtendrıa usando la sentencia deasignacion correspondiente. Por esta razon, se deben usar estos operadores siempre que sea posible. Laprecedencia de los operadores aritmeticos es la siguiente.

mayor ++ --

- (unario)

* / %

menor + -

Tomemos los siguientes dos segmentos de codigo.

void myFunc(char *s) {

int x ;

x = 0 ;

while (x < strlen(s)) {

printf("%c",s[x]) ;

x++ ;

46

Operador Descripcion∗e, p→ e Contenido de e, para punteros

&v Direccion de v, para punteros-e Unario menos!e Negacion booleana∧ e Complemento de e

++v Equivale a v = v+1;–v Equivale a v =v-1;

v++ Utiliza y luego lo incrementa en 1v– Utiliza y luego lo incrementa en 1

type(e) Cast, obliga a e a ser del tipo typesizeof(e) Numero de bytes ocupados por e

sizeof(type) Numero de bytes de type

Cuadro 4.10: Operadores unarios.

}

printf("\n") ;

}

void myFunc(char *s) {

int x = 0, z ;

z = strlen(s) ;

while (x < z) {

printf("%c",s[x++]) ;

}

printf("\n") ;

}

Ambos segmentos hacen exactamente lo mismo, imprimen el contenido del string ”scaracter a caracter.Pero el segundo es mas eficiente que el primero, inicializa x en 0 dentro de la declaracion de la variabley evita un linea extra dentro del loop utilizando un post incremento (”x++”), que probablemente leahorrara a la CPU algunos ciclos de proceso. Tambien utiliza una variable (”z”) para guardar el largodel string en vez de llamar a la funcion strlen en cada iteracion.

4.6.10. Activar las funciones de optimizacion.

Por supuesto, por mas que se intente optimizar el codigo a medida que se escriba, un compiladordecente puede salvar el problema que vemos arriba (la llamada a strlen) y optimizar el codigo de formaapropiada.

Es una triste verdad, que muchas personas no saben como activar las opciones de optimizacion de suscompiladores o aun ni saben que estas opciones existen. Agregando tres caracteres a la lınea de comandosdel compilador de C de Unix ”gcc”, se puede mejorar eventualmente la performance del codigo en unbuen porcentaje.

Tambien se puede tomar decisiones sobre como equilibrar la performance de un codigo en contra deltamano total del codigo. En algunos sistemas se puede compilar funciones de librerıas estaticas en elcodigo en vez de realizar llamadas a las librerıas dinamicas en tiempo de ejecucion. El problema aquıesque se gana velocidad, pero se incrementa el tamano del archivo ejecutable, asıque si hay mucha RAM,esta es una buena opcion.

4.6.11. Control de Flujo.

C provee instrucciones de control de flujo condicionales e iterativas.

47

Sentencias condicionales.

Una sentencia condicional tiene la forma :

if (expresion)

Instruccion 1;

else

Instruccion 2;

Se evalua la expresion y si el resultado es distinto de cero (verdadero) se ejecuta instruccion 1, de locontrario instruccion 2.

La instruccion else puede ser omitida obteniendose una sentencia como la siguiente:

if (expresion1)

if (expresion2)

instruccion1;

else

instruccion2;

Que origina una posible ambigedad que es resuelta por C de la siguiente manera: cada instruccionelse sera asignada a la ultima instruccion if encontrada.

La instruccion switch provee un conjunto de opciones que puede coincidir con la expresion que seevalua.

switch (expresion) {

case expresion-constante1:

instruccion 1;

[break;]

....

case expresion-constante n-1:

instruccion n-1;

[break];

default:

instruccion n;

}

La expresion se evalua y se obtiene un resultado el cual sera comparado con cada una de las expre-siones constantes al encontrar una con la que coincida se ejecutara desde ese bloque de instruccioneshasta encontrar la instruccion break. Si la expresion evaluada no coincide con ninguna de las expresionesconstantes se ejecutara el bloque de instrucciones que acompanan a la instruccion default.

No se requiere que la instruccion default forme parte del switch, como tampoco que todas las posibi-lidades esten reflejadas en las expresiones constantes.

4.6.12. Sentencias de Iteracion.

Entre estas se cuenta el ciclo del tipo:

whiIe (expresion)

instruccion;

La instruccion for es util cuando se desea tener una condicion inicial, un incremento y una condicionde salida, la sintaxis es la siguiente:

for (expresion1; expresion2; expresion3)

instruccion;

Formalmente esto es equivalente a:

48

expresion1 /*inicializa el parametro

de iteracion*/

whiIe ( expresion2 ) {

instruccion;

expresion3; /*incrementa el parametro

de iteracion*/

}

Por ejemplo, dada la siguiente instruccion for:

int i;

for (i=0; i<100; i++)

instruccion;

es equivalente a:

i=0;

while ( i < 100 ) {

instruccion;

i++;

}

Otra forma de instruccion iterativa es :

do {

instruccion

} whiIe (expresion3);

En esta instruccion se ejecuta la instruccion y luego se chequea la expresion antes de la segundaejecucion.

Existe instrucciones que alteran la ejecucion de un ciclo, estas son:

break.

continue.

goto label.

la primera permite abandonar o quebrar un ciclo, la segunda pasar a la siguiente instancia del ciclo y laultima saltar a la instruccion rotulada con la palabra label. La instruccion goto es indeseable desde elpunto de vista de la programacion estructurada, pero es aceptable en programacion a bajo nivel.

49

50

Capıtulo 5

Arreglos.

5.1. Introduccion.

En principio los arreglos en C son similares a los que se presentan en otros lenguajes de progra-macion. Pero veremos que los arreglos en este lenguaje se definen de una forma un poco diferente, y queademas existen algunas diferencias sutiles debido al cercano enlace entre arreglos y punteros.

El tamano de un arreglo permanece constante durante la ejecucion del programa y se define antes demanipularlo. El tamano puede ser definido en el mismo programa, o leıdo desde la entrada estandar, porejemplo:

int arreglo[100];

o

#define SIZE_ARRARY 100

int arreglo [SIZE_ARRAY];

o

int size_array;

scanf(%d,&size_array);

Este valor leıdo debe ser validado, ya que de todas maneras el arreglo debe tener definido un tamanomaximo. Esto es necesario debido a que el compilador necesita saberlo.

5.2. Arreglos uni y multi-dimensionales.

Veamos primero como se define un arreglo en C:

type array_name[dim1][dim2]...[dimN];

donde type es el tipo de los elementos del arreglo, array name su nombre (nombre de variable), y dimi

define el tamano de la dimension i del arreglo. A continuacion se presentan dos ejemplos donde se defineun arreglo de una dimension (o vector) y otro de dos dimensiones (o matriz).

int valores_enteros[20];

float valores_reales[10][20];

NOTA: En C los subındices de los arreglos comienzan en 0 y terminan con un valor menor en uno queel tamano que se define en el array. Por ejemplo, para el caso anterior, los valores validos del subındicepara el arreglo valores enteros van desde 0 hasta 19. Cualquier valor fuera de rango puede provocar

51

resultados impredecibles.Los elementos pueden ser accesados de las siguientes formas:

int quinto_elemento, elemento_matriz;

quinto_elemento = valores_enteros[4];

asignando un valor en una posicion del arreglo:

valores_enteros[10] = 500;

Los arreglos multi-dimensionales pueden ser definidos de la siguiente forma:

#define MAX_FIL 100

#define MAX_COL 100

int matriz_enteros[MAX_FIL][MAX_COL]; /*que define una matriz cuadrada, de 10000

elementos*/

Para arreglos de mas de dos dimensiones, se deben agregar definiciones de dimensiones con los sımbolos[ ] (parentesis cuadrados, o corchetes).

int arreglo_multidimensional[20][30][40][50]......[20];

Donde los elementos pueden ser accesados de la siguiente forma:

elemento_matriz = matriz_enteros[10][20];

donde se accede al elemento de tipo entero que se ubica en la undecima fila, y vigesimo primera columna.Ese valor es asignado a la variable elemento matriz.Ejemplo: la siguiente funcion retorna el tamano de un arreglo de caracteres:

int length (char buffer[]) {

int i = 0;

while ( buffer[ i ] != \0 ) )

i ++;

return ( i ) ;

}

donde:

buffer: es el nombre del arreglo, como parametro formal.

size: el tamano del arreglo (de una dimension). El tamano size define la cantidad de elementos delarreglo unidimensional, los cuales son de tipo entero.

Cuando un arreglo se recibe como parametro, el pasaje de parametro es por referencia, es decir, quese recibe en la funcion la referencia o direccion del primer elemento del arreglo (en realidad, al direciondel primer byte del primer elemento). No se reserva espacio para el arreglo mismo cuando aparecen comoparametros de una funcion. Una forma de efectuar el llamado a la funcion dada anteriormente, es lasiguiente:

longitud = 1- length ( array, sizeof (array) );

donde sizeof(array) determina el tamano del arreglo.Convencionalmente en C, los strings finalizan con el caracter nulo ′\0′ (\ seguido de cero), por ejemplo:

52

void copy_string (char str1[], char str2[]) {

int i;

for ( i = 0; (str2 [ i ] = str1 [i ] ) ! = \0 ; i++ )

;

}

copia el string str1 al string str2 dentro del los parentesis del for. El ciclo termina cuando str2[i] sea igualal caracter fin de string (′\0′).

5.3. Los Arreglos bidimensionales.

Un arreglo en 2D (dos dimensiones) realmente es un arreglo de 1D (una dimension), donde cadade uno de los elementos es un arreglo.Los elementos del arreglo son almacenados por filas, es decir, unafila tras otra.

Cuando un arreglo en 2D es pasado como parametro a una funcion, debemos especificar el numero decolumnas (el numero de filas es irrelevante). La razon de esto, es debido al puntero al arreglo. C necesitasaber cuantas columnas (o elementos por fila) debe saltar de una fila a otra en memoria.

Consideremos el arreglo definido anteriormente pasado como parametro a una funcion:

int matriz_enteros[MAX_FIL][MAX_COL];

La llamada la podemos hacer de la siguiente forma:

funcion(matriz_enteros);

y la definicion de la funcion como:

void funcion(int matriz[][MAX_COL]) {cuerpo de la funci\’on}

o tambien:

void funcion(int matriz(*fil)[MAX_COL]) {cuerpo de la funci\’on}

De esta forma:

int (∗m)[MAX COL]: declara un puntero a un arreglo de MAX COL enteros.

int ∗m[MAX COL]: declara un arreglo de MAX COL punteros a enteros.

Ahora veamos, la diferencia entre punteros y arreglos. Los strings comunmente son una aplicacionparticular, por ejemplo:

char *str[100];

char character[100][200];

Podemos hacer correctamente: str[30][40] y character[30][40] en C. Si embargo:

character es en verdad un arreglo de tipo char de dos dimensiones, de 20000 elementos, que accesacada elemento de la forma: 200 ∗ fila + columna + direccion base, en memoria.

str es un arreglo de 100 punteros a caracteres.

La ventaja de str, es que cada puntero puede apuntar a 100 arreglos de tipo char, incluso de diferentetamano.

53

5.4. Strings.

En el lenguaje C, los strings son definidos como arreglos de caracteres. C no posee el tipo de datosconstruido o compuesto String, tal como en los lenguajes Pascal y Java. Para manejar datos de este tipo,se puede hacer de dos formas distintas:

1. A traves de un array de caracteres, de la forma:

char nombre[MAX SIZE];

que define un arreglo de caracteres o string de tamano MAX SIZE

2. A traves de un puntero a un area de datos, de la siguiente forma:

char ∗apellido;

por tanto se debe primero pedir memoria para el string, ya que la variable apellido no es el string mismo,sino que un puntero a este. Esto lo hacemos de la siguiente forma:

apellido = (char *)malloc(20*sizeof(char)); /*\’o (char *)malloc(20)*/

con la funcion malloc estamos pidiendo un espacio de memoria de 20 bytes. Esto es ası debido a quecada caracter ocupa exactamente un byte de memoria.

No podemos asignar string a una variable de este tipo, ya sea un arreglo o a un puntero a string. Porejemplo, las siguientes sentencias de asignacion son incorrectas:

nombre = "Arturo";

apellido = "Collao";

Para lograr asignar un valor a estas variables, C cuenta con una librerıa especial que proporcionarutinas para este tipo de propositos. Estas rutinas o funciones las estudiaremos posteriormente. Comoejemplo, si queremos asignar los valores anteriores, debemos utilizar entonces la funcion strcpy, paracopiar un string en un area de memoria:

#include <string.h>

strcpy(nombre,"Arturo");

strcpy(apellido,"Collao");

Tambien es incorrecto comparar dos string de las siguientes formas:

if ( "Arturo" == "Collao" )

o

if ( nombre == apellido),

esto se debe a que C no posee mecanismos para manipular strings, por tanto,debemos valernos de ciertasfunciones de bibliotecas, incluyendo al principio del programa el header string.h:

#include <string.h>

if ( strcmp("Arturo",Collao") == 0 )

o

if (strcmp(nombre,apellido) == 0 )

que, en ambos casos sera verdadero si la funcion strcmp retorna el valor menor que 0 (¡0).Para imprimir un string, utilizamos la funcion printf con el caracter de control especial que define el

formato para string %s.

54

printf("nombre: %s; apellido: %s\n",nombre,apellido);

Es importante destacar que los strings terminan con el caracter de fin de string ′\0′. Si tenemos elsiguiente string:char nombre[20]; y almacenamos en este el valor .Arturo”su contenido se vera de la siguiente forma:

nombre: A r t u r o ’\0’

donde cada caracter ocupa un byte, incluyendo el caracter de fin de string, y queda espacio de sobra aunen el arreglo.

55

56

Capıtulo 6

Punteros

6.1. Antecedentes Basicos.

Un puntero es un tipo especial de variable, que almacena una direccion de memoria. Esta direccionpuede ser la de una variable individual, pero frecuentemente puede ser de elementos como un array, yuna estructura. Los punteros, al igual que una variable comun, pertenecen a un tipo (type), se dice queun puntero ’apunta a’ ese tipo al que pertenece. Ejemplos:

int *pint; //Declara un puntero a entero

char *pchar; //Puntero a char

float *pfecha; //Puntero a float

Independientemente del tamano (sizeof) del elemento apuntado, el valor almacenado por el punterosera el de una unica direccion de memoria. En sentido estricto un puntero no puede almacenar la direccionde memoria de un array (completo), sino la de un elemento de un array, y por este motivo no existendiferencias sintacticas entre punteros a elementos individuales y punteros a arrays. La declaracion de unpuntero a un char y otro a un arreglo de char es igual.

Al definir variables o arrays hemos visto que el tipo modifica la cantidad de bytes que se usaranpara almacenar tales elementos, asi un elemento de tipo char utiliza 1 byte, y un entero 4. No ocurre lomismo con los punteros, el tipo no influye en la cantidad de bytes asociados al puntero, pues todas lasdirecciones de memoria se pueden expresar con solo 4 bytes de memoria.

El lenguaje C provee rutinas especiales para operar con memoria dinamica:

malloc(). Con esta funcion se solicita memoria en tiempo de ejecucion para una variable dinamica.Con malloc se indica la cantidad de bytes para esa variable.

free(). Para liberar la memoria que ocupaba una variable una vez que se ha dejado de utilizar. Esconveniente y recomendable utilizar esta funcion para procurar dejar siempre memoria disponibleen el heap del proceso, debido a que este generalmente es de capacidad limitada.

Por ejemplo:

NODO *ptr;

ptr = (type *) malloc(sizeof(type));

....

free((type *)ptr);

El tamano en bytes se especifica con el operador: sizeof(type);

Veamos ahora los efectos de un codigo como el siguiente, en la area de almancenamiento de datos:

char s[] = "hola";

char *point;

p = &c; /*Puntero ’p’ apunta a k */

57

El operador & permite obtener la direccion de una variable. Por ejemplo:p = &v;

almacena la direccion de la variable v y la almacena en la variable puntero p.El operador ∗ permite accesar el contenido de la variable puntero, ası, para asignar un valor a la

variable apuntada por p, podrıamos escribir la siguiente instruccion:

p = (int *)malloc(sizeof(int));

*p = 5;

6.2. Opciones de inicializacion.

6.2.1. Inicializacion Estatica.

Un puntero puede ser inicializado con la direccioon de memoria de un elemento, ese elemento debepertenecer al mismo tipo con el cual fue definido el puntero. Puede tratarse de la direccion de un elementode un array o de una variable simple, el operador & antepuesto a una variable devuelve su direccion dememoria.

Tambien puede utilizarse un ”literal” (un valor constante), ya sea numerico, de caracter, o de otrotipo, y puede inicializarse como puntero nulo.

Suponiendo un tipo ”T” cualquiera, las siguientes inicializaciones son validas:

Puntero inicializado Declaracion e Declaracion ea partir de inicializacion juntas inicializacion separadas

Un objeto individual T∗ ptr = &x; T∗ ptr;T x; ptr = &x;

Un array de objetos T∗ ptr = &x[0]; T∗ ptr;T x[10]; ptr = &x[0];

T∗ ptr = x; T∗ ptr;ptr = x;

Otro puntero del T∗ ptr = x; T∗ ptr;mismo tipo ptr = x;

T* x;Valor 0 = puntero nulo T∗ ptr = 0; T∗ ptr;

Null=0 ptr = 0;T∗ ptr = NULL; T∗ ptr;

ptr = NULL;Una literal T∗ ptr = [literal] T∗ ptr;

ptr = [literal];

Cuadro 6.1: Inicializaciones validas de un puntero.

Sobre la tabla del cuadro 6.1 caben las siguientes aclaraciones:

1. Inicializar un puntero apuntando al primer elemento de un array admite dos notaciones equivalentes,en la segunda se sobreentiende que el elemento apuntado es el primer elemento del array.

2. La equivalencia entre el valor 0 (cero) y NULL es de uso general, sin embargo existen compiladoresque dan a NULL un valor diferente a cero.

3. Un ’literal’ debe ser el apropiado para el tipo de puntero inicializado. Si es un puntero a char, unstring de caracteres cualquiera (ej: ”hola”) sera un literal adecuado, si se trata de tipo numerico,puede ser por ejemplo ”100”.

Si tomamos como ejemplo el tipo ”char”, siguiendo al cuadro anterior, tenemos las siguientes opcionesde inicializacion:

58

Puntero inicializado Declaracion e Declaracion ea partir inicializacion inicializacion

de: conjunta separadaUn elemento char∗ p = &ch; char∗ p;

char ch; p = &ch;Un array char∗ p = cad; char∗ p;

char cad[10]; p = cad;char∗ p = &cad[0]; char∗ p;

p = &cad[0];Valor 0 = puntero nulo char∗ p = 0; char∗ p;

Null=0 p = 0;(0 es el unico valorentero que puede

inicializar un puntero)char∗ p = NULL; char∗ p;

p = NULL;Otro puntero (ya inicializado) char∗ p = ptr; char∗ p;

char ∗ptr; p = ptr;Un literal de cadena char∗ p = ”hola”; char∗ p;

”hola”; p = ”hola”;

Cuadro 6.2: Tomando como ejemplo el tipo char.

Ya se ha dicho antes que un puntero almacena como valor una direccion de memoria, por eso lapresencia de un ”literal”, en la tabla 6.2 con el string ”hola”, puede sorprendernos. Es importante tenerclaro que todos los literales se almacenan en un lugar del segmento de datos, no es posible obtener sudireccion (por medios normales), pero existe.

Un literal es tratado como constante, esto es lo que permite que una funcion pueda retornar unaconstante sin temor a que dicho almacenamiento se pierda al salir de la funcion, un literal no es unavariable local. No hay obstaculos para inicializar un puntero con una variable constante, por lo tanto lomismo se aplica a literales de cualquier tipo.

Las tablas anteriores no abarcan todos los casos posibles de inicializacion de punteros, aun no se hanmencionado los casos donde el puntero apunta a una funcion o un objeto miembro de una clase, ni laopcion de inicializar a traves de memoria dinamica.

6.2.2. Inicializacion a traves de memoria dinamica.

Esta modalidad se diferencia de todas las enumeradas hasta ahora y puede considerarse como laprincipal. Todas las formas vistas hasta aquı asignan al puntero la direccion de memoria de otra entidad(elemento, array, puntero, literal) ademas del caso especial del valor NULL. La declaracion de un punterono implica la reserva de memoria, salvo 4 bytes para almacenar una direccion.

La reserva de memoria dinamica requiere el uso obligado de un puntero, el cual apuntara al primerbyte de esa area de memoria.

Reserva LiberacionElemento individual T∗ ptr = (T ∗)malloc(sizeof(T)); free(ptr);

de tipo ’T’Array de ’n’ elementos T∗ ptr = (T ∗)malloc(sizeof(n∗T)); free(ptr);

de tipo ’T’

Cuadro 6.3: Reserva y Liberacion de memoria.

A traves de la funcion malloc solicitamos una cierta cantidad de memoria dinamica, es posible que noexista suficiente memoria disponible, en tal caso, malloc nos devolvera un puntero NULL (o apuntando

59

a 0), y es por esta razon que luego de una solicitud es recomendable inspeccionar si el puntero devueltoes nulo.

6.2.3. Asignacion de punteros.

Un puntero puede ser asignado a otro puntero del mismo tipo a traves del operador ”=”. El significadode tal asignacion es similar al de una asignacion entre variables, el valor almacenado en el elemento dela derecha se copia en el elemento de la izquierda. Solo que en el caso de punteros, este valor es unadireccion de memoria, y esto puede producir un efecto distinto al esperado.

void f (char* str1, char* str2) {

str1 = str2;

*str1 = ’3’; /*Efecto: modificacion de string "s2".*/

/*.................................*/

}

char *s1 = "1111";

char *s2 = "2222";

f(s1, s2); /*Llamada a la funcion f();*/

La funcion ”f()” recibe dos punteros a char desde otra funcion, sigue luego una asignacion de ”str2” en”str1”, y una modificacion de un char a traves de desreferenciacion del puntero. Si la intencion era copiarel contenido del string original ”s2” en el string ”s1”, para modificar ”s1” sin alterar ”s2”, estamos anteun error. El caracter ’3’ se copiara en el string original ”s2”, por la razon de que luego de la asignacionde punteros (str1=str2) ambos apuntan a ”s2”.

Hay casos donde puede ser util que dos punteros apunten a una misma direccion, y entonces sera co-rrecto asignar punteros mediante el operador ”=”, pero si lo que se busca es copiar el contenido de unarray, entonces se debe hacer de otro modo, copiando uno a uno los elementos de dicho array.

Dados dos punteros (pt1 y pt2) a array, que apuntan a direcciones diferentes (son dos arrays diferen-tes), los efectos de una asignacion de punteros y copia de array son los siguientes:

Operacion Efectopt1 = pt2; Asignacion de punteros. Lo que se copia

realmente son los 4 bytesde direccion de memoria. El puntero pt1 deja

de apuntar a al array original,ahora apunta a la misma direccion que pt2.

while (∗ pt2 != 0) ∗pt1 = ∗pt2; Copia de array. La copia se realiza elemento porelemento. Se copian tantos elementos como caracteres

tenga el array pt2. En el caso de una cadena decaracteres, podemos confiar en el ’\0’ para saber

cuantos elementos copiar.

Cuadro 6.4: Asignacion de Punteros.

Es muy importante diferenciar ambas operaciones. Un array no puede ser copiado mediante el ope-rador de asignacion ”=”, hay que copiar elemento por elemento, un puntero puede ser copiado con taloperador, pero el efecto provocado puede ser distinto al efecto deseado.

La confusion entre copia de punteros y copia de array puede provocar otro tipo de problemas enrelacion a memoria dinamica.

6.2.4. Mas sobre punteros a caracteres y string.

Si tenemos las siguientes declaraciones:

60

char ∗s;char buffer[100];

las instrucciones:s = &buffer[0];y s = buffer;

son equivalentes. Ambas apuntan al inicio del string buffer. En forma similar:s = &buffer[4];ys = buffer + 4;asignan a s la direccion del cuarto elemento del arreglo buffer. Para declarar una variable constantepodemos realizar la siguiente instruccion:

char *cp = "Hola, queaalt " ;

Ası tambien buffer[i];Es equivalente a ∗(s + i);

Los operadores de autoincremento (++s o s++) y los de autodecremento (–s o s –) pueden ser utili-zados en variables de tipo puntero.

Ejemplos. Ilustraremos el uso de punteros, para manipular un string:

#include <stdio.h>

#define MAXBUF 512

char *mensaje="este es el mensaje\0";

char *s;

main(int argc, char *argv[]) {

int i;

for (i=0; mensaje[i] != ’\0’; i++)

printf("%c",mensaje[i]);

printf("\n1"); /*salto de l\’{\i}nea*/

printf("%s\n",mensaje);

s = &mensaje[0];

printf("\n2"); /*salto de l\’{\i}nea*/

for (i=0; s[i] != ’\0’; i++)

printf("%c",s[i]);

printf("\n3"); /*salto de l\’{\i}nea*/

for (i=0; *(s + i) != ’\0’; i++)

printf("%c",*(s+i));

printf("\n4"); /*salto de l\’{\i}nea*/

for (;*s != ’\0’; *s++)

printf("%c",*s);

printf("\n5"); /*salto de l\’{\i}nea*/

/*hasta aqui, el puntero queda apuntando al caracter de fin de string*/

61

s = &mensaje[0];

while ( *s != ’\0’ )

printf("%c",*s++);

printf("\n6\n"); /*salto de l\’{\i}nea*/

s = mensaje;

while ( *s != ’\0’ )

printf("%c",*s++);

printf("\n7\n"); /*salto de l\’{\i}nea*/

}

6.2.5. Ejemplos.

Los siguientes ejemplos, muestran el uso de punteros para manejar arreglos:

Asignando la direccion del arreglo a un puntero:

#include<stdio.h>

#define MAXFIL 10

#define MAXCOL 10

int matriz[MAXFIL][MAXCOL];

main(int argc, char *argv[]) {

int i,j;

int *p;

for (i=0; i<10; i++)

for (j=0; j<10; j++)

matriz[i][j] = i*j;

p = &matriz[0][0];

for (i=0; i < 10; i++) {

for (j=0; j < 10; j++)

printf("%i ",*(p+i+j));

printf("\n");

}

}

lo mismo sobre un vector:

#include<stdio.h>

#define MAXFIL 10

int vector[MAXFIL];

main(int argc, char *argv[]) {

int i;

int *p;

p = &vector[0];

printf("Direcci\’on inicial de p = %d\n",p);

/*for (i=0; i<10; i++)

*(p + i) = i << 2;*/

for (i=0; i < 10; i++) vector[i] = i;

62

/*vector[i] = i << 2;*/

for (i=0; i < 10; i++)

printf("%d\n",vector[i]);

printf("\n");

for (i=0; i < 10; i++) {

printf("%d\n",*p);

p++; /*que es lo mismo que *p = *p + 4;*/

}

}

Un matriz de dos dimensiones de tipo entero, donde es posible accesarla traves de un ciclo. Lamatriz tambien puede ser de tipo float.

#include<stdio.h>

#define MAXFIL 10

#define MAXCOL 10

int matriz[MAXFIL][MAXCOL];

main(int argc, char *argv[]) {

int i,j;

int *p;

for (i=0; i<10; i++)

for (j=0; j<10; j++)

matriz[i][j] = 100;

/* p = &matriz[0][0];

for (i=0; i<100; i++) {

*p= 100;

p++;

}*/

p = &matriz[0][0]; /* Ojo con esto!!! */

for (i=0; i < 100; i++) {

printf("%d ",*p); /* printf("%d ",*p++);*/

if ( (i > 0) && ((i+1) % 10 == 0) ) printf("\n");

p++;

}

printf("\n");

printf("Imprimiendo por segunda vez la matriz\n");

for (i=0; i<10; i++) {

for (j=0; j<10; j++)

printf("%d ",matriz[i][j]);

printf("\n");

}

}

Ejemplo utilizando punteros para manejar matrices de enteros de 3x3. Aquı tambien se accesa lamatriz a traves de un solo ciclo.

#include<stdio.h>

#define MAXFIL 2

63

#define MAXCOL 2

#define MAXZ 2

int matriz[MAXFIL][MAXCOL][MAXZ];

main(int argc, char *argv[]) {

int i,j,k;

int *p;

for (i=0; i<2; i++)

for (j=0; j<2; j++)

for (k=0; k<2; k++)

matriz[i][j][k] = 100;

/* p = &matriz[0][0][0];

for (i=0; i<8; i++) {

*p= 100;

p++;

}*/

/*Aqu\’{\i} es se accesa la matriz a trav\’es de un solo ciclo*/

p = &matriz[0][0][0]; /* Ojo con esto!!! */

for (i=0; i < 8; i++) {

printf("%d ",*p); /* printf("%d ",*p++);*/

p++;

/* Esto demuestra, que los elementos de las tres dimensiones del

* arreglo son adyacentes en la memoria*/

}

printf("\n");

printf("Imprimiendo por segunda vez la matriz de la forma

convencional\n");

for (i=0; i<2; i++) {

for (j=0; j<2; j++)

for (k=0; k<2; k++)

printf("%d ",matriz[i][j][k]);

printf("\n");

}

}

Sumando matrices de forma mas eficiente, a traves de punteros y usando un algoritmo de orden n(O(n)).

#include<stdio.h>

#define MAXFIL 10

#define MAXCOL 10

int matrizA[MAXFIL][MAXCOL], matrizB[MAXFIL][MAXCOL],

matrizC[MAXFIL][MAXCOL];

main(int argc, char *argv[]) {

int i;

int *p,*q,*r;

p = &matrizA[0][0];

q = &matrizB[0][0];

64

r = &matrizC[0][0];

for (i=0; i<100; i++) /*Llenando la matriz A*/

*p++ = 100;

for (i=0; i<100; i++) /*Llenando la matriz B

*q++= 200;

p = &matrizA[0][0];

q = &matrizB[0][0];

for (i=0; i < 100; i++)

*r++ = *p++ + q++; /* Sumando A y B para obtener C*/

printf("\n");

printf("Imprimiendo matriz resultado ... \n");

r = &matrizC[0][0];

for (i=0; i<100; i++) {

printf("%d ",*r++);

if ( i % 10 == 0 ) printf("\n");

}

printf("\n");

}

6.3. Punteros a void.

Un puntero puede apuntar a un objeto de cualquier tipo, predefinido por el lenguaje o definido porel usuario.

void es el nombre de un tipo, pero su uso esta sujeto a mayores restricciones respecto a otros tipos.El termino void puede ser usado como tipo de una funcion o de un puntero, pero no para declarar

un objeto. Las declaraciones posibles de tipo void, en C y C++, se resumen en el siguiente cuadro.

Declaraciones C C++a objetos tipo void

Ejemplo: void xNo permitido. Mensaje de error: objeto

de sizeof desconocido.El compilador no esta en condiciones

de determinar la cantidad dememoria que requiere el elemento.

Retorno de unafuncion

Ej. void func() {... Significa que la funcion no

retorna ningun valor(tipo pseudodevuelto).

Puntero a voidEj. void ∗ p; Un puntero a void es tratado Puntero a objeto de tipo desconocido.

como un puntero a char. Requiere conversion explicita a otro tipoConversion implıcita antes de ser utilizado.

Cuadro 6.5: Asignacion de Punteros.

En C se accede a bytes no vinculados a ningun tipo mediante punteros a char, esto es natural si seconsidera que un char ocupa 1 byte de almacenamiento, y de ahı que exista conversion implıcita entreambos tipos.

La funcion C standard ”memset()”, retorna un puntero a void. El siguiente codigo es aceptable en C:

void f(char* cad, char ch, int n){

65

char* s;

s = memset(cad,ch,n); //conversion implicita de void* a char*

} //valido en C.

6.4. Punteros y const.

Un puntero implica la intervencion de dos elementos: el puntero y el objeto apuntado (salvo que seanulo). La palabra reservada ”const” puede tener dos significados diferentes segun el lugar que ocupe enla declaracion, haciendo constante al puntero o al objeto apuntado.

1. Puntero constante. El operador ∗const, en lugar de ∗ solo, declara al puntero como constante, estosignifica que la direccion a la que apunta el puntero no puede cambiar en todo el programa. Lavariable apuntada sı puede cambiar. Ejemplo:

int a = 5;

int *const ptr = &a; //Puntero constante a int

*ptr = 4; //Bien, se modifica la variable

ptr = NULL; //Error, intento de modificar el puntero constante

Al no poder modificar la direccion a la que apuntan, estos punteros se aproximan al sentido quetiene una referencia.

2. Puntero a constante. Aquı el termino ”const” afecta al tipo al que apunta el puntero.

int a = 5;

const int* ptr = &a;

ptr = NULL; //Bien, el puntero puede cambiar, ser reasignado

*ptr = 6; //Error. No se puede cambiar el objeto apuntado.

Es importante observar que en el ejemplo la variable ”a” no fue declarada originalmente como ”const”,pero el puntero la toma como ”constante”. En este sentido, aunque la variable no puede ser modificadaa traves de ese puntero, sı podrıa serlo a traves de otro identificador, el propio nombre de la variable uotro puntero que no apunte a const.

El siguiente cuadro resume la sintaxis de punteros y ”const”:

Entidad Ejemplo ComentarioPuntero constante int ∗const ptr = &a; Puntero constante, no puede

modificar la direccion a la queapunta.

Puntero a const const int* ptr = &a; Puntero que apunta a const. No puede modificarse elint const* ptr = &a; objeto apuntado a traves de ese puntero.

Puntero que apunta a const. No puede modificarse elobjeto apuntado a traves de ese puntero.

Notacion alternativa para puntero a const.Puntero const

a const const int ∗const ptr = &a; No puede modificarse la direccionapuntada ni el objeto

a traves de ese puntero.

Cuadro 6.6: Sintaxis y punteros a const.

66

6.5. Puntero nulo (”Null pointer”).

Algunos autores definen a este puntero como ”aquel que no apunta a ningun sitio” y otros como ”unpuntero que no apunta a ningun objeto”. La segunda definicion es mas clara, mientras que la primerapuede introducir alguna confusion. De hecho no esta claro que podrıa significar que ”no apuntar a nigunsitio”. El concepto de ”puntero nulo” existe por la necesidad practica de hablar de un puntero que noesta ligado a ningun objeto.

La localidad de memoria donde esta el puntero contiene siempre algun valor (!no existen celdas vacias!cero es un valor!). Un puntero apunta a una direccion, la indicada por el valor que almacena, por lo tantoes logico concluir que un puntero siempre apunta a algun sitio.

Lo que distingue a un puntero nulo no es que ”no apunte a ningun sitio” sino que apunta a algunalocalidad de memoria que, por convencion del compilador utilizado, no puede estar asociada a ningunobjeto o variable.

Existe otro concepto que no debe confundirse con el de puntero nulo, el de ”wild pointer”. Un punteronulo apunta a un sitio bien determinado, en cambio un ”wild pointer” puede estar apuntando a cualquiersitio, una direccion indeterminada dentro del segmento de datos.

6.6. Puntero a puntero.

Un puntero almacena la direccion de un objeto, puesto que ese objeto puede ser otro puntero, esposible declarar un puntero que apunta a otro puntero. La notacion de puntero a puntero requiere de undoble asterisco, ”∗∗”, la sola notacion suele generar un efecto de confusion considerable.

Sin embargo, como se vera, el concepto en sı mismo no es complejo. La relacion entre una variablecomun, un puntero y un puntero a puntero se muestra en las siguientes lıneas:

int a = 4;

int* pt1 = &a;

int** pt2 = &pt1;

Por un lado tenemos el valor que almacena la variable ”a”, el puntero ”pt1” almacena la direccionde esa variable, y el puntero ’pt2’ almacena la direccion del puntero ”pt1”.

Son tres identificadores, cada uno tiene un doble aspecto: la localidad de memoria donde se asienta,y el valor que almacena en esa localidad de memoria.

Declaracion e Direccion de memoria Valor que almacena eninicializacion (hipotetica) tal direccion de memoria

int a = 4; 0xfff6 4int∗ pt1 = &a; 0xfff4 0xfff6

int∗∗ pt2 = &pt1; 0xfff2 0xfff4

Cuadro 6.7: Contenidos de localidades de memoria.

Es interesante comprobar las diferentes salidas en pantalla de ”pt2” en los siguientes casos:

printf("%x",pt2); //Imprime la direccion del propio puntero ’pt2’, aqui: "0xfff2"

printf("%x",*pt2); //Imprime la direccion almacenada en ’pt2’, "0xfff4"

printf("%i",**pt2); //Imprime el valor almacenado en ’*pt1 = a’, "4".

El comportamiento de la salida en pantalla es coherente, pues se cumplen las siguientes igualdades:

*pt2 == pt1; //Desreferenciacion de ’pt2’

*(*pt2) == *(pt1); //Aplicamos ’*’ a ambos lados

*pt1 == a; //De esto y la linea previa se deduce...

**pt2 == a; //...esta igualdad

67

Leanse las anteriores lıneas como ”igualdades” (comparaciones que dan ”Verdadero”) y no comoasignaciones.

La estrecha relacion existente entre los conceptos de puntero y array, es la razon de que el dobleasterisco (∗∗) pueda ser interpretado indistintamente como puntero a puntero, o bien como un array depunteros.

6.7. Puntero a funcion.

Es posible tomar la direccion de una funcion y asignarla a un puntero. Una funcion tiene una direccion,esta se encuentra dentro del segmento de codigo y marca el comienzo del codigo para esa funcion.

Un puntero a funcion se declara especificando el tipo devuelto y los argumentos aceptados por lafuncion a la que apunta, estos dos elementos del puntero deben coincidir con los de la funcion.

La sintaxis para estos punteros es la siguiente:

FUNCION Type Nombre (argumento/s)devuelto

Ejemplo: int f1 (int,char∗);PUNTERO A FUNCION Type (∗Nombre) (argumento/s)

devueltoEjemplo: int (∗pf) (int, char∗);

Incializacion de puntero pf = &f1; (Las dos formas estana funcion pf = f1; bien)

Cuadro 6.8: Inicializacion de un puntero a una funcion.

La sintaxis de una expresion como:

int (*pf) (int, char*);

puede resultar poco obvia, a veces es comodo definir un tipo (con typedef) para simplificar las declara-ciones. Tambien puede declararse un array de punteros a funcion (todas deben coincidir en tipo devueltoy parametros), un ejemplo de ambos recursos se ve a continuacion:

typedef void (*pmenu) ();

pmenu Archivo [] = {&Abrir, &Guardar, &Cerrar, &Salir};

En primer lugar definimos un tipo, que es un puntero a funciones. Ese tipo nos permite inicializarotros punteros a funciones, en este caso un array de punteros a funciones.

Invocar un puntero a funcion no requiere de desreferenciacion y es muy similar a un llamado comunde funcion. Su sintaxis es:

Nombre_de_funcion (argumento/s); //Puntero a funcion

Nombre_de_funcion [indice] (argumento/s); //Para un array de punteros a funcion

Un puntero a funcion solo puede ser inicializado utilizando la direccion de una funcion, debe existirconcordarncia entre funcion y puntero respecto a tipo devuelto y argumentos. Se trata de un punteroespecial, no requiere almacenamiento extra de memoria y no debe ser utlizado para aritmetica de pun-teros, solo para almacenar la direccion de una funcion, por medio de la cual esta sera llamada.

El siguiente ejemplo esta estructurado a traves de diferentes archivos fuentes. Aquı tenemos unaforma de invocar a una funcion a traves de un puntero.

/* El archivo head.h */

#ifndef _OBJ_H

#define _OBJ_H

68

typedef struct obj {

int dim;

void (*function)(int *, int *, int);

}clase_matriz;

#endif

/* El archivo ini.c */

#include "head.h"

#define MAXCOL 10

#define MAXFIL 10

extern void suma_matrices(int *, int *, int);

inicia(clase_matriz *c) {

c->dim = MAXFIL*MAXCOL;

c->function = (void* )suma_matrices;

}

/* El archivo matriz.c */

#include <stdio.h>

#define MAXFIL 10

#define MAXCOL 10

int matrizC[MAXFIL][MAXCOL];

void suma_matrices(int *p, int *q, int dim) {

int i;

int *r;

r = &matrizC[0][0]; /*porque requiere memoria para matriz resultado*/

for (i=0; i < dim; i++)

*r++ = *p++ + *q++;

printf("\n");

printf("Imprimiendo matriz resultado ... \n");

r = &matrizC[0][0];

for (i=0; i < dim; i++) {

printf("%d ",*r++);

if ( (i>0) && ((i+1) % 10 == 0) ) printf("\n");

}

printf("\nTamao matriz = %d",sizeof(matrizC));

}

/* El archivo main.c */

/* Ejemplo utilizando punteros para manejar matrices de forma mas eficiente */

#include <stdio.h>

#include "head.h"

#define MAXFIL 10

69

#define MAXCOL 10

int matrizA[MAXFIL][MAXCOL],matrizB[MAXFIL][MAXCOL],matrizC[MAXFIL][MAXCOL];

void inicia(clase_matriz *c);

main(int argc, char *argv[]) {

int i;

int *p,*q;

clase_matriz *m;

m = (clase_matriz *)malloc(sizeof(clase_matriz));

m->dim = MAXFIL*MAXCOL;

p = &matrizA[0][0];

q = &matrizB[0][0];

for (i=0; i<100; i++)

*p++ = 100;

for (i=0; i<100; i++)

*q++= 200;

p = &matrizA[0][0];

q = &matrizB[0][0];

/* Inicializa la estrucura */

inicia(m);

/*Realiza llamada a funcin correspondiente*/

(m->function)(p,q,m->dim);

}

/*Como ejercicio, escribir un programa que multiplique dos matrices

cuadradas, pero manipulando sus elementos con punteros */

/* El Makefile para compilar */

#

all: mat

.SUFFIXES:

.SUFFIXES: .o .c

.c.o .s.o:

gcc -c $<

mat: matriz.o ini.o main.o

gcc -o $@ matriz.o ini.o main.o

clean:

rm -f *.o mat *~

70

Capıtulo 7

Funciones.

7.1. Introduccion.

El lenguaje C proporciona funciones o subrutinas las cuales son similares a como se utilizan enotros lenguajes. Un programa en C puede estar compuesto por al menos una funcion. Esta funcion sedenomina main(), y siempre debe estar presente debido a que desde ahı es donde debe comenzar laejecucion del programa. El kernel del sistema operativo posee una funcion especial que llama a main()para iniciar la ejecucion del programa.

C es distinto a Pascal, debido a que este lenguaje define otro tipo de elementos que se denominaprocedimiento. El lenguaje C solo utiliza funciones.

Veamos un ejemplo que permite devolver o retornar la suma de dos numeros enteros:

int calcular_suma(int a, int b)

suma = a + b;

return suma;

}

o tambien de esta otra forma:

int calcular_suma(int a, int b) {

return (a + b);

}

main()

}

En C, una funcion es el equivalente a un procedimiento o funcion en Pascal. Una funcion es unacoleccion de sentencias que ejecutan una tarea especıfica y proporciona una forma conveniente de en-capsular algunos calculos que se pueden emplear despues sin preocuparse de su implementacion. Con lasfunciones es posible ignorar como se hace un trabajo; es suficiente saber que hace. Es mas facil trabajarcon piezas pequenas que con una grande. Ejemplo:

#include <stdio.h>

int power(int m, int n);

void main() {

int i;

for (i=0; i<10; ++i)

printf("%d %4d %6d\n",i,power(2,i), power(-3,i));

}

71

0 1 11 2 -32 4 93 8 -274 16 815 32 -2436 64 7297 128 -21878 256 65619 512 -19683

int power(int base, int n) {

int i, p=1;

for (i=1; i<=n; ++i)

p = p * base;

return p;

}

Este programa cuenta con una funcion llamada power que eleva la base a la n- esima potencia. Paraeste caso las bases son 2 y -3. El resultado en pantalla es:

7.2. Declaracion de una funcion.

Como podemos observar en la segunda lınea del programa, se encuentra declarada la funcion powerque debe coincidir con la definicion y su uso. A esta declaracion se le llama funcion prototipo, los nombresde los parametros no necesitan coincidir, de hecho son optativos. De modo que el prototipo se pudo haberescrito como:

int power(int, int)

Entonces, la declaracion de una funcion tiene el siguiente formato:

tipo retornonombre funcion([listadetiposdeargumentos]);

7.3. Definicion de una funcion.

Una definicion de funcion consta de una cabecera de una funcion y del cuerpo de la funcion encerradoentre llaves. Esta tiene la siguiente forma:

tipo retorno nombre funcion([declaracion de parametros]) { declaracion de variables localessentencias

[return (expresion);]}

Las variables locales declaradas dentro del cuerpo de la funcion solamente pueden utilizarse dentrodel mismo.

El tipo retorno especifica que tipo de datos retorna la funcion que puede ser cualquier tipo funda-mental o tipo definido por el usuario pero no puede ser un array o una funcion. Este valor es devuelto ala sentencia de llamada, por medio de la sentencia:

72

return (expresion);

Esta sentencia puede ser o no la ultima, y puede aparecer mas de una vez en el cuerpo de la funcion.En el caso de que la funcion no retorne un valor, se omite.

Los parametros de una funcion son variables que reciben los valores de los argumentos en la llamadaa la funcion; consiste en una lista de identificadores con sus tipos separados por comas.

7.4. Llamada a una funcion.

7.4.1. Para ejecutar una funcion hay que llamarla.

La llamada a una funcion consta del nombre de la misma y de una lista de argumentos separados porcoma (,). Ejemplo:

B = power(3,4);

7.4.2. Pasando Argumentos a Funciones.

Al llamar a una funcion, el valor del primer argumento es pasado al primer parametro y el valor delsegundo argumento es pasado al segundo parametro y ası sucesivamente, todos los argumentos exceptolos arrays son pasados por valor. Es decir, a la funcion se pasa una copia del argumento no su direccion,esto hace que no se puedan alterar los contenidos de las variables pasadas.

Se desea poder alterar los contenidos de los argumentos en la llamada, entonces hay que pasarlos porreferencia. Por tanto, a la funcion se pasa la direccion del argumento y no su valor por lo que el parame-tro correspondiente tiene que ser un puntero. Para pasar la direccion de un argumento utilizaremos eloperador &.

Ejemplo:

#include <stdio.h>

void intercambio(int *,int *); // funcion prototipo

void main() {

int a=20, b=30;

intercambio(&a,&b); // a y b son pasados por referencia

printf("a es %d y b es %d\n",a,b);

}

void intercambio(int *x,int *y) {

int z = *x; // z = contenido de la direccion de x

*x = *y; // cont. de x = cont. de y

*y = z; // cont. de y = z

}

7.4.3. Funciones void.

Este tipo de funciones permiten emular a los procedimientos de lenguajes como Pascal, debido aque no retornan ningun valor. Este tipo de funciones se define de la siguiente forma:

void nombre_funcion(...)

Por tanto, en este tipo de funciones el comando return no es necesario.

void squares(int value)

printf("%d\n",value*value);

}

73

desde la funcion main() se invoca de la siguiente forma:

main() {

squares(10);

}

7.5. Funciones y Arrays

Cuando pasamos un array como parametro en realidad estamos pasando un puntero al primer ele-mento del array, ası que las modificaciones que hagamos en los elementos del array dentro de la funcionseran validos al retornar.

Sin embargo, si solo pasamos el nombre del array de mas de una dimension no podremos acceder alos elementos del array mediante subındices, ya que la funcion no tendra informacion sobre el tamanode cada dimension. Para tener acceso a arrays de mas de una dimension dentro de la funcion se debedeclarar el parametro como un array. Ejemplo:

#include <stdio.h>

#define N 10

#define M 20

void funcion(int tabla[][M]);

/* Se debe destacar que el nombre de los parametro en el

prototipo es opcional, la forma:

void funcion(int [][M]);

es tambien es valida*/

int main() {

int Tabla[N][M];

...

funcion(Tabla);

...

return 0;

}

Antes de utilizar una funcion, C debe tener conocimiento acerca del tipo que la funcion retornay los tipos de parametros que la funcion recibe. El estandar ANSI de C introduce una nueva y mejorforma de hacer esto.

El prototipo es importante debido a que:

Esto hace mas estructurado y por tanto mas facil de leer el codigo.

Esto permite que el compilador de C chequee la sintaxis de las llamadas a funciones.

Como se hace esto?, depende del scope (ambito o alcance) de la funcion. Basicamente si algunasfunciones fueron definidas antes que estas sean utilizadas (llamadas) entonces no es necesario definirsus prototipos al inicio del programa. En caso contrario, se debe declarar las funciones al principio delprograma. La declaracion simplemente establece el tipo que la funcion retorna y el tipo de sus parametros,sin necesidad de la funcion, simplemente se establece el tipo que la funcion retorna, el nombre de la funciony la lista del tipo de los parametros, en el mismo orden que aparecen en la definicion de la funcion.Por ejemplo:

int calcular_suma(int, int);

Si la funcion recibe como parametro un string:

int strlen(char []);

74

Capıtulo 8

Procesos en Unix

8.1. Creacion de procesos utilizando la funcion fork()

.Es la forma ma natural con la cual Unix crea nuevos procesos (procesos hijos). Con fork(), el

espacio de direcciones del proceso padre, quien invoca este comando, es clonado para formar el espaciode direcciones del hijo.

Utilizando primitivas para obtener el PID del proceso actual y el PID de su padre. Ademas, se utilizala llamada WEXITSTATUS para obtener el valor retornado por exit(). El proceso se bloqueara (pasara aestado sleep) hasta que uno de los hijos termine su ejecucion.

Forma general:

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status)

#include<stdio.h>

#include <sys/types.h>

#include <sys/wait.h>

int count=10;

void Hijo1();

void Hijo2();

void Hijo3();

int *valor;

main() {

int pid1,pid2,pid3;

int status;

printf("Proceso Padre con PID = %d\n",getpid());

printf("Mi Padre tiene PID = %d\n",getppid());

pid1 = fork();

if ( pid1 == 0 ) Hijo1();

printf("Creando Proceso Hijo2\n");

if ( (pid2 = fork()) == 0 ) Hijo2();

printf("Creando Proceso Hijo3\n");

75

if ( (pid3 = fork()) == 0 ) Hijo3();

pid1 = wait(&status);

printf("Proceso Hijo %d termino primero\n",WEXITSTATUS(status));

printf("Termino proceso padre\n");

exit(0);

}

void Hijo1() {

printf("Proceso Hijo1 con PID = %d\n",getpid());

while ( count-- != 0 ) {

sleep(1);

printf("Proceso Hijo1\n");

}

exit(1);

}

void Hijo2() {

printf("Proceso Hijo2 con PID = %d\n",getpid());

while ( count-- != 0 ) {

sleep(1);

printf(" Proceso Hijo2\n");

}

exit(2);

}

void Hijo3() {

printf("Proceso Hijo3 con PID = %d\n",getpid());

while ( count-- != 0 ) {

sleep(1);

printf(" Proceso Hijo3\n");

}

exit(3);

}

8.2. Terminacion de procesos hijos, utilizando la llamada wait-pid.

Un proceso padre esperara que el hijo especificado por pid termine. En este caso el padre puedecontinuar corriendo si el hijo especificado aun no ha terminado, en este caso, waitpid recibe el parametroWNOHANG (asıncrono); o espera hasta que termine, donde waitpid recibe el parametro WUNTRACED.Forma general: pid twaitpid(pid tpid, int ∗ status, intoptions);

#include<stdio.h>

#include <sys/types.h>

#include <sys/wait.h>

int count=10;

void Hijo1();

76

void Hijo2();

void Hijo3();

main() {

int pid1,pid2,pid3,pid;

int status,opt;

printf("Proceso Padre con PID = %d\n",getpid());

printf("Mi Padre tiene PID = %d\n",getppid());

pid1 = fork();

if ( pid1 == 0 ) Hijo1();

printf("Creando Proceso Hijo2\n");

pid2 = fork();

if ( pid2 == 0 ) Hijo2();

pid3 = fork();

printf("Creando Proceso Hijo3\n");

if ( pid3 == 0 ) Hijo3();

printf("Esperando a Proceso Hijo1\n");

/*waitpid(pid1,status,WNOHANG);*/

pid = waitpid(pid1,&status,WUNTRACED);

printf("Proceso Hijo1 termino con pid = %d\n",pid);

printf("Proceso Hijo1 termino con estado = %d\n",WIFEXITED(status));

printf("Esperando a Proceso Hijo2\n");

/*waitpid(pid2,status,WNOHANG);*/

pid = waitpid(pid2,&status,WUNTRACED);

printf("Proceso Hijo2 termino con estado =%d\n",WEXITSTATUS(status));

if ( !WIFSTOPPED(status) )

printf("Proceso Hijo2 no esta detenido\n");

printf("Esperando a Proceso Hijo3\n");

/*waitpid(pid3,status,WNOHANG);*/

pid = waitpid(pid3,&status,WUNTRACED);

printf("Proceso Hijo3 termino con estado =%d\n",WEXITSTATUS(status));

printf("Termino proceso padre\n");

exit(0);

}

void Hijo1() {

printf("Proceso Hijo1 con PID = %d\n",getpid());

while ( count-- != 0 ) {

sleep(1);

printf("Proceso Hijo1\n");

}

exit(0);

}

void Hijo2() {

77

printf("Proceso Hijo2 con PID = %d\n",getpid());

while ( count-- != 0 ) {

sleep(1);

printf(" Proceso Hijo2\n");

}

exit(20);

}

void Hijo3() {

printf("Proceso Hijo3 con PID = %d\n",getpid());

while ( count-- != 0 ) {

sleep(1);

printf(" Proceso Hijo3\n");

}

exit(35);

}

8.2.1. Descripcion de llamadas al sistema wait y waitpid.

Definicion:

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status)

pid_t waitpid(pid_t pid, int *status, int options);

Descripcion:La funcion wait suspende la ejecucion del proceso actual hasta que uno de sus hijos termine su

ejecucion o si una senal fue enviada al proceso para que termine con su ejcucion. Si el proceso hijo yahabıa terminado, el padre retorna de inmediato de wait(). Cualquier recurso ocupado por el hijo, esliberado inmediatamente.

La funcion waitpid suspende la ejecucion del proceso actual hasta que el hijo espeificado por elargumento PID haya finalizado con su ejecucion, o hasta que una senal sea enviada al proceso. Si el hijoya habıa terminado, el proceso padre retorna inmediatamente.

El valor de options es un OR de cero o mas de las siguientes constantes:

WNOHANG: significa retornar inmediatamente si el hijo aun no ha terminado.

WUNTRACED: significa retornar cuando el hijo se ha detenido o terminado, o cuyo estado no fuereporteado.

El estado de un proceso puede ser evaluado con las siguientes macros (que accesan el inodo a travesde un buffer de tipo stat como argumento, y no como puntero).

WIFEXITED(status): es distinto de cero si el hijo retorno en forma normal.

WEXITSTATUS(status): evalua los 8 bits menos significativos del codigo de retorno del hijo quetermino de ejecutarse.. Este codigo de retorno es el argumento entregado a exit() (dentro del codigodel hijo). Esta macro funciona bien siempre que WIFEXITED retorno un valor distinto de cero.

WIFSIGNALED(status): retorno verdadero si el proceso hijo termino debido a que una senal queno fue atrapada.

WTERMSIG(status): retorna el numero de la senal que causo que el hijo termino.

WIFSTOPPED(status): retorna verdadero si el hijo que cauo el retorno esta detenido. Solo esposible si fue hecho usando WUNTRACED.

78

WSTOPSIG(status): retorna el numero de la senal que causo que el hijo terminara. Esta macrosolo es posible utilizarla si WIFSTOPPED retorno un valor distinto de cero.

8.3. Comandos de la familia exec.

Con esta llamada al sistema, el binario del proceso que realiza la llamada, es reemplazado por unbinario o ejecutable nuevo, dentro del mismo espacio de direcciones. Se pueden distinguir cuatro saboresdistintos de este comando:

#include <unistd.h>

int execl( const char *path, const char *arg, ...);

int execlp( const char *file, const char *arg, ...);

int execv( const char *path, char *const argv[]);

int execvp( const char *file, char *const argv[]);

Las funciones de la familia exec reemplazan la imagen del proceso actual con la imagen del nuevoproceso. Los segmentos de texto (codigo ejecutable), datos, bss, y la pila del proceso actuales son reescritoso reemplazados por los del nuevo proceso. El proceso invocado hereda el PID del proceso antiguo. Lassenales pendientes del proceso antiguo, seteadas o eliminadas.

El primer argumento de estas funciones es el pathname o ruta del archivo ejecutable.El argumento const char *arg y los siguientes argumentos en las funciones execl, execlp y execle

pueden ser vistos como arg0, arg1, ... ,argn. En conjunto, ellos describen una lista de punteros a stringque representan la lista de argumentos disponible para el programa a ejecutar. El primer argumento, porconvencion, deberıa ser el nombre del archivo que contiene el ejecutable. La lista de argumentos debeterminar con NULL.

Las funciones execv y execvp proporcionan un arreglo de punteros a strings que representan la lista deargumentos disponibles para el nuevo programa. El primer argumento, por convencion, deberıa apuntaral nombre del archivo con el ejecutable. El arreglo de punteros debe terminar con NULL.

En las funciones execl y execv, se debe proporcionar el pathname completo o absoluto, para indicarla ubicacion del archivo ejecutable. No ası con las funciones execlp y execvp, donde solo es necesarioproporcionar el nombre o pathname relativo del comando o archivo ejecutable, y este sera ubicado deacuerdo a la informacion que la variable de ambiente PATH proporcione.

8.3.1. La funcion execl.

#include<stdio.h>

main() {

execl("/usr/bin/netscape","netscape",NULL,NULL);

printf("Sere el mismo Proceso con PID = %d??\n",getpid());

exit(0);

}

8.3.2. La funcion execlp.

#include<stdio.h>

main(int argc, char *argv[]) {

status = execlp("ls","ls","-la",NULL);

if ( status == -1 )

printf("Proceso con PID = %d y estado = %d\n",getpid(),status);

exit(0);

}

79

8.3.3. La funcion execv.

#include<stdio.h>

main(int argc, char *argv[]) {

execv("/usr/bin/netscape",NULL);

printf("Sere el mismo Proceso con PID = %d\n",getpid());

exit(0);

}

8.3.4. La funcion execvp.

Caso a: insertando los argumentos en un arreglo.

#include<stdio.h>

#include <string.h>

main(int argc, char *argv[]) {

int status;

char *arg[4];

arg[0] = (char *)malloc(sizeof(char));

strcpy(arg[0],"ls");

arg[1] = (char *)malloc(sizeof(char));

strcpy(arg[1],"-l");

arg[2] = (char *)malloc(sizeof(char));

strcpy(arg[2],"-a");

arg[3] = NULL;

printf("Proceso Padre con PID = %d\n",getpid());

printf("Mi Padre tiene PID = %d\n",getppid());

status = execvp("ls",arg);

if ( status == -1 )

printf("Proceso con PID = %d y estado = %d\n",getpid(),status);

exit(0);

}

Caso b: el comando y los argumentos son ingresados desde la lınea de comandos.

#include<stdio.h>

main(int argc, char *argv[]) {

int pid1,pid2,pid3;

int status;

printf("Proceso Padre con PID = %d\n",getpid());

printf("Mi Padre tiene PID = %d\n",getppid());

if ( argc < 2 ) {

printf("Debe escribir: ./execlp arg (donde arg es 1 a 3)\n");

exit(-1);

}

status = execvp(argv[1],&argv[1],NULL);

if ( status == -1 )

printf("Proceso con PID = %d y estado = %d\n",getpid(),status);

exit(0);

}

80

8.4. Creacion de procesos utilizando la funcion vfork.

Aquı el kernel no realiza una copia del espacio de direcciones del padre, si no que tanto el padrecomo el hijo, comparten el mismo espacio de direcciones.

#include<stdio.h>

#include <sys/types.h>

#include <sys/wait.h>

int count=5;

void Hijo1();

void Hijo2();

void Hijo3();

main() {

int pid1,pid2,pid3;

int status;

printf("Proceso Padre con PID = %d\n",getpid());

printf("Mi Padre tiene PID = %d\n",getppid());

pid1 = vfork();

if ( pid1 == 0 ) Hijo1();

printf("Creando Proceso Hijo2\n");

if ( (pid2 = vfork()) == 0 ) Hijo2();

printf("Creando Proceso Hijo3\n");

if ( (pid3 = vfork()) == 0 ) Hijo3();

pid1 = wait(&status);

printf("Proceso Hijo %d termino ultimo\n",WEXITSTATUS(status));

printf("Termino proceso padre\n");

exit(0);

}

void Hijo1() {

printf("Proceso Hijo1 con PID = %d\n",getpid());

while ( count-- != 0 ) {

sleep(1);

printf("Proceso Hijo1\n");

}

exit(1);

}

void Hijo2() {

printf("Proceso Hijo2 con PID = %d\n",getpid());

count = 5;

while ( count-- != 0 ) {

sleep(1);

printf(" Proceso Hijo2\n");

}

exit(2);

}

void Hijo3() {

count = 5;

printf("Proceso Hijo3 con PID = %d\n",getpid());

while ( count-- != 0 ) {

sleep(1);

printf(" Proceso Hijo3\n");

81

}

exit(3);

}

82

Capıtulo 9

Comunicacion entre Procesos.

9.1. Tipos de Comunicaciones.

9.1.1. Mensajes.

Los sistemas de mensajes permiten en general que cuando tenemos un conjunto de procesos queno comparten memoria, ya sea que se ejecuten en la misma maquina o en una red, puedan comunicarseusando algun sistema de paso de mensajes. Tambin podemos usar un sistema de mensajerıa en ambientesde memoria compartida para sincronizar el acceso a secciones crıticas de procesos livianos (threads).

En un ambiente de comunicacion siempre existe un emisor y un receptor. Para que el emisor y receptorpuedan comunicarse, los sistemas de mensajes deben proporcionar primitivas que permita al emisor enviarun mensaje y al receptor recibir el mensaje. Cada un de ellos, emisor y receptor debera contar ademscon algun buffer interno donde alojar el mensaje que se envıa o se recibe.

Los sistemas de mensajerıa pueden ser clasificados de la siguiente forma:

9.1.2. Comunicacion directa.

El emisor envıa directamente el mensaje a un receptor determinado, con una identificacion es-pecıfica. Por lo tanto, el mensaje lleva consigo la identificacion del receptor.

Este tipo de sistema de mensajes se implementa generalmente en ambientes con memoria compartida,donde el identificador del proceso receptor puede ser un puntero a su PCB o descriptor de proceso, o suPID (process identification).

Forma general:

send(dest pid, msg)

Para que un proceso receptor pueda recibir varios mensajes de distintos procesos emisores, se imple-menta una cola de mensajera donde se encolan los PCB de los procesos emisores. Estos procesos emisoresencolados quedan en estado SLEEP hasta que el receptor los despierte y atienda sus mensajes..

9.1.3. Comunicacion indirecta.

En este caso el emisor no maneja la identificacion del proceso receptor, sino que usa algn canal decomunicacion ya sea por ejemplo un pipe (tuberıa) para comunicacion unidireccional, streams para co-municacion bidireccional, o un puerto de comunicacion, donde el emisor podrıa especificar otro canalpara recibir la respuesta.

Forma general:

send(chan id,msg);

83

Buffering.

Los sistemas de mensajes se agrupan segun la cantidad de mensajes que se pueden enviar:

1. 0-buffering: Al enviar un mensaje, el emisor se queda bloqueado hasta que:

a) El receptor reciba el mensaje (con receive).

b) El receptor responda el mensaje (con reply).

Al segundo tipo de mensajes corresponde nSystem y los sistemas de RPC o Remote ProcedureCall. La comunicacion del tipo 0-buffering tambien se denomina sıncrona.

2. buffer acotado: Los mensajes se copian en un buffer de tamano finito de modo que el emisor puedecontinuar y enviar nuevos mensajes. Si el buffer esta lleno, el emisor se bloquea hasta que algunproceso receptor desocupe parte del buffer. Este tipo de comunicacion se denomina asıncrona. Alenviar un mensaje, el solo hecho de que send retorne no implica que el mensaje fue recibido. Si elemisor necesita conocer cuando un mensaje fue recibido, debe esperar un mensaje de vuelta (unreconocimiento) del receptor.

Emisor:

send(dest pid,msg);ack= receive(dest pid);

Receptor:

msg= receive(&dest pid);send(dest pid, ack);

3. buffer ilimitado: Como el caso anterior, solo que el buffer es de tamano infinito. Esta clasificacionsolo sirve para fines teoricos, pues en la practica no existe.

Bibliotecas de paso de mensajes tales como MPI (Message Passing Interface) implementan tantocomunicacion sıncrona como asıncrona para comunicar y coordinar procesos ejecutandose en ambientesdistribuidos.

9.2. Ejemplos de Mecanismos de Comunicacion en ProcesosUnix.

9.2.1. Pipe’s.

Una forma de comunicar a dos procesos pero que pertenecen a una misma familia, es mediante unpipe. Un pipe es una abstraccion de un archivo que permite un flujo de datos en orden firts-in, firts-out.El pipe se utiliza como un buffer circular. Generalmente, la comunicacion se lleva a cabo en una direc-cion, ası fueron implementados originalmente. Hoy en dıa existen otras formas de implementacion depipes que permiten comunicacion bidireccional mediante un descriptor de archivo, o tambien mediantesockets. Tanto los pipes como los sockets son abstracciones proporcionadas por el modulo IPC (InterProcess Communication) del Kernel de Unix/Linux.

Un pipe es creado utilizando la llamada al sistema pipe(), el cual retorna un descriptor para lectura,y otro para escritura. Un proceso que crea un pipe puede pasar estos descriptores a sus descendientesa traves de fork(), ası , este comparte el pipe con ellos (pero solo para aquellos descendientes que soncreados despues del pipe). Un pipe puede tener multiples productores y consumidores (en realidad, larelacion o el papel que juegan los procesos a traves de un pipe, es de productores y consumidores, y no delectores y escritores, como algunos autores anotan). Cuando ya no queda ningun productor o consumidor,el kernel elimina el pipe automaticamente.

Cuando un proceso productor escribe algo sobre el pipe, lo hace en un extremo de este, mientras que

84

el consumidor lo saca desde el otro extremo en orden FIFO. El arreglo de dos enteros que recibe pipe seusa para dejar el descriptor en el extremo de lectura (desc[0]) y el de escritura (desc[1]).

Una vez creado el pipe se escribe y lee desde el con las funciones usuales de entrada/salida (read/write).Cuando ya no se va a usar mas, se cierran los extremos con close.

Un read desde un pipe vacıo queda bloqueado hasta que hayan nuevos datos.Un write en un pipe lleno (tamano maximo predefinido, por ejemplo: 4096 bytes), queda bloqueado

hasta que haya espacio.Una restriccion importante, es que el pipe debe ser creado por un ancestro comun a los proceso, si

estos quieren comunicarse.Generalmente el kernel define un parametro llamado PIPE BUF de 5120 bytes por defecto, lo cual

limita la cantidad de datos que el pipe puede mantener.En aplicaciones tıpicas tales como el shell el uso mas comun de un pipe es hacer que la salida de un

programa se trasforme en la entrada de otro. Comunmente, se relacionan dos programas por medio deun pipe utilizando el operador ’—’ del shell. Por ejemplo, en los siguiente comandos:

$ps -aux | sort y

$ls -l | more

Existen varias formas de implementar un pipe. La forma tradicional (en SVR2, por ejemplo) es usarlos mecanismos del sistema de archivos y asocia un inode y una entrada en la tabla de archivos paracada pipe. Muchas variantes basadas en BSD usan sockets para implementar un pipe. En cambio, SVR4proporciona pipe’s basados en STREAM bidireccionales.

Ejemplo: el uso de pipes:

#include <stdio.h>

#define READ 0

#define WRITE 1

int count = 20;

void Productor(int fd);

void Consumidor(int fd);

int main() {

int desc[2];

pipe(desc);

if ( fork() != 0 )

Productor( desc[WRITE] );

else

Consumidor( desc[READ] );

}

void Productor(int fd) {

int x;

while ((count--) != 0) {

x = rand() % 100;

write(fd,&x,sizeof x);

printf("%s %i","Valor producido> ",x);

printf("\n");

sleep(1);

}

exit(0);

}

void Consumidor(int fd) {

85

int x;

while ((count--) != 0) {

read(fd, &x, sizeof x);

printf("%s %i"," Valor leido> ",x);

printf("\n");

}

exit(0);

}

9.2.2. Sockets.

Un socket es una interfaz de comunicacion que permite que dos o mas procesos se comuniquen enforma local (procesos que corren en una misma maquina), o en forma remota a traves de la red. Unsocket es tambien un objeto abstracto que un proceso puede utilizar para enviar y recibir mensajes. Lasllamadas para manejar sockets se ejecutan en el contexto de un proceso.

Los sockets pueden ser utilizados en uno de los siguientes dominios:

AF INET : que permiten la comunicacion mediante una red, tal como TCP/IP.

AF UNIX: que permite la comunicacion en forma local, es decir, en el mismo sistema.

Otros (que no interesan en este curso).

La figura 9.1 muestra la forma de como Unix implementa los sockets. Se distinguen claramente elespacio de direcciones del usuario y el espacio del kernel. En el espacio de usuario, la aplicacion hace usode las librerıas o bibliotecas, las que son incluidas durante el proceso de compilacion, carga y ejecucion.

Figura 9.1: Implementacion de sockets en Unix..

Tipos de sockets en los dominios AF INET y AF UNIX.

Existen varios tipos de sockets:

1. Stream socket: proporciona una comunicacion bidireccional, confiable, secuenciada, y no duplicadade flujo de datos. Estos utilizan el protocolo de transporte TCP.

86

2. Datagram socket: soporta un flujo bidireccional de datos que no se asegura que sea secuenciada,confiable (pueden tener errores), o incluso duplicada. Es decir, un proceso puede recibir mensajesduplicados , y posiblemente, en un orden distinto con el cual fueron enviados. Utilizan el protocolode comunicacion UDP.

3. Raw socket: normalmente orientados a datagramas, pero proporcionan principalmente facilida-des para el desarrollo de nuevos protocolos de comunicaciones o aprovechar las servicios de otrosprotocolos.

Byte Order.

Existen dos formas en las que un sistema puede almacenar y manejar informacion, los cuales sonllamados: network byte order y host byte order, como se muestra en la figura 9.2. Estos esquemas dealmacenamiento estan relacionados con el orden en que los bytes estan almacenados en una palabra dememoria. Una maquina puede disponer los bytes de informacion en una palabra de memoria de una dedos formas, conocidas como big-endian y little-endian. La forma big-endian dispone en las direccionesmas bajas de la palabra de memoria los bytes de mayor orden o mas significativos (este corresponde alnetwork byte order), mientras que en la forma little-endian, las direcciones mas bajas de la palabra dememoria contienen los bytes de menor orden.

Esto depende del microprocesador que se esta utilizando, se podrıa estar programando en un sistemahost byte order o network byte order, pero cuando se envıan los datos por la red, los datos deben ir enun orden especificado, sino los datos llegarıan en un orden distinto al definido por la maquina.

Los protocolos de TCP/IP resuelven el problemas de byte-order definiendo un estandar llamado net-work byte order que todas las maquinas deben utilizar para los campos en los paquetes de internet. Cadahost o router convierte los elementos binarios desde la representacion local al estandar de red antes deenviar los paquetes, y convertirlos desde network byte order al orden especıfico del host local.

El estandar internet para un byte order especıfico de un entero, envıa primero los bytes mas signifi-cativo (es decir, en la forma big endian).

Figura 9.2: Orden de los bytes.

Funciones de conversion.

Para realizar las conversiones necesarias, se pueden utilizar las siguientes funciones:

1. htons(): host to network short, que convierte un short int de host byte order a network byte. order.

87

2. htonl(): host to network long, que convierte un long int de host byte order a network byte order.

3. ntohs(): network to host short, que convierte un short int de network byte order a host byte order.

4. ntohl(): network to host long, que convierte un long int de network byte order a host byte order.

Ejemplo:

#include <netinet/in.h>

port = htons(3490); /*Se convierte a network byte order el numero de puerto a utilizar*/

Creacion de un socket.

Los sockets se crean llamando a la funcion socket(). Esta funcion retorna un descriptor de socket, quees de tipo int.

#include <sys/types.h>

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

Si hay algun error en la creacion del socket, la funcion devuelve -1. Los parametros que recibe lafuncion son:

domain: dominio donde se realizara la conexion.

type: tipo de socket: SOCK STREAM , SOCK DGRAM o SOCK RAW .

protocol: 0 (cero selecciona el protoclo apropiado).

Por ejemplo:

int sock;

sock = socket(AF_INET,SOCK_STREAM,0);

Si se especifica el protocolo como cero, el sistema selecciona el protocolo apropiado de uno de aquellosdisponibles (definidos en /etc/protocols, en Linux), dependiendo del socket requerido.

Si se desea especificar el protocolo, entonces se puede hacer de la siguiente forma:

struct protoent *protocolo

int sock;

protocolo = getprotobyname("tcp");

sock = socket(AF_INET, SOCK_STREAM, protocolo->p_proto);

La funcion bind(),

Cuando el socket se crea, se hace sin nombre, por tanto, se debe asignarle uno para poder recibirconexiones. La funcion bind() se utiliza para darle un nombre al socket, es decir, una direccion IP y unnumero de puerto del host local por donde va a escuchar. Slo cuando se esta programando un servidor,se hace necesario llamar a la funcion bind().

Forma general:

#include <sys/types.h>

#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

88

donde:

sockfd: descriptor del socket devuelto por la funcion socket.

my addr: es un puntero a una estructura sockaddr que contiene la direccion IP del host local y elnumero de puerto que se va a asignar al socket.

addrlen: Debe ser establecido al tamano de la estructura sockaddr. sizeof(struct sockaddr).

Ejemplo:

struct sockaddr_in sin;

bind(sock, (struct sockaddr *)&sin, sizeof(sin));

Estructuras de datos definidas por el kernel.

struct sockaddr {

unsigned short sa_family; /* AF_* */

char sa_data[14];

};

struct in_addr {

unsigned long s_addr; /*4 bytes*/

};

struct sockaddr_in {

short int sin_familiy; /*AF_INET*/

unsigned short sin_port; /*N\’umero de puerto */

struct in_addr sin_addr; /*Direcci\’on IP*/

unsigned char sin_zero[8];

};

La estructura sockaddr almacena la direccion de protocolo para varios tipos de protocolos.

sa family puede ser AF INET, AF UNIX u otro dominio.

sa data contiene la direccion IP y el numero de puerto asignado al socket.

La estructura sockaddr in fue creada para el caso internet, para poder referenciar a los elementos deforma mas simple. Los punteros a la estructura sockaddr in deben ser precedidos con un cast tipo ∗structsockaddr antes de pasarlos como parametros a funciones.

La funcion listen().

Es llamada desde la parte servidor de una conexion. Solo se aplica a sockets del tipo SOCK STREAM.Su forma general es la siguiente:

#include <sys/socket.h>

int listen(int sock, int backlog);

donde:

sock: es el descriptor de socket devuelto por la funcion socket().

backlog: Es el numero maximo de conexiones en la cola de entrada de conexiones o tamano maximode la cola. Las conexiones de entrada quedan en estado de espera en esta cola hasta que sonaceptadas con la funcion accept().

89

La funcion accept().

Se utiliza en el lado servidor pata aceptar conexiones por medio de un socket ya habilitado. Estafuncion retorna un nuevo descriptor de socket al recibir la conexion del cliente en el puerto configurado.

Forma general:

#include <sys/types.h>

#include <sys/socket.h>

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

donde:

sock: es el descriptor de socket.

addr: es un puntero a una estructura de tipo sockaddr in. Aquı se almacena la informacion dela conexion entrante. Se utiliza para determinar que host esta llamando y desde que numero depuerto.

addrlen: debe establecer el tamano de la estructura sockaddr, con sizeof(struct sockaddr).

La funcion accept() no escribira mas de addrlen bytes en addr. Si escribe menos de esa longitud debytes, entonces modifica el valor de addrlen a la cantidad de bytes realmente escritos.Ejemplo:

int sock, new_sock;

struct sockaddr_in my_addr;

struct sockaddr_in remote_addr;

int addlen;

/*crea el socket*/

sock = socket(AF_INET, SOCK_STREAM, 0);

/∗ Se le asigna un numero de puerto al socket por donde el servidor va a escuchar. Antes de llamara bind(), se debe asignar los valores a my addr.∗/

bind(sock, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

/*Se habilita el socket para recibir conexiones*/

listen(sock,5);

addrlen = sizeof(struct sockaddr);

/*Luego, se llama a accept() y el servidor duerme esperando conexiones*/

new_sock = accept(sock, &remote_addr, &addrlen);

La funcion connect().

Inicia la conexion con el servidor remoto. Esta funcion debe ser invocada desde el lado cliente.Forma general:

#include <sys/types.h>

#include <sys/socket.h>

int connect(int sock, const struct sockaddr *serv_addr, socklen_t addrlen);

donde:

90

sock: es el descriptor de socket.

serv addr: es una estructura de tipo sockaddr. Aquı se almacena la direccion IP y numero de puertodestino.

addrlen: debe establecer el tamano de la estructura sockaddr, con sizeof(struct sockaddr).

La funcion send().

Despues de establecer la conexion, se puede comenzar con la transferencia de datos. Esta funcion seutiliza para enviar informacion hacia algun destinatario (es equivalente a la funcion write()).

Forma general:

#include <sys/types.h>

#include <sys/socket.h>

int send(int sock, const void *msg, size_t len, int flags);

int sendto(int sock, const void *msg, size_t len, int flags,

const struct sockaddr *to, socklen_t tolen);

donde:

sock: es el descriptor de socket.

msg: puntero a los datos que seran enviados.

len: longitud de los datos a ser enviados.

flags: ver man 2 send.

Generalmente, con send() el proceso emisor se bloquea (para comunicacion sı ncrona), a menos queel socket haya sido definido para realizar I/O no bloqueante (en comunicacion asıncrona).

send() retorna la cantidad de datos enviados, la cual podra ser menor que la cantidad de datos quese escribieron en el buffer. send() enviara la maxima cantidad de datos que pueda manejar y retorna lacantidad de datos enviados. Es responsabilidad del programador comparar la cantidad de datos enviadoscon len y si no se enviaron todos los datos, enviarlos en la proxima llamada a send().

La funcion sendto() se utiliza para enviar datos sobre un socket datagram. Los otros parametros tieneel siguiente significado:

to: puntero a una estructura sockaddr que contiene la direccion IP y el numero de puerto destino.

tolen: Debe ser inicializado con el tamano de struct sockaddr.

sendto() tambien retorna el numero de bytes enviados, el cual puede ser menor que len, al igual queen send().

La funcion recv().

La funcion recv() (que es equivalente a la read()) permite recibir datos desde algun socket stream.Su forma general es la siguiente:

#include <sys/types.h>

#include <sys/socket.h>

int recv(int sock, void *buf, size_t len, int flags);

int recvfrom(int sock, void *buf, size_t len, int flags,

struct sockaddr *from, socklen_t *fromlen);

donde:

91

Cliente Servidorsocket() socket()

bind()listen()

connect() accept()send()/recv() recv()/send()

Cuadro 9.1: Protocolo que deben seguir cliente y servidor para una comunicacion TCP.

sock: es el descriptor de socket.

buf: puntero al buffer donde se almacenan los datos recibidos.

len: longitud de los datos recibidos.

flags: ver man 2 recv.

Si no hay datos que recibir en el socket, la llamada recv() no retorna y el proceso se bloquea (duermeplacidamente, hasta que el kernel lo despierte) hasta que lleguen datos. Esta funcion retorna la cantidadde bytes recibidos.

La funcion recvfrom() se utiliza para recibir datos de un socket datagram. El resto de los parametrostiene el siguiente significado:

from: puntero a una estructura sockaddr que contiene la direccion IP y el numero de puerto delhost de origen de los datos.

fromlen: Debe ser inicializado con el tamano de struct sockaddr.

Si no hay datos que recibir desde el socket, el proceso se bloquea y la funcion recvfrom() no retornahasta que lleguen datos. Al igual que recv(), retorna el numero de bytes recibidos.

9.2.3. Pasos o etapas para establecer una conexion con socket.

Utilizando socket Stream.

A continuacion, se muestran los pasos que deben considerare para establecer una conexion utilizandosockets stream, desde el punto de vista del servidor y del cliente.

1. Tanto el cliente, como el servidor, deben crear un socket mediante la funcion socket().

2. El servidor llama a bind() para darle un nombre al socket, y luego poder recibir conexiones (por cualnumero de puerto). El cliente no necesita establecer un numero de puerto, porque no recibira in-tentos de conexion (a menos que la aplicacion juegue de las dos formas, es decir, como servidor ycomo cliente). El numero de puerto los establece el kernel, ya que es el quien tiene acceso exclusivoa este tipo recursos.

3. El servidor habilita su socket para poder recibir conexiones, mediante la funcion listen(), indicandoel tamano de la cola. El cliente no necesita llamar a esta funcion.

4. El servidor ejecuta la funcion accept() y queda en estado de espera. Esta funcion no retorna hastaque hasta un intento de conexion. Esta funcion retorna un nuevo descriptor de socket, el cual seutiliza para realizar la transferencia de datos por al red con el cliente.

5. Una vez establecida la conexion, se utilizan las funcione send () (write()) y recv() (read()) con eldescriptor de socket del paso anterior para realizar la transferencia de datos.

6. Para finalizar la conexion se utilizan las funciones close() o shutdown().

92

Cliente Servidorsocket() socket()

bind()sendto()/recvfrom() recvfrom()/sendto()

Cuadro 9.2: Protocolo que deben seguir cliente y servidor para una comunicacion UDP.

Utilizando sockets Datagram.

Ahora, veremos los pasos que son necesarios para establecer una conexion utilizando sockets data-gram, desde el punto de vista del servidor y del cliente.

1. Tanto el cliente, como el servidor, deben crear un socket mediante la funcion socket().

2. El servidor llama a bind() para establecer por cual numero de puerto recibira los datos, en estecaso no existe la conexion, porque los datos se envı an como si fueran mensajes. El numero depuerto los establece el kernel, ya que es el quien tiene acceso exclusivo a este tipo recursos.

3. Se utilizan las funcione send () (write()) y recv() (read()) con el descriptor de socket del pasoanterior para realizar la transferencia de datos.

9.2.4. Algunas estructuras de datos relacionadas con comunicaciones IPC.

Tomado del sistema operativo Linux.

/* Description of data base entry for a single host. */

struct hostent {

char *h_name; /* Official name of host. */

char **h_aliases; /* Alias list. */

int h_addrtype; /* Host address type. */

int h_length; /* Length of address. */

char **h_addr_list; /* List of addresses from name server. */

#define h_addr h_addr_list[0] /* Address, for backward compatibility. */

};

Tomados desde el sistema Solaris.

/*

* Structure used by kernel to store most

* addresses.

*/

struct sockaddr {

u_short sa_family; /* address family */

char sa_data[14]; /* up to 14 bytes of direct address */

};

/*

* Socket address, internet style.

*/

struct sockaddr_in {

short sin_family;

u_short sin_port;

struct in_addr sin_addr;

93

Figura 9.3: Servidor simple

char sin_zero[8];

};

9.2.5. La funcion select.

Una forma de lograr que un proceso se bloquee hasta que una operacion de I/O (o entrada y salida)este disponible desde un conjunto de descriptores de archivos, es a traves de la llamada al sistema select.El bloqueo del proceso se produce hasta que al menos uno de los elementos del conjunto de condicionessea verdadero. Esto se denomina sincronizacion OR. La condicion para este caso consiste en una entradadisponible en alguno de los descriptores.

La llamada al sistema select proporciona un metodo de monitoreo de descriptores de archivos atraves de una de tres posibles condiciones, es decir: que una lectura puede ser hecha sin bloqueo; queuna escritura puede ser hecha sin bloqueo; o que una condicion excepcional esta pendiente.

La forma general de la llamada select, es la siguiente:

#include <sys/time.h>

#include <sys/types.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,

fd_set *exceptfds, struct timeval *timeout);

94

Figura 9.4: Servidor concurrente

El primer parametro de select (nfds), es el numero de bits que seran chequeados en el conjunto dedescriptores de archivo, es decir, especifica el rango de descriptores de archivo. Este valor debe ser almenos uno mas que el descriptor de archivo mas grande que ha de ser chequeado. El parametro readfdsespecifica el conjunto de descriptores que seran monitoreados para lectura. De forma similar, writefdsespecifica el conjunto de descriptores a ser monitoreados para escritura, y exceptfds especifica a los des-criptores de archivos que seran monitoreados para casos de condiciones excepcionales. El conjunto dedescriptores, son del tipo fd set, el cual consiste de un arreglo de mascaras de bits, de tipo long int.

Cuando select retorna, este limpia todos los descriptores en cada conjunto readfds, writefds, y ex-ceptfds, excepto aquellos descriptores que esten listos. El numero entero que retorna select es el numerode descriptores de archivo que estan listos o -11, si hubo algun error. El ultimo parametro es un valor detiempo, utilizado para forzar un retorno desde select despues de de un cierto perıodo de tiempo aunqueno hayan descriptores listos. Cuando timeout es NULL, select se bloquea por tiempo indefinido. Si selectes interrumpido por una senal, este retorna -1 y fija la variable errno a EINTER.

Historicamente el conjunto de descriptores era implementado como una mascara de bits de tipo en-tero, pero esa implementacion no funciona para mas de 32 descriptores de archivos. Los conjuntos dedescriptores son representados ahora por campos de bits en arreglos de enteros, y las macros FD SET ,FD CLR, FD ISSET , y FD ZERO se utilizan para manipular los conjuntos de descriptores en unaforma que sea independiente de la implementacion. Estas macros tienen la siguiente definicion:

#include <sys/time.h>

95

#include <sys/types.h>

void FD_SET(int fd, fd_set *fdset);

void FD_CLR(int fd, fd_set *fdset);

int FD_ISSET(int fd, fd_set *fdset);

void FD_ZERO(fd_set *fdset);

Descripcion:

FD SET : inicializa los bits en fdset correspondiente al descriptor de archivo fd. Adhiere un nuevodescriptor fd al conjunto fdset.

FD CLR: limpia los correspondientes bits. Elimina el descriptor fd del conjunto fdset.

FD ISSET : se usa despues de select para testear si los bits correspondientes al descriptor dearchivo fd estan inicializados en la mascara fdset, es decir, permite indicar si el descriptor fdesta listo para una lectura, o escritura, o si tiene una excepcion pendiente en el conjunto fdset.

FD ZERO: limpia todos los bits en el conjunto fdset.

FD SETSIZE: fija el tamano del arreglo.

Ejemplo:

#include <stdio.h>

#include <string.h>

#include <sys/time.h<

#include <sys/types.h>

#include <errno.h>

fd_set readset;

int maxfd;

int pipe1;

int pipe2;

maxfd = pipe1;

if ( pipe2 > maxfd ) maxfd = pipe2;

for ( ; ) {

FD_ZERO(&readset);

FD_SET(pipe1, &readset);

FD_SET(pipe2, &readser);

if ( (select(maxfd+1, &readset, NULL, NULL, NULL) == -1 )

&& (errno != EINTER) )

perror("Select failed);

else {

if ( FD_ISSET(pipe1, &readset) ) {

/*leer y procesar pipe1 */

}

if ( FD_ISSET(pipe2, &readset) ) {

/*leer y procesar pipe2 */

}

}

}

Ejemplo de utilizacion de la funcion select() con tiempo limitado. El programa espera por 5 segundospara ver si hay datos desde la entrada estandar.

96

#include <stdio.h>

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

int main(void) {

fd_set rfds;

struct timeval tv;

int valret;

/* Mirar stdin (df 0) para ver si tiene entrada */

FD_ZERO(&rfds);

FD_SET(0, &rfds);

/* Esperar hasta 5 s */

tv.tv_sec = 5;

tv.tv_usec = 0;

valret = select(1, &rfds, NULL, NULL, &tv);

/* No confiar ahora en el valor de tv! */

if (valret)

printf("Los datos ya estn disponibles.\n");

/* FD_ISSET(0, &rfds) ser verdadero */

else

printf("Ningn dato en 5 segundos.\n");

return(0);

}

En este ejemplo, select se bloquea hasta que exista alguna lectura desde los descriptores pipe1 opipe2. Lo bueno de este metodo es que no gasta o no desperdicia tiempo de CPU.

Otro ejemplo, donde select es utilizado para monitorear la entrada desde la entrada estandar (teclado)y desde un socket, se muestra a continuacion. Este ejemplo sirve para implementar un programa telnetcliente:

#include <sys/types.h>

#include <sys/socket.h>

#include <sys/time.h>

#include <sys/signal.h>

#include <sys/errno.h>

#define STANDAR_READ 0

int rtelnet(char *host) {

int s, nfds; /* socket y descriptor de archivo*/

fd_set arfds, awfds, rfds, wfds;

FILE *sfp;

next_state = IS_DATA;

/*Estado inicial de la m\’aquina de estado finita de telnet*/

s = connectTcpTelnet(host);

/*pide una conexi\’on TCP para telnet, en el puerto 23 en la

m\’aquina host. Esta funci\’on hay que implementarla*/

97

nfds = getdtablesize();

/*obtiene el tama\~no de la tabla de descriptores*/

/*Ahora se inicializan las variables de tipo fd_set*/

FD_ZERO(&arfds); /*Para lectura*/

FD_ZERO(&awfds); /*Para escritura*/

/*Agrega los descriptores a los conjuntos correspondientes*/

FD_SET(s, &arfds); /* Incluye el descriptor de socket */

FD_SET(STANDAR_READ, &arfds);

/* Incluye el descriptor para la entrada est\’andar

(teclado) */

sfp = fdopen(s, "w");

/* Para conseguir salida buffereada */

while (1) {

/*Loop infinito para procesar datos de entrada desde teclado y socket*/

bcopy((fd_set *)&arfds, (fd_set *)&rfds, sizeof(fd_set));

/*Copia arfds en rfds*/

bcopy((fd_set *)&awfds, (fd_set *)&wfds, sizeof(fd_set));

/*Copia awfds en wfds*/

if (select(nfds, &rfds, &wfds, NULL, 0) < 0)

printf("select: %s\n", strerror(errno));

if (FD_ISSET(s, &rfds))

/*procesar entrada desde socket, que provienen desde el

servidor remoto*/

if (FD_ISSET(STANDAR_READ, &rfds))

/*procesar entrada desde teclado y enviar al

servidor*/

}

}

98

Capıtulo 10

Sockets en Java.

10.1. Introduccion.

En este lenguaje, tanto el servidor como los clientes, son objetos. Para definir un servidor, se debeutilizar un socket de la clase ServerSocket y en el lado cliente se utilizan sockets de la clase Socket. Elservidor tambien crea un socket de la clase Socket para atender a cada cliente, tal como se muestra enlas figuras 10.1 y 10.2:

1. Se establece la comunicacion.

2. El servidor crea un nuevo socket para realizar la comunicacion con el nuevo cliente.

La clase ServerSocket se utiliza para representar un socket servidor. El constructor ServerSocket() creaun socket servidor y lo enlaza al puerto local especificado. Las dos formas conocidas para el construcutores la siguiente:

public ServerSocket(int port) throws IOException

public ServerSocket(int port, int backlog) throws IOException

donde port es el numero de puerto, y backlog especifica el numero maximo de conexiones pendientes. Siport es cero, entonces el socket estara ligado a cualquier puerto disponible en el sistema. Para solicitaro definir un port, se debe utilizar el metodo getLocalPort() de la clase ServerSocket. Este metodo debeser utilizado de la siguiente forma:

SocketServer sockServer = null;

sockServer = new ServerSocket(0);

int port = sockServer.getLocalPort();

Tambien se puede especificar un numero de puerto disponible, de la siguiente forma:

sockServer = new ServerSocket(4312);

El servidor podra aceptar conexiones de clientes, a traves del metodo accept() (de la clase Server-Socket), de forma similar como se vieron los sockets en lenguaje C. Este metodo, obtiene la primeraconexion desde la cola de conexiones pendientes y crea (devuelve) un nuevo objeto socket (instancia dela clase Socket) que manejara esa conexion. Si no existen conexiones pendientes, entonces accept() sebloqueara hasta que llegue una nueva. El nuevo socket sera usado para leer y escribir datos desde y haciael socket del otro extremo de la comunicacion. El nuevo socket se cerrara cuando la conexion se terminey ya no sea usado para aceptar mas conexiones.

El metodo close() se usara para cerrar un socket. Para obtener la direccion a la cual el socket esta co-nectado, se debe utilizar el metodo getInetAddress(). Este metodo devuelve un puntero a un objeto dela clase InetAddress.

El metodo toString permite generar una representacion tipo string de un objeto de la clase Server-Socket, entregando como informacion la direccion de la maquina y el numero de puerto.

99

Cliente Servidor

Red

Socket

ServerSocket

Objeto ClienteObjeto Servidor

Figura 10.1: Estableciendo la conexion entre el cliente y el servidor.

Socket sockCli;

try {

sockServer = new ServerSocket( port );

} catch( IOException e ) {

e.printStackTrace();

}

En la implementacion de un servidor es necesario crear un objeto socket desde el ServerSocket paraque este atento a las conexiones que puedan solicitar los clientes y poder aceptar esas conexiones:

try {

sockCli = sockServer.accept();

} catch( IOException e ) {

e.printStackTrace();;

}

Si estamos programando un cliente, el socket se crea de la siguiente forma:

Socket sockCliente;

sockCliente = new Socket( "host", port );

Donde host es el nombre de la maquina en donde estamos intentando abrir la conexion y port es elnumero de puerto del servidor. Cuando se selecciona un numero de puerto, se debe tener en cuenta quelos puertos en el rango 0-1023 estan reservados para usuarios con muchos privilegios (superusuarios oroot). Estos puertos son los que utilizan los servicios estandar del sistema como email, ftp o http. Paralas aplicaciones que se desarrollen, asegurarse de seleccionar un puerto por encima del 1023.

por ejemplo: sockCliente = new Socket( "pebu.face.ubiobio.cl", 4312 );

tambien se puede utilizar el numero IP de la maquina.Es muy importante utilizar excepciones por si hay problemas en el momento de crear el socket; de

todas maneras, el compilador, reclamara las excepciones. El ejemplo anterior quedarıa como:

Socket sockCliente;

try {

100

Cliente Servidor

Red

Socket

ServerSocket

Objeto ClienteObjeto Servidor

accept()

Nuevo

Socket

Figura 10.2: El servidor crea un nuevo socket para realizar la comunicacion con el nuevo cliente.

sockCliente = new Socket( "host",port );

} catch( IOException e ) {

e.printStackTrace( );

}

10.1.1. Entrada y Salida en Sockets.

Creacion de Streams de Entrada.

En la parte cliente de la aplicacion, se puede utilizar la clase DataInputStream para crear un streamde entrada que este listo a recibir todos los mensajes desde el servidor.

DataInputStream input;

try {

input = new DataInputStream( sockCliente.getInputStream() );

} catch( IOException e ) {

e.printStackTrace();

}

La clase DataInputStream permite la lectura de lıneas de texto y tipos de datos primitivos de Javade un modo altamente portable; dispone de metodos para leer todos esos tipos como: read(), readChar(),readInt(), readDouble() y readLine(). Se debera utilizar la funcion adecuada, dependiendo del tipo dedato que se espera recibir del servidor.

En el lado del servidor, tambien se usan InputStream, pero en este caso para recibir las entradas quese produzcan de los clientes que se hayan conectado. En los ejemplos que se muestran mas adelante, seutilizan las clases InputStream y OuputStream para entrada y salida, respectivamente.

DataInputStream input;

try {

input = new DataInputStream( sockServer.getInputStream() );

} catch( IOException e ) {

e.printStackTrace();

}

101

Creacion de Streams de Salida.

En el lado del cliente, se puede crear un stream de salida para enviar informacion al socket servidor,utilizando las clases PrintStream o DataOutputStream:

PrintStream sending;

try {

sending = new PrintStream( sockCliente.getOutputStream() );

} catch( IOException e ) {

e.printStackTrace();

}

La clase PrintStream tiene metodos para la representacion textual de todos los datos primitivos deJava. Este proporciona los metodos write y println() para ello. Para el envıo de informacion al servidortambien se puede utilizar tambien la clase DataOutputStream:

DataOutputStream output;

try {

output = new DataOutputStream( sockCli.getOutputStream() );

} catch( IOException e ) {

e.printStackTrace();

}

La clase OutputStream permite escribir cualquiera de los tipos primitivos de Java, muchos de susmetodos escriben un tipo de dato primitivo en el stream de salida. De todos esos metodos, el mas utilquizas sea writeBytes().

En el lado del servidor, podemos utilizar la clase PrintStream para enviar informacion al cliente:

PrintStream salida;

try {

salida = new PrintStream( socketServicio.getOutputStream() );

} catch( IOException e ) {

System.out.println( e );

}

Pero tambien podemos utilizar la clase DataOutputStream como en el caso de envıo de informaciondesde el cliente.

10.1.2. Resumen de clases importantes.

Estas clases corresponden a implementaciones de la version Unix de Solaris (de Sun). Pero tambienvarias de ellas son conocidas en otras plataformas. Estas se encuentran definidas en el paquete sun.net.

1. Socket. Es el objeto basico en toda comunicacion a traves de Internet, bajo el protocolo TCP.Esta clase proporciona metodos para la entrada/salida a traves de streams que hacen la lectura yescritura a traves de sockets bastante simple.

2. ServerSocket. Es un objeto utilizado en las aplicaciones servidor para escuchar las peticiones querealicen los clientes conectados a ese servidor. Este objeto no realiza el servicio, sino que crea unobjeto Socket en funcion del cliente para realizar toda la comunicacion a traves de el.

3. DatagramSocket. La clase de sockets datagrama puede ser utilizada para implementar datagramasno fiables (sockets UDP), no ordenados. Aunque la comunicacion por estos sockets es muy rapidaporque no hay que perder tiempo estableciendo la conexion entre cliente y servidor.

4. DatagramPacket. Clase que representa un paquete datagrama conteniendo informacion del paque-te, longitud del paquete, direcciones Internet y numeros de puerto.

5. MulticastSocket. Clase utilizada para crear una version multicast de las clase socket datagrama.Multiples clientes/servidores pueden transmitir a un grupo multicast (un grupo de direcciones IPcompartiendo el mismo numero de puerto).

102

6. NetworkServer. Una clase creada para implementar metodos y variables utilizadas en la creacionde un servidor TCP/IP.

7. NetworkClient. Una clase creada para implementar metodos y variables utilizadas en la creacionde un cliente TCP/IP.

8. SocketImpl. Es un Interface que nos permite crear nuestro propio modelo de comunicacion. Ten-dremos que implementar sus metodos cuando la utilicemos.

Si vamos a desarrollar una aplicacion con requerimientos especiales de comunicaciones, como puedenser la implementacion de un firewall (por tanto, TCP es un protocolo no seguro), o acceder a equiposespeciales (como un lector de codigo de barras), necesitaremos nuestra propia clase Socket.

La clase SocketImpl, proporciona los siguientes metodos:

1. accept(): acepta una conexion para este socket.

2. bind(): enlaza el socket a un puerto local.

3. close(): cierra el socket.

4. connect(): conecta al socket a su destinatario.

5. create(): crea un nuevo socket no conectado.

6. listen(): escucha por las conexiones para un socket stream.

7. available(): determina el numero de bytes que pueden ser leıdos sin bloquearse.

8. getInputStream(): crea un input stream para el socket.

9. getOuputStream(): crea un output stream para el socket.

10. address: la direccion a la cual el socket esta conectado. Es una variable de instancia.

11. fd: el descriptor del socket. Es una variable de instancia.

12. getFileDescriptor(): obtiene el descriptor de archivo utilizado por el socket.

13. getInetAddress(): obtiene la direccion internet del socket.

14. getLocalPort(): obtiene el numero de puerto local.

15. getPort(): obtiene el numero de puerto remoto.

16. port: numero de puerto remoto del socket. Es una variable de instancia.

17. toString(): genera la representacion string del objeto.

La clase SocketImpl es usada por las clases Socket y ServerSocket.

10.1.3. Ejemplos.

Este ejemplo es la primera piedra para la siguiente tarea en Java. La idea es implementar una especiede chat (aplicacion tipo IRC), que permita la comunicacion de varios clientes que se sientan a conversar.Se debe implementar el lado cliente y el lado servidor.

El lado cliente debe presentar una interfaz grafica, que permita mostrar los mensajes recibidos por elservidor (que son en el fondo un eco de los mensajes proporcionados por los otros clientes conectados);otra seccion donde se coloca el nombre del usuario; y una seccion donde se escribe el texto del mensajeque va a enviar el usuario.

El servidor debe ser concurrente, y por tanto debe crear un thread por cada cliente que se conecte,y replicar los mensajes de cada cliente al resto de los clientes conectados.El Servidor.

103

//El Servidor. Desde aqui se debe comenzar con la implementacion del

//servidor. Este debe ser concurrente.

import java.awt.*;

import java.net.*;

import java.io.*;

class Servidor {

public static void main( String args[] ) {

ServerSocket sockServer = null;

Socket sockClient;

OutputStream output;

int lengthString;

String msg = "Estableciste la conexion con el servidor!!";

try { //crea un socket Servidor.

//El constructor recibe como parametro

//el valor cero, para solicitar al sistema un puerto.

sockServer = new ServerSocket(0);

//solicitamos un puerto

int port = sockServer.getLocalPort();

//Mostramos el numero de puerto por pantalla

System.out.println("Puerto --> " + port);

//Mostrar datos del servidor (direccion IP y puerto).

System.out.println(sockServer.toString());

} catch( IOException e ) {

e.printStackTrace();

}

while( true ) {

try {

// Espera para aceptar una conexion

sockClient = sockServer.accept();

// Obtiene un stream de salida para el socket cliente

output = sockClient.getOutputStream();

//Calculando longitud del string a enviar.

lengthString = msg.length();

// Enviando el texto

for( int i=0; i < lengthString; i++ )

output.write( (int)msg.charAt( i ) );

//Cierra la conexion con el cliente.

sockClient.close();

} catch( IOException e ) {

e.printStackTrace();

}

}

}

}

El Cliente.

import java.applet.Applet;

import java.awt.*;

import java.net.*;

import java.io.*;

104

public class Cliente extends Frame implements Runnable {

// parametros

String serv;

String sport;

int port;

// comunicacion

Socket client;

InputStream input;

OutputStream output;

// Interface grafica

Label mssg;

//TextField fieldClient;

TextArea areaServ; //Falta una area para el cliente

Button botEnd;

Thread thread;

Cliente(String argv[]) {

//Invocamos al constructor de la superclase Frame

super("Cliente del mini Chat");

//La Interfaz grafica

Panel PanelBoton = new Panel();

PanelBoton.setLayout(new GridLayout(1,1));

//Ubicamos en el sur del frame el panel de botones.

add("South",PanelBoton);

botEnd = new Button("Exit");

PanelBoton.add(botEnd);

//Para colocar los mensajes que el servidor nos envia, indicando

//la coordenada en donde se escribe.

areaServ = new TextArea("Mensajes del Servidor \n",14,30);

areaServ.setEditable(false);

//Ubicamos en el norte del frame al area Servidor.

add("North", areaServ);

//Intenta crear un socket cliente, y leer lo recibido

try {

Integer intSt = new Integer(argv[1]);

int port = intSt.intValue();

client = new Socket(argv[0],port);

//Se escribe un mensaje en el area de texto Servidor

areaServ.appendText("Se establecio conexion\n");

areaServ.appendText("-------------------------- \n");

//Stream de entrada (para recibir los mensajes del servidor)

input = client.getInputStream();

//Stream de salida (para enviar mensajes al servidor)

output = client.getOutputStream();

}catch (SocketException e) {

mssg.setText("Servidor desconocido " + serv);

}catch (IOException e) {

mssg.setText("No se pudo hacer I/O con "+serv);

}catch (NumberFormatException e) {

e.printStackTrace(); //Problemas con creaci\’on de objeto Integer()

105

}

//Thread para atender los mensajes del servidor

//Define salida del frame o ventana.

resize(500,600);

setBackground(Color.lightGray);

show();

//Thread para atender los mensajes del servidor

thread = new Thread(this);

thread.start();

}

public static void main(String args[]) {

//Para chequeo de cantidad de parametros leidos desde el shell.

if ( args.length < 2 ) {

System.out.println("Debe escribir: java Cliente <host> <# puerto>");

System.exit(0);

}

else new Cliente(args); //Crea, por fin, el objeto Cliente().

}

public void run() {

int c;

//Intenta accesar lo recibido del servidor e imprime en el AreaText

//de Servidor.

try {

while ( (c = input.read() ) != -1 ) {

areaServ.appendText( String.valueOf( (char)c ) );

}

}catch (IOException e) {

mssg.setText("Error en la lectura del Servidor \n");

}

}

//Para detectar acciones desde el mouse.

public boolean action(Event evt, Object arg) {

String str = new String();

if (arg == "Enviar") {

//Aqui va codigo para enviar mensajes al servidor.

}

else if ( arg == "Exit" ) {

System.exit(0); //Terminar con la ejecuci\’on de la aplicaci\’on.

}

return true;

}

//Aqui deben escribir el codigo para enviar mensajes al servidor.

public void sendString(String str) {

}

//Para cerrar la conexion y matar el thread que atiende conexion son el servidor.

public void destroy(){

thread.stop();

106

thread=null;

try {

mssg.setText("Cerrando conexion\n");

input.close();

}catch(IOException e){

mssg.setText("Error al tratar de cerrar la comunicacion");

}

}

107

108

Capıtulo 11

El Sistema de Archivos de Unix.

11.1. Introduccion.

Un sistema de archivos reside en un disco logico o tambien llamado particion, y cada disco logicopuede mantener a lo mas un sistema de archivos.

Cada sistema de archivos esta contenido por sı mismo, y completo con su propio directorio raız,subdirectorios, archivos, y todos los datos y metadatos asociados. El arbol de archivos visible al usuarioesta formado por la union de uno o mas de tales sistemas de archivos.

Una particion puede ser vista logicamente como un arreglo lineal de bloques. El tamano del bloquefısico de un disco es generalmente de 512 bytes multiplicado por alguna potencia de 2 (diferentes versioneshan usado tamanos de bloques de 512, 1024, y 2048 bytes).

El numero de un bloque fısico (o simplemente el numero de bloque) es un ındice dentro de este arreglo,y unicamente identifica un bloque en una particion dada de disco. Este numero debe ser traducido anumeros de cilindro, pista y sector.

Al comienzo de la particion se encuentra el area de booteo, el cual puede contener el codigo requeridopara el bootstrap (carga e inicializacion) del sistema operativo. Aunque solo una particion necesitacontener esta informacion, cada particion contiene posiblemente un area de booteo vacıa. El area debooteo esta seguido por el superblock que contiene atributos y metadatos del sistema de archivos. Lafigura 11.1 ilustra la estructura de un sistema de archivos:

Figura 11.1: Estructura del sistema de archivos.

A continuacion del superblock, se encuentra la lista de inodos (la inode list), la cual es un arreglolineal de inodes. Existe un inode por cada archivo. Cada inode puede ser identificado por un numero deinode, el cual es igual al ındice en la inode list. El tamano de un inode es de 64 bytes. El numero deinode puede ser traducido facilmente a un numero de bloque y el offset del inode desde el comienzo deese bloque. La tt inode list tiene un tamano fijo (configurado mientras se crea el sistema de archivos enesa particion), lo cual limita el numero maximo de archivos que la particion puede contener.

109

Campo bytes Descripcion.di node 2 Tipo de archivo, permisos, etc.di nlinks 2 numero de links duros a un archivo.di uid 2 UID del dueno.di gid 2 GID del dueno.di size 4 Tamano en bytes.di addr 39 arreglo de direcciones de bloques.di gen 1 numero de generacion (incrementa en 1 cada vez

que el i-node es reutilizado por un nuevo archivo)di time 4 tiempo de ultimo acceso.di mtime 4 tiempo de ultima modificacion.di ctime 4 tiempo en que el inode tuvo el ultimo cambio

(excepto para cambios de di time y di ctime).

Cuadro 11.1: La estructura i-node.

El campo o area a continuacion de la inode list es el area de datos. Este mantiene los bloques dedatos para archivos y directorios, ademas de bloques de archivos de datos.

11.2. Los directorios.

Un directorio, es un archivo especial que contiene una lista de archivos y directorios. Este contieneregistros de tamano fijo de 16 bytes cada uno. Los dos primeros bytes contienen el numero de inode, y los14 siguientes bytes contienen el nombre del archivo (con menos de 14 caracteres este termina en NULL).Este impone un lımite de 65535 archivos por particion de disco (puesto que 0 no es un numero de inodevalido). Las dos primeras entradas contienen ”.”que representa al mismo directorio, y ”..”, el cual denotaal directorio padre. Si el numero de inode de una entrada es cero, esto indica que el correspondientearchivo no existe.

11.2.1. El inode.

Contiene informacion de tipo administrativa, o metadatos, de un archivo. Cuando un archivoesta abierto, o un directorio esta activo, el kernel almacena los datos desde el disco del inode en unaestructura de datos en memoria, tambien llamada inode (en disco se denomina on-disk inode o structdinode, tal como se aprecia en la figura 11.2) y el in-core correspondiente a la estructura en memoria(struct inode).

Figura 11.2: La estructura di node.

110

El campo type es utilizado por las funciones IFREG, IFDIR, IFBLK, IFCHR, etc.di addr: Los archivos Unix no son contiguos en disco. A medida que un archivo crece, el kernel asignanuevos bloques en cualquier localizacion conveniente en el disco (se produce fragmentacion interna, yaque generalmente el ultimo bloque nunca esta lleno).

00

11

22

..

..

..

10

11

12

Inode block array

Disco

Triple indirect.

Doble indirect.

Simple indirect.

Bloques

de Datos

Figura 11.3: La Estructura i-node y los bloques fısicos.

Esta estructura corresponde al campo di addr, con 13 elementos dispuestos en un arreglo (ver figura11.3), donde cada elemento dispone de 3 bytes para el numero de bloque fısico.

Los elementos del 0 al 9 en este arreglo contienen los numeros de bloques del 0 al 9 del archivo. Ası,si un archivo contiene 10 o menos bloques, todas las direcciones de bloques estan contenidos en el mismoinode. El elemento 10 es el numero de bloque de un bloque indirecto, esto es, el bloque que contieneun arreglo de numeros de bloques. El elemento 11 apunta a un double-indirect block, el cual contienelos numeros de bloques de otros bloques indirectos. Finalmente, el bloque 12 apunta a un triple-indirectblock, el cual contiene numeros de bloque double-indirect blocks.

Con este esquema, si contamos con bloques fısicos de tamano 1024 bytes, entonces esta estructurapermite direccionar a 10 bloques directamente, 256 bloques mas a traves del bloque indirecto; 65536(256x256) bloques mas a traves de un bloque indirecto doble; y 16.777.216 (26x256x256) bloques mas atraves de un bloque triple indirecto.

11.2.2. Accesando el i-node de un archivo. Utilizacion de la funcion stat().

Su forma general es la siguiente:

111

#include <sys/stat.h>

#include <unistd.h>

int stat(const char *file_name, struct stat *buf);

Esta funcion retorna informacion sobre el archivo especificado en file name. No se necesita algunderecho de acceso hacia el archivo para obtener la informacion que se necesita.

La funcion stat accesa la informacion del archivo file filename y llena el buffer buf. Esta funcionretorna una estructura stat, la cual contiene los siguientes campos (version Linux):

struct stat {

dev_t st_dev; /* dispositivo */

ino_t st_ino; /* inode */

mode_t st_mode; /* proteccin */

nlink_t st_nlink; /* n\’umero de hard links */

uid_t st_uid; /* ID del due\~no */

gid_t st_gid; /* ID del grupo */

dev_t st_rdev; /* tipo de dispositivo (si es inode device) */

off_t st_size; /* tamaoo total, en bytes */

unsigned long st_blksize; /* blocksize para filesystem de E/S */

unsigned long st_blocks; /* numero de bloques asignados */

time_t st_atime; /* tiempo de ultimo acceso */

time_t st_mtime; /* tiempo de ultima modificacion */

time_t st_ctime; /* tiempo de ultimo cambio administrativo*/

};

struct dirent {

ino_t d_ino; /*numero de i-node */

char d_name[NAME_MAX + 1]; /*tremina en NULL*/

};

DIR *dirp = opendir(const char *filename);

struct dirent *direntp = readdir(dirp);

Ejemplo 1.

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <dirent.h>

char permisos[] = {’x’,’w’,’r’};

char *mensaje; /* = "Hola, que tal";*/

main(int argc, char *argv[], char **envp) {

int i, *k;

float *f;

void estado(); /*Funci\’on que imprime el estado de un archivo*/

if ( argc < 2 ) {

printf("%s","Error, ingrese: inodo <nombre-archivo> \n ");

exit(-1);

}

for (i=1; i < argc; i++ ) estado(argv[i]);

}

112

void estado(char *archivo) {

struct stat buf; /*Variable usada en la llamada stat()*/

int i;

DIR *dir, *dirp;

struct dirent *direntp;

/*Realizando la llamada al sistema stat()*/

if ( stat(archivo, &buf) == -1 ) {

perror(archivo);

exit(-1);

}

/*Imprimiendo el nombre del archivo*/

printf("\n");

printf("Archivo : %s\n",archivo);

printf("Numero de inode : %d\n",buf.st_ino);

dirp = opendir(".");

while ( (direntp = readdir(dirp)) != NULL ) {

if ( !strcmp(direntp->d_name,archivo) == 0 )

printf("Nombre archivo: %s\n",direntp->d_name);

}

/*Imprimiendo tipo de archivo*/

printf("Tipo archivo : ");

if ( S_ISDIR(buf.st_mode) ) {

printf("directorio!!\n");

if ( (dir = opendir(archivo)) == NULL ) {

perror(archivo);

exit(-1);

}

else {

printf("Directorio abierto!!. Su contenido es el siguiente:\n");

while ( (direntp = readdir(dir)) != NULL )

printf("Nombre archivo : %s\n",direntp->d_name);

closedir(dir);

}

}

else if ( S_ISREG(buf.st_mode) )

printf("regular!!\n");

else if ( S_ISCHR(buf.st_mode) )

printf("especial modo caracter!!\n");

else if ( S_ISBLK(buf.st_mode) )

printf("especial modo bloque!!\n");

else if ( S_ISFIFO(buf.st_mode) )

printf("especial FIFO!!\n");

/*Comprobar si bits 11 (Set User ID) esta activo*/

if ( buf.st_mode & S_ISUID )

printf("Bit 11 (Set User ID) activo!!\n");

/*Comprobar si bits 10 (Set Group ID) esta activo*/

if ( buf.st_mode & S_ISGID )

printf("Bit 10 (Set Group ID) activo!!\n");

/*Comprobar si bit 9 (Stiky bit) esta activo*/

if ( buf.st_mode & S_ISVTX )

113

printf("Bit 9 (Stiky bit) activo!!\n");

/*Impresion de los permisos del archivo*/

printf("Permisos : ");

printf("%o ", buf.st_mode & 0777);

for ( i=0; i < 9; i++ )

if ( (buf.st_mode & (0400 >> i)) )

printf("%c", permisos[(8-i)%3]);

else printf("-");

printf("\n");

printf("Los caracteres de permisos: %c %c

%c\n",permisos[0],permisos[1],permisos[2]);

/*Impresion el numero de links duros o "hard links"*/

printf("Enlaces : %d\n", buf.st_nlink);

/*Impresion del UID y el GID*/

printf("User ID : %d\n", buf.st_uid);

printf("Group ID : %d\n", buf.st_gid);

/*Impresion de la longitud del archivo*/

printf("Longitud archivo: %d\n", buf.st_size);

/*Impresion fecha de ultimo acceso, ultima modificacion,

* y ultimo cambio en el estado del archivo*/

printf("Fecha de ultimo acceso: %s\n",asctime(localtime(&buf.st_atime)));

printf("Fecha de ultima modificacion: %s\n",asctime(localtime(&buf.st_mtime)));

printf("Fecha de ultimo cambio de estado: %s\n",asctime(localtime(&buf.st_ctime)));

}

Ejemplo 2.

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <sys/dir.h>

FILE *fd_in, *fd_out;

char *file;

struct stat buffer1, buffer2;

main (int argc, char *argv[]) { /*char **argv[]*/

int sock,n;

char car;

if ( argc < 3 ) {

perror ("Usar : copy arch_actual arch_nuevo\n");

exit (1);

}

if ( stat(argv[1],&buffer1) == -1 ) {

perror(argv[1]);

exit(-1);

}

fd_in = fopen (argv[1],"r");

fd_out = fopen (argv[2],"w");

114

/*copia el modo del archivo antiguo, al nuevo archivo*/

if ( stat(argv[2],&buffer2) == -1 ) {

perror(argv[1]);

exit(-1);

}

chmod(argv[2],buffer1.st_mode);

fclose(fd_in);

fclose(fd_out);

}

Cuando un proceso ejecuta una llamada al sistema tal como fopen, el kernel devuelve un descriptor dearchivo. Despues, cuando el programa realiza las llamadas fread o fwrite, el kernel utiliza el descriptor dearchivo para acceder a la tabla de descriptores de archivo del proceso, desde ahıaccesa los punteros parala tabla de archivos y luego la tabla de inodes, y por ultimo accesa a los bloques de datos del archivo.

Cada entrada de la tabla de archivos (que es global) contiene, entre otros campos, un puntero a unaestructura de datos que contiene informacion sobre el estado actual del archivo:

1. I-node asociado a la entrada de la tabla.

2. Desplazamiento (offset) de lectura y escritura, que indican sobre que byte del archivo, van a tenerefecto las siguientes operaciones de lectura y escritura.

3. Permisos de acceso para el proceso que ha abierto el archivo.

4. Flags del modo de apertura del archivo, que se verificaran cada vez que efectue una operacion.

5. Un contador que indica el numero de entradas de tablas de descriptores que tiene asociada estaentrada de la tabla de archivos.

Cuando se crea un proceso Unix, el sistema abre, por defecto, tres archivos que ocupan las tresprimeras entradas de la tabla de descriptores (donde cada proceso posee una tabla de descriptores dearchivos):

1. Entrada estandar (descriptor 0).

2. Salida estandar (descriptor 1).

3. Error estandar (descriptor 2).

La figura 11.4 muestra la estructura y la relacion entre las tablas que crea el kernel para los archivosde un proceso y del sistema.Ejemplo 3. Busqueda de una archivo.

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <dirent.h>

void find(char *,char *, char *);

main(int argc, char *argv[]) {

char *camino;

if ( argc < 2 ) {

printf("%s","Error, ingrese: f <nombre-archivo> \n ");

exit(-1);

115

00

11

22

33

44

55

66..

..

..

..

..

..

..

..

count = 1

read ..

..

..

count = 1

read− write

Count = 2

read..

..

..

..

..

..

..

..

..

..

..

Count = 2

"archivo1"

count = 2

"archivo2" ..

..

..

..

..

Tabla de descriptores

de archivos Tabla de archivos Tabla de i− nodes

Figura 11.4: Tablas de descriptores de archivos y de i-nodes.

}

camino = (char *)malloc(sizeof(char));

strcpy(camino,"./");

printf("file %s ... \n",argv[1]);

find(".",camino,argv[1]);

}

void find(char *dir, char *path, char *file) {

struct stat st;

struct dirent *fd;

DIR *dirp;

char *direct, *arch, *new_path;

if ( (dirp = opendir(dir)) == NULL ) {

printf("Error al abrir directorio!! \n");

exit(-1);

}

else {

chdir(dir);

while ( (fd = readdir(dirp)) != NULL ) {

stat(fd->d_name,&st);

if( (strcmp(fd->d_name,".") != 0) && (strcmp(fd->d_name,"..") != 0) ) {

if ( strcmp(fd->d_name,file) == 0 ) printf(" %s .\n",path);

if ( ((st.st_mode & S_IFMT) == S_IFDIR) ) { /*&& (fd->d_name != file) ) { */

new_path = (char *)malloc(sizeof(char));

strcpy(new_path,path);

strcat(new_path,fd->d_name);

strcat(new_path,"/");

/*printf("Directorio %s\n",new_path);*/

find(fd->d_name,new_path,file);

chdir("..");

}

}/*end if*/

116

} /*end while*/

close(dir);

} /*end else*/

return;

} /*end find*/

Ejemplo 4. Calculando el tamao de un directorio, version secuencial.

#include <sys/types.h>

#include <sys/dir.h>

#include<stdio.h>

#include<sys/stat.h>

main(int argc, char *argv[]) {

char *path = ".";

int sz = 0;

if(argc >=2) path = argv[1];

printf("El numero de bytes ocupados por el directorio %s es %d

\n",path,espacio(path));

}

int espacio(char *p) {

struct stat st;

struct direct *fd;

DIR *dirp;

int tam = 0;

char *np = p;

if((dirp = opendir(p)) ==NULL) {printf("Acceso denegado al Directorio %s \n"); }

while((fd=readdir(dirp)) !=NULL) {

stat(fd->d_name,&st);

if((st.st_mode & S_IFMT) != S_IFDIR) tam +=st.st_size;

else

if(strcmp(fd->d_name,".") && strcmp(fd->d_name,"..")) {

/** arreglar aqui el path*/

chdir(fd->d_name);

tam += espacio(fd->d_name);

}

}

closedir(dirp);

return tam;

}

Ejemplo 5. Calculando el tamao de un directorio, con una implementacion concurrente.Ejemplo 6. Implemetacion del comando pwd.

#include <stdio.h>

#include <dirent.h>

#include <sys/stat.h>

typedef struct Nodo{

char Nombre[MAXNAMLEN];

struct Nodo *Siguiente;

} Lista;

void AgregarNodo(Lista **, char *);

void ImprimirLista(Lista * );

117

char *Nombre(char *, int);

main(){

Lista *l=NULL;

struct stat sp,spp;

stat(".",&sp);

stat("..",&spp);

while(sp.st_ino != spp.st_ino){

AgregarNodo(&l,Nombre("..",sp.st_ino));

chdir("..");

stat(".",&sp);

stat("..",&spp);

}

ImprimirLista(l);

printf("\n");

}

void AgregarNodo(Lista **l, char *elNombre) {

Lista *n = (Lista *) malloc(1*sizeof(Lista));

strcpy(n->Nombre,elNombre);

n->Siguiente = *l;

*l = n;

}

void ImprimirLista(Lista * l) {

if (l!=NULL) {

printf("/%s",l->Nombre);

ImprimirLista(l->Siguiente);

}

}

Ejemplo 7. Comando grep concurrente (tgrep).

/* Copyright (c) 1993, 1994 Ron Winacott */

/* This program may be used, copied, modified, and redistributed freely */

/* for ANY purpose, so long as this notice remains intact. */

#define _REENTRANT

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <unistd.h>

#include <assert.h>

#include <errno.h>

#include <ctype.h>

#include <sys/types.h>

#include <time.h>

#include <sys/stat.h>

#include <dirent.h>

#include "version.h"

#include <fcntl.h>

#include <sys/uio.h>

#include <pthread.h>

#include <sched.h>

#ifdef MARK

#include <prof.h> /* to turn on MARK(), use -DMARK to compile (see man prof5)*/

#endif

#include "pmatch.h"

118

#define PATH_MAX 1024 /* max # of characters in a path name */

#define HOLD_FDS 6 /* stdin,out,err and a buffer */

#define UNLIMITED 99999 /* The default tglimit */

#define MAXREGEXP 10 /* max number of -e options */

#define FB_BLOCK 0x00001

#define FC_COUNT 0x00002

#define FH_HOLDNAME 0x00004

#define FI_IGNCASE 0x00008

#define FL_NAMEONLY 0x00010

#define FN_NUMBER 0x00020

#define FS_NOERROR 0x00040

#define FV_REVERSE 0x00080

#define FW_WORD 0x00100

#define FR_RECUR 0x00200

#define FU_UNSORT 0x00400

#define FX_STDIN 0x00800

#define TG_BATCH 0x01000

#define TG_FILEPAT 0x02000

#define FE_REGEXP 0x04000

#define FS_STATS 0x08000

#define FC_LINE 0x10000

#define TG_PROGRESS 0x20000

#define FILET 1

#define DIRT 2

typedef struct work_st {

char *path;

int tp;

struct work_st *next;

} work_t;

typedef struct out_st {

char *line;

int line_count;

long byte_count;

struct out_st *next;

} out_t;

#define ALPHASIZ 128

typedef struct bm_pattern { /* Boyer - Moore pattern */

short p_m; /* length of pattern string */

short p_r[ALPHASIZ]; /* "r" vector */

short *p_R; /* "R" vector */

char *p_pat; /* pattern string */

} BM_PATTERN;

/* bmpmatch.c */

extern BM_PATTERN *bm_makepat(char *p);

extern char *bm_pmatch(BM_PATTERN *pat, register char *s);

extern void bm_freepat(BM_PATTERN *pattern);

BM_PATTERN *bm_pat; /* the global target read only after main */

/* pmatch.c */

extern char *pmatch(register PATTERN *pattern, register char *string, int *len);

extern PATTERN *makepat(char *string, char *metas);

extern void freepat(register PATTERN *pat);

119

extern void printpat(PATTERN *pat);

PATTERN *pm_pat[MAXREGEXP]; /* global targets read only for pmatch */

#include "proto.h" /* function prototypes of main.c */

/* local functions to POSIX only */

void pthread_setconcurrency_np(int con);

int pthread_getconcurrency_np(void);

void pthread_yield_np(void);

pthread_attr_t detached_attr;

pthread_mutex_t output_print_lk;

pthread_mutex_t global_count_lk;

int global_count = 0;

work_t *work_q = NULL;

pthread_cond_t work_q_cv;

pthread_mutex_t work_q_lk;

pthread_mutex_t debug_lock;

#include "debug.h" /* must be included AFTER the

mutex_t debug_lock line */

work_t *search_q = NULL;

pthread_mutex_t search_q_lk;

pthread_cond_t search_q_cv;

int search_pool_cnt = 0; /* the count in the pool now */

int search_thr_limit = 0; /* the max in the pool */

work_t *cascade_q = NULL;

pthread_mutex_t cascade_q_lk;

pthread_cond_t cascade_q_cv;

int cascade_pool_cnt = 0;

int cascade_thr_limit = 0;

int running = 0;

pthread_mutex_t running_lk;

pthread_mutex_t stat_lk;

time_t st_start = 0;

int st_dir_search = 0;

int st_file_search = 0;

int st_line_search = 0;

int st_cascade = 0;

int st_cascade_pool = 0;

int st_cascade_destroy = 0;

int st_search = 0;

int st_pool = 0;

int st_maxrun = 0;

int st_worknull = 0;

int st_workfds = 0;

int st_worklimit = 0;

int st_destroy = 0;

int all_done = 0;

int work_cnt = 0;

int current_open_files = 0;

int tglimit = UNLIMITED; /* if -B limit the number of

threads */

int progress_offset = 1;

int progress = 0; /* protected by the print_lock ! */

unsigned int flags = 0;

int regexp_cnt = 0;

char *string[MAXREGEXP];

int debug = 0;

int use_pmatch = 0;

120

char file_pat[255]; /* file patten match */

PATTERN *pm_file_pat; /* compiled file target string (pmatch()) */

/*

* Main: This is where the fun starts

*/

int main(int argc, char **argv) {

int c,out_thr_flags;

long max_open_files = 0l, ncpus = 0l;

extern int optind;

extern char *optarg;

int prio = 0;

struct stat sbuf;

pthread_t tid,dtid;

void *status;

char *e = NULL, *d = NULL; /* for debug flags */

int debug_file = 0;

struct sigaction sigact;

sigset_t set,oset;

int err = 0, i = 0, pm_file_len = 0;

work_t *work;

int restart_cnt = 10;

/* NO OTHER THREADS ARE RUNNING */

flags = FR_RECUR; /* the default */

while ((c = getopt(argc, argv, "d:e:bchilnsvwruf:p:BCSZzHP:")) != EOF) {

switch (c) {

#ifdef DEBUG

case ’d’:

debug = atoi(optarg);

if (debug == 0)

debug_usage();

d = optarg;

fprintf(stderr,"tgrep: Debug on at level(s) ");

while (*d) {

for (i=0; i<9; i++)

if (debug_set[i].level == *d) {

debug_levels |= debug_set[i].flag;

fprintf(stderr,"%c ",debug_set[i].level);

break;

}

d++;

}

fprintf(stderr,"\n");

break;

case ’f’: debug_file = atoi(optarg); break;

#endif /* DEBUG */

case ’B’:

flags |= TG_BATCH;

#ifndef __lock_lint

/* locklint complains here, but there are no other threads */

if ((e = getenv("TGLIMIT"))) {

tglimit = atoi(e);

}

else {

if (!(flags & FS_NOERROR)) /* order dependent! */

121

fprintf(stderr,"env TGLIMIT not set, overriding -B\n");

flags &= ~TG_BATCH;

}

#endif

break;

case ’p’:

flags |= TG_FILEPAT;

strcpy(file_pat,optarg);

pm_file_pat = makepat(file_pat,NULL);

break;

case ’P’:

flags |= TG_PROGRESS;

progress_offset = atoi(optarg);

break;

case ’S’: flags |= FS_STATS; break;

case ’b’: flags |= FB_BLOCK; break;

case ’c’: flags |= FC_COUNT; break;

case ’h’: flags |= FH_HOLDNAME; break;

case ’i’: flags |= FI_IGNCASE; break;

case ’l’: flags |= FL_NAMEONLY; break;

case ’n’: flags |= FN_NUMBER; break;

case ’s’: flags |= FS_NOERROR; break;

case ’v’: flags |= FV_REVERSE; break;

case ’w’: flags |= FW_WORD; break;

case ’r’: flags &= ~FR_RECUR; break;

case ’C’: flags |= FC_LINE; break;

case ’e’:

if (regexp_cnt == MAXREGEXP) {

fprintf(stderr,"Max number of regexp’s (%d) exceeded!\n",

MAXREGEXP);

exit(1);

}

flags |= FE_REGEXP;

if ((string[regexp_cnt] =(char *)malloc(strlen(optarg)+1))==NULL){

fprintf(stderr,"tgrep: No space for search string(s)\n");

exit(1);

}

memset(string[regexp_cnt],0,strlen(optarg)+1);

strcpy(string[regexp_cnt],optarg);

regexp_cnt++;

break;

case ’z’:

case ’Z’: regexp_usage();

break;

case ’H’:

case ’?’:

default : usage();

}

}

if (flags & FS_STATS)

st_start = time(NULL);

if (!(flags & FE_REGEXP)) {

if (argc - optind < 1) {

fprintf(stderr,"tgrep: Must supply a search string(s) "

"and file list or directory\n");

usage();

122

}

if ((string[0]=(char *)malloc(strlen(argv[optind])+1))==NULL){

fprintf(stderr,"tgrep: No space for search string(s)\n");

exit(1);

}

memset(string[0],0,strlen(argv[optind])+1);

strcpy(string[0],argv[optind]);

regexp_cnt=1;

optind++;

}

if (flags & FI_IGNCASE)

for (i=0; i<regexp_cnt; i++)

uncase(string[i]);

if (flags & FE_REGEXP) {

for (i=0; i<regexp_cnt; i++)

pm_pat[i] = makepat(string[i],NULL);

use_pmatch = 1;

}

else {

bm_pat = bm_makepat(string[0]); /* only one allowed */

}

flags |= FX_STDIN;

max_open_files = sysconf(_SC_OPEN_MAX);

ncpus = sysconf(_SC_NPROCESSORS_ONLN);

if ((max_open_files - HOLD_FDS - debug_file) < 1) {

fprintf(stderr,"tgrep: You MUST have at least ONE fd "

"that can be used, check limit (>10)\n");

exit(1);

}

search_thr_limit = max_open_files - HOLD_FDS - debug_file;

cascade_thr_limit = search_thr_limit / 2;

/* the number of files that can be open */

current_open_files = search_thr_limit;

pthread_attr_init(&detached_attr);

pthread_attr_setdetachstate(&detached_attr,

PTHREAD_CREATE_DETACHED);

pthread_mutex_init(&global_count_lk,NULL);

pthread_mutex_init(&output_print_lk,NULL);

pthread_mutex_init(&work_q_lk,NULL);

pthread_mutex_init(&running_lk,NULL);

pthread_cond_init(&work_q_cv,NULL);

pthread_mutex_init(&search_q_lk,NULL);

pthread_cond_init(&search_q_cv,NULL);

pthread_mutex_init(&cascade_q_lk,NULL);

pthread_cond_init(&cascade_q_cv,NULL);

if ((argc == optind) && ((flags & TG_FILEPAT) || (flags & FR_RECUR))) {

add_work(".",DIRT);

flags = (flags & ~FX_STDIN);

}

for ( ; optind < argc; optind++) {

restart_cnt = 10;

flags = (flags & ~FX_STDIN);

STAT_AGAIN:

if (stat(argv[optind], &sbuf)) {

if (errno == EINTR) { /* try again !, restart */

if (--restart_cnt)

123

goto STAT_AGAIN;

}

if (!(flags & FS_NOERROR))

fprintf(stderr,"tgrep: Can’t stat file/dir %s, %s\n",

argv[optind], strerror(errno));

continue;

}

switch (sbuf.st_mode & S_IFMT) {

case S_IFREG :

if (flags & TG_FILEPAT) {

if (pmatch(pm_file_pat, argv[optind], &pm_file_len))

DP(DLEVEL1,("File pat match %s\n",argv[optind]));

add_work(argv[optind],FILET);

}

else {

add_work(argv[optind],FILET);

}

break;

case S_IFDIR :

if (flags & FR_RECUR) {

add_work(argv[optind],DIRT);

}

else {

if (!(flags & FS_NOERROR))

fprintf(stderr,"tgrep: Can’t search directory %s, "

"-r option is on. Directory ignored.\n",

argv[optind]);

}

break;

}

}

pthread_setconcurrency_np(3);

if (flags & FX_STDIN) {

fprintf(stderr,"tgrep: stdin option is not coded at this time\n");

exit(0); /* XXX Need to fix this SOON */

search_thr(NULL);

if (flags & FC_COUNT) {

pthread_mutex_lock(&global_count_lk);

printf("%d\n",global_count);

pthread_mutex_unlock(&global_count_lk);

}

if (flags & FS_STATS)

prnt_stats();

exit(0);

}

pthread_mutex_lock(&work_q_lk);

if (!work_q) {

if (!(flags & FS_NOERROR))

fprintf(stderr,"tgrep: No files to search.\n");

exit(0);

}

pthread_mutex_unlock(&work_q_lk);

DP(DLEVEL1,("Starting to loop through the work_q for work\n"));

/* OTHER THREADS ARE RUNNING */

while (1) {

pthread_mutex_lock(&work_q_lk);

124

while ((work_q == NULL || current_open_files == 0 || tglimit <= 0) &&

all_done == 0) {

if (flags & FS_STATS) {

pthread_mutex_lock(&stat_lk);

if (work_q == NULL)

st_worknull++;

if (current_open_files == 0)

st_workfds++;

if (tglimit <= 0)

st_worklimit++;

pthread_mutex_unlock(&stat_lk);

}

pthread_cond_wait(&work_q_cv,&work_q_lk);

}

if (all_done != 0) {

pthread_mutex_unlock(&work_q_lk);

DP(DLEVEL1,("All_done was set to TRUE\n"));

goto OUT;

}

work = work_q;

work_q = work->next; /* maybe NULL */

work->next = NULL;

current_open_files--;

pthread_mutex_unlock(&work_q_lk);

tid = 0;

switch (work->tp) {

case DIRT:

pthread_mutex_lock(&cascade_q_lk);

if (cascade_pool_cnt) {

if (flags & FS_STATS) {

pthread_mutex_lock(&stat_lk);

st_cascade_pool++;

pthread_mutex_unlock(&stat_lk);

}

work->next = cascade_q;

cascade_q = work;

pthread_cond_signal(&cascade_q_cv);

pthread_mutex_unlock(&cascade_q_lk);

DP(DLEVEL2,("Sent work to cascade pool thread\n"));

}

else {

pthread_mutex_unlock(&cascade_q_lk);

err = pthread_create(&tid,&detached_attr,cascade,(void *)work);

DP(DLEVEL2,("Sent work to new cascade thread\n"));

if (flags & FS_STATS) {

pthread_mutex_lock(&stat_lk);

st_cascade++;

pthread_mutex_unlock(&stat_lk);

}

}

break;

case FILET:

pthread_mutex_lock(&search_q_lk);

if (search_pool_cnt) {

if (flags & FS_STATS) {

pthread_mutex_lock(&stat_lk);

125

st_pool++;

pthread_mutex_unlock(&stat_lk);

}

work->next = search_q; /* could be null */

search_q = work;

pthread_cond_signal(&search_q_cv);

pthread_mutex_unlock(&search_q_lk);

DP(DLEVEL2,("Sent work to search pool thread\n"));

}

else {

pthread_mutex_unlock(&search_q_lk);

err = pthread_create(&tid,&detached_attr,

search_thr,(void *)work);

pthread_setconcurrency_np(pthread_getconcurrency_np()+1);

DP(DLEVEL2,("Sent work to new search thread\n"));

if (flags & FS_STATS) {

pthread_mutex_lock(&stat_lk);

st_search++;

pthread_mutex_unlock(&stat_lk);

}

}

break;

default:

fprintf(stderr,"tgrep: Internal error, work_t->tp not valid\n");

exit(1);

}

if (err) { /* NEED TO FIX THIS CODE. Exiting is just wrong */

fprintf(stderr,"Could not create new thread!\n");

exit(1);

}

}

OUT:

if (flags & TG_PROGRESS) {

if (progress)

fprintf(stderr,".\n");

else

fprintf(stderr,"\n");

}

/* we are done, print the stuff. All other threads are parked */

if (flags & FC_COUNT) {

pthread_mutex_lock(&global_count_lk);

printf("%d\n",global_count);

pthread_mutex_unlock(&global_count_lk);

}

if (flags & FS_STATS)

prnt_stats();

return(0); /* should have a return from main */

}

/*

* Add_Work: Called from the main thread, and cascade threads to add file

* and directory names to the work Q.

*/

int add_work(char *path,int tp) {

work_t *wt,*ww,*wp;

126

if ((wt = (work_t *)malloc(sizeof(work_t))) == NULL)

goto ERROR;

if ((wt->path = (char *)malloc(strlen(path)+1)) == NULL)

goto ERROR;

strcpy(wt->path,path);

wt->tp = tp;

wt->next = NULL;

if (flags & FS_STATS) {

pthread_mutex_lock(&stat_lk);

if (wt->tp == DIRT)

st_dir_search++;

else

st_file_search++;

pthread_mutex_unlock(&stat_lk);

}

pthread_mutex_lock(&work_q_lk);

work_cnt++;

wt->next = work_q;

work_q = wt;

pthread_cond_signal(&work_q_cv);

pthread_mutex_unlock(&work_q_lk);

return(0);

ERROR:

if (!(flags & FS_NOERROR))

fprintf(stderr,"tgrep: Could not add %s to work queue. Ignored\n",

path);

return(-1);

}

/*

* Search thread: Started by the main thread when a file name is found

* on the work Q to be serached. If all the needed resources are ready

* a new search thread will be created.

*/

void *search_thr(void *arg) /* work_t *arg */ {

FILE *fin;

char fin_buf[(BUFSIZ*4)]; /* 4 Kbytes */

work_t *wt,std;

int line_count;

char rline[128];

char cline[128];

char *line;

register char *p,*pp;

int pm_len;

int len = 0;

long byte_count;

long next_line;

int show_line; /* for the -v option */

register int slen,plen,i;

out_t *out = NULL; /* this threads output list */

pthread_yield_np();

wt = (work_t *)arg; /* first pass, wt is passed to use. */

/* len = strlen(string);*/ /* only set on first pass */

while (1) { /* reuse the search threads */

/* init all back to zero */

127

line_count = 0;

byte_count = 0l;

next_line = 0l;

show_line = 0;

pthread_mutex_lock(&running_lk);

running++;

pthread_mutex_unlock(&running_lk);

pthread_mutex_lock(&work_q_lk);

tglimit--;

pthread_mutex_unlock(&work_q_lk);

DP(DLEVEL5,("searching file (STDIO) %s\n",wt->path));

if ((fin = fopen(wt->path,"r")) == NULL) {

if (!(flags & FS_NOERROR)) {

fprintf(stderr,"tgrep: %s. File \"%s\" not searched.\n",

strerror(errno),wt->path);

}

goto ERROR;

}

setvbuf(fin,fin_buf,_IOFBF,(BUFSIZ*4)); /* XXX */

DP(DLEVEL5,("Search thread has opened file %s\n",wt->path));

while ((fgets(rline,127,fin)) != NULL) {

if (flags & FS_STATS) {

pthread_mutex_lock(&stat_lk);

st_line_search++;

pthread_mutex_unlock(&stat_lk);

}

slen = strlen(rline);

next_line += slen;

line_count++;

if (rline[slen-1] == ’\n’)

rline[slen-1] = ’\0’;

/*

** If the uncase flag is set, copy the read in line (rline)

** To the uncase line (cline) Set the line pointer to point at

** cline.

** If the case flag is NOT set, then point line at rline.

** line is what is compared, rline is what is printed on a

** match.

*/

if (flags & FI_IGNCASE) {

strcpy(cline,rline);

uncase(cline);

line = cline;

}

else {

line = rline;

}

show_line = 1; /* assume no match, if -v set */

/* The old code removed */

if (use_pmatch) {

for (i=0; i<regexp_cnt; i++) {

if (pmatch(pm_pat[i], line, &pm_len)) {

if (!(flags & FV_REVERSE)) {

add_output_local(&out,wt,line_count,

byte_count,rline);

continue_line(rline,fin,out,wt,

128

&line_count,&byte_count);

}

else {

show_line = 0;

} /* end of if -v flag if / else block */

/*

** if we get here on ANY of the regexp targets

** jump out of the loop, we found a single

** match so do not keep looking!

** If name only, do not keep searcthing the same

** file, we found a single match, so close the file,

** print the file name and move on to the next file.

*/

if (flags & FL_NAMEONLY)

goto OUT_OF_LOOP;

else

goto OUT_AND_DONE;

} /* end found a match if block */

} /* end of the for pat[s] loop */

}

else {

if (bm_pmatch( bm_pat, line)) {

if (!(flags & FV_REVERSE)) {

add_output_local(&out,wt,line_count,byte_count,rline);

continue_line(rline,fin,out,wt,

&line_count,&byte_count);

}

else {

show_line = 0;

}

if (flags & FL_NAMEONLY)

goto OUT_OF_LOOP;

}

}

OUT_AND_DONE:

if ((flags & FV_REVERSE) && show_line) {

add_output_local(&out,wt,line_count,byte_count,rline);

show_line = 0;

}

byte_count = next_line;

}

OUT_OF_LOOP:

fclose(fin);

/*

** The search part is done, but before we give back the FD,

** and park this thread in the search thread pool, print the

** local output we have gathered.

*/

print_local_output(out,wt); /* this also frees out nodes */

out = NULL; /* for the next time around, if there is one */

ERROR:

DP(DLEVEL5,("Search done for %s\n",wt->path));

free(wt->path);

free(wt);

notrun();

pthread_mutex_lock(&search_q_lk);

129

if (search_pool_cnt > search_thr_limit) {

pthread_mutex_unlock(&search_q_lk);

DP(DLEVEL5,("Search thread exiting\n"));

if (flags & FS_STATS) {

pthread_mutex_lock(&stat_lk);

st_destroy++;

pthread_mutex_unlock(&stat_lk);

}

return(0);

}

else {

search_pool_cnt++;

while (!search_q)

pthread_cond_wait(&search_q_cv,&search_q_lk);

search_pool_cnt--;

wt = search_q; /* we have work to do! */

if (search_q->next)

search_q = search_q->next;

else

search_q = NULL;

pthread_mutex_unlock(&search_q_lk);

}

}

/*NOTREACHED*/

}

/*

* Continue line: Special case search with the -C flag set. If you are

* searching files like Makefiles, some lines might have escape char’s to

* contine the line on the next line. So the target string can be found, but

* no data is displayed. This function continues to print the escaped line

* until there are no more "\" chars found.

*/

int continue_line(char *rline, FILE *fin, out_t *out, work_t *wt,int *lc, long *bc) {

int len;

int cnt = 0;

char *line;

char nline[128];

if (!(flags & FC_LINE))

return(0);

line = rline;

AGAIN:

len = strlen(line);

if (line[len-1] == ’\\’) {

if ((fgets(nline,127,fin)) == NULL) {

return(cnt);

}

line = nline;

len = strlen(line);

if (line[len-1] == ’\n’)

line[len-1] = ’\0’;

*bc = *bc + len;

*lc++;

add_output_local(&out,wt,*lc,*bc,line);

cnt++;

130

goto AGAIN;

}

return(cnt);

}

/*

* cascade: This thread is started by the main thread when directory names

* are found on the work Q. The thread reads all the new file, and directory

* names from the directory it was started when and adds the names to the

* work Q. (it finds more work!)

*/

void *cascade(void *arg) /* work_t *arg */ {

char fullpath[1025];

int restart_cnt = 10;

DIR *dp;

char dir_buf[sizeof(struct dirent) + PATH_MAX];

struct dirent *dent = (struct dirent *)dir_buf;

struct stat sbuf;

char *fpath;

work_t *wt;

int fl = 0, dl = 0;

int pm_file_len = 0;

pthread_yield_np(); /* try toi give control back to main thread */

wt = (work_t *)arg;

while(1) {

fl = 0;

dl = 0;

restart_cnt = 10;

pm_file_len = 0;

pthread_mutex_lock(&running_lk);

running++;

pthread_mutex_unlock(&running_lk);

pthread_mutex_lock(&work_q_lk);

tglimit--;

pthread_mutex_unlock(&work_q_lk);

if (!wt) {

if (!(flags & FS_NOERROR))

fprintf(stderr,"tgrep: Bad work node passed to cascade\n");

goto DONE;

}

fpath = (char *)wt->path;

if (!fpath) {

if (!(flags & FS_NOERROR))

fprintf(stderr,"tgrep: Bad path name passed to cascade\n");

goto DONE;

}

DP(DLEVEL3,("Cascading on %s\n",fpath));

if (( dp = opendir(fpath)) == NULL) {

if (!(flags & FS_NOERROR))

fprintf(stderr,"tgrep: Can’t open dir %s, %s. Ignored.\n",

fpath,strerror(errno));

goto DONE;

}

while ((readdir_r(dp,dent)) != NULL) {

restart_cnt = 10; /* only try to restart the interupted 10 X */

131

if (dent->d_name[0] == ’.’) {

if (dent->d_name[1] == ’.’ && dent->d_name[2] == ’\0’)

continue;

if (dent->d_name[1] == ’\0’)

continue;

}

fl = strlen(fpath);

dl = strlen(dent->d_name);

if ((fl + 1 + dl) > 1024) {

fprintf(stderr,"tgrep: Path %s/%s is too long. "

"MaxPath = 1024\n",

fpath, dent->d_name);

continue; /* try the next name in this directory */

}

strcpy(fullpath,fpath);

strcat(fullpath,"/");

strcat(fullpath,dent->d_name);

RESTART_STAT:

if (stat(fullpath,&sbuf)) {

if (errno == EINTR) {

if (--restart_cnt)

goto RESTART_STAT;

}

if (!(flags & FS_NOERROR))

fprintf(stderr,"tgrep: Can’t stat file/dir %s, %s. "

"Ignored.\n",

fullpath,strerror(errno));

goto ERROR;

}

switch (sbuf.st_mode & S_IFMT) {

case S_IFREG :

if (flags & TG_FILEPAT) {

if (pmatch(pm_file_pat, dent->d_name, &pm_file_len)) {

DP(DLEVEL3,("file pat match (cascade) %s\n",

dent->d_name));

add_work(fullpath,FILET);

}

}

else {

add_work(fullpath,FILET);

DP(DLEVEL3,("cascade added file (MATCH) %s to Work Q\n",

fullpath));

}

break;

case S_IFDIR :

DP(DLEVEL3,("cascade added dir %s to Work Q\n",fullpath));

add_work(fullpath,DIRT);

break;

}

}

ERROR:

closedir(dp);

DONE:

free(wt->path);

free(wt);

notrun();

132

pthread_mutex_lock(&cascade_q_lk);

if (cascade_pool_cnt > cascade_thr_limit) {

pthread_mutex_unlock(&cascade_q_lk);

DP(DLEVEL5,("Cascade thread exiting\n"));

if (flags & FS_STATS) {

pthread_mutex_lock(&stat_lk);

st_cascade_destroy++;

pthread_mutex_unlock(&stat_lk);

}

return(0); /* pthread_exit */

}

else {

DP(DLEVEL5,("Cascade thread waiting in pool\n"));

cascade_pool_cnt++;

while (!cascade_q)

pthread_cond_wait(&cascade_q_cv,&cascade_q_lk);

cascade_pool_cnt--;

wt = cascade_q; /* we have work to do! */

if (cascade_q->next)

cascade_q = cascade_q->next;

else

cascade_q = NULL;

pthread_mutex_unlock(&cascade_q_lk);

}

}

/*NOTREACHED*/

}

/*

* Print Local Output: Called by the search thread after it is done searching

* a single file. If any oputput was saved (matching lines), the lines are

* displayed as a group on stdout.

*/

int print_local_output(out_t *out, work_t *wt) {

out_t *pp, *op;

int out_count = 0;

int printed = 0;

pp = out;

pthread_mutex_lock(&output_print_lk);

if (pp && (flags & TG_PROGRESS)) {

progress++;

if (progress >= progress_offset) {

progress = 0;

fprintf(stderr,".");

}

}

while (pp) {

out_count++;

if (!(flags & FC_COUNT)) {

if (flags & FL_NAMEONLY) { /* Pint name ONLY ! */

if (!printed) {

printed = 1;

printf("%s\n",wt->path);

}

}

133

else { /* We are printing more then just the name */

if (!(flags & FH_HOLDNAME))

printf("%s :",wt->path);

if (flags & FB_BLOCK)

printf("%ld:",pp->byte_count/512+1);

if (flags & FN_NUMBER)

printf("%d:",pp->line_count);

printf("%s\n",pp->line);

}

}

op = pp;

pp = pp->next;

/* free the nodes as we go down the list */

free(op->line);

free(op);

}

pthread_mutex_unlock(&output_print_lk);

pthread_mutex_lock(&global_count_lk);

global_count += out_count;

pthread_mutex_unlock(&global_count_lk);

return(0);

}

/*

* add output local: is called by a search thread as it finds matching lines.

* the matching line, its byte offset, line count, etc. are stored until the

* search thread is done searching the file, then the lines are printed as

* a group. This way the lines from more then a single file are not mixed

* together.

*/

int add_output_local(out_t **out, work_t *wt,int lc, long bc, char *line) {

out_t *ot,*oo, *op;

if (( ot = (out_t *)malloc(sizeof(out_t))) == NULL)

goto ERROR;

if (( ot->line = (char *)malloc(strlen(line)+1)) == NULL)

goto ERROR;

strcpy(ot->line,line);

ot->line_count = lc;

ot->byte_count = bc;

if (!*out) {

*out = ot;

ot->next = NULL;

return(0);

}

/* append to the END of the list; keep things sorted! */

op = oo = *out;

while(oo) {

op = oo;

oo = oo->next;

}

op->next = ot;

ot->next = NULL;

return(0);

ERROR:

if (!(flags & FS_NOERROR))

134

fprintf(stderr,"tgrep: Output lost. No space. "

"[%s: line %d byte %d match : %s\n",

wt->path,lc,bc,line);

return(1);

}

/*

* print stats: If the -S flag is set, after ALL files have been searched,

* main thread calls this function to print the stats it keeps on how the

* search went.

*/

void prnt_stats(void) {

float a,b,c;

float t = 0.0;

time_t st_end = 0;

char tl[80];

st_end = time(NULL); /* stop the clock */

printf("\n----------------- Tgrep Stats. --------------------\n");

printf("Number of directories searched: %d\n",st_dir_search);

printf("Number of files searched: %d\n",st_file_search);

c = (float)(st_dir_search + st_file_search) / (float)(st_end - st_start);

printf("Dir/files per second: %3.2f\n",c);

printf("Number of lines searched: %d\n",st_line_search);

printf("Number of matching lines to target: %d\n",global_count);

printf("Number of cascade threads created: %d\n",st_cascade);

printf("Number of cascade threads from pool: %d\n",st_cascade_pool);

a = st_cascade_pool; b = st_dir_search;

printf("Cascade thread pool hit rate: %3.2f%%\n",((a/b)*100));

printf("Cascade pool overall size: %d\n",cascade_pool_cnt);

printf("Number of search threads created: %d\n",st_search);

printf("Number of search threads from pool: %d\n",st_pool);

a = st_pool; b = st_file_search;

printf("Search thread pool hit rate: %3.2f%%\n",((a/b)*100));

printf("Search pool overall size: %d\n",search_pool_cnt);

printf("Search pool size limit: %d\n",search_thr_limit);

printf("Number of search threads destroyed: %d\n",st_destroy);

printf("Max # of threads running concurrenly: %d\n",st_maxrun);

printf("Total run time, in seconds. %d\n",

(st_end - st_start));

/* Why did we wait ? */

a = st_workfds; b = st_dir_search+st_file_search;

c = (a/b)*100; t += c;

printf("Work stopped due to no FD’s: (%.3d) %d Times, %3.2f%%\n",

search_thr_limit,st_workfds,c);

a = st_worknull; b = st_dir_search+st_file_search;

c = (a/b)*100; t += c;

printf("Work stopped due to no work on Q: %d Times, %3.2f%%\n",

st_worknull,c);

if (tglimit == UNLIMITED)

strcpy(tl,"Unlimited");

else

sprintf(tl," %.3d ",tglimit);

a = st_worklimit; b = st_dir_search+st_file_search;

c = (a/b)*100; t += c;

printf("Work stopped due to TGLIMIT: (%.9s) %d Times, %3.2f%%\n",

135

tl,st_worklimit,c);

printf("Work continued to be handed out: %3.2f%%\n",100.00-t);

printf("----------------------------------------------------\n");

}

/*

* not running: A glue function to track if any search threads or cascade

* threads are running. When the count is zero, and the work Q is NULL,

* we can safely say, WE ARE DONE.

*/

void notrun (void) {

pthread_mutex_lock(&work_q_lk);

work_cnt--;

tglimit++;

current_open_files++;

pthread_mutex_lock(&running_lk);

if (flags & FS_STATS) {

pthread_mutex_lock(&stat_lk);

if (running > st_maxrun) {

st_maxrun = running;

DP(DLEVEL6,("Max Running has increased to %d\n",st_maxrun));

}

pthread_mutex_unlock(&stat_lk);

}

running--;

if (work_cnt == 0 && running == 0) {

all_done = 1;

DP(DLEVEL6,("Setting ALL_DONE flag to TRUE.\n"));

}

pthread_mutex_unlock(&running_lk);

pthread_cond_signal(&work_q_cv);

pthread_mutex_unlock(&work_q_lk);

}

/*

* uncase: A glue function. If the -i (case insensitive) flag is set, the

* target strng and the read in line is converted to lower case before

* comparing them.

*/

void uncase(char *s) {

char *p;

for (p = s; *p != NULL; p++)

*p = (char)tolower(*p);

}

/*

* usage: Have to have one of these.

*/

void usage(void) {

fprintf(stderr,"usage: tgrep <options> pattern <{file,dir}>...\n");

fprintf(stderr,"\n");

fprintf(stderr,"Where:\n");

#ifdef DEBUG

136

fprintf(stderr,"Debug -d = debug level -d <levels> (-d0 for usage)\n");

fprintf(stderr,"Debug -f = block fd’s from use (-f #)\n");

#endif

fprintf(stderr," -b = show block count (512 byte block)\n");

fprintf(stderr," -c = print only a line count\n");

fprintf(stderr," -h = Do NOT print file names\n");

fprintf(stderr," -i = case insensitive\n");

fprintf(stderr," -l = print file name only\n");

fprintf(stderr," -n = print the line number with the line\n");

fprintf(stderr," -s = Suppress error messages\n");

fprintf(stderr," -v = print all but matching lines\n");

#ifdef NOT_IMP

fprintf(stderr," -w = search for a \"word\"\n");

#endif

fprintf(stderr," -r = Do not search for files in all "

"sub-directories\n");

fprintf(stderr," -C = show continued lines (\"\\\")\n");

fprintf(stderr," -p = File name regexp pattern. (Quote it)\n");

fprintf(stderr," -P = show progress. -P 1 prints a DOT on stderr\n"

" for each file it finds, -P 10 prints a DOT\n"

" on stderr for each 10 files it finds, etc...\n");

fprintf(stderr," -e = expression search.(regexp) More then one\n");

fprintf(stderr," -B = limit the number of threads to TGLIMIT\n");

fprintf(stderr," -S = Print thread stats when done.\n");

fprintf(stderr," -Z = Print help on the regexp used.\n");

fprintf(stderr,"\n");

fprintf(stderr,"Notes:\n");

fprintf(stderr," If you start tgrep with only a directory name\n");

fprintf(stderr," and no file names, you must not have the -r option\n");

fprintf(stderr," set or you will get no output.\n");

fprintf(stderr," To search stdin (piped input), you must set -r\n");

fprintf(stderr," Tgrep will search ALL files in ALL \n");

fprintf(stderr," sub-directories. (like */* */*/* */*/*/* etc..)\n");

fprintf(stderr," if you supply a directory name.\n");

fprintf(stderr," If you do not supply a file, or directory name,\n");

fprintf(stderr," and the -r option is not set, the current \n");

fprintf(stderr," directory \".\" will be used.\n");

fprintf(stderr," All the other options should work \"like\" grep\n");

fprintf(stderr," The -p patten is regexp; tgrep will search only\n");

fprintf(stderr,"\n");

fprintf(stderr," Copy Right By Ron Winacott, 1993-1995.\n");

fprintf(stderr,"\n");

exit(0);

}

/*

* regexp usage: Tell the world about tgrep custom (THREAD SAFE) regexp!

*/

int

regexp_usage (void)

{

fprintf(stderr,"usage: tgrep <options> -e \"pattern\" <-e ...> "

"<{file,dir}>...\n");

fprintf(stderr,"\n");

fprintf(stderr,"metachars:\n");

fprintf(stderr," . - match any character\n");

fprintf(stderr," * - match 0 or more occurrences of previous char\n");

137

fprintf(stderr," + - match 1 or more occurrences of previous char.\n");

fprintf(stderr," ^ - match at beginning of string\n");

fprintf(stderr," $ - match end of string\n");

fprintf(stderr," [ - start of character class\n");

fprintf(stderr," ] - end of character class\n");

fprintf(stderr," ( - start of a new pattern\n");

fprintf(stderr," ) - end of a new pattern\n");

fprintf(stderr," @(n)c - match <c> at column <n>\n");

fprintf(stderr," | - match either pattern\n");

fprintf(stderr," \\ - escape any special characters\n");

fprintf(stderr," \\c - escape any special characters\n");

fprintf(stderr," \\o - turn on any special characters\n");

fprintf(stderr,"\n");

fprintf(stderr,"To match two diffrerent patterns in the same command\n");

fprintf(stderr,"Use the or function. \n"

"ie: tgrep -e \"(pat1)|(pat2)\" file\n"

"This will match any line with \"pat1\" or \"pat2\" in it.\n");

fprintf(stderr,"You can also use up to %d -e expressions\n",MAXREGEXP);

fprintf(stderr,"RegExp Pattern matching brought to you by Marc Staveley\n");

exit(0);

}

/*

* debug usage: If compiled with -DDEBUG, turn it on, and tell the world

* how to get tgrep to print debug info on different threads.

*/

#ifdef DEBUG

void debug_usage(void) {

int i = 0;

fprintf(stderr,"DEBUG usage and levels:\n");

fprintf(stderr,"--------------------------------------------------\n");

fprintf(stderr,"Level code\n");

fprintf(stderr,"--------------------------------------------------\n");

fprintf(stderr,"0 This message.\n");

for (i=0; i<9; i++) {

fprintf(stderr,"%d %s\n",i+1,debug_set[i].name);

}

fprintf(stderr,"--------------------------------------------------\n");

fprintf(stderr,"You can or the levels together like -d134 for levels\n");

fprintf(stderr,"1 and 3 and 4.\n");

fprintf(stderr,"\n");

exit(0);

}

#endif

/* Pthreads NP functions */

#ifdef __sun

void pthread_setconcurrency_np(int con) {

thr_setconcurrency(con);

}

int pthread_getconcurrency_np(void) {

return(thr_getconcurrency());

}

138

void pthread_yield_np(void) {

/* In Solaris 2.4, these functions always return - 1 and set errno to ENOSYS */

if (sched_yield()) /* call UI interface if we are older than 2.5 */

thr_yield();

}

#else

void pthread_setconcurrency_np(int con) {

return;

}

int pthread_getconcurrency_np(void) {

return(0);

}

void pthread_yield_np(void) {

return;

}

#endif

139