mmap - dwa pytania dotyczące użycia

0

Witam

  1. Mmap gdy mu się poda file deskryptor i ustawi obojętnie jaką flagę - nawet MAP_SHARED - odwzorowuje to co znajduje się w pliku na przestrzeń adresową procesu.

W przypadku plików z katalogu /dev jeżeli w kernel spejsie drivera jest zaimplementowana funkcja mmap która następnie jest przekazywana do file operations tablicy podczas rejestracji drivera można uzyskać nawet odwzorowanie w przestrzeni adresowej procesu mapy pamięci urządzenia zewnętrznego podłączonego np. przez GPIO do procesora.

Ale tutaj mam pytanie. To wyżej sprawia że zżeramy pamięć RAM, czy mam rację? Jakbyśmy tego wyżej nie robili tylko za każdym razem otwierali plik (wywołanie syscalla open), odczytywali/zapisywali dane (kolejne syscalle) to mielibyśmy więcej pamięci RAM, ale za to wolniejsze dostępy do interesujących nas danych przez to że syscalle leciałyby do kernel spejsa i głównie po to się używa mmap aby mieć szybszy dostęp, czy mam rację?

  1. Gdy mamy proces z którego forkujemy inny proces to zauważyłem że nie trzeba używać w ogóle shm_open, shm_unlink do tworzenia współdzielonej pamięci, wystarczy użyć tylko mmap:
static int* counterGlob = nullptr;

void SemaphoreTest()
{
#ifdef LINUX_USED
    cout << "\nSemaphoreTest()" << endl;

    counterGlob = (int*)mmap(NULL, sizeof(counterGlob), PROT_WRITE | PROT_READ, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if(counterGlob == MAP_FAILED)
    {
        perror("Mapping failed");
        exit(EXIT_FAILURE);
    }

    int counter = 0;
    pid_t pid = fork();

    if (pid == 0)
    {
        cout << "Child process with pid " << getpid() << ", local counter = " << ++counter << endl;
        cout << "Child process with pid " << getpid() << ", global counter = " << ++*counterGlob << endl;
        this_thread::sleep_for(chrono::milliseconds(2000));
        exit(0);
    }
    else if (pid > 0)
    {
        cout << "Parent process with pid " << getpid() << ", local counter = " << ++counter << endl;
        int status = 0;
        pid_t pid = wait(&status);
        cout << "Child process with pid " << pid << " exited with code " << status 
             << " in parent context with pid " << getpid() << endl;
        cout << "Parent process with pid " << getpid() << ", global counter = " << ++*counterGlob << endl;
    }
    else
        cout << "Fork failed " << endl;

    if(munmap(counterGlob, sizeof(counterGlob)) == -1)
    {
        perror("unmapping failed");
        exit(EXIT_FAILURE);
    }

    exit(0);
#endif
}

Stąd mój wniosek, że dodatkowego interfejsu shm_open, shm_unlink używa się tylko gdy mamy procesy które nie powstały z forka, wtedy nie da się użyć tylko mmapy do stworzenia pamięci współdzielonej i stąd potrzeba tego dodatkowego interfejsu, mam rację?

0

Cóż, trudno odpowiedzieć na twoje pytanie, nie jestem ekspertem od kernela, pewnie nawet zależy to od implementecji zarządzania pamięci, może być zależne od platformy, itd. Napiszę to co wiem.
Podejrzewam, że na x86(_64) mmap używa stronnicowania (paging), cechy procesora związanej z pamięcią wirtualną. Innym kandydatem jest DMA — bezpośredni dostęp urządzeń do pamięci operacyjnej. Te słowa klucze na pewno przydadzą ci się w dalszych poszukiwaniach. Może być tak, że są używane oba w zależności od sytuacji.
Jeśli używany jest paging to nie zajmujesz nic w RAMie, po prostu mapujesz wirtualny adres na dysk, jedyny wypadek kiedy efektywnie tracisz miejsce w pamięci to wtedy gdy na architekturze 32bit przez takie mapowanie zabraknie ci przestrzeni adresowej na cały RAM. Przy 64-bitowej przestrzeni raczej ci miejsca na RAM nie zbraknie. xD
Jeśli używane jest DMA rzeczywiście zajmujesz miejsce w RAMie. Możesz zrobić prosty eksperyment. W /proc/meminfo znajdziesz dość szczegółowe informacje nt. zużycia pamięci/przestrzeni adresowej. W necie na pewno znajdziesz szczegóły tego co tam jest.
Co do shm_open, nie mam doświadczenia, ale wiem tyle, że fork powoduje skopiowanie przestrzeni adresowej procesu, więc zmapowaną pamięć mapuje tak samo w drugim procesie, zatem plik robi ci za SHM z automatyczną archiwizacją na dysku. To by pasowało do opisywanych przez Ciebie obserwacji.

0

Biorąc pod uwagę, ile razy pada w dokumentacji mmap pada słówko „page”, to raczej używany jest paging ;)
I tak, mmap jest preferowaną (bo prostszą), nowszą formą współdzielenia pamięci pomiędzy procesami potomnymi. No i oprócz shm_open masz jest shmget.

Wg mojej wiedzy - jeśli piszesz na cudaczne i stare systemy (Solaris?), ew. ma być aż boleśnie przenośnie - używaj shmget. A w każdym innym wypadku - co Ci pasuje i działa.
Stare (sysV) API jest upierdliwe w użyciu ale ma chyba więcej możliwości od nowego. Co kto lubi o czego potrzebuje. Więcej tu: https://stackoverflow.com/questions/21311080/linux-shared-memory-shmget-vs-mmap?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa

Ale ogólnie: tak, współdzielenie zmapowanej pamięci pomiędzy procesami potomnymi to ficzer mmap, to jest napisane explicite w jego manualu.

0
tomek_m333 napisał(a):

Ale tutaj mam pytanie. To wyżej sprawia że zżeramy pamięć RAM, czy mam rację? Jakbyśmy tego wyżej nie robili tylko za każdym razem otwierali plik (wywołanie syscalla open), odczytywali/zapisywali dane (kolejne syscalle) to mielibyśmy więcej pamięci RAM, ale za to wolniejsze dostępy do interesujących nas danych przez to że syscalle leciałyby do kernel spejsa i głównie po to się używa mmap aby mieć szybszy dostęp, czy mam rację?

Nie koniecznie. Normalne otworzenie i czytanie go skutkuje załadowaniem całych stron pliku (tzn nie całego pliku, tylko z obszaru, który czytasz) i trzymaniem tej zawartości w tym samym page cache. Czyli dla małych plików zużycie pamięci będzie porównywalne. Dla większych alokacji trzeba pamiętać, że dopóki nie użyjesz zamapowanej strony to fizycie jej nie dostaniesz, jedynie przestrzeń wirtualną. Do tego dochodzi readahed (sprawdź co robi funkcja madvise).

mmap nie da ci od razu całego pliku, chyba że rozgrzejesz tą pamięć, czytając po kolei jej części.

W przypadku urządzenia implementującego mmap nie widzę powodu fizycznej alokacji pamięci (poza samym sterownikiem). Jedynie przestrzeń wirtualna procesu zostanie użyta.

0
nalik napisał(a):

W przypadku urządzenia implementującego mmap nie widzę powodu fizycznej alokacji pamięci (poza samym sterownikiem). Jedynie przestrzeń wirtualna procesu zostanie użyta.

OK rozumiem, czyli dla mmap w przypadku mapowania plików, pamięci urządzeń zewnętrznych jedyne niebezpieczeństwo to w architekturze 32 bitowej zajęcie całej wirtualnej przestrzeni adresowej procesu, tak że możemy wykrzaczyć proces. Niemniej jednak nie ma niebezpieczeństwa przez tą akcje wykrzaczenia całego systemu (innych procesów) gdyż mmap nie zużywa pamięci RAM w tym wypadku a jedynie zajmuje przestrzeń wirtualną procesu.
Jeśli jednak używamy mmap do alokacji pamięci (zamiast malloca,new) wtedy zajmujemy pamięć RAM.

W przypadku alokacji pamięci współdzielonej zadeklarowany obszar pamięci musi być wielokrotnością rozmiaru strony. Jak chcę zadeklarować x intów to jest coś prostszego od tego niżej?

    int fd = shm_open("./sharedMem", O_RDWR | O_CREAT, 0)    //create and opens shared memory object
    if(fd < 0)
    {
        perror("shm_open failure");
        exit(EXIT_FAILURE);
    }

    const int NUMBER_OF_INTS = 10000; //the amount of memory which I want to allocate
    //however it has to be page alignment so let's do it
    int pageAligmentBytes = (NUMBER_OF_INTS/pagesize)*pagesize + (NUMBER_OF_INTS %pagesize ? pagesize : 0)

    if(ftruncate(fd, pageAligmentBytes ) == -1)
    {
        perror("ftruncate failure")
        exit(EXIT_FAILURE)
    }
0

Właściwie to błąd, brakuje jeszcze sizeof(int), powinno być:

```

int pageAligmentBytes = ((sizeof(int)*NUMBER_OF_INTS)/pagesize)*pagesize + ((sizeof(int)*NUMBER_OF_INTS) %pagesize ? pagesize : 0)

ale i tak to się robi nieczytelne to zalignowanie do wielokrotności rozmiaru strony
0
 int pageAligmentBytes = ((sizeof(int)*NUMBER_OF_INTS)/pagesize)*pagesize + ((sizeof(int)*NUMBER_OF_INTS) %pagesize ? pagesize : 0)
0

API sysV (shmget/shmat) zaokrągla samo do rozmiaru strony. Czy calosciowo jest lepsze czy gorsze to sprawa gustu.

0
tomek_m333 napisał(a):

OK rozumiem, czyli dla mmap w przypadku mapowania plików, pamięci urządzeń zewnętrznych jedyne niebezpieczeństwo to w architekturze 32 bitowej zajęcie całej wirtualnej przestrzeni adresowej procesu, tak że możemy wykrzaczyć proces. Niemniej jednak nie ma niebezpieczeństwa przez tą akcje wykrzaczenia całego systemu (innych procesów) gdyż mmap nie zużywa pamięci RAM w tym wypadku a jedynie zajmuje przestrzeń wirtualną procesu.

Używanie mmap dla pliku i czytanie zawartość przez odwzorowaną pamięć jak najbardziej zajmuje pamięć RAM.

  • Normalne otwarcie pliku (np. przez open czy fopen) też zajmuje tą pamięć RAM, ponieważ zawartość plików jest wczytywana do page cache. Ten sam page cache jest używany przez mmap.
  • W przypadku odwzorowania pliku w pamięci przez mmap, jego zawartość będzie wczytywana w jądrze podczas odniesień do kolejnych stron pamięci. Tzn. że cały plik nie zostanie automatcznie wczytany i odwzorowany. Można w ograniczonym zakresie sterować strategią wczytywania stron (a więc i pliku) poprzez funkcję madvise.
  • W przypadku braku wolnych fizycznych stron w page cache system je odzyska, tzn. zeswapuje (o ile swap jest skonfigurowany), a w przypadku plików odwzorowanych w pamięci przez mmap, zapisze do oryginalnego pliku lub jeżeli zawartość strony nie uległa zmianie po prostu "odmapuje" od procesu fizyczną stronę (logicznie nadal będzie "zmapowana" i w razie potrzeby zostanie ponownie wczytana z dysku). Swapowanie można zablokować przez mlock. Można też określić jego agresywność przez parametr swappiness (/proc/sys/vm/swappiness).

Jeśli jednak używamy mmap do alokacji pamięci (zamiast malloca,new) wtedy zajmujemy pamięć RAM.

Tak. Generalnie to malloc to funkcja biblioteczna ze standardu C. Z tego co wiem to jej implementacja (w unixach) pod spodem używają sbrk albo mmap (w zależności jak duży obiekt próbujemy zaalokować).

0

O ile pamiętam (ale nie bijcie, jeśli teraz skłamię, nie mam tej książki pod ręką) APUE zaleca używanie mmap dla alokowania dużych ilości pamięci, bo może to być zauważalnie szybsze od malloc.

0

Mam tutaj pytanie.
W przypadku gdy wykonujemy:

int fd = open("sciezka_do_pliku", ..)
char* wsk = mmap(....,PROT_WRITE | PROT_READ, MAP_SHARED, fd)

to jak ktoś wyżej wspomniał mmap mapuje nam zawartość pliku w przestrzeni wirtualnej procesu, czyli innymi słowy strony procesu zaczynają wskazywać na poszczególne bloki na dysku twardym a związane z plikiem. Dopiero gdy wywołamy:

wsk[23] = 'a';

to wtedy zgłasza się sygnał PAGE_FAULT (strona nie zmapowana na żaden adres pamięci fizycznej) w kernel spejsie i jest ono obsługiwane w ten sposób, że blok pamięci z dysku twardego jest kopiowany do wolnej ramki pamięci fizycznej a następnie do PTE (page table entry) jest kopiowany adres tej pamięci fizycznej tak że już teraz strona wskazuje na ramkę pamięci fizycznej. Teraz jak ponownie zmodyfikujemy
wsk[23] = 'a'
to już nie zgłosi się sygnał PAGE_FAULT, nastąpi modyfikacja w ramce pamięci fizycznej na którą wskazuje strona i od tego momentu blok na dysku twardym jest różny od tego co w zmapowanej pamięci RAM. Jeżeli wykonamy msynca (bądź fscyna już nie pamiętam) to wtedy nastąpi przepisanie ramki do bloku i tym samym kontent pliku będzie się pokrywał z kontentem tego co w zmapowanej pamięci RAM.

To do powyższego mam pytanie co w przypadku gdy open zastąpimy funkcją shm_open która alokuje część pamięci fizycznej na który wskazuje string jako pierwszy argument. Czy wszystko zostaje tak samo? Czy może nie zgłosi się już nam PAGE_FAULT, bo de fakto strona od razu wskazuje nam na pamięć fizyczną?

To wszystko wyżej dla przykładu gdy w mmap podajemy MAP_SHARED czyli nie występuje mechanizm copy-on-write.

0

OK z dokumentacji:

The effect of msync() on a shared memory object is unspecified.

Czyli PAGE_FAULT nie zgłasza się dla memory mapped obiektów

1 użytkowników online, w tym zalogowanych: 0, gości: 1