ERROR USUARIO NO VALIDADO 1
El módulo de memoria se estructura en los siguientes apartados:
En este módulo se realizan ejercicios en los que intervienen los servicios de gestión de memoria de un sistema operativo UNIX, así como las funcionalidades suministradas por dicho sistema operativo en protección de memoria.
Los mandatos nuevos utilizados en este módulo son los siguientes:
Del directorio proc se manejaran los siguientes subdirectorios:
Se utilizarán los siguientes programas, que deberá descargarse de la web y compilar para su ejecución:
Los servicios UNIX utilizados en dichos programas son los siguientes:
Las funciones de biblioteca utilizadas en dichos programas son las siguientes:
En un sistema con memoria virtual existe un trasiego de páginas entre la memoria principal y la zona de intercambio del disco o swap.
Linux gestiona la memoria trabajando con unidades a las que denomina páginas, de modo que cuando un proceso solicita memoria, el kernel le asigna páginas virtuales. Todas estas páginas virtuales tendrán un soporte físico ya sea en memoria principal (RAM) o en la zona de intercambio de disco (SWAP). En principio, Linux puede distribuir tanta memoria como le soliciten los procesos, sin embargo, no toda esta memoria estará disponible en memoria RAM, sino que parte estará en SWAP. Por este motivo, existe un trasiego de información entre memoria y disco. A este trasiego de información también se le conoce con el nombre de paginación.
Este trasiego de información afecta al rendimiento del sistema, pudiendo darse el caso de que el sistema pase la mayor parte del tiempo gestionando este trasiego de información, en lugar de realizar trabajo útil. Para probar la paginación se va a someter al sistema a una fuerte carga de memoria mediante un proceso que ejecute el programa Trasiego, cuya única función es consumir dicho recurso.
Obviamente, este ejercicio no puede realizarse en una máquina multiusuario, puesto que unos usuarios afectarían a otros, por lo que planteamos su ejecución en un sistema personal con sistema operativo Windows.
Los pasos a seguir son los siguientes:
Abra uno o dos programas como pueden ser un editor o navegador. Observará que el sistema va lentísimo, puesto que está paginando fuertemente.
El fichero ejecutable tiene una cabecera y unas secciones que permiten construir los segmentos de la imagen de memoria del proceso.
El sistema operativo tiene una visión del proceso consistente en un conjunto de regiones. En sistemas con memoria virtual, las diferentes regiones que integran el proceso suelen estar separadas y tienen un tamaño de un número entero de páginas.
Las regiones más relevantes de la imagen de memoria de un proceso son:
El modo en el que se estructuran las regiones depende del diseño del sistema operativo.
Vamos a comenzar el análisis de las regiones de memoria de un proceso a través de la información proporcionada por el directorio /proc. Para ello vamos a utilizar el programa Cada3segs.c y además vamos a compilarlo estáticamente, es decir, sin bibliotecas dinámicas. Para ello utilizaremos la opción -static del mandato gcc.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ gcc -static Cada3Segs.c -o Estatico alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ ./Estatico & [1] 18785
A continuación, podemos obtener el mapa de memoria de memoria del proceso, accediendo al fichero maps del directorio /proc/<pid>:
alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ cat /proc/18785/maps 00400000-00481000 r-xp 00000000 00:17 4166491 /home/alumno/PracticasAnalisis/ModuloMemoria/Estatico 00680000-00681000 rw-p 00080000 00:17 4166491 /home/alumno/PracticasAnalisis/ModuloMemoria/Estatico 00681000-00684000 rw-p 00681000 00:00 0 06516000-06538000 rw-p 06516000 00:00 0 [heap] 7fff9573b000-7fff95750000 rw-p 7ffffffea000 00:00 0 [stack] ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0 [vdso]
Como podemos comprobar, el proceso dispone de 6 regiones de memoria, todas ellas privadas, puesto que tienen activo el bit p en los permisos.
El directorio /proc/<pid>/ contiene un fichero en el que se muestra de manera más detallada la información sobre los segmentos de memoria de un proceso, en concreto, podríamos conocer cuanta información del segmento tienen soporte en memoria RAM.
Finalmente se termina el proceso Estatico mediante un mandato kill.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ kill 18785
Es de destacar que el rango de direcciones mostrado en el fichero maps no es del todo correcto. Esto se puede ver claramente en el caso de los segmentos de memoria 2 y 3, ya que no es posible que una dirección de memoria (0x00681000) pertenezca a dos segmentos diferentes. Por lo que para el segmento 2 el rango real debería ser 00680000-00680FFF en lugar de 00680000-00681000.
Adicionalmente, con el mandato size, podemos obtener un resumen de las necesidades de memoria del proceso:
alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ size Estatico text data bss dec hex filename 524253 3360 12336 539949 83d2d Estatico
La columna text indica el tamaño (en bytes) de la región de Código (texto), la columna data el tamaño de la región de datos con valor inicial, y la columna bss el tamaño de la región de los datos sin valor inicial. Por último, las columnas dec y hex indican el tamaño total expresado tanto en formato decimal con en formato hexadecimal.
Para poder profundizar un poco más en las características de las regiones de memoria del proceso, vamos a hacer uso del mandato readelf. Como ya sabemos, un fichero ejecutable, está formado por una cabecera y un conjunto de secciones. La información contenida en la cabecera será accesible ejecutando el mandato readelf con las siguientes opciones:
alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ readelf -h Estatico ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2s complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400190 Start of program headers: 64 (bytes into file) Start of section headers: 528648 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 5 Size of section headers: 64 (bytes) Number of section headers: 26 Section header string table index: 23
Analicemos el contenido de la cabecera. Note que algunos datos están en formato hexadecimal y otros, como por ejemplo los tamaños están expresados en formato decimal.
Por último podemos conocer el número de cabeceras del programa y de sección junto con sus respectivos tamaños. En concreto:
Las cabeceras de programa ocupan, por tanto, 5•56 = 280 B. Por lo que, entre la cabecera del fichero y las cabeceras de programa se ocupa 64+280 = 344 bytes.
Mediante el mandato readelf, también podemos conocer las cabeceras de programa contenidas en el fichero, que como ya sabemos, en nuestro caso son 5.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ readelf -l -W Estatico Elf file type is EXEC (Executable file) Entry point 0x400190 There are 5 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x080143 0x080143 R E 0x200000 LOAD 0x080148 0x0000000000680148 0x0000000000680148 0x000d28 0x003d38 RW 0x200000 NOTE 0x000158 0x0000000000400158 0x0000000000400158 0x000020 0x000020 R 0x4 TLS 0x080148 0x0000000000680148 0x0000000000680148 0x000020 0x000050 R 0x8 GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x8 Section to Segment mapping: Segment Sections... 00 .note.ABI-tag .init .text __libc_freeres_fn .fini .rodata __libc_subfreeres __libc_atexit .eh_frame .gcc_except_table 01 .tdata .ctors .dtors .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs 02 .note.ABI-tag 03 .tdata .tbss 04
La información proporcionada en este caso es:
Analicemos brevemente la primera cabecera.
De la información proporcionada por maps obtenemos un segmento que ocupa el rango de direcciones 0x0400000 - 0x0481000.
De la información contenida en la cabecera de programa, sabemos que el tamaño de dicha región es 0x0080143.
Ejecutando getconf PAGESIZE, obtenemos un tamaño de página de 4096=4KB=0x1000.
Por tanto, si asumimos un alineamiento al tamaño de una página, es decir 4KB o 0x1000, la región debería abarcar un conjunto de direcciones múltiplo de 0x1000, lo cual sudece en nuestro ejemplo.
Si como indica la cabecera del programa, tuviéramos un alineamiento a 0x200000, el rango de direcciones abarcado por la región debería ser: 0x0400000 - 0x0600000, lo cuál no es cierto.
El error, tiene lugar debido a que el compilador gcc, para la arquitectura x86_64, genera un valor P_ALIGN 0x200000 == 2MB, que en realidad se podría corresponder con el tamaño máximo de página que puede gestionar el sistema, y no con el tamaño de página utilizado en realidad.
http://sourceware.org/ml/binutils/2007-08/msg00219.html
La segunda cabecera de programa corresponde al segmento de datos, puesto que tiene permisos de lectura y escritura, es de tipo LOAD y como soporte, como vimos en maps tiene al propio fichero ejecutable. Un aspecto destacable en este caso, es que si nos fijamos en las secciones que tiene asociadas este segmento (según información proporcionada por readelf), nos encontramos con .data y .bss, lo cual quiere decir, que contendrá tanto los datos con valor inicial como los datos sin valor inicial. Por tanto el tamaño en memoria de este segmento según readelf, debería ser aproximadamente igual a la suma de las columnas data y bss obtenidas con el mandato size.
Compile el programa Cada3Segs.c sin la opción -static y conteste a las siguientes preguntas:
alumno@mnaquinaLinux:~/PracticasAnalisis/ModuloMemoria$ gcc Cada3Segs.c -o Dinamico
Un proceso puede generar cualquier dirección de memoria en el intervalo que le permite el rango de representación de direcciones del procesador
En arquitecturas de 32bits el rango de direcciones que puede generar un proceso es desde la dirección 0 hasta la dirección 232-1, mientras que en arquitecturas de 64bits, un proceso podrá generar direcciones en el rango 0 a 264-1. Sin embargo, el proceso sólo tiene acceso a algunas partes de ese intervalo, que corresponden con las partes que le ha asignado el sistema operativo y que ya vimos analizando el fichero maps del directorio /proc. Asimismo, dentro de las posiciones accesibles, algunas tienen acceso de sólo lectura, mientras que otras permiten la lectura y escritura.
El programa Volcado_mem.c permite conocer si una determinada posición de memoria está asignada al proceso y, en caso afirmativo, si dicha dirección de memoria es de lectura y escritura o solo de lectura. Para ello recibe como parámetros de entrada una dirección (en hexadecimal) de inicio y un valor entero que determina el tamaño de la zona de memoria en la que se va a realizar la búsqueda.
Como es lógico, el acceso por parte de un proceso a una zona de memoria que no tiene asignada, debería provocar la muerte del proceso que realizó dicho acceso (acción por defecto). Sin embargo en el caso del programa Volcado_mem esto no sucede.
Como ya hemos comentado, cada proceso puede direccionar todo el rango de direcciones de memoria, sin embargo estas direcciones son interpretadas dentro del ámbito del propio proceso. Es decir, dos procesos que generan la misma dirección de memoria (virtual), no están accediendo a la misma posición de memoria (física).
Analicemos este hecho en tres situaciones diferentes:
Utilizaremos en este caso, los programas Productor.c y Consumidor.c. Ambos están pensados para ejecutarse de manera conjunta conectados mediante una tubería.
Compile y ejecute ambos programas conectados mediante una tubería:
alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ gcc Productor.c -o Productor alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ gcc Consumidor.c -o Consumidor alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ Productor|Consumidor
Recordemos que mediante el servicio fork, un proceso puede crear un nuevo proceso (hijo), que es un clon del original. Para analizar la gestión de los mapas de memoria cuando se hace uso del servicio fork, utilizaremos el programa Lincon.c. A continuación se reproducen ciertas partes del código de dicho programa:
LC-01.- #define MYNAME "Lincon" LC-02.- #define TAMBUF 65536 LC-03.- LC-04.- int total=0; LC-05.- LC-06.- int incognita(char *arg) LC-07.- { LC-08.- int fo, nbytes, i, valor=0; LC-09.- char buf[TAMBUF]; LC-10.- LC-11.- if ((fo=open(arg, O_RDONLY))<0) LC-12.- return 1; LC-13.- LC-14.- while ( (nbytes= read(fo, buf, TAMBUF)) >0) LC-15.- for (i=0; i<nbytes; i++) LC-16.- if (buf[i]=='\n') valor++; LC-17.- LC-18.- if (nbytes<0) LC-19.- return 2; LC-20.- LC-21.- total+= valor; LC-22.- printf("%d\t%s\n", valor, arg); LC-23.- return 0; LC-24.- } LC-25.- LC-26.- int main(int argc, char *argv[]) LC-27.- { LC-28.- int i, estado; LC-29.- LC-30.- if (argc<2) LC-31.- { LC-32.- fprintf(stderr, "Uso: %s fichero ...\n", MYNAME); LC-33.- exit(1); LC-34.- } LC-35.- LC-36.- for (i=1; i<argc; i++) LC-37.- if (fork()==0) LC-38.- { LC-39.- estado=incognita(argv[i]); LC-40.- exit(estado); LC-41.- } LC-41.- LC-42.- for (i=1; i<argc; i++) LC-43.- wait(NULL); LC-44.- LC-45.- printf("Total:\t%d\n", total); LC-46.- exit(0); LC-47.- }
Compile y ejecute el programa Lincon.c especificando como parámetro *.c . Comprobará que el resultado obtenido en la variable total no es el esperado.
El programa Lincon_th realiza la misma labor que el programa anterior utilizando la misma estructura, pero usando threads. Compile el programa y ejecútelo especificando *.c como argumento. Recuerde que para compilar un programa que utiliza threads es necesario añadir la opción -pthread.
Comprobará que en este caso, el valor obtenido en la variable total si es el esperado.
En esta sección analizaremos las distintas regiones que aparecen en el mapa de memoria de un proceso.
Como ya hemos visto anteriormente, podemos conocer el mapa de memoria de un proceso mediante el acceso al fichero /proc/<PID>/maps que tiene asociado. Además, en Linux, disponemos del mandato pmap, que proporciona el mapa de memoria de un proceso, a partir de su pid, aunque con un formato ligeramente diferente al presentado en el fichero /proc/<PID>/maps. A continuación se muestra el resultado de invocar el mandato aplicado al pid correspondiente a un mandato cat previamente arrancado.
alumno@maquinaLinux~/PracticasAnalisis/ModuloMemoria$ cat & [1] 20773 [1]+ Stopped cat alumno@maquinaLinux~/PracticasAnalisis/ModuloMemoria$ pmap 20773 20773: cat 0000000000400000 20K r-x-- /bin/cat 0000000000604000 8K rw--- /bin/cat 00000000020b9000 132K rw--- [ anon ] 000000381d000000 112K r-x-- /lib64/ld-2.5.so 000000381d21b000 4K r---- /lib64/ld-2.5.so 000000381d21c000 4K rw--- /lib64/ld-2.5.so 000000381d400000 1328K r-x-- /lib64/libc-2.5.so 000000381d54c000 2048K ----- /lib64/libc-2.5.so 000000381d74c000 16K r---- /lib64/libc-2.5.so 000000381d750000 4K rw--- /lib64/libc-2.5.so 000000381d751000 20K rw--- [ anon ] 00002b4d6659b000 4K rw--- [ anon ] 00002b4d665bc000 8K rw--- [ anon ] 00002b4d665be000 55132K r---- /usr/lib/locale/locale-archive 00007fff69c21000 84K rw--- [ stack ] ffffffffff600000 8192K ----- [ anon ] total 67116K
Como se puede apreciar, el mandato muestra una línea por cada región que posee el proceso en su mapa de memoria, especificando las características de la misma:
Como ya vimos anteriormente el mapa de memoria de un proceso está organizado como un conjunto de regiones de diversas propiedades. Si nos centramos únicamente en las variables que maneja un proceso durante su ejecución, nos encontraremos con que éstas estarán ubicadas en diferentes regiones cuyas características se adaptan a las necesidades intrínsecas de dichas variables.
El programa Regiones.c, nos permite observar la distribución de ciertas variables en las diferentes regiones que componen su mapa de memoria. En dicho programa se definen constante, variables globales y locales (tanto inicializadas como sin inicializar) y además debemos tener en cuenta que los parámetros de la llamada a una función también se consideran variables.
Analice el programa Regiones.c
R-01.- #define MYNAME "Regiones" R-02.- #include <stdio.h> R-03.- #include <unistd.h> R-04.- #include <stdlib.h> R-05.- #include <errno.h> R-06.- /* Función que imprime el mapa de memoria del proceso en ese instante */ R-07.- static void mostrar_mapa(){ R-08.- char mandato[256]; R-09.- int mipid; R-10.- printf("\n-------------------------------------------------------------------\n"); R-11.- mipid= getpid(); R-12.- sprintf(mandato, "pmap %d ", mipid); R-13.- system(mandato); R-14.- printf("-------------------------------------------------------------------\n"); R-15.- } R-16.- R-17.- int A; R-18.- int B=666; R-19.- int C[4000]; R-20.- const int D=1000; R-21.- int E[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; R-22.- R-23.- int main(int argc, char **argv) { R-24.- int F; R-25.- int G[2500]; R-26.- R-27.- /* Se muestra el mapa */ R-28.- mostrar_mapa(); R-29.- R-30.- /* Se imprime dirección de variables especificadas en enunciado */ R-31.- printf("\n%p\tmain\n%p\tA\n%p\tB\n%p\tC\n%p\tD\n%p\tE\n%p\tF\n%p\tG\n%p\targc\n", main, &A, &B, C,&D, E, &F, G, &argc); R-32.- R-33.- return 0; R-34.- }
Compile y ejecute el programa Regiones.c
Algunas regiones del proceso, como ocurre con la pila y el heap, tienen un tamaño dinámico, que se va ajustando según evolucionan las necesidades del programa. En ambos casos, el tamaño va aumentando de acuerdo con los requisitos del programa, pero, sin embargo, nunca disminuye aunque se reduzcan las necesidades de espacio de esas regiones durante la ejecución del programa.
Analice el programa MemEvol.c. Posteriormente compile y ejecute dicho programa. Comprobará como a lo largo de la ejecución del mismo, se crean nuevas regiones de memoria y algunas modifican su tamaño.
Como hemos indicado anteriormente, el tamaño de la pila o el heap no disminuye a pesar de liberar los recursos. Este hecho lo podemos comprobar al utilizar el free, ya que vemos como no se reduce el tamaño de la pila. Por lo tanto dicha zona de memoria sigue estando asociada al proceso.
Como ya hemos visto el mapa de memoria de un proceso es dinámico, pudiendo crearse y liberarse regiones durante la ejecución del mismo. Esto es especialmente claro cuando se utilizan los servicios fork y exec o cuando se crean threads.
El programa Thread.c muestra la evolución del mapa de memoria de un proceso cuando crea un thread. Recordemos que cuando se crea un thread este tiene asociada una pila. Compile y ejecute dicho programa.
Con respecto a la llamada fork, recordemos que genera un clon del proceso que la invoca, creando un mapa de memoria que es un duplicado del correspondiente al proceso padre. De esa manera, el nuevo proceso se inicia desde la misma situación que el padre, pero a partir de ese punto cada uno tiene su propia ejecución independiente.
Compile y ejecute el programa Fork.c. Comprobará que antes del fork disponemos de un único proceso y que tras el fork existen 2 procesos en ejecución pero al contrario de lo indicado anteriormente no poseen el mismo mapa de memoria.
En cuanto a la llamada al servicio exec, esta llamada provoca la destrucción del mapa de memoria del proceso que existe en el momento de su invocación y la construcción de un nuevo mapa asociado al ejecutable.
Compile y ejecute el programa Exec_th. Analice como cambia el mapa de memoria y como evoluciona el proceso.
Las bibliotecas constituyen un mecanismo eficiente para compartir código y para la reutilización del mismo. En la mayoría de los sistemas operativos se ofrece a los usuarios tres modalidades a la hora de utilizar bibliotecas: con enlace estático, con enlace dinámico o con carga explicita en tiempo de ejecución.
En apartado relativo a Fichero Ejecutable ya vimos como mediante la opción -static, podemos indicar al compilador que las bibliotecas necesarias para la ejecución del programa se enlacen de manera estática. También comprobamos las diferencias en las regiones del mapa de memoria de un mismo programa cuando se compila con la opción -static y sin ella. Ahora comprobaremos las diferencias entre los 2 modos de compilación en tiempo de ejecución. Para ello utilizaremos el programa Usa_biblioteca.c. Dicho programa utiliza la librería libm para el cálculo del coseno, por lo que durante la compilación se añade la opción -lm. Compile el programa y ejecute las versiones con enlace estático y enlace dinámico.
alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ gcc -Wall -o Usa_biblioteca_est Usa_biblioteca.c -static -lm alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ gcc -Wall -o Usa_biblioteca_din Usa_biblioteca.c -lm
Comprobará que en la versión dinámica el número de regiones es mayor que en la versión estática, puesto que se cargan las bibliotecas necesarias para la ejecución del programa.
Con la opción de la carga explicita en tiempo de ejecución, se carga la biblioteca necesaria en el momento en que se necesita y una vez que se ha utilizado la funcionalidad requerida, se puede liberar la biblioteca. Un ejemplo de este uso lo podemos ver en el programa Carga_biblioteca.c.
Compile y ejecute el programa Carga_biblioteca.c
La técnica de carga explicita en tiempo de ejecución además, permite aumentar la funcionalidad de un programa sin necesidad de modificarlo, recompilarlo o, incluso, pararlo o volverlo a arrancar. Dicho de manera formal, el programa puede, a posteriori, y sin modificación alguna, aprender cosas que no estaban previstas cuando se desarrolló. Un ejemplo puede ser un navegador de ficheros (o Web), en el que el tratamiento de cada fichero depende de su tipo, y donde puede requerir añadir la capacidad de procesar nuevos tipos de ficheros sin necesidad de modificar ni recompilar el navegador.
El programa Navegador.c intenta ilustrar, de forma simplificada, la estrategia que se acaba de explicar. Analice dicho programa. Comprobará que son necesarios, para su correcto funcionamiento, la existencia de archivos adicionales. En concreto, un fichero de configuración config que indica, en función del tipo de extensión a tratar, que biblioteca se debe utilizar y por tanto, será necesario disponer también de la biblioteca a utilizar.
Compile y ejecute el programa Navegador.c y los archivos necesarios para su correcto funcionamiento.
El API del sistema de memoria ofrece operaciones para proyectar archivos en memoria, lo que constituye un modo alternativo de acceso a los archivos frente al basado en operaciones de lectura y escritura. La proyección de un archivo crea una nueva región de memoria en el mapa del proceso, cuyas características dependerán de los parámetros especificados en la función de proyección. En el caso de POSIX, la función que nos permite proyectar un archivo en memoria se denomina mmap.
A la hora de proyectar un fichero se pueden controlar dos aspectos que van a definir cómo se comporta una proyección. Por un lado, se puede establecer si la proyección es de tipo privada (MAP_PRIVATE) o de tipo compartida (MAP_SHARED). Por otro lado, si está basada en un fichero o es de carácter anónimo (MAP_ANON).
Es importante saber qué tipo de proyección se debe usar dependiendo de qué tipo de circunstancias concurren en la aplicación. Una selección errónea puede causar que el programa no funcione como está previsto.
Por norma general, cuando se proyecta un archivo en memoria, se reserva un espacio que será múltiplo del tamaño de página utilizado. Si el tamaño del fichero no es múltiplo de este tamaño de página, el espacio sobrante de la última página ocupada por el fichero será anulado (rellenado con 0s).
Compile y ejecute el programa Proyeccion, que permite que el usuario especifique en los argumentos del programa los parámetros de una llamada mmap mostrando cómo esa llamada afecta al mapa de memoria del proceso. Pruebe con distintos argumentos.
A continuación vamos a analizar el uso de la técnica de proyección de archivos, a través del siguiente código.
D-01.- #define MYNAME "Desconocido" D-02.- #include <sys/types.h> D-03.- #include <sys/stat.h> D-04.- #include <sys/mman.h> D-05.- #include <fcntl.h> D-06.- #include <stdio.h> D-07.- #include <unistd.h> D-08.- #include <stdlib.h> D-09.- #include <string.h> D-00.- D-11.- int main(int argc, char **argv) D-12.- { D-13.- int fd, tam; D-14.- char *org, *p, *q; D-15.- struct stat bstat; D-16.- D-17.- if (argc!=3) { D-18.- fprintf(stderr, "Uso: %s archivo_origen archivo_destino\n", MYNAME); D-19.- return(1); D-20.- } D-21.- D-22.- if ((fd=open(argv[1], O_RDONLY))<0) { D-23.- perror(MYNAME": No puede abrirse el archivo"); D-24.- return(1); D-25.- } D-26.- D-27.- if (fstat(fd, &bstat)<0) { D-28.- perror(MYNAME": Error en fstat del archivo"); D-29.- close(fd); D-30- return(1); D-31.- } D-32.- D-33.- if ((org=mmap((caddr_t) 0, bstat.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { D-34.- perror(MYNAME": Error en la proyeccion del archivo"); D-35.- close(fd); D-36.- return(1); D-37.- } D-38.- D-39.- close(fd); D-40.- D-41.- p=org; D-42.- D-43.- for ( ; p<org+bstat.st_size; p++) D-44.- (*p)=toupper(*p); D-45.- D-46.- if ((fd=open(argv[2], O_CREAT|O_TRUNC|O_RDWR, 0640))<0) { D-47.- perror(MYNAME": No puede crearse el archivo destino"); D-48.- exit(1); D-49.- } D-50.- D-51.- if (ftruncate(fd, bstat.st_size)<0) { D-52.- perror(MYNAME": Error en ftruncate del archivo destino"); D-53.- close(fd); D-54.- unlink (argv[2]); D-55.- exit(1); D-56.- } D-57.- D-58.- if ((q=mmap((caddr_t) 0, bstat.st_size, PROT_WRITE,MAP_SHARED, fd, 0)) == MAP_FAILED) { D-59.- perror(MYNAME": Error en la proyeccion del archivo destino"); D-60.- close(fd); D-61.- unlink (argv[2]); D-62.- exit(1); D-63.- } D-64.- D-65.- memcpy(q, org, bstat.st_size); D-66.- D-67.- munmap(p, bstat.st_size); D-68.- munmap(q, bstat.st_size); D-69.- D-70.- return(0); D-71.- }
El uso de la técnica de la proyección de ficheros presenta numerosas ventajas sobre la utilización convencional de las llamadas al sistema de lectura y escritura. Además de la evidente disminución en el número de llamadas al sistema, lo que redunda en una mayor eficiencia, también se facilita la programación.
Para comprobar esta mayor eficiencia, se propone hacer uso del programa Times_B.c que ya se utilizó en el Módulo de Procesos y el programa Desconocido_fich.c, que realiza la misma tarea que el programa Desconocido.c pero en lugar de proyecciones en memoria utiliza llamadas convencionales de lectura y escritura sobre ficheros.
Compile el programa Desconocido_fich.c y ejecútelo a través de Times_B:
alumno@maquinaLinux:~/PracticasAnalisis/ModuloMemoria$ ../ModuloProcesos/Times_B ./Desconocido_fich origen destino
Compare los resultados obtenidos con Desconocido_fich.c y los obtenidos con Desconocido.c
En esta sección se plantean distintas situaciones problemáticas relacionadas con el uso de la técnica de ficheros proyectados.
Como ya hemos dicho anteriormente, cuando se proyecta un archivo en memoria, se reserva tanto espacio como páginas sean necesarias para alojar el fichero. Sin embargo lo más habitual es que el tamaño del fichero no sea múltiplo del tamaño de página, por lo que lo más probable es que haya una zona de la última página asignada a la proyección que no contendrá información relativa al fichero.
Pero, ¿qué sucede cuando intentamos acceder a esa zona de memoria? ¿Y si nos salimos de la zona de memoria asignada al fichero durante su proyección?. Analizaremos ambos aspectos tanto en el caso de que se acceda a la proyección para lectura como para escritura.
El programa Lector_sinfreno.c accede en modo lectura a un fichero proyectado. Compile y ejecute dicho programa.
El programa Escritor_sinfreno.c accede en modo escritura a un fichero proyectado. Analice el código de dicho programa.
Compile y ejecute el programa.
El uso de la memoria dinámica en C es propenso a errores ya que, a diferencia de lo que ocurre con otros lenguajes de mayor nivel, el programador debe encargarse de controlar toda la evolución de los datos reservados de esta forma. Esta sección no pretende ser un catálogo exhaustivo de qué tipos de errores se pueden producir en este ámbito, sino que, simplemente, plantea un ejemplo ilustrativo.
Analice el siguiente fragmento de código.
MD-01.- char *p; MD-02.- char *q MD-03.- p=malloc(256); MD-04.- q=malloc(256); MD-05.- strcpy(p, "Hola"); MD-06.- q=p; MD-07.- strcat(q, "Adios"); MD-08.- free(p); MD-09.- printf("%s\n",q);