2015年5月9日 星期六

bare metal programming for stm32f4 - discovery: using c++ std::vector

上篇敗家之後, 該收心了, 回到上上篇的使用 std::vector 的問題上吧!

在克服 exception handling 的編譯問題後, 可以試試看呼叫 std::vector。mycpp.cpp L150 ~ L153 是熟悉的 vector 用法, 若能在 bare metal 上用上這個, 多美好阿! 老是用固定大小的 array, 彈性不夠, 覺得很不爽。

在使用了 vector 之後, 會有很多 undefined symbol, mycpp.cpp L22 ~ L84 把他們一一補上, 你說 vector 為什麼會和 _write 有關係? 我也不知道, 反正補上就是了。

由於在 bare metal 環境並沒有類似 malloc 的記憶體配置 function (你應該不會預期有這樣的 function 可以用), 而這種會自己長大的容器一定是需要這種操作的, 那該怎麼辦呢?

從 toolchain library 補上 malloc/free, new/delete 這些東西, 這個可能工程大了點, 我改用另外一個途徑, 使用自己的 allocator, 所以要先寫一個 allocator。

看來好像也是一個大工程, 不過有現成的可以抄, 我就抄過 (就是常常被詬病的 copy/paste) 來了 (mycpp.cpp L88 ~ 140)。再把原本的 malloc 換成我自己的 mymalloc 就成了。

一開始的 mymalloc 只是回傳一個固定的記憶體位址, L86 的 memarea, 看起來很簡單也很可疑, 不過的確可以正常運作。

void *mymalloc(u32 size)
{ 
  return memarea;
}

mycpp.cpp
  1 #include "stm32.h"
  2 
  3 #include "mem.h"
  4 
  5 #include <vector>
  6 
  7 int print(int i)
  8 {
  9   i+=1;
 10 }
 11 
 12 int print(char c)
 13 {
 14  c-=1;
 15 }
 16 
 17 int print(const char *str)
 18 {
 19 }
 20 
 21 
 22 void *dso_handle_;
 23 void *__dso_handle;
 24 
 25 extern "C" void _exit()
 26 {
 27 }
 28 
 29 char brk_area[10240];
 30 
 31 extern "C" char *_sbrk(char *increment)
 32 {
 33   char *ptr = brk_area;
 34   return ptr;
 35 }
 36 
 37 extern "C"
 38 int _kill(int a, int b)
 39 {
 40   return a;
 41 }
 42 
 43 extern "C"
 44 int _getpid()
 45 {
 46   int i;
 47   return i;
 48 }
 49 
 50 extern "C"
 51 int _write(int fd, const void *buf, int count)
 52 {
 53 }
 54 
 55 extern "C"
 56 int open(const char *pathname, int flags, int mode)
 57 {
 58 }
 59 
 60 extern "C"
 61 int _isatty(int fd)
 62 {
 63 }
 64 
 65 
 66 extern "C"
 67 int _close(int fd)
 68 {
 69 }
 70 
 71 extern "C"
 72 int _fstat(int fd, struct stat *buf)
 73 {
 74 }
 75 
 76 extern "C"
 77 int _read(int fd, void *buf, int count)
 78 {
 79 }
 80 
 81 extern "C"
 82 int _lseek(int fd, int offset, int whence)
 83 {
 84 }
 85 
 86 static char memarea[10240];
 87 
 88 template <class T>
 89 class my_allocator
 90 {
 91 public:
 92   typedef int    size_type;
 93   typedef int difference_type;
 94   typedef T*        pointer;
 95   typedef const T*  const_pointer;
 96   typedef T&        reference;
 97   typedef const T&  const_reference;
 98   typedef T         value_type;
 99 
100   my_allocator() {}
101   my_allocator(const my_allocator&) {}
102 
103 
104 
105   pointer   allocate(size_type n, const void * = 0) {
106               T* t = (T*) mymalloc(n * sizeof(T));
107               print("used my_allocator to allocate   at address ");
108               print((int)t);
109               print("\r\n");
110 
111               return t;
112             }
113   
114   void      deallocate(void* p, size_type) {
115               if (p) {
116                 myfree(p);
117                 print("used my_allocator to deallocate at address ");
118                 print((int)p);
119                 print("\r\n");
120               } 
121             }
122 
123   pointer           address(reference x) const { return &x; }
124   const_pointer     address(const_reference x) const { return &x; }
125   my_allocator<T>&  operator=(const my_allocator&) { return *this; }
126   void              construct(pointer p, const T& val) 
127                     { new ((T*) p) T(val); }
128   void              destroy(pointer p) { p->~T(); }
129 
130   size_type         max_size() const { return int(-1); }
131 
132   template <class U>
133   struct rebind { typedef my_allocator<U> other; };
134 
135   template <class U>
136   my_allocator(const my_allocator<U>&) {}
137 
138   template <class U>
139   my_allocator& operator=(const my_allocator<U>&) { return *this; }
140 };
141 
142 void mymain(void)
143 {
144 #if 1
145   char a, b, c , d, e, f;
146   //std::vector<int, __gnu_cxx::new_allocator<int> > vec;
147   std::vector<char, my_allocator<char> > vec;
148   print(35);
149   print('A');
150   for (int i=0 ; i < 5 ; ++i)
151   {
152     vec.push_back(i);
153   }
154   a = vec[0];
155   b = vec[1];
156   c = vec[2];
157   d = vec[3];
158   e = vec[4];
159 
160   char z=a+b+c+d;
161 
162   while(1);
163 #endif 
164 }
165 

當然這個實在是太蠢了, 也會有一些問題, vector 在記憶體不夠的情況下, 會以 1, 2, 4, 8 的大小去配置需要的記憶體 (和書上寫的一樣耶!)。若是 vector<int> 就是 1*4, 2*4, 4*4, 8*4 去配置記憶體, 所以在某個情況下, 那個寫死的大小就會不夠用了。

所以我開發了一個相對不那麼蠢的, 對, 其實還是很蠢, 不過簡單, 又具有實用性, 還可以驗證如果配置不到需要的記憶體, 會怎麼辦? 你一定很少看到 vector 要不到記憶體的情況吧?

mem.cpp
 1 #include <stdio.h>
 2 
 3 const int PAGE = 64;
 4 const int HEAP_SIZE = PAGE*1024;
 5 char heap[HEAP_SIZE];
 6 unsigned char mem_area[PAGE];
 7 int free_index = 0;
 8 
 9 void print_memarea()
10 {
11   printf("free_index: %d\n", free_index);
12   for (int i = 0 ; i < PAGE ; ++i)
13   {
14     if (i % 8 == 0)
15       printf("\n");
16     printf("%02d ", mem_area[i]);
17   }
18   printf("\n");
19 }
20 
21 // size: 1 means 1K, size max is 64
22 void *mymalloc(unsigned char size)
23 {
24   if (free_index + size > PAGE)
25   {
26     return 0;
27   }
28   char * ptr = heap + free_index * 1024;
29   #if 0
30   printf("xx free_index: %d\n", free_index);
31   printf("xx heap: %p\n", heap);
32   printf("xx ptr: %p\n", ptr);
33   #endif
34   *(mem_area + free_index) = size;
35 
36   for (int i = 1 ; i < size ; ++i)
37     *(mem_area + free_index + i) = 1;
38 
39   free_index += size;
40   return (void*)ptr;
41 }
42 
43 void myfree(void *ptr)
44 {
45   int index = ((char *)ptr - heap) / 1024;
46   unsigned char size = *mem_area;
47   for (int i=0 ; i < size ; ++i)
48     *(mem_area + i) = 0;
49 }

mem.cpp 從 64k 的 heap 分配記憶體, 很蠢的只有分配, 無法再度使用 myfree 回收的記憶體。不過實作就容易多了。

RESULT
 1
 2   used my_allocator to allocate   at address 0x804c6e0n: 1 size: 4 (+)
 3   used my_allocator to allocate   at address 0x804d6e0n: 2 size: 8 (+)
 4   used my_allocator to deallocate at address 0x804c6e0 (-)
 5   used my_allocator to allocate   at address 0x804f6e0n: 4 size: 16 (+)
 6   used my_allocator to deallocate at address 0x804d6e0 (-)
 7   used my_allocator to allocate   at address 0x80536e0n: 8 size: 32 (+)
 8   used my_allocator to deallocate at address 0x804f6e0 (-)
 9 cannot alloc memory
10 terminate called after throwing an instance of 'std::bad_alloc'
11   what():  std::bad_alloc
12 Aborted

以目前的記憶體管理設計, 當 vector 要以 32 去要記憶體時, 就會不夠用, 這時候 allocate 就會丟出 std::bad_alloc() (RESULT L11)。

RESULT 是在 linux 平台下的測試結果, 再搬到 stm32f4-discovery 上測試, 可以正常的使用

vec.push_back(0)
vec.push_back(1)
vec.push_back(2)
vec.push_back(3)
vec.push_back(4)

不過由於在 stm32f4-discovery 不能丟出 std::bad_alloc(), 我得想個辦法處理要不到記憶體的情況, 否則, vector 就不威了, 處理方式也很簡單, 印出訊息然後無窮回圈。stm32f4-discovery sram 只有 192k, 很小的。

目前 github 的版本已經稍做改良, 可以用更細緻的方式配置記憶體, myfree 也會去回收用過的記憶體, 當然還是比不上一般作業系統的的精緻度。


這是 gcc malloc_allocator 的實作, L98 也是使用 malloc, 並丟出 bad_alloc exception。

gcc-4.9.2/libstdc++-v3/include/ext/malloc_allocator.h
  1 // Allocator that wraps "C" malloc -*- C++ -*-
  2 
  3 // Copyright (C) 2001-2014 Free Software Foundation, Inc.
  4 //
  5 // This file is part of the GNU ISO C++ Library.  This library is free
  6 // software; you can redistribute it and/or modify it under the
  7 // terms of the GNU General Public License as published by the
  8 // Free Software Foundation; either version 3, or (at your option)
  9 // any later version.
 10 
 11 // This library is distributed in the hope that it will be useful,
 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14 // GNU General Public License for more details.
 15 
 16 // Under Section 7 of GPL version 3, you are granted additional
 17 // permissions described in the GCC Runtime Library Exception, version
 18 // 3.1, as published by the Free Software Foundation.
 19 
 20 // You should have received a copy of the GNU General Public License and
 21 // a copy of the GCC Runtime Library Exception along with this program;
 22 // see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 23 // <http://www.gnu.org/licenses/>.
 24 
 25 /** @file ext/malloc_allocator.h
 26  *  This file is a GNU extension to the Standard C++ Library.
 27  */
 28 
 29 #ifndef _MALLOC_ALLOCATOR_H
 30 #define _MALLOC_ALLOCATOR_H 1
 31 
 32 #include <cstdlib>
 33 #include <new>
 34 #include <bits/functexcept.h>
 35 #include <bits/move.h>
 36 #if __cplusplus >= 201103L
 37 #include <type_traits>
 38 #endif
 39 
 40 namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
 41 {
 42 _GLIBCXX_BEGIN_NAMESPACE_VERSION
 43 
 44   using std::size_t;
 45   using std::ptrdiff_t;
 46 
 47   /**
 48    *  @brief  An allocator that uses malloc.
 49    *  @ingroup allocators
 50    *
 51    *  This is precisely the allocator defined in the C++ Standard. 
 52    *    - all allocation calls malloc
 53    *    - all deallocation calls free
 54    */
 55   template<typename _Tp>
 56     class malloc_allocator
 57     {
 95       // NB: __n is permitted to be 0.  The C++ standard says nothing
 96       // about what the return value is when __n == 0.
 97       pointer
 98       allocate(size_type __n, const void* = 0)
 99       {
100  if (__n > this->max_size())
101    std::__throw_bad_alloc();
102 
103  pointer __ret = static_cast<_Tp*>(std::malloc(__n * sizeof(_Tp)));
104  if (!__ret)
105    std::__throw_bad_alloc();
106  return __ret;
107       }
148 
149 _GLIBCXX_END_NAMESPACE_VERSION
150 } // namespace
151 
152 #endif

以下是在 stm32 模擬器的完整範例:
https://github.com/descent/progs/tree/master/mem_alloc/mem.h
https://github.com/descent/progs/tree/master/mem_alloc/mem.cpp

https://github.com/descent/stm32_p103_demos/tree/master/demos/uart_echo/myvec
commit 317bde9c68f38eab73969a9526247dfb6bd1a870

output
 1 descent@debianlinux:myvec$ /media/2/qemu_stm32/arm-softmmu/qemu-system-arm -display sdl -M stm32-p103 -kernel myvec.bin -nographic
 2 STM32_UART: UART1 clock is set to 0 Hz.
 3 STM32_UART: UART1 BRR set to 0.
 4 STM32_UART: UART1 Baud is set to 0 bits per sec.
 5 STM32_UART: UART2 clock is set to 0 Hz.
 6 STM32_UART: UART2 BRR set to 0.
 7 STM32_UART: UART2 Baud is set to 0 bits per sec.
 8 STM32_UART: UART3 clock is set to 0 Hz.
 9 STM32_UART: UART3 BRR set to 0.
10 STM32_UART: UART3 Baud is set to 0 bits per sec.
11 STM32_UART: UART4 clock is set to 0 Hz.
12 STM32_UART: UART4 BRR set to 0.
13 STM32_UART: UART4 Baud is set to 0 bits per sec.
14 STM32_UART: UART5 clock is set to 0 Hz.
15 STM32_UART: UART5 BRR set to 0.
16 STM32_UART: UART5 Baud is set to 0 bits per sec.
17 STM32_UART: UART5 clock is set to 0 Hz.
18 STM32_UART: UART5 BRR set to 0.
19 STM32_UART: UART5 Baud is set to 0 bits per sec.
20 STM32_UART: UART4 clock is set to 0 Hz.
21 STM32_UART: UART4 BRR set to 0.
22 STM32_UART: UART4 Baud is set to 0 bits per sec.
23 STM32_UART: UART3 clock is set to 0 Hz.
24 STM32_UART: UART3 BRR set to 0.
25 STM32_UART: UART3 Baud is set to 0 bits per sec.
26 STM32_UART: UART2 clock is set to 0 Hz.
27 STM32_UART: UART2 BRR set to 0.
28 STM32_UART: UART2 Baud is set to 0 bits per sec.
29 STM32_UART: UART1 clock is set to 0 Hz.
30 STM32_UART: UART1 BRR set to 0.
31 STM32_UART: UART1 Baud is set to 0 bits per sec.
32 LED Off
33 CLKTREE: HSI Output Change (SrcClk:None InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
34 CLKTREE: HSI/2 Output Change (SrcClk:HSI InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
35 CLKTREE: SYSCLK Output Change (SrcClk:HSI InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
36 CLKTREE: HCLK Output Change (SrcClk:SYSCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
37 STM32_RCC: Cortex SYSTICK frequency set to 8000000 Hz (scale set to 125).
38 STM32_RCC: Cortex SYSTICK ext ref frequency set to 1000000 Hz (scale set to 1000).
39 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
40 CLKTREE: PCLK2 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
41 CLKTREE: HSE Output Change (SrcClk:None InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
42 CLKTREE: HSE/2 Output Change (SrcClk:HSE InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
43 CLKTREE: PLLXTPRE Output Change (SrcClk:HSE InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
44 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
45 CLKTREE: PLLCLK Output Change (SrcClk:PLLXTPRE InFreq:8000000 OutFreq:72000000 Mul:9 Div:1 Enabled:1)
46 CLKTREE: SYSCLK Output Change (SrcClk:PLLCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
47 CLKTREE: HCLK Output Change (SrcClk:SYSCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
48 STM32_RCC: Cortex SYSTICK frequency set to 72000000 Hz (scale set to 13).
49 STM32_RCC: Cortex SYSTICK ext ref frequency set to 9000000 Hz (scale set to 111).
50 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:72000000 OutFreq:36000000 Mul:1 Div:2 Enabled:1)
51 CLKTREE: PCLK2 Output Change (SrcClk:HCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
52 CLKTREE: GPIOA Output Change (SrcClk:PCLK2 InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
53 CLKTREE: AFIO Output Change (SrcClk:PCLK2 InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
54 CLKTREE: UART2 Output Change (SrcClk:PCLK1 InFreq:36000000 OutFreq:36000000 Mul:1 Div:1 Enabled:1)
55 STM32_UART: UART2 clock is set to 36000000 Hz.
56 STM32_UART: UART2 BRR set to 0.
57 STM32_UART: UART2 Baud is set to 0 bits per sec.
58 STM32_UART: UART2 clock is set to 36000000 Hz.
59 STM32_UART: UART2 BRR set to 3750.
60 STM32_UART: UART2 Baud is set to 9600 bits per sec.
61 mymalloc: 1 byte(s)
62 used my_allocator to allocate at address 20002374 n: 1 sizeof(T): 1
63 mymalloc: 2 byte(s)
64 used my_allocator to allocate at address 20002774 n: 2 sizeof(T): 1
65 myfree: p
66 index: 0
67 size: 1
68 used my_allocator to free address 20002374
69 mymalloc: 4 byte(s)
70 used my_allocator to allocate at address 20002B74 n: 4 sizeof(T): 1
71 myfree: p
72 index: 1
73 size: 1
74 used my_allocator to free address 20002774
75 mymalloc: 8 byte(s)
76 used my_allocator to allocate at address 20002F74 n: 8 sizeof(T): 1
77 myfree: p
78 index: 2
79 size: 1
80 used my_allocator to free address 20002B74
81 0
82 1
83 2
84 3
85 4
86 
87 ----------------
88 free_index: 4
89 
90 0 0 0 1 0 0 0 0 
91 0 0 0 0 0 0 0 0 
92 0 0 0 0 0 0 0 0 
93 0 0 0 0 0 0 0 0 
94 0 0 0 0 0 0 0 0 
95 0 0 0 0 0 0 0 0 
96 0 0 0 0 0 0 0 0 
97 0 0 0 0 0 0 0 0 
98 ================

ref:
Using Custom Allocators

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。