Página siguiente Página anterior Índice general

4. Memoria Intermedia de Páginas Linux

En este capítulo describimos la memoria intermedia de páginas de Linux 2.4. La memoria intermedia de páginas es - como sugiere el nombre - una memoria intermedia de páginas físicas. En el mundo UNIX el concepto de memoria intermedia de páginas se convirtió popular con la introdución de SVR4 UNIX, donde reemplazó a la antememoria intermedia para las operaciones de E/S.

Mientras la memoria intermedia de páginas de SVR4 es sólamente usada como memoria intermedia de datos del sistema de ficheros y estos usan la estructura vnode y un desplazamiento dentro del fichero como parámetros hash, la memoria intermedia de páginas de Linux está diseñada para ser más genérica, y entonces usa una estructura address_space (explicada posteriormente) como primer parámetro. Porque la memoria intermedia de páginas Linux está cerradamente emparejada a la notación del espacio de direcciones, necesitarás como mínimo un conocimiento previo del adress_spaces para entender la forma en la que trabaja la memoria intermedia de páginas. Un address_space es algún tipo de software MMU que mapea todas las páginas de un objeto (ej. inodo) a otro concurrentemente (tipicamente bloques físicos de disco). La estructura address_space está definida en include/linux/fs.h como:


        struct address_space {
                struct list_head        clean_pages;
                struct list_head        dirty_pages;
                struct list_head        locked_pages;
                unsigned long           nrpages;
                struct address_space_operations *a_ops;
                struct inode            *host;
                struct vm_area_struct   *i_mmap;
                struct vm_area_struct   *i_mmap_shared;
                spinlock_t              i_shared_lock;
 
        };

Para entender la forma en la que address_spaces trabaja, sólo necesitamos mirar unos pocos de estos campos: clean_pages, dirty_pages y locked_pages son listas doblemente enlazadas de páginas limpias, sucias y bloqueadas pertenecientes a este address_space, nrpages es el número total de páginas en este address_space. a_ops define los métodos de este objeto y host es un puntero perteneciente a este inodo address_space - quizás sea NULL, ej. en el caso del swapper (intercambiador) address_space (mm/swap_state.c,).

El uso de clean_pages, dirty_pages, locked_pages y nrpages es obvio, por lo tanto hecharemos un severo vistazo a la estructura address_space_operations, definida en la misma cabecera:


        struct address_space_operations {
                int (*writepage)(struct page *);
                int (*readpage)(struct file *, struct page *);
                int (*sync_page)(struct page *);
                int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
                int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
                int (*bmap)(struct address_space *, long);
        };

Para una vista básica del principio de address_spaces (y de la memoria intermedia de páginas) necesitamos hechar una mirada a ->writepage y ->readpage, pero en la práctica necesitamos también mirar en ->prepare_write y ->commit_write.

Probablemente supongas que los métodos address_space_operations lo hacen en virtud de sus nombres solamente; no obstante, requieren hacer alguna explicación. Su uso en el camino de las E/S de los datos de los sistemas de ficheros, por lo lejos del más común camino a través de la memoria intermedia de páginas, suministra una buena forma para entenderlas. Como la mayoría de otros sistemas operativos del estilo UNIX, Linux tiene unas operaciones genéricas de ficheros (un subconjunto de las operaciones vnode SYSVish) para los datos E/S a través de la memoria intermedia de páginas. Esto significa que los datos no interactúan directamente con el sistema de ficheros en read/write/mmap, pero serán leidos/escritos desde/a la memoria intermedia de páginas cuando sea posible. La memoria intermedia de páginas tiene que obtener datos desde el sistema de ficheros actual de bajo nivel en el caso de que el usuario quiera leer desde una página que todavía no está en memoria, o escribir datos al disco en el caso de que la memoria sea insuficiente.

En el camino de lectura, los métodos genéricos primero intentarán encontrar una página que corresponda con la pareja buscada de inodo/índice.

hash = page_hash(inode->i_mapping, index);

Entonces testeamos cuando la página actualmente existe.

hash = page_hash(inode->i_mapping, index); page = __find_page_nolock(inode->i_mapping, index, *hash);

Cuando no existe, asignamos una nueva página, y la añadimos al hash de la memoria intermedia de páginas.

page = page_cache_alloc(); __add_to_page_cache(page, mapping, index, hash);

Después de que la página haya sido hashed (ordenada) utilizamos la operación ->readpage address_space para en este instante rellenar la página con datos (el fichero es una instancia abierta del inodo).

error = mapping->a_ops->readpage(file, page);

Finalmente podemos copiar los datos al espacio de usuario.

Para escribir en el sistema de archivos existen dos formas: una para mapeos escribibles (mmap) y otra para la familia de llamadas al sistema write(2). El caso mmap es muy simple, por lo tanto será el que primero discutamos. Cuando un usuario modifica los mapas, el subsistema VM marca la página como sucia.

SetPageDirty(page);

El hilo del núcleo bdflush que está intentando liberar páginas, como actividad en segundo plano o porque no hay suficiente memoria, intentará llamar a ->writepage en las páginas que están explicitamente marcadas como sucias. El método ->writepage tiene ahora que escribir el contenido de las páginas de vuelta al disco y liberar la página.

El segundo camino de escritura es _mucho_ más complicado. Para cada página que el usuario escribe, tenemos básicamente que hacer lo siguiente: (para el código completo ver mm/filemap.c:generic_file_write()).

page = __grab_cache_page(mapping, index, &cached_page); mapping->a_ops->prepare_write(file, page, offset, offset+bytes); copy_from_user(kaddr+offset, buf, bytes); mapping->a_ops->commit_write(file, page, offset, offset+bytes);

Por lo tanto intentamos encontrar la página ordenada o asignar una nueva, entonces llamamos al método ->prepare_write address_space, copiamos la antememoria del usuario a la memoria del núcleo y finalmente llamamos al método ->commit_write. Tal como probablemente has visto ->prepare_write y ->commit_write son fundamentalmente diferentes de ->readpage y ->writepage, porque ellas no sólo son llamadas cuando la E/S física se quiere actualizar sino que son llamadas cada vez que el usuario modifica el fichero. Hay dos (¿o más?) formas para manejar esto, la primero es usar la antememoria intermedia de Linux para retrasar la E/S física, rellenando un puntero page->buffers con buffer_heads, que será usado en try_to_free_buffers (fs/buffers.c) para pedir E/S una vez que no haya suficientemente memoria, y es usada de forma muy difundida en el actual núcleo. La otra forma justamente establece la página como sucia y confía en que ->writepage realice todo el trabajo. Debido a la carencia de un bitmap de validez en la página de estructuras, esto no realiza todo el trabajo que tiene una granularidad más pequeña que PAGE_SIZE.


Página siguiente Página anterior Índice general