PRÁCTICAS DE SISTEMAS OPERATIVOS II

NOVENA SESIÓN


  1. Sistemas de ficheros en Windows.

    Los sistemas de ficheros montados en un ordenador que corra Windows están organizados en volúmenes. Para hacer referencia a un volumen, se usan las letras de unidad. Esta situación cambia en Windows 2000.

    Podemos saber cuántas unidades hay montadas en el sistema con GetLogicalDrives o con GetLogicalDriveStrings. La primera nos devuelve la información sobre un mapa de bits donde la unidad A viene representada por el bit menos significativo. La segunda nos da los nombres de las unidades en un búfer separados por el carácter nulo. Debido a la gran cantidad de llamadas que existen, os comentaremos solo las dificultades de manejo de cada una, debiéndoos remitir al resumen para encontrar la información completa.

    Una vez sabemos que una unidad existe, podemos obtener su tipo con GetDriveType. Hay que pasarle la ruta del directorio raíz como parámetro. Por ejemplo, GetDriveType("a:\\");. El hecho de doblar el carácter \ viene porque estamos programando en C y el C exige doblarlo para que no actúe como carácter de escape en las cadenas. Tened cuidado porque incluso en la ayuda del compilador ¡a veces se les olvida!

    Sabemos ya el tipo de la unidad. ¿Qué más datos podemos obtener? Por ejemplo los que nos brinda:
    BOOL GetVolumeInformation( LPCTSTR lpRootPathName, LPTSTR lpVolumeNameBuffer, 
                               DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber,
                               LPDWORD lpMaximumComponentLength, LPDWORD lpFileSystemFlags,
                               LPTSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize);
    La mayor parte de parámetros de la función son parámetros de retorno, donde la función nos devuelve información y nosotros solo tenemos que proporcionarle la variable donde almacenarla. En el caso de que sean arrays o cadenas de caracteres le pasamos también la longitud para que la función no invada zonas de memoria que no le correspondan. ¿Pero qué os vamos a contar a estas alturas? Esta función, en concreto, devuelve el nombre del volumen, su número de serie, el tamaño máximo que puede tener en caracteres el nombre de un fichero, información adicional del volumen y de qué tipo es el sistema de ficheros: FAT, NTFS, etc. Los valores almacenados en lpFileSystemFlags vienen en el resumen.

    Tenemos también una función para cambiar el nombre de un volumen, a saber, SetVolumeLabel y otra que nos dará el espacio libre que queda en la unidad: GetDiskFreeSpace. Esta información nos la dará en clústeres (sectores lógicos).

  2. Directorios y manejo de ficheros.

    En el tratamiento de los directorios, Windows no presenta ninguna novedad. Disponemos de las funciones de la API: GetCurrentDirectory, SetCurrentDirectory, CreateDirectory y RemoveDirectory, de significado evidente. Para borrar un directorio, tiene que estar vacío.

    Lo que es más novedoso es que se disponga de llamadas a la API para realizar operaciones sobre ficheros. Fáciles de manejar, tenemos CopyFile, DeleteFile y MoveFile.

  3. Obtener información de un fichero.

    De un fichero podemos conocer su tamaño con:
    DWORD GetFileSize( HANDLE hFile, LPDWORD lpFileSizeHigh); 
    La razón de tan extraños parámetros la acabamos de ver unas líneas más arriba.

    También podemos conocer o cambiar alguno de los tiempos propios del fichero:
    BOOL GetFileTime( HANDLE hFile, LPFILETIME lpCreationTime, 
                      LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime); 
    Estos tiempos son el tiempo en que se creó el fichero, el tiempo en que por última vez se accedió a él y el tiempo más tardío en el que ha sido modificado. Si el sistema de ficheros no proporciona alguno de estos tiempos (p. ej. FAT), nos devolverá cero. Sin embargo, el problema es que la estructura donde nos da los datos no es "de consumo humano". Una vez obtenida la información debemos usar otras funciones para interpretarla. Si queremos comparar dos de estos tiempos, usaremos CopareFileTime y si lo que deseamos es que nos lo traduzcan, usaremos:
    BOOL FileTimeToSystemTime( CONST FILETIME *lpFileTime, LPSYSTEMTIME lpSystemTime);
    La estructura SYSTEMTIME ya sí tiene interpretación directa. Para cambiar los tiempos de un fichero (haciendo trampa) usaremos SetFileTime. Los más avispados os habréis dado ya cuenta de que hace falta la función opuesta de traducción. Pues sí, se llama SystemTimeToFileTime.

    Los muy exigentes disponen de la función que más información da de un fichero (la "fstat" de Windows). Su nombre es GetFileInformationByHandle y su especificación está en el resumen.

  4. Listado de directorios/búsqueda de ficheros.

    El procedimiento para encontrar un fichero o para hacer un listado de los ficheros contenidos en un directorio es el mismo. Consta de tres pasos:
    1. Apertura de un objeto de búsqueda y obtención del correspondiente handle con:
      HANDLE FindFirstFile( LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData); 
      En lpFileName irá el nombre del fichero que se busca (con posibilidad de uso de comodines para hacer listados).
    2. Llamar cuantas veces sea necesario hasta acabar con el directorio a la función FindNextFile.
    3. Destruir el objeto de búsqueda con la función FindClose.

    Estas funciones, según van encontrando ficheros, devuelven información en una estructura del tipo WIN32_FIND_DATA. Sus campos tienen interpretación directa, excepción hecha del último:
    typedef struct _WIN32_FIND_DATA 
       {DWORD dwFileAttributes;
        FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime;
        DWORD nFileSizeHigh; DWORD nFileSizeLow;
        DWORD dwReserved0; DWORD dwReserved1;
        TCHAR cFileName[ MAX_PATH ];
        TCHAR cAlternateFileName[ 14 ]; } WIN32_FIND_DATA; 
    El último es el nombre alternativo que usan los sistemas FAT para nombrar los ficheros largos. Por ejemplo, "Escritorio" puede aparecer como "ESCRIT~1".

  5. Práctica.

    Hacer un programa que imprima el nombre y tamaño de los ficheros del directorio actual que han sido modificados por última vez un miércoles.

  6. Entrada/salida solapada. Versión simple.

    La entrada/salida solapada consiste en un método de poder solapar entrada/salida con trabajo intenso de CPU en una aplicación. Cuando efectuamos una petición de lectura de un fichero con ReadFile por ejemplo, sabemos que el hilo solicitante se queda bloqueado hasta que los datos estén disponibles. En la entrada/salida solapada, el hilo efectúa la petición de lectura y no se bloquea. Cuando los datos estén listos es notificado de modo asíncrono.

    Para poder realizar la entrada/salida solapada necesitamos especificar el flag FILE_FLAG_OVERLAPPED en CreateFile. Además, en las funciones de lectura y escritura usaremos una estructura del tipo:
    typedef struct _OVERLAPPED {
          DWORD Internal; DWORD InternalHigh; /* Reservado, no tocar. */
          DWORD Offset; DWORD OffsetHigh; /* Desplazamiento donde leer/escribir */
          HANDLE hEvent; /* NulL, o evento que queremos se active cuando acabe */
          } OVERLAPPED; 
    De esta estructura no tocaremos ninguno de los cuatro primeros campos. De hecho, el tercero y el cuarto especificarán el desplazamiento del puntero de fichero. El último parámetro tiene que ver con el método como deseamos ser avisados de que la entrada/salida solapada ha acabado. Si hEvent es NulL, no seremos avisados. Si no, tiene que ser un handle a un objeto de tipo evento de los vistos en la sesión anterior. Este objeto pasará a estar señalado cuando haya acabado la entrada/salida.

    Si lo dejamos a NulL, ¿cómo nos enteraremos? Pues podemos usar la función GetOverlappedResult. Que dónde podéis encontrar cómo se maneja... efectivamente, en el resumen.

    Las funciones de lectura y escritura, cuando estamos trabajando con entrada/salida solapada, si la petición se va a posponer devuelven fallo y el error ERROR_IO_PENDING en GetLastError, aunque sabemos que esto no es un error en sí.

  7. Entrada/salida solapada. Versión avanzada.

    El colmo de la comodidad en esto de la entrada/salida solapada es que cuando se realice la operación de entrada/salida, automáticamente se invoque una función de rellamada. Pues es posible. Para ello, solo hay que usar las funciones de lectura y escritura extendidas:
    BOOL ReadFileEx( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
                     LPOVERLAPPED lpOverlapped, 
                     LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
    BOOL WriteFileEx( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
                      LPOVERLAPPED lpOverlapped, 
                      LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
                
    En la estructura OVERLAPPED, que sigue siendo necesaria, hay que dejar el campo hEvent a NulL. El prototipo de la función de rellamada será:
    VOID CALLBACK FunciOn( DWORD dwErrorCode, 
                         DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped); 
    Como en todas las funciones de rellamada, los parámetros con los que se llamará variarán y serán puestos por el sistema operativo.

    Disponemos también de las versiones extendidas SleepEx, WaitForSingleObjectEx y WaitForMultipleObjectsEx de sus correspondientes versiones simples para poder detectar si ya se ha llamado a la función de rellamada de la entrada/salida solapada.

  8. Para saber más

    ¿Habéis aprendido a valeros por vosotros mismos con esta asignatura? ¿No importa lo que os pidan porque aunque no lo sepáis sabréis cómo buscarlo? Esta puede ser la prueba de fuego. Construid un ejecutable que mediante llamadas a la API de Windows logre apagar acabar con la sesión actual de Windows y apagar el ordenador automáticamente. En los ordenadores más modernos el sistema realmente se apagará. En los más antiguos, se pedirá al usuario que lo realice por sí mismo. El programa en sí no debería ocupar más de 30 líneas y es una aplicación de consola.

    Con la llegada de las últimas versiones de Windows, hacer un programa como este se complica aún más. Se debe lidiar con los nuevos mecanismos de seguridad que impiden a los procesos comunes realizar según qué acciones.

  9. Aplicaciones relacionadas.



  10. Funciones de biblioteca relacionadas.



  11. LPEs.


© 2025 Guillermo González Talaván.