0 Linux – obsługa systemu plików
Tomasz Zaworski edited this page 2020-11-15 02:28:24 +01:00
  1. Obsługa systemu plików
  2. Funkcje systemowe i ich argumenty
  3. creat
  4. open
  5. close
  6. read
  7. write
  8. Trawersowanie katalogów
  9. Zadania
  10. Zadania do wykonania na zajęciach
  11. Zadania domowe

Obsługa systemu plików

Jądro systemu operacyjnego UNIX udostępnia dwie podstawowe operacje na plikach — odczyt i zapis — realizowane odpowiednio przez funkcje systemowe read i write. Z punktu widzenia jądra w systemie UNIX plik nie ma żadnej struktury, tzn. nie jest podzielony na przykład na rekordy. Plik jest traktowany jako tablica bajtów, zatem operacje odczytu lub zapisu mogą dotyczyć dowolnego fragmentu pliku, określonego z dokładnością do bajtów.

Wykonanie operacji wymaga wskazania pliku, na którym operacja ma zostać wykonana. Plik w systemie UNIX identyfikowany jest przez nazwę (w szczególności podaną w postaci ścieżki katalogowej), przy czym podawanie nazwy pliku przy każdym odwołaniu do niego wymagałoby każdorazowego przeszukiwania odpowiednich katalogów w celu ostatecznego ustalenia jego lokalizacji. W celu uniknięcia czasochłonnego przeszukiwania katalogów podczas lokalizowania pliku przy każdej operacji na nim, wprowadzona została funkcja systemowa open, której zadaniem jest zaalokowanie niezbędnych zasobów w jądrze, umożliwiających wykonywanie dalszych operacji na pliku bez potrzeby przeszukiwania katalogów. Funkcja open zwraca deskryptor, który jest przekazywany jako parametr aktualny, identyfikujący plik, do funkcji systemowych związanych z operacjami na otwartych plikach. (Standardowo zajęte są deskryptory 0, 1 i 2, odpowiadające standardowemu wejściu, standardowemu wyjściu i standardowemu wyjściu diagnostycznemu. Wszystkie te deskryptory najczęściej związane są z plikiem specjalnym, jakim jest terminal).

Przy otwieraniu pliku przekazywany jest tryb otwarcia, określający dopuszczalne operacje, jakie można wykonać w związku z tym otwarciem, np. tylko zapis, tylko odczyt lub zapis i odczyt. Tryb otwarcia może mieć również wpływ na sposób wykonania tych operacji, np. każda operacja zapisu dopisuje dane na końcu pliku.

Jądro systemu operacyjnego dostarcza też mechanizm tworzenia plików. Mechanizm tworzenia plików zwykłych dostępny jest przez funkcję systemową creat, która tworzy plik o nazwie podanej jako parametr aktualny i otwiera utworzony plik w trybie do zapisu, zwracając odpowiedni deskryptor.

Funkcje tworzące pliki i operujące na nich opisane są w części 2 pomocy systemowej.

Tworzenie i otwieranie plików realizowane jest za pomocą funkcji:

  • open - otwarcie pliku (uogólniona funkcja open umożliwia również utworzenie pliku),
  • creat - utworzenie pliku i otwarcie do zapisu,
  • close - zamknięcie deskryptora otwartego pliku,
  • unlink - usunięcie dowiązania do pliku.

Operacje na plikach realizowane są za pomocą funkcji:

  • read - odczyt fragmentu pliku,
  • write - zapis fragmentu pliku,
  • lseek - przesunięcie wskaźnika bieżącej pozycji.

Opis możliwych kodów błędów omawianych funkcji znajduje się w podręczniku użytkownika.

Funkcje systemowe i ich argumenty

creat

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int creat( const char *pathname, mode_t mode );

Wartości zwracane:

  • poprawne wykonanie funkcji: deskryptor otwartego pliku,
  • zakończenie błędne: -1.

Argumenty funkcji:

  • pathname - wskaźnik do napisu zawierającego nazwę ścieżki pliku, który ma być otwarty (nazwa bezwzględna lub względna),
  • mode - prawa dostępu (np. 0640)

Funkcja tworzy plik, którego lokalizację wskazuje parametr pathname. Prawa dostępu do utworzonego pliku ustawiane są zgodnie z parametrem mode. Jeśli plik o takiej nazwie już istnieje, a proces wywołujący funkcję creat ma prawo do zapisu tego pliku, to jego zawartość jest usuwana (następuje obcięcie pliku). Plik wskazywany przez pathname otwierany jest w trybie do zapisu.

open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open( const char *pathname, int flags[, mode_t mode] );

Wartości zwracane:

  • poprawne wykonanie funkcji: deskryptor otwartego pliku,
  • zakończenie błędne: -1.

Argumenty funkcji:

  • pathname - wskaźnik do napisu zawierającego nazwę ścieżki pliku, który ma być otwarty (nazwa bezwzględna lub względna),
  • flags - metoda dostępu:
  • O_RDONLY - otwarcie w trybie tylko do odczytu,
  • O_WRONLY - otwarcie w trybie tylko do zapisu,
  • O_RDWR -- otwarcie w trybie do odczytu i do zapisu.

Argument flags może być połączony bitowym OR z jedną (lub więcej) z następujących wartości:

  • O_CREAT - utworzenie pliku, jeśli plik jeszcze nie istnieje,

  • O_TRUNC - obcięcie pliku, jeśli plik istnieje i otwierany jest w trybie O_WRONLY lub O_RDWR,

  • O_EXCL - powoduje zgłoszenie błędu jeśli plik już istnieje i otwierany jest z flagą O_CREAT

  • O_APPEND - operacje pisania odbywają się na końcu pliku.

  • mode - prawa dostępu do pliku (np. 0666); używany tylko z flagą O_CREAT; jest modyfikowany przez atrybut umask procesu, tzn. prawa dostępu do tworzonego pliku będą równe (mode & ~umask).

Parametr wejściowy pathname jest nazwą (w szczególności pełną nazwą ścieżkową) pliku, parametr wejściowy flags oznacza tryb otwarcia pliku pliku i może mieć następujące wartości: O_RDONLY, O_WRONLY, O_RDWR.

Dodatkowo w trybie zapisu możliwe jest użycie flagi O_APPEND, która jest sumowana bitowo z O_WRONLY lub O_RDWR i powoduje, że zapis wykonywany jest zawsze na końcu pliku. Dane są więc dopisywane do pliku i system gwarantuje, że nie nastąpi nadpisanie danych zapisanych wcześniej.

Poza funkcjami open i creat istnieje uogólniona, trzyparametrowa wersja funkcji open, która łączy cechy obu tych funkcji. Dodatkowy parametr prawa określa prawa dostępu do pliku (podobnie jak dla funkcji creat) i wykorzystywany jest wówczas, gdy tryb otwarcia wymusza tworzenie pliku. Przydatne są wówczas dodatkowe flagi umieszczane w trybie otwarcia: O_CREAT, O_TRUNC, O_EXCL.

Funkcja creat jest równoważna uogólnionej funkcji open z parametrem tryb równym O_WRONLY|O_CREAT|O_TRUNC, czyli poniższe wywołania są równoważne:

creat( nazwa_pliku, prawa );
open( nazwa_pliku, O_WRONLY|O_CREAT|O_TRUNC, prawa );

umask to maska uprawnień dla nowotworzonych plików. Najczęściej domyślny umask to 0022, tzn. nowy plik będzie miał uprawnienia 0644. Więcej informacji o umask znajdziesz w zadaniu 1.

close

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int close( int fd );

Wartości zwracane:

  • poprawne wykonanie funkcji: 0,
  • zakończenie błędne: -1 .

Argumenty funkcji:

  • fd - deskryptor zamykanego pliku .

Funkcja zamyka deskryptor pliku przekazany przez parametr fd. Po zamknięciu pliku zwalniana jest pozycja w tablicy deskryptorów i może ona zostać ponownie wykorzystana przy otwarciu kolejnego pliku, czyli nowo otwarty plik może otrzymać ten sam deskryptor, który miał plik wcześniej zamknięty. Ponadto zmniejszany jest o 1 licznik deskryptorów w tablicy otwartych plików. Jeśli fd jest ostatnią kopią deskryptora pliku, to zasoby z nim związane zostają zwolnione, natomiast jeśli deskryptor był ostatnia referencją do pliku, który usunięto komendą unlink, plik jest kasowany.

read

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int read( inf fd, void *buf, size_t count );

Wartości zwracane:

  • poprawne wykonanie funkcji: rzeczywista liczba bajtów, jaką udało się odczytać,
  • zakończenie błędne: -1.

Argumenty funkcji:

  • fd - deskryptor pliku z którego mają zostać odczytane dane,
  • buf - adres bufora znajdującego się w segmencie danych procesu, do którego zostaną przekazane dane odczytane z pliku w wyniku wywołania funkcji read,
  • count - ilość bajtów do odczytania.

Funkcja powoduje odczyt count bajtów z otwartego pliku, identyfikowanego przez deskryptor fd, począwszy od bieżącej pozycji wskaźnika do pliku i umieszczenie ich pod adresem buf w przestrzeni adresowej procesu. Funkcja zwraca liczbę bajtów, na której udało się wykonać operację (zero oznacza koniec pliku).

Odczyt powoduje zmianę wskaźnika bieżącej pozycji w pliku. Po otwarciu pliku wskaźnik ten ustawiony jest na 0, czyli na początek pliku, a po kolejnych operacjach przesuwa się w kierunku końca pliku o tyle bajtów ile udało się odczytać.

Podając jako deskryptor fd makro STDIN_FILENO możemy czytać ze standardowego wejścia:

#include <unistd.h>

#define STDIN_FILENO    0       /* standardowe wejście */

write

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int write( inf fd, void *buf, size_t count );

Wartości zwracane:

  • poprawne wykonanie funkcji: rzeczywista liczba bajtów, jaką udało się zapisać,
  • zakończenie błędne: -1,

Argumenty funkcji:

  • fd - deskryptor pliku do którego mają zostać zapisane dane,
  • buf - adres bufora znajdującego się w segmencie danych procesu, z którego zostaną pobrane dane zapisane przez funkcję write,
  • count - ilość bajtów do zapisania.

Funkcja powoduje zapis count bajtów do otwartego pliku, identyfikowanego przez deskryptor fd, począwszy od bieżącej pozycji wskaźnika do pliku i umieszczenie ich pod adresem buf w przestrzeni adresowej procesu. Funkcja zwraca liczbę bajtów, na której udało się wykonać operację.

Podobnie jak dla funkcji read, zapis powoduje zmianę wskaźnika bieżącej pozycji w pliku.

Podając jako deskryptor fd makro STDOUT_FILENO lub STDERR_FILENO, możemy pisać na odpowiednio na standardowe wyjście lub standardowe wyjście diagnostyczne:

#include <unistd.h>

#define STDOUT_FILENO   1       /* standardowe wyjście */
#define STDERR_FILENO   2       /* standardowe wyjście diagnostyczne */

Przykład

Wyświetlenie zawartości pliku file1.txt na standardowe wyjście:

#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

int main()
{
    char buffer[BUFFER_SIZE];
    int fd_in, num;

    fd_in = open("file1.txt", O_RDONLY);

    while ((num = read(fd_in, &buffer, BUFFER_SIZE)) > 0)
    {
        write(STDOUT_FILENO, &buffer, num);
    }

    close(fd_in);

    exit(EXIT_SUCCESS);
}

Trawersowanie katalogów

#include <sys/types.h>
#include <dirent.h>

DIR* opendir(const char* name);
struct dirent* readdir(DIR* dirp);

Aby wyświetlić zawartość katalogu name możemy posłużyć się funkcją opendir. Dla pomyślnie otwartego katalogu, możemy kolejno odczytywać jego zawartość funkcją readdir, która zwraca strukturę dirent. Tak zdefiniowana struktura posiada m.in. pole char d_name[256], które zawiera nazwę pliku w obecnie trawersowanym katalogu.

Przykład

Wyświetlenie zawartości katalogu głównego (/):

#include <dirent.h>
#include <errno.h>
#include <sys/types.h>
#include <stdio.h>

int main()
{
    DIR *dir;
    struct dirent *entry;

    if ((dir = opendir("/")) == NULL)
        perror("blad opendir");
    else
    {
      printf("zawartosc katalogu / :\n");

      while ((entry = readdir(dir)) != NULL)
          printf("  %s\n", entry->d_name);

      closedir(dir);
    }

    return 0;
}

Zadania

Zadania do wykonania na zajęciach

Zadanie 1

a)

Napisz program lab_cp.c, który działa jak program cp [źródło] [cel], kopiujący pliki.

  • Przyjmij, że nowy plik będzie mieć ustawione prawa dostępu -rw-rw-rw- (0666).

Dlaczego nowy plik ma inne prawa dostępu? Sprawdź w manualu (man 2 umask) w jaki sposób sprawić by plik otrzymał pożądane uprawa dostępu.

b)

Tworzony jest nowy plik w języku C przy pomocy funkcji systemowej open; żądane prawa dostępu do nowego pliku mają wartość 0666, natomiast maska dopełnień procesu umask wynosi 0012. Jakie prawa dostępu będzie miał utworzony plik?

Podpowiedź: obie wartości zapisane są w systmie ósemkowym; przedstaw obie wartości w systemie binarnym, dokonaj negacji maski dopełnień, dokonaj operacji logicznej and (&) na odpowiadających sobie bitach, a na końcu zapisz wynik w systemie ósemkowym.

Zadanie 2

Napisz program lab_cat.c, który działa jak program cat [plik], wyświetlający na standardowe wyjście:

  1. zawartość pliku (argument przy uruchomieniu [plik]),
  2. tekst ze standardowego wejścia (brak argumentu przy uruchomieniu).
Zadanie 3

Napisz program lab_ls.c, który działa jak program ls [katalog], wyświetlający zawartość:

  1. katalogu roboczego (brak argumentu przy uruchomieniu),
  2. dowolnego katalogu (argument przy uruchomieniu [katalog]).

Zadania domowe

Zadanie domowe 1

Napisz program lab_ps.c, który działa jak program ps -e, wyświetlający wszystkie działające procesy w systemie.

  • Listę procesów znajdziesz w katalogu /proc; zapoznaj się z dokumentacją man proc.
  • W swoim rozwiązaniu wyświetl kolumny PID oraz CMD.

Przekaż plik prowadzącemu zgodnie z zasadami ustalonymi na ćwiczeniach.

Zadanie domowe 2

Napisz program lab_ls_l.c, który działa jak program ls -l [katalog], wyświetlający zawartość:

  1. katalogu roboczego (brak argumentu przy uruchomieniu),
  2. dowolnego katalogu (argument przy uruchomieniu [katalog]).

Prawa dostępu, user id, group id oraz rozmiar danych mogą się wyświetlać jako dane numeryczne. Zapoznaj się z dokumentacją man 2 stat.


Źródło materiałów