Elektroda.pl
Elektroda.pl
X

Search our partners

Find the latest content on electronic components. Datasheets.com
Elektroda.pl
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

[STM32F1][C] - Lista jedokierunkowa - wyciek pamięci

Co_pat 26 Jun 2014 14:24 3186 30
  • #1
    Co_pat
    Level 15  
    Witam!

    Kilka słów o projekcie.
    Urządzenie wykonuje odczyt z czujników - częstotliwość pomiarów i ilość punktów pomiarowych jest zmienna.
    Pomiary następnie są wysyłane do urządzenia nadzorującego w odpowiedzi na jego zapytanie.
    Moim założeniem jest kolejkowanie pełnych ramek z pomiarami i czasem ich wykonania w postaci listy jednokierunkowej, a gdy jest to możliwe wysyłane ich do mastera.

    Element listy zawiera wskaźniki do zaalokowanej pamięci na ramkę i jej długość.
    Ograniczeniem długości listy jest wielkość zużywanej pamięci.

    Problem polega na wyciekach pamięci i/lub nadpisywaniu błędnego obszaru pamięci.

    Obsługę list wykonałem w oparciu o książkę TMF:
    pliki .h, .c
    Code: c
    Log in, to see the code


    Wykorzystanie w głównej pętli:
    Code: c
    Log in, to see the code


    Z góry dziękuję za pomoc!
  • #2
    michcior
    Level 30  
    Tak nie zagłębiając się za bardzo w kod, skąd jest malloc/free? Jesteś pewien że to działa? Jest przydzielony obszar dla malloc poprawnie i wystarczająco duży?
    Taki kontroler nie ma wirtualizacji pamięci, to i uprzątanie obszaru przydzielonego dla malloc jest koszmarne. W zasadzie jeśli się chce mieć w miarę wydajność, to lepiej uciekać od malloc i korzystać ze statycznych zasobów. Tutaj, zadeklarowałbym statycznie tablicę elementów "node" raz "list_element_t" o rozmiarze 512 i z nich pobierał te które są aktywne. A może cricular-buffer, FIFO, jest kilka wzorców do wykorzystania.
  • #5
    michcior
    Level 30  
    GrzegorzKostka
    Niby tak, ale to są alokatory stworzone dla systemów gdzie mamy dajmy na to 500kB - kilku MB przeznaczonych na stertę. To nie będzie działać kiedy sterta będzie miała 30kB. Cokolwiek by nie robić, defragmentacja to ciągłe przekopiowywanie danych. Można to robić lepiej, gorzej, z wykorzystaniem DMA ale zawsze będzie trzeba mieć rezerwę w stercie żeby system był stabilny.
  • #6
    Co_pat
    Level 15  
    Dziękuję wszytkim za odpowiedzi!
    michcior wrote:
    Tak nie zagłębiając się za bardzo w kod, skąd jest malloc/free? ....

    malloc i free pochodzą z bleeding-edge-toolchain od Frediego tak jak syscalls i szablon projektu.
    michcior wrote:
    ... Tutaj, zadeklarowałbym statycznie tablicę elementów "node" raz "list_element_t" o rozmiarze 512 i z nich pobierał te które są aktywne. A może cricular-buffer, FIFO, jest kilka wzorców do wykorzystania.

    Właśnie chciałem odchodzić od statycznych tablic na rzecz dynamicznego przydzielania pamięci, ale kolejny raz czytam, że to nie jest najlepsze rozwiązanie.
    Dużo osób pisze o statycznym deklarowaniu obszaru pamięci i późniejszym podziale na drobniejsze fragmenty. Z czego to wynika, jakie są główne wady i zalety tych rozwiązań?

    Freddie Chopin wrote:
    C++11 i nagłówek <forward_list>.

    Jeśli używasz tego kodu wielowątkowo (lub przerwania i pętla główna), to na 99% masz błąd typu "data race". Poza tym ten kod jest mocno dziwny.

    4\/3!!


    Obszary pamięci przydzielone na listę są tylko używane w głównej pętli.
    Możesz naprowadzić dlaczego ten kod jest dziwny? Za wszelkie uwagi będę wdzięczny.
  • #7
    Freddie Chopin
    MCUs specialist
    Co_pat wrote:
    Właśnie chciałem odchodzić od statycznych tablic na rzecz dynamicznego przydzielania pamięci, ale kolejny raz czytam, że to nie jest najlepsze rozwiązanie.

    Każde przemyślane rozwiązanie jest dobre. Jeśli nie przesadzisz z dynamiczną alokacją to też będzie ona działać OK.

    Co_pat wrote:
    Dużo osób pisze o statycznym deklarowaniu obszaru pamięci i późniejszym podziale na drobniejsze fragmenty. Z czego to wynika, jakie są główne wady i zalety tych rozwiązań?

    Główną zaletą będzie to, że wynajdziesz koło na nowo. Przecież malloc() działa tak samo - ma obszar pamięci i dzieli go na mniejsze kawałki...

    Po prostu we wszystkim trzeba mieć umiar - jeśli planujesz używać 95% pamięci sterty na bloki o rozmiarze 5% tej sterty, to pewnie faktycznie problem fragmentacji w końcu Cię dotknie, ale jeśli jest to "uporządkowane" to ja bym się specjalnie nie przejmował... Choć faktycznie na swoje potrzeby też zrobiłem sobie alokator statyczny (;

    Co_pat wrote:
    Możesz naprowadzić dlaczego ten kod jest dziwny? Za wszelkie uwagi będę wdzięczny.

    Bo alokacja i dealokacja odbywa się w 10-ciu miejscach na raz. Pozatym jest jakiś nad-wyraz zakręcony...

    4\/3!!
  • #8
    BlueDraco
    MCUs specialist
    A po co ta dynamiczna alokacja? W mikrokontrolerach zwykle jest to główne źródło błędów w programach, w których się jej używa. Każdy alokowany obszar - to strata zapewne 8 bajtów. Sensu nie widzę w tym żadnego - pamięci od tego nie przybędzie, a o błędy bardzo łatwo.
  • #9
    grko
    Level 33  
    BlueDraco

    Nie mogę się z tym zgodzić. Jakoś w każdym szanującym się RTOS'ie jest dostępna dynamiczna alokacja pamięci. Oczywiście jest to często źródło problemów ale takie ale są sytuacje gdzie bez dynamicznej alokacji nie da się zbyt wiele zdziałać. W rzeczywistości w duzych projektach dzięki dynamicznej alokacji jest więcej dostępnej pamięci ;). Przykładowo jak potrzebuje na odegranie pliku wave podwójnego buforowania 2x8KB. Nie wydaje Ci się bezsensowne alkowanie statyczne takich buforów ?. Przecież potrzebne są tylko na czas odgywania komunikatu dźwiękowego. W małych projektach oczywiście malloc nie ma sensu.

    Narzut na każda alokację to, tak jak piszesz 8 bajtów (czasami więcej). Taki TLSFpotrzebuje na dzień dobry 3KB. Ale alokacja w czasie O(1) i niski poziom fragmentacji kosztuje :). Nie wiem jaki ma narzut alokator z newliba bo go nie używam (Freddie jest ekspertem od toolchainów to może się wypowie).
  • #10
    BlueDraco
    MCUs specialist
    Bardzo sensowne, pod warunkiem, że zagwarantujesz, że nikt inny w tym czasie na pewno nie będzie potrzebował tej pamięci - bo inaczej masz błąd. Z kolei jeśli możesz to zagwarantować, to zapewne nie potrzebujesz dynamicznej alokacji - wystarczy przecież synchronizować np. dostęp dwóch wątków do tego samego obszaru pamięci, statycznie zaalokowanego i wykorzystywanego przez nie naprzemiennie do różnych celów.
  • #11
    grko
    Level 33  
    Quote:

    Bardzo sensowne, pod warunkiem, że zagwarantujesz, że nikt inny w tym czasie na pewno nie będzie potrzebował tej pamięci - bo inaczej masz błąd.


    Tak mam błąd. Zaloguje sobe, że coś takiego sie stało. Mogę z tym żyć, nie jest to krytyczny element systemu. Nie używam dynamicznej alokacji do podejmowania decyzji o "wystrzeleniu" poduszki powietrznej w samochodzie.

    Quote:

    Z kolei jeśli możesz to zagwarantować, to zapewne nie potrzebujesz dynamicznej alokacji - wystarczy przecież synchronizować np. dostęp dwóch wątków do tego samego obszaru pamięci, statycznie zaalokowanego i wykorzystywanego przez nie naprzemiennie do różnych celów.


    A co jak któryś z wątków nie zwolni zasobu. Na moje oko to będzie wyciek pamięci ;)
  • #12
    Freddie Chopin
    MCUs specialist
    BlueDraco wrote:
    Z kolei jeśli możesz to zagwarantować, to zapewne nie potrzebujesz dynamicznej alokacji - wystarczy przecież synchronizować np. dostęp dwóch wątków do tego samego obszaru pamięci, statycznie zaalokowanego i wykorzystywanego przez nie naprzemiennie do różnych celów.

    No i właśnie wymyśliłeś malloc()...

    4\/3!!
  • #13
    Co_pat
    Level 15  
    Problemem dla mnie w moim projekcie jest to, że ramki które składam i wysyłam mają klika długości (12B, 20B,48B i 248B). W związku z tym może lepszym rozwiązaniem będą osobne bufory statyczne dla każdego typu ramki np. bufor kołowy z tablicami dwuwymiarowymi?
  • #14
    grko
    Level 33  
    Wydaje mi się, że zwykła kolejka FIFO spełni swoje zadanie. Kiedyś napisałem coś takiego:

    fqueue.h
    Code: c
    Log in, to see the code


    fqueue.c
    Code: c
    Log in, to see the code


    main.c
    Code: c
    Log in, to see the code


    Nie jest to rocket science ale spełnia swoje zadania. Głównie wykorzystywałem to do komunikacji aplikacji z przerwaniami. Aby bezpiecznie używać tego z przerwaniami należy zdefiniować makra:
    FQUEUE_CRITICAL_ENTER
    FQUEUE_CRITICAL_EXIT


    [/code]
  • #15
    BlueDraco
    MCUs specialist
    Freddie: no niezupełnie - zwykły malloc nie blokuje wątku do czasu zwolnienia pamięci przez inny wątek - na tym właśnie polega problem z dynamiczną alokacją w małym mikrokontrolerze z RTOSem.
  • #16
    Freddie Chopin
    MCUs specialist
    A malloc coś blokuje? Przecież tam jest identyczna synchronizacja jak ta którą opisałeś - czy zrobisz statyczny alokator, czy użyjesz malloc(), to tak czy siak nie można przeprowadzać "jednocześnie" dwóch operacji...

    4\/3!!
  • #17
    Co_pat
    Level 15  
    GrzegorzKostka dziękuję za udostępnienie Twojego rozwiązania, jak najszybciej postaram się je przetestować.
    Przy okazji chciałbym zapytać o Twój styl pisania który znacznie odbiega od tego czego ja się uczyłem.
    Dlaczego np. makro INCREMENT_CYCLIC jest napisane z wykorzystaniem pętli?
    Dlaczego używasz etykiet zamiast w ich miejscu wstawić return, a z tego co wiem kompilatory mają kłopoty z optymalizacją kodu, w którym występują skoki.
  • #20
    rajszym
    Level 20  
    GrzegorzKostka wrote:
    Co do konstrukcji do-while(0) w makrach to korzystam z tego aby zamknąć makro w jednym scope.

    Czy kompilator będzie miał problem, jeśli zamiast konstrukcji do-while(0) zostaną zastosowane nawiasy klamrowe {..}?
  • #21
    grko
    Level 33  
    Nie będzie miał problemu. Konstrukcja do-while(0) jest jednak lepsza bo wymusza postawienie ";" po użyciu makra.
  • #22
    gaskoin
    Level 38  
    GrzegorzKostka wrote:
    Zdania są podzielone na ten temat, polecam lekturę:
    http://koblents.com/Ches/Links/Month-Mar-2013/20-Using-Goto-in-Linux-Kernel-Code/

    Poza tym podaj choć jeden powód dlaczego to jest nieczytelne ?

    Co do konstrukcji do-while(0) w makrach to korzystam z tego aby zamknąć makro w jednym scope.


    Trochę uogólniłem, bo zazwyczaj goto jest nadużywane. W Twoim przypadku też jest nadużyte bo jest zbędne. Jedyne sensowne użycie wg mnie to w zagnieżdżonych pętlach. Wezmę pierwszą z brzegu funkcję:

    Code: c
    Log in, to see the code
    która jest tożsama z:

    Code: c
    Log in, to see the code


    Niby to samo a nie trzeba nic szukać. Mimo, że etykieta End jest niedaleko, to sprawia mi to jakiś dyskomfort w czytaniu.
  • #23
    grko
    Level 33  
    Nie jestem jakimś fanem goto ale używam tej konstrukcji w 2 przypadkach:
    1. Wyjście z zagniżdżonych pętli (bardzo rzadko używam)
    2. Cleanup w funkcji

    W moim zdaniem goto w moim przykładzie łapie się pod 2 przypadek ;). W bardziej skomplikowanych przypadkach implementacja z goto jest moim zdaniem ładniejsza:

    Wersja z goto:
    Code: c
    Log in, to see the code


    Wersja bez goto 1:
    Code: c
    Log in, to see the code


    wersja bez goto 2:
    Code: c
    Log in, to see the code


    Quote:

    Niby to samo a nie trzeba nic szukać. Mimo, że etykieta End jest niedaleko, to sprawia mi to jakiś dyskomfort w czytaniu.


    Kwestia gustu :). W sumie kiedyś widziałem jak ktoś na siłę chciał uniknąć goto i zrobił coś w stylu:

    Code: c
    Log in, to see the code


    Od tego to już wole goto.
  • #24
    Freddie Chopin
    MCUs specialist
    GrzegorzKostka wrote:
    W bardziej skomplikowanych przypadkach implementacja z goto jest moim zdaniem ładniejsza

    C++ i destruktor obiektu - aż sam się dziwię, że jest tyle rzeczy które da się rozwiązać przez przejście na C++ (;

    GrzegorzKostka wrote:
    Wyjście z zagniżdżonych pętli (bardzo rzadko używam)

    Równie dobrze można pętle (wszystkie) wyrzucić jako funkcję i po prostu użyć return.

    4\/3!!
  • #25
    gaskoin
    Level 38  
    W cpp podany kod jest bardzo prosty do napisania (możemy się pozbyć sprawdzania nulli co rusz). Ja jeszcze jestem zwolennikiem niepisania komentarzy :P Wersja bez goto4 i bez udziwnień. Moim zdaniem czytelniejsza. Ale nie chcę nikogo przekonywać, bo i tak mi się to nie uda :)

    Code: c
    Log in, to see the code
  • #26
    grko
    Level 33  
    Spoko. Ja też nie bedę nikogo przekonywał :). Zdania są podzielone nawet gronie najlepszych programistów kernela (ogólnie polecam książkę Roberta Love o kernelu). Ale dam jeszcze jeden przykład (już ostatni) znaleziony gdzieś w sieci:

    Code: c
    Log in, to see the code


    Jak to będzie wyglądało bez goto ? :P

    Quote:

    Równie dobrze można pętle (wszystkie) wyrzucić jako funkcję i po prostu użyć return.


    Bardzo rzadko == zazwyczaj znajduję jakies lepsze rozwiązanie :)
  • #27
    BlueDraco
    MCUs specialist
    O tak by wyglądało bez goto - fatalnie wprost, bo krócej i bez goto. ;)

    Code: c
    Log in, to see the code
  • #29
    Freddie Chopin
    MCUs specialist
    GrzegorzKostka wrote:
    Wydaje mi się, że wersja z goto jest czytelniejsza (tzn w naturalny sposób implementuje algorytm).

    Kwestia osobistych preferencji - dla mnie pętle są czytelniejsze, a nad goto trzeba pomyśleć, bo jednak zaburza ono "naturalny" sposób postrzegania kodu. (;

    4\/3!!
  • #30
    BlueDraco
    MCUs specialist
    Dla mnie czytelniejszy jest kod, który zawiera mniej instrukcji - wtedy łatwiej ogarnąć to wzrokiem.
    Nie wiem, co jest takiego czytelnego w przeplatających się skokach.