你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32F7xx —— 内存管理

[复制链接]
STMCU小助手 发布时间:2021-12-11 12:00
目的:高效、快速的分配,并在适当的时候回收内存资源。最终就是实现malloc和free函数。(实现方法参考原子哥)
- N% S6 O' w% j: ]8 M2 z: z5 y( G8 i% J' H4 o
  1. #define CONFIG_SRAM_OUT_ENABLE     00 l  n9 N! F8 Z& m2 l

  2. 2 [- S9 O& Q2 u9 N$ h
  3. typedef enum
    2 t% k7 B" e# V9 x4 M; [
  4. {
    6 U4 L% ]9 i$ ~, b9 Q
  5.   SRAM_TYPE_IN,   // 内部内存池
    6 t+ w' ]( @- z  k$ `5 i' d2 W
  6. #if(CONFIG_SRAM_OUT_ENABLE == 1)
    + E0 C1 s6 H" X
  7.   SRAM_YPE_OUT,   // 外部内存池(SDRAM)
    * W0 a7 o+ [- y/ a/ |* A
  8. #endif
    : f4 y) z# E- @+ y& P
  9.   SRAM_TYPE_DTCM, // DTCM内存池(此部分SRAM仅仅CPU可以访问!!!)/ j6 L2 }) `! k& Z7 ]
  10.   SRAM_TYPE_MAX,  // 最多支持的SRAM块数.
    6 j6 D2 A( z( D1 W2 q0 P
  11. } sram_type_t;
    9 i( W2 \6 D( V& h6 ~
  12. ! t. h4 r6 {1 m. d+ }: b
  13. // 内部SRAM
    3 m" ~6 Y8 `! D) M7 ?
  14. #define SRAM_IN_BLOCK_SIZE        64            // 内存块大小为64字节0 i' X* S, t6 s+ d& }+ f
  15. #define SRAM_IN_MAX_SIZE          (160 * 1024)  // 最大管理内存160K/ n% E6 B- D5 z& v; Q7 D0 u
  16. #define SRAM_IN_ALLOC_TABLE_SIZE  (SRAM_IN_MAX_SIZE / SRAM_IN_BLOCK_SIZE)
    # t, |3 K  h  G$ v! a
  17. - b" X) U6 O5 g9 D1 k2 S
  18. #if(CONFIG_SRAM_OUT_ENABLE == 1)( O  _/ H' `6 J0 C- A
  19. // 外部SDRAM里面
    9 C/ ^- T6 o: {- p. H
  20. #define SRAM_OUT_BLOCK_SIZE       64            // 内存块大小为64字节
    6 N0 A8 W& I' q) A& ~
  21. #define SRAM_OUT_MAX_SIZE         (100 *1024)   // 最大管理内存100K: ~: u: e+ L" c: l& I8 I' w2 D* [
  22. #define SRAM_OUT_ALLOC_TABLE_SIZE (SRAM_OUT_MAX_SIZE / SRAM_OUT_BLOCK_SIZE)7 d) Q2 `+ z& k, x
  23. #endif
    : z, T' S+ p; d( G; T0 ^

  24. 5 V; N! a0 a3 Y+ Z, i8 n, e$ I
  25. // CCM,用于管理DTCM(特别注意,这部分SRAM,仅CPU可以访问!!)
    ; a; _7 ?2 I* M6 _/ O9 k5 l/ X
  26. #define SRAM_DTCM_BLOCK_SIZE      64                  //内存块大小为64字节
    3 S, o3 l9 B; l. K. m4 P1 t
  27. #define SRAM_DTCM_MAX_SIZE        (100 *1024)             //最大管理内存60K
    $ c' B+ s9 ^# T
  28. #define SRAM_DTCM_ALLOC_TABLE_SIZE  (SRAM_DTCM_MAX_SIZE/SRAM_DTCM_BLOCK_SIZE)
    3 p, w; A/ f) d, G8 G) {
  29. //内存池(32字节对齐)2 D+ s" ^8 G; @7 J' B1 |* h
  30. __align(32) uint8_t sram_in_base[SRAM_IN_MAX_SIZE];                                      // 内部SRAM内存池+ X2 O  {+ n0 }% l' T
  31. uint32_t sram_in_map_base[SRAM_IN_ALLOC_TABLE_SIZE];- r0 v* S9 c( ^, h  V& b9 W& n

  32. 9 ?6 l3 ^- I) D- R" P
  33. #if(CONFIG_SRAM_OUT_ENABLE == 1)0 |; x4 q! U5 J4 _
  34. __align(32) uint8_t sram_out_base[SRAM_OUT_MAX_SIZE] __attribute__((at(0xC01F4000)));   // 外部SDRAM内存池$ r2 k3 x6 U( f3 Q  k+ D: r
  35. uint32_t sram_out_map_base[SRAM_OUT_ALLOC_TABLE_SIZE] __attribute__((at(0xC01F4000 + SRAM_OUT_MAX_SIZE))); // 外部SRAM内存池MAP7 L) m% l/ X' S' [- F! ]& i
  36. #endif
    ; R! ]  `* m4 U
  37. ! r& X4 R$ e' c+ T
  38. __align(32) uint8_t sram_dtcm_base[SRAM_DTCM_MAX_SIZE] __attribute__((at(0x20000000))); // 内部DTCM内存池7 D, g( p1 V/ @) ^+ Z' @
  39. uint32_t sram_dtcm_map_base[SRAM_DTCM_ALLOC_TABLE_SIZE] __attribute__((at(0x20000000 + SRAM_DTCM_MAX_SIZE))); //内部DTCM内存池MAP
    2 }7 P( ]. p( v/ J( k1 A( A
  40. 2 j: A9 K5 `5 U6 u1 a% `
  41. static const uint32_t memtblsize[SRAM_TYPE_MAX] = {SRAM_IN_ALLOC_TABLE_SIZE,
    , V3 l$ _) f: {) @6 O% R7 p( t
  42. #if(CONFIG_SRAM_OUT_ENABLE == 1)
    ' `% Y3 X! N: S
  43.                                             SRAM_OUT_ALLOC_TABLE_SIZE,+ M5 I3 [/ ?' a5 {/ {& J
  44. #endif
    5 T' ?& W/ [: p. `. N
  45.                                             SRAM_DTCM_ALLOC_TABLE_SIZE
    0 t) `4 k  e1 z$ u
  46.                                            }; //内存表大小  k7 \, g5 Y5 n2 i( E5 H5 ~2 p
  47. ; ^) y' {8 T, A. s
  48. static const uint32_t memblksize[SRAM_TYPE_MAX] = {SRAM_IN_BLOCK_SIZE,
    ) A) i, R) H9 F# i! G. O
  49. #if(CONFIG_SRAM_OUT_ENABLE == 1)
    % T( m, T6 V$ ^7 X) K* ?
  50.                                             SRAM_OUT_BLOCK_SIZE,) v3 R- f; D$ u9 G, n, |
  51. #endif
    ' j3 [: R6 A: ]0 K# Z
  52.                                             SRAM_DTCM_BLOCK_SIZE
    * S- ~4 k1 T3 [7 N5 ~( x, K0 e
  53.                                            };     //内存分块大小
    : {/ n* r! l; J+ V9 ^* u& {

  54. 8 Z7 L3 R6 z6 [
  55. static const uint32_t memsize[SRAM_TYPE_MAX] = {SRAM_IN_MAX_SIZE,% S1 L% a& L/ ~) A; V3 Q
  56. #if(CONFIG_SRAM_OUT_ENABLE == 1): |1 m/ ~6 _1 T2 s( X! V# D& l
  57.                                          SRAM_OUT_MAX_SIZE,8 s) l8 X% i5 z) c5 |
  58. #endif6 a  H* ?' h% Q/ ]3 U0 K
  59.                                          SRAM_DTCM_MAX_SIZE
    2 v2 l5 w" O% m4 ^8 F4 v
  60.                                         };          //内存总大小
    $ v+ {* Z6 {) _5 z: Q

  61. : h% ~2 x: `6 Q4 N1 T: n% U, F' m, x
  62. void mymem_init(uint8_t type);
    ' z) p' v- W: J* F4 \$ j
  63. uint16_t mymem_perused(uint8_t type);
    8 f( u& y) l$ P5 j6 v1 t

  64. 0 x8 X: l0 J$ R; q+ G
  65. typedef struct
    ( e. s4 R, B7 n! P  j# t" Z
  66. {
    3 ]# y$ U- Q8 n) P6 V
  67.         void (* init)(uint8_t );* D8 g% A& w' t
  68.         uint16_t (* perused)(uint8_t);                 // 内存使用率# K/ E3 @8 _  s
  69.         uint8_t         *membase[SRAM_TYPE_MAX]; // 内存池 管理SRAMBANK个区域的内存" V" g  W; ?3 e" K, b5 H+ {
  70.         uint32_t *memmap[SRAM_TYPE_MAX];          // 内存管理状态表) x2 `' H0 q4 j
  71.         uint8_t  memrdy[SRAM_TYPE_MAX];    // 内存管理是否就绪
    & R! i, p1 I8 i- ?5 d* v
  72. } sram_dev_t;- F+ V3 d5 p- r

  73. 1 S4 q6 t9 P( o( [
  74. //内存管理控制器
    6 }9 n3 S6 @) Z  F
  75. static sram_dev_t sram_dev =
    ) g6 {3 r$ A) V7 o' S
  76. {
    . K, j( q, I0 ]# D7 m" P
  77.   mymem_init,        // 内存初始化
    " ?- t  d, G) o( E0 u" G: L9 u, T
  78.   mymem_perused,     // 内存使用率. H% K4 e0 M; U2 ^7 G$ l- B! u
  79.   sram_in_base,
    2 O7 `5 ]4 O( ]  Z  i2 n5 W- o/ e

  80. 8 e* H7 Q2 ~. _7 a  d) F0 y& `
  81. #if(CONFIG_SRAM_OUT_ENABLE == 1)
    6 r7 J7 j- B; V1 y3 Q0 k
  82.   sram_out_base,
    ! U, x! T/ T% \% U# ~$ I0 n8 i
  83. #endif
    ' G" o) d% x8 e: ?
  84.   sram_dtcm_base,  // 内存池, x" X7 q6 j% r0 C4 T1 [
  85.   sram_in_map_base,# w3 ], t2 V* X& b! E# C1 D
  86. #if(CONFIG_SRAM_OUT_ENABLE == 1)1 N$ r8 s" f# c/ @
  87.   sram_out_map_base," C. V; p& X4 x1 W
  88. #endif
    : _1 i# n7 W4 {  z1 \& e( N# e
  89.   sram_dtcm_map_base,1 p1 M4 N/ J( ]; o
  90.   0,
    % P- X: e  \2 Z4 @# ^* `- ]- g9 O
  91. #if(CONFIG_SRAM_OUT_ENABLE == 1)
    ' r7 t: A% l+ N' L; k) p
  92.   0,* m( T/ B3 |; a- {, L/ _
  93. #endif$ d$ Y% C. z; f, V9 x! D
  94.   0,2 M% }/ l! V7 X( [) g9 N3 N
  95. };7 |( u+ {7 n5 c' s

  96. - g* l- o! r' H- U3 g! z$ d% `% \( {
  97. // 内存管理初始化! d, i- P: s$ k, R
  98. static void mymem_init(uint8_t memx)
    & z; F' d8 f) L# _: v
  99. {% l9 Q5 Y7 H9 Z( Z/ u
  100.   memset(sram_dev.memmap[memx], 0, memtblsize[memx] * 4); // 内存状态表数据清零
    " l4 i- W9 d* v6 X9 j- d; U5 g3 c
  101.   sram_dev.memrdy[memx] = 1;                              // 内存管理初始化OK/ N7 r: L7 z0 p8 G
  102. }
    & d% h- s  j6 |2 N. Q
  103. ) n. E. E2 k  D6 v
  104. // 获取内存使用率
    + d& Q1 v, _6 S, `3 J
  105. // type:所属内存块5 z% l) {4 u; q, c- b5 O
  106. // 返回值:使用率(扩大了10倍,0~1000,代表0.0%~100.0%)( r7 y" k; ]+ a8 e3 M" j$ {  j
  107. static uint16_t mymem_perused(uint8_t type)( O) U- k' C5 m  r
  108. {+ P1 Z1 ~4 c, G0 [; {% |4 I0 c
  109.   uint32_t used = 0;
    5 U- m) m) a0 m* d. F3 b
  110.   uint32_t i;
    + ?! G' F. y3 D. v& L7 p9 Y
  111.   for(i = 0; i < memtblsize[type]; ++i), e4 G  R6 h0 E3 V
  112.   {6 `- R( `1 a! A( p: k7 ]  K0 _
  113.     if(sram_dev.memmap[type]<i>)</i>' q% t8 n' Q, m- n* l
  114.     {
    $ b* |! J; Q. a3 s
  115.       used++;- B2 U! L* o2 h6 d- s: j0 y9 [! |6 _* G
  116.     }
    8 H6 w0 L8 P$ X; J% Y# U1 q
  117.   }7 v& F8 f# b( c' P
  118.         : ^$ m5 t: \% |
  119.   return (used * 1000) / (memtblsize[type]);! }' V  k% c8 ^  H4 ]8 s
  120. }
    4 Z- R2 Y% m! P0 O0 U' ~# n# w

  121. : }  E, X; z$ b5 `0 e
  122. // 内存分配(内部调用); k) V% _; W- l2 W: R/ l6 T$ }
  123. // type:所属内存块
    5 k$ b5 y- Z" Y+ a% \0 \
  124. // size:要分配的内存大小(字节)
    ) H% q$ J" C0 Y- P" Q; p- H
  125. // 返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址
    7 y5 @2 Z- |+ d1 [. V* F
  126. static uint32_t mymem_malloc(uint8_t type, uint32_t size)
    4 V1 g2 v* _- w
  127. {4 \* l7 T' F; A0 f& V, w
  128.   signed long offset = 0;
    7 j. k& z: K0 u. l
  129.   uint32_t nmemb;     // 需要的内存块数" N! j* P' [: y; r% k% W' |
  130.   uint32_t cmemb = 0; // 连续空内存块数) u$ \' x. H9 f* I4 k
  131.   uint32_t i;4 f' L1 w1 S# P5 s

  132. # h7 D  m' H. S% |/ [: L
  133.   if(!sram_dev.memrdy[type])  u% y. B& F0 b& H9 R; G% g! N  I
  134.   {
    & P2 c! ~" q1 m2 w/ W1 w4 p1 C
  135.     sram_dev.init(type);  // 未初始化,先执行初始化
    % p0 I4 d* Q8 Q- m8 ~6 z, ~# c
  136.   }
      e6 ^- U% s0 ~1 A0 |* p) G
  137.   if(size == 0)
    / L! {( W  s6 X6 r& s' G4 h
  138.   {
    1 F( ]0 u7 C$ t' y
  139.     return 0xFFFFFFFF;  // 不需要分配
    * j% }& b) S, H
  140.   }5 j, ?5 I; H; X/ o# j- k1 K3 ^7 n
  141.   nmemb = size / memblksize[type]; // 获取需要分配的连续内存块数' _: F% Z- I7 ~/ ^$ R) {
  142.   if(size % memblksize[type])# M3 y- J& q/ f$ M0 W: R
  143.   {9 y- J* ]2 e# o0 q# C9 u" k
  144.     nmemb++;1 X0 _2 J6 u  T; I: P# Q
  145.   }
    8 V1 u5 w8 h! f) w' T
  146.   for(offset = memtblsize[type] - 1; offset >= 0; offset--) // 搜索整个内存控制区: q. Y; h! s* [- i- Y) _9 L
  147.   {
    3 n# E( B% L  X% n, W
  148.     if(!sram_dev.memmap[type][offset])
    . [1 ^) H# v; P4 \0 b; ], l7 \5 w! e
  149.     {, w& S, Z' \# f4 M/ _
  150.       cmemb++;    // 连续空内存块数增加
    6 ?" C2 f# m/ H" m' [7 A
  151.     }: s1 V, q, Z% p, e
  152.     else4 ]3 y' Q$ C' @* @/ W3 \  U
  153.     {8 ^9 ^6 c4 y) L! X/ I* H
  154.       cmemb = 0;  // 连续内存块清零( E) t6 s' n2 G2 G3 `8 I' ^3 q
  155.     }
    5 f/ Q! Q% J& P: @. A
  156.     if(cmemb == nmemb)                // 找到了连续nmemb个空内存块
    6 O, w% O0 z/ O
  157.     {
    . j1 U1 \8 D) j; l( k5 E. Q7 W
  158.       for(i = 0; i < nmemb; i++)      // 标注内存块非空' L8 W% w, G9 S1 i
  159.       {& W- B2 r+ |9 |: k3 k& H4 H
  160.         sram_dev.memmap[type][offset + i] = nmemb;. V* e0 ~; l3 u0 D
  161.       }
    2 N0 h+ u" h+ x5 p$ p. T1 [- W
  162.       return (offset * memblksize[type]); // 返回偏移地址& x2 S; a% |  o
  163.     }* r. j/ ]& ]( r: S* d( j/ D0 f
  164.   }+ u0 V& H# \- h# m( Q
  165.   return 0xFFFFFFFF; // 未找到符合分配条件的内存块
      o- c8 u# Y! r, W2 V$ r7 o
  166. }4 w! z* v( F- G1 a4 z7 S
  167. . d- k; x6 _6 _$ a7 ]+ T2 [( Y
  168. // 释放内存(内部调用): |" l% g. y! A% ]
  169. // type:所属内存块
    ' |1 @! I3 q5 f- ?
  170. // offset:内存地址偏移5 N: v7 V# J" N$ X+ t- Q8 ], r
  171. // 返回值:0,释放成功;1,释放失败;8 L% s3 A2 e( J- m' H
  172. static uint8_t mymem_free(uint8_t type, uint32_t offset); L2 ], s  U) r3 I
  173. {
    0 Z' ^2 t9 a5 q/ h) q
  174.   int i;
    ( k9 ?2 z3 c9 ~5 f; Y3 E
  175.   if(!sram_dev.memrdy[type])// 未初始化,先执行初始化
    " P% y9 Y3 J5 |9 b3 L
  176.   {
    0 G0 t, C; n3 d: `; }4 r
  177.     sram_dev.init(type);
    - x% T! Y# Z+ B. u2 l$ k
  178.     return 1;// 未初始化) ~9 |3 I0 n; O. G1 }# a+ ?! p& |
  179.   }
      ?! b  ^8 Y, U, E0 X$ o/ U, A5 t
  180.   if(offset < memsize[type]) // 偏移在内存池内.* X  \  E! m" j/ m5 p
  181.   {
    # j" g0 y1 W, i; w. m1 d2 i
  182.     int index = offset / memblksize[type];    // 偏移所在内存块号码% _" v5 r: B" @0 f5 q% ~
  183.     int nmemb = sram_dev.memmap[type][index]; // 内存块数量
    2 z4 a  a2 Z; q  r* r$ F
  184.     for(i = 0; i < nmemb; i++)                // 内存块清零' Z6 y$ ^8 Q4 T# A, y, N. Z
  185.     {
    6 A0 m) g1 }: L
  186.       sram_dev.memmap[type][index + i] = 0;
    9 t4 a0 D/ F; ^6 _
  187.     }
    6 L! W9 \1 P' V
  188.     return 0;
    / A% g& m7 y3 R5 p( A7 e  r  f: h
  189.   }
    8 W/ ^0 G% F0 M1 M$ W: V/ g
  190.   else. h0 Z$ B. W! K, Y% @. w
  191.   {& J+ \) v. \/ |3 j1 x
  192.     return 2;  // 偏移超区了.; b7 G1 P* s$ O) i
  193.   }
    5 I- x4 t9 l6 |9 B5 w- ]; m
  194. }1 I9 Y0 B' @  K& S1 f
  195. # q% Q3 E- k& R* W* X
  196. void MemInit(uint8_t type)# B4 g6 {2 [, Y8 ]5 j, i
  197. {
    # z" _3 T5 F9 k
  198.   memset(sram_dev.memmap[type], 0, memtblsize[type] * 4); // 内存状态表数据清零
    % E5 {, f% }! ~- w" T& i
  199.   sram_dev.memrdy[type] = 1;              // 内存管理初始化OK  k. M% L9 q( R+ D9 S' V# k
  200. }
    2 J% @! Y0 p& }9 [5 l

  201. " D% [% e$ c( u& o# c
  202. // 释放内存(外部调用)& B: V, g9 y0 b, g) o+ {- m
  203. // type:所属内存块" R9 r2 Z% K3 E8 U
  204. // ptr:内存首地址
    6 o5 }3 o9 G. j( r! a3 B) h
  205. void MemFree(uint8_t type, void *ptr)% y/ f$ I) ^4 Z5 Z: ^: x# L
  206. {9 _3 l/ I  ?# H
  207.   uint32_t offset;
    - K( k+ f5 ^5 ]8 [* D$ Z
  208.   if(ptr == NULL)% _$ F3 a3 Y7 ^$ v  W- i. z
  209.   {; a+ a5 O% }/ t/ r
  210.     return;  // 地址为0.
    $ ^( H  T  ?4 n( M* @" S; J- G5 z3 J
  211.   }+ K  E8 o/ O  A, \: {: ^
  212.   offset = (uint32_t)ptr - (uint32_t)sram_dev.membase[type];. h6 E1 }% t9 f' Z: j7 q0 u/ i
  213.   mymem_free(type, offset); // 释放内存* M2 r2 W8 `% e& O% l. Z( y
  214. }4 N% u. C9 f" t- _
  215.   K; z: u9 x+ ]+ ^, n
  216. // 分配内存$ v/ V4 u0 f% \
  217. void *MemAlloc(uint8_t type, uint32_t size)
    % C, D: g. F  |* ?' U2 a8 `; M
  218. {3 r4 i( V# ]1 j4 k
  219.   uint32_t offset;, [8 N- i. Q: a9 \) X/ m, a. \; x
  220.   offset = mymem_malloc(type, size);3 Q  _; ^; a; W  \! a1 w* }' X
  221.   if(offset == 0XFFFFFFFF)
    3 j% h1 }4 H3 K1 Z. u& ~0 J
  222.   {
    6 S9 D4 k  t9 C+ m6 X3 E
  223.     return NULL;) }% L" V9 v' k$ i# U
  224.   }
    % H$ D9 h1 t* D, c% {  y
  225.   else
    & G) I2 d! ^% A; ?
  226.   {+ ]- q1 X6 ?9 r# q6 y, {: i6 j
  227.     return (void*)((uint32_t)sram_dev.membase[type] + offset);
    , ^) D( G/ a# X: j: [
  228.   }
    7 J* ?; G; J# u5 [
  229. }
    ! J4 w) ~; a+ p) I, w: O

  230. 4 {# ^  H. Z5 x; y  ]9 V
  231. // 重新分配内存8 h& p1 O/ _% p$ C# E9 z
  232. void *MemRealloc(uint8_t type, void *ptr, uint32_t size)
    1 A5 m( @  r& Z. v8 \3 l& F+ A
  233. {. {% |1 u. f, q
  234.   uint32_t offset;
    ) f8 {, B; _6 U9 |" X
  235.   offset = mymem_malloc(type, size);
    ) ?- ^# o  ]$ n8 m
  236.   if(offset == 0xFFFFFFFF)
    $ j* e+ i( {0 J
  237.   {
    . W6 N- ?' y) K1 h, H+ e5 i7 x
  238.     return NULL;+ k, Z* \. {. i% M5 j6 Q  n
  239.   }
    # s2 p+ E9 ^8 u; R/ q
  240.   else
    2 L% l' ^$ W/ n3 x
  241.   {
    $ L( b& ]" }; X  D  M
  242.     memcpy((void*)((uint32_t)sram_dev.membase[type] + offset), ptr, size); // 拷贝旧内存内容到新内存
    ! q& J8 C! j- X; v* C' F9 \' Q
  243.     MemFree(type, ptr);                                                     // 释放旧内存& t# j+ f# `( Z( I5 [" q
  244.     return (void*)((uint32_t)sram_dev.membase[type] + offset);              // 返回新内存首地址
    4 i/ ^1 V4 f4 Q/ H
  245.   }5 w8 z. v+ f1 A3 Z
  246. }
    7 _( f, J6 N0 \: D* ~( T" R
  247. . X6 `  |; p$ @1 V
复制代码
/ M8 e8 F+ q" P
收藏 评论0 发布时间:2021-12-11 12:00

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版