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.

Nie wykonuje się _sbrk

wojtekkk09 13 Aug 2009 03:25 2234 19
Computer Controls
  • #1
    wojtekkk09
    Level 15  
    Witam!
    Próbowałem napisać jakiś prosty program wykorzystujący zmienne dynamiczne, niestety program przestawał działać w momencie wywoływania funkcji malloc bądź sprintf. Podobne przypadki były już tu omawiane, jednak ten jest trochę inny:

    syscalls.c
    Code:

    #include <sys/types.h>
    #include <errno.h>
    #include "uart.h"

    caddr_t _sbrk(int incr)
    {
       uart0_sendstr("w sbrk");
       extern char _end;      /* Defined by the linker */
       extern char _ram_end;
       static char *heap_end;
       char *prev_heap_end;

       if(heap_end == 0)
       {
          heap_end = &_end;
       }
       prev_heap_end = heap_end;
       if(heap_end + incr > (char*)&_ram_end)
       {
          errno = ENOMEM;
          uart0_sendstr("blad");
          return (caddr_t) -1;
       }


       heap_end += incr;
       return (caddr_t) prev_heap_end;
    }


    funkcja main:
    Code:

    #include <stdio.h>
    #include <stdlib.h>
    int main(void)
    {
       uart_init();
       uart0_sendstr("1");
       char *p;
       char str[32];
       sprintf(str,"%f",15);
       uart0_sendstr("2");
       p = malloc(2);
       uart0_sendstr("3");
       while(1);
    }



    Terminal wyrzuca mi tylko "1", wnioskuję, że _sbrk też się nie wywołuje.
    Czy ktoś wie jaka może być tego przyczyna?


    Pozdrawiam
    Wojt
    [28-30.06.2022, targi] PowerUP EXPO 2022 - zasilanie w elektronice. Zarejestruj się za darmo
  • Computer Controls
  • #3
    wojtekkk09
    Level 15  
    Witam!
    To jest bardzo dziwne, ponieważ wciąż uart wypisuje tylko "1",obecnie main.c wygląda tak:

    main.c
    Code:

    #include "LPC214x.h"
    #include <stdlib.h>
    #include <sys/types.h>
    #include <errno.h>
    #include "uart.h"


    caddr_t _sbrk(int incr)
    {
       uart0_sendstr("w sbrk");
       return 0;
    }


    int main(void)
    {
       uart_init();
       uart0_sendstr("1");
       char *p;
       p = (char*)malloc(sizeof(char)*2);
       uart0_sendstr("2");
       uart0_sendint((int)p);
       while(1);
    }



    Nie mam dołączonych, żadnych innych plików ani żadnej innej funkcji (oprócz obsługi uart).

    Jeżeli zmienię nazwe sbrk na jakąś inną, to kompilator wyrzuca błąd "undefined referenced to _sbrk", więc chyba mogę być pewny, że moja funkcja jest kompilowana.

    Wcześniej w syscalls miałem zdefiniowaną tylko funkcję _sbrk a kompilator błędów nie wyrzucał, a rzeczywiście w dokumentacji napisano, że sprintf korzysta jeszcze z innych funkcji (close, fstat, isatty, lseek, read, write ).

    Korzystam z kompilatora CodeSourcery, procesor to LPC2148

    Pozdrawiam
    Wojt
  • #5
    User removed account
    User removed account  
  • Computer Controls
  • #6
    Freddie Chopin
    MCUs specialist
    Funkcja printf jest bardzo wygodna, mozna więc napisać swoją, z ograniczoną funkcjonalnością - w takim wypadku zajmuje może kilkaset bajtów (a nie 10k). Nawet sobie taką stworzyłem, obsługuje tylko %s, %d i %x - zupełnie wystarcza dla typowych zastosowań.

    Co do dynamicznej alokacji pamięci, to wszyscy na nią narzekają, ale mnie osobiście wydaje się, że jest to praktycznie najlepsza rzecz w kodzie w c++. Wiadomo, że program na uC praktycznie musi używać zmiennych globalnych, bo bez tego ciężko stworzyć typową aplikację. No i teraz jak wrzucić wszystkie obiekty jako zmienne globalne, to nie wiadomo jaka będzie kolejność wykonania konstruktorów (co czasem ma znaczenie), a do tego czasem dobrze byłoby wykonać jakiś kod PRZED jakimkolwiek konstruktorem. Można to zrobić wywołując jakieś funkcje w startupie asm, ale... To rozwiązanie nie budzi mojego szczególnego entuzjazmu... W przypadku dynamicznej alokacji pamięci na obiekty nic prostszego - kolejność konstruktorów jest jawna, do tego można sobie przed nimi zrobić co kto chce. Globalne są tylko wskaźniki na owe obiekty. To trochę obok wątku, ale... Łatwo znaleźć miliony przykładów kodów w C, dobrych, lepszych i kiepskich - wybierając sobie z nich rodzynki (dobre rozwiązania). Znalezienie kodów w C++ dla mikrokontroleów w zasadzie graniczy z cudem... Czyżby wyrobienie sobie "dobrego sposobu" pisania w C++ dla mikrokontrolerów było możliwe tylko doświadczalnie?

    4\/3!!
  • #7
    wojtekkk09
    Level 15  
    No niestety, nie pomogła zmiana zwracanego adresu, przez _sbrk.
    Pozbyłem się również funkcji uart i zamieniłem na diodę - działa jak wcześniej - źle.
    Sama funkcja _sbrk ,gdy jest wywoływana "ręcznie" z main, to działa poprawnie. Jednak malloc jej nie wywołuje w dalszym ciągu. Oglądałem plik disassembly i widziałem, że w funkcji malloc jest wywołanie funkcji _sbrk, niestety nie mam JTAG-a, żeby zobaczyć do którego miejsca dochodzi.

    Czy biblioteka libc, którą używam w codesourcery, jest dostępna w formie źródeł? Może mógłbym się bliżej przyjrzeć wtedy tej funkcji malloc i powstawiać w różnych miejscach "uart0_sendstr()" i tak dość prymitywnie to debugować?

    Pozdrawiam
    Wojt

    edit:
    Co do funkcji sprintf, to tak na prawdę jej nie potrzebuję koniecznie i pewnie też sobie poradzę bez malloca, ale nie lubię zostawiać nierozwiązanych problemów...
  • #8
    User removed account
    User removed account  
  • #9
    Freddie Chopin
    MCUs specialist
    Newlib (libc dostępne z CodeSourcery G++) jest dostepne oczywiście w postaci źródeł, ale nie wiem czy tak łatwo sobie coś tam powstawiasz [; Jak większość bibliotek z netu ta jest napisana tak, że cieżko byłoby to skompilować bez setki nagłówków i specjalnego makefile'a <:

    http://en.wikipedia.org/wiki/Newlib

    4\/3!!
  • #10
    wojtekkk09
    Level 15  
    OK, problem rozwiązany:)

    Okazało się, że brakuje parametru -DROM_RUN przy kompilacji plików (asm i c), niestety nie mogłem się doszukać co tak na prawdę ta opcja robi, może ktoś wie? I dlaczego napisałem już do tej pory 70kb kodu (bez malloca oraz -DROM_RUN) i wszystko ładnie śmigało...

    I jeszcze jedno, zauważyłem, że funkcja sprintf nie działa poprawnie gdy SVC_STACK_SIZE wynosi 4, zaczęło działać gdy zmieniłem wartość na 8.

    I tutaj pytanie, które stosy są używane przeze mnie i jak się tego dowiedzieć?
    w rozbiegówce:
    Code:

    .set  UND_STACK_SIZE, 0x00000004
            .set  ABT_STACK_SIZE, 0x00000004
            .set  FIQ_STACK_SIZE, 0x00000200
            .set  IRQ_STACK_SIZE, 0X00000200
            .set  SVC_STACK_SIZE, 0x00000008

    w lpc2148-rom.ld mam jeszcze to:
    STACK_SIZE = 0x2000;

    w rozbiegówce zostają zainicjonowane rozmiary wszystkich stosów, ale aktywny jest system_mode, więc domyślam się, że rozmiar stosu dla system_mode to STACK_SIZE czy dobrze to rozumiem? czy może STACK_SIZE to suma wszystkich stosów a rozmiar stosu dla system_mode to STACK_SIZE - stosy_wymienione_w_rozbiegówce?

    Pozdrawiam
    Wojt
  • #11
    User removed account
    User removed account  
  • #12
    Dr.Vee
    VIP Meritorious for electroda.pl
    Freddie Chopin wrote:
    Co do dynamicznej alokacji pamięci, to wszyscy na nią narzekają, ale mnie osobiście wydaje się, że jest to praktycznie najlepsza rzecz w kodzie w c++. Wiadomo, że program na uC praktycznie musi używać zmiennych globalnych, bo bez tego ciężko stworzyć typową aplikację. No i teraz jak wrzucić wszystkie obiekty jako zmienne globalne, to nie wiadomo jaka będzie kolejność wykonania konstruktorów (co czasem ma znaczenie), a do tego czasem dobrze byłoby wykonać jakiś kod PRZED jakimkolwiek konstruktorem. Można to zrobić wywołując jakieś funkcje w startupie asm, ale... To rozwiązanie nie budzi mojego szczególnego entuzjazmu... W przypadku dynamicznej alokacji pamięci na obiekty nic prostszego - kolejność konstruktorów jest jawna, do tego można sobie przed nimi zrobić co kto chce. Globalne są tylko wskaźniki na owe obiekty.

    Kolejność konstruktorów to jeszcze małe piwo - zawsze można użyć patternu podobnego do singletona, gdzie obiekt inicjalizuje się przy pierwszym użyciu.
    Za to kolejność wołania destruktorów - tu jest problem :)
    Alexandrescu opisuje jedno rozwiązanie tego problemu - pattern Feniksa - w książce "Modern C++ design".

    Opóźniona inicjalizacja:
    Code:

    #include <iostream>

    class K
    {
        private:
            int m_i;
        public:
            K(int i) : m_i(i)   { std::cout << "K(" << i << ")\n"; };
            ~K()                { std::cout << "~K(" << m_i << ")\n"; };
            void incr()         { m_i += 1; };
    };

    K* getK(int i=0)
    {
        static K inst(i);
        return &inst;
    };

    int main()
    {
        std::cout << "Enter main\n";
        getK()->incr();
        getK()->incr();
        getK()->incr();
        std::cout << "Exit main\n";
    };


    Pozdrawiam,
    Dr.Vee
  • Helpful post
    #13
    User removed account
    User removed account  
  • #14
    wojtekkk09
    Level 15  
    Ok już doczytałem. Dzięki za literaturę!

    Mogę prosić o potwierzenie/zanegowanie poszczególnych wniosków?:

    Z tego co zrozumiałem, to moja rozbiegówka zostawia procesor w system mode, rozmiar stosu dla tego trybu to suma pozostałych stosów minus Stack_size zdefiniowany w lpc2148-rom.ld.

    startup.s
    Code:

    ...

    ldr   r0,=_stack
            msr   CPSR_c,#MODE_UND|I_BIT|F_BIT // Undefined Instruction Mode
            mov   sp,r0
            sub   r0,r0,#UND_STACK_SIZE
            msr   CPSR_c,#MODE_ABT|I_BIT|F_BIT // Abort Mode
            mov   sp,r0
            sub   r0,r0,#ABT_STACK_SIZE
            msr   CPSR_c,#MODE_FIQ|I_BIT|F_BIT // FIQ Mode
            mov   sp,r0
            sub   r0,r0,#FIQ_STACK_SIZE
            msr   CPSR_c,#MODE_IRQ|I_BIT|F_BIT // IRQ Mode
            mov   sp,r0
            sub   r0,r0,#IRQ_STACK_SIZE
            msr   CPSR_c,#MODE_SVC|I_BIT|F_BIT // Supervisor Mode
            mov   sp,r0
            sub   r0,r0,#SVC_STACK_SIZE
            msr   CPSR_c,#MODE_SYS|I_BIT|F_BIT // System Mode
            mov   sp,r0

    ...


    W moim pliku lpc2148-rom.ld stos jest zdefiniowany zaraz za sekcją bss, jednak nie jest on na końcu pamięci RAM, więc każde przepełnienie stosu będzie kasować zmienne niezainicjowane, czy nie lepiej dla ochrony ustawić stos na koniec pamięci RAM? Rozumiem, że stos rozrasta się w dół.
    lpc2148-rom.ld
    Code:

    ...

     _etext = . ;
      PROVIDE (etext = .);

      /* .data section which is used for initialized data */
      .data : AT (_etext)
      {
        _data = .;
        *(.data)
       *(.data.*)
       *(.gnu.linkonce.d*)
       SORT(CONSTRUCTORS) /* mt 4/2005 */
       . = ALIGN(4);
       *(.fastrun) /* !!!! "RAM-Function" example */
      } > RAM
     
      . = ALIGN(4);
      _edata = . ;
      PROVIDE (edata = .);

      /* .bss section which is used for uninitialized data */
      .bss (NOLOAD) :
      {
        __bss_start = . ;
        __bss_start__ = . ;
        *(.bss)
       *(.gnu.linkonce.b*)
        *(COMMON)
        . = ALIGN(4);
      } > RAM

      . = ALIGN(4);
      __bss_end__ = . ;
      PROVIDE (__bss_end = .);

      .stack ALIGN(256) :
      {
        . += STACK_SIZE;
        PROVIDE (_stack = .);
      } > RAM

      _end = . ;
      PROVIDE (end = .);

      /* Stabs debugging sections.  */
    ...


    Z drugiej strony funkcja _sbrk tworzy stertę zaraz za początkiem stosu(wyższy adres) i rozrasta się w górę.Czy przy takiej konfiguracji funkcja ta nie powinna mieć zabezpieczenia przed przekroczeniem pamięci RAM?
    Code:

    void * _sbrk_r(
        struct _reent *_s_r,
        ptrdiff_t nbytes)
    {
       char  *base;      /*  errno should be set to  ENOMEM on error   */

       if (!heap_ptr) {   /*  Initialize if first time through.      */
          heap_ptr = &_end;
       }
       base = heap_ptr;   /*  Point to end of heap.         */
       heap_ptr += nbytes;   /*  Increase heap.            */
       
       return base;      /*  Return pointer to start of new heap area.   */
    }


    To chyba wszystko:) Dzięki wielkie za pomoc w tym całym temacie.

    Pozdrawiam
    Wojt
  • #15
    User removed account
    User removed account  
  • #16
    Freddie Chopin
    MCUs specialist
    Swoją drogą kiedyś wymyśliłem, że najlepiej byłoby umieścić stos na samym początku RAM, potem .data, .bss i na końcu heap - tym sposobem przekroczenie zarówno stosu jak i heap nie spowoduje kasowania zmiennych, a do tego jeśli procesor generuje jakieś przerwanie z okazji zapisu pod nieistniejący adres, to można nawet obsłużyć taką sytuację <: Niestety - ARMy mają tyle rodzajów stosów, że wciąż jest ryzyko, że jeden wjedzie na drugi.

    albertb wrote:
    Używanie alokacji pamięci jak pisze Freddie i metod opisanych przez Dr.Vee niestety jest tylko maskowaniem problemu, który wystąpi później.

    Nie do końca jasno się wyraziłem - podam konkretny przykład. No więc jest procesor STM32 (Cortex-M3) i chcemy na nim stworzyć klasę Pin (wiadomo do czego [; ). Układ ten jest tak skonstruowany, że przed użyciem dowolnego modułu peryferyjnego trzeba go włączyć w tzw module RCC. Dokładnie tak samo jest z GPIO - przed użyciem trzeba fizycznie włączyć zegar dla tego modułu. Dodatkowo po resecie wszystkie piny układu są w trybie wejściowym, pływającym, a jak wiadomo to nie jest szczególnie szczęśliwe ustawienie na dłuższy czas (zakłócenia, zużycie prądu). Rozwiązanie jest proste - w konstruktorze klasy wystarczy sprawdzić, czy dany port był już zainicjalizowany (sprawdzić flagę zegara) - jeśli nie, to właczyć zegar i ustawić profilaktycznie wszystkie piny na wejście z pull-down (albo pull-up, obojętne). Problem jest tylko tego typu, że taki kod zajmie pewnie więcej miejsca i czasu niż osobna funkcja, która - przed wszystkimi innymi konstruktorami - włączy wszystkie GPIO i ustawi je na wejścia z pullupem.

    Jak widać mam poważne problemy z akceptacją kodu który nie jest wyjątkowo optymalny, ale cały czas z tym walczę... Niestety nie wiem czy mi się to uda, bo skąd wiedzieć kiedy kod jest w odpowiednim miejscu na linii <---wygodny-----------------------optymalny---> ?

    Problem może trochę naciągany, ale jest wiele sytuacji tego typu. Gdy na przykład procesor ma PLL, to czemu by go nie włączyć przed wszystkimi konstruktorami - niech wykonają się szybciej.

    Quote:
    Natomiast zyski w środowisku mikrokontrolerów z użycia c++ są dla mnie za małe, zwłaszcza, że aby kod się nie rozrastał często musimy zrezygnować z RTTI i obsługi wyjątków i podobnych dobrodziejstw C++

    Obecnie przeżywam fascynację możliwością użycia C++ w uC [; Czy wyjątki i RTTI to takie dobrodziejstwo? Swoją drogą RTTI chyba nie jest aż takie cieżkie, niestety zajmuje trochę RAMu, bo każdy obiekt musi nosić swoją nazwę. Anyway - wyjątki i RTTI dla mnie są mniej istotne niż inne fajne mechanizmy C++ - enkapsulacja i polimorfizm, konstruktory, destruktory. Wiadomo, że większośc tych mechanizmów da się zrobić "ręcznie" w C, ale... w ASM też można, ale używa się C, bo wygodniejszy. Dokładnie tak samo w C++ te mechanizmy są wygodniejsze niż w C.

    Quote:
    Jeśli pisząc program zamiast głowić się nad problemem myślę, nad kolejnością konstruktorów, czy mogę użyć jakiejś konstrukcji języka, czy nie bo program spuchnie to gra jest dla mnie nie warta świeczki, stąd często pozostaję przy C.

    Czyli też masz problem z wybraniem odpowiedniego miejsca na linii
    <---wygodny-----------------------optymalny---> [; ARMy zasadniczo są na tyle mocne, że chyba można sobie pozwolić na odrobinę wygody i hi-tech-u w postaci C++ <:

    4\/3!!
  • #17
    wojtekkk09
    Level 15  
    albertb wrote:
    wojtekkk09 wrote:
    [...]
    rozmiar stosu dla tego trybu to suma pozostałych stosów minus Stack_size zdefiniowany w lpc2148-rom.ld.

    Nie, to różnica pomiędzy STACK_SIZE a sumą pozostałych stosów. Inaczej miałbyś stos o ujemnym rozmiarze.

    No jasne, pomyślałem inaczej niż napisałem.
    A przesunięcie stosu na koniec RAM, chciałem użyć w przypadku nie korzystania z malloca i sprintf

    Czy używacie jakiś sposobów, aby spawdzić, czy stos dla jakiegoś trybu procesora nie jest za duży/za mały? Czy wielkość stosu dobiera się metodą "ad hoc"?Chodzi mi o taką sytuację kiedy to kończy się miejsce w pamięci RAM mikroprocesora i może jest szansa na zwolnienie trochę miejsca przez zmniejszenie, któregoś ze stosu. Ciężko zmniejszać rozmiar stosu o xBajtów i sprawdzać poprawność całej aplikacji w różnych kombinacjach i tak w kółko.

    Dziękuję za odpowiedzi
    Pozdrawiam
    Wojt
  • #18
    User removed account
    User removed account  
  • #19
    wojtekkk09
    Level 15  
    Znalazłem dobry sposób na kontrolowanie przepełnienia poszczególnych stosów. Wystarczy je porozdzielać znanymi danymi (np. 0xDEADBEEF) i w głównym programie sprawdzać czy wartości te nie są zmienione. Dzięki takim testom rozmiar stosów można zoptymalizować.

    Pozdrawiam
    Wojt
  • #20
    BoskiDialer
    Level 34  
    wojtekkk09 wrote:
    Wystarczy je porozdzielać znanymi danymi (np. 0xDEADBEEF) i w głównym programie sprawdzać czy wartości te nie są zmienione. Dzięki takim testom rozmiar stosów można zoptymalizować.

    Równie dobrze można wypełnić całą pamięć znaną wartością i obserwować które obszary się zmieniły. Wtedy zamiast informacji o przepełnieniu można uzyskać informację o największym zużyciu stosu bez przepełniania go.