打印
[其他]

C关键字section的作用 Pragma section

[复制链接]
2263|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
aoyi|  楼主 | 2023-10-27 22:00 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
1、section的作用:
section主要作用是将函数或者变量放在指定段中,可在指定的地方取函数执行。

//section demo with gcc
#include "stdio.h"  
          
int __attribute__((section("my_fun"))) test1(int a,int b)  
{  
        return (a+b);  
}

int test(int b)  
{  
        return 2*b;  
}

int __attribute__((section("my_fun"))) test0(int a,int b)  
{  
        return (a*b);  
}  
          
int __attribute__((section("my_val"))) chengi;  
int __attribute__((section("my_val"))) chengj;  
          
int main(void)  
{  
        int sum,c,j;           
        chengi=1,chengj=2;  
          
        sum=test1(chengi,chengj);  
        c=test(100);  
        j=test0(chengi,chengj);  
             
         printf("sum=%d,c=%d,j=%d\r\n",sum,c,j);  

         return 0;  
}  
编译生成map文件:

gcc -o main.exe main.c -Wl,-Map,my_test.map

my_test.map 文件片段如下:

   .text  0x00401460 0xa0 C:\\Users\\think\\ccmGLaeH.o
           0x00401460         test
           0x0040146a         main

   .text 0x00401500 0x0 c:/mingw/bin/../libmingw32.a(CRTglob.o)

   ......
   my_fun 0x00404000 0x200
   [!provide] PROVIDE (___start_my_fun, .)

   my_fun  0x00404000 0x1c C:\\Users\\think\\ccmGLaeH.o

  0x00404000 test1
  0x0040400d test0

  [!provide] PROVIDE (___stop_my_fun, .)

  .data 0x00405000 0x200

  0x00405000 \__data_start_\_ = .
  ......
  \*(.data_cygwin_nocopy)
  my_val 0x00406000 0x200

  [!provide] PROVIDE (___start_my_val, .)

  my_val  0x00406000 0x8 C:\\Users\\think\\ccdMcTrl.o
     0x00406000 chengi
     0x00406004 chengj

  [!provide] PROVIDE (___stop_my_val, .)

  .rdata 0x00407000 0x400

分析可见,使用section修饰的函数和变量在自定义的片段,而且是连续存放在___start_xx到___stop_xx之间,这样可根据变量的地址得出与其同段变量的地址,为后续自动初始化等功能提供了基础。

2:自动初始化:

基于前面section的作用,可以将同类函数指针全部使用同一个段名修饰,然后开机后系统自动检索段内函数指针,逐个执行,对上层应用就是无需主动调用,系统自动初始化。

考虑到硬件初始化与应用功能初始化的先后顺序,可以对段名进行分配,map文件按段名排序。自动初始化主体是OS_INIT_EXPORT宏。

范例代码出自中国移动的oneos开源版本,使用gcc,方案和国产RT-Thread类似。

typedef os_err_t (*os_init_fn_t)(void);

#define OS_INIT_EXPORT(fn, level) \
const os_init_fn_t __os_call_##fn OS_SECTION(".init_call."level) = fn

#define OS_BOARD_INIT(fn) OS_INIT_EXPORT(fn, "1")

#define OS_PREV_INIT(fn) OS_INIT_EXPORT(fn, "2")

#define OS_DEVICE_INIT(fn) OS_INIT_EXPORT(fn, "3")

#define OS_CMPOENT_INIT(fn) OS_INIT_EXPORT(fn, "4")

#define OS_ENV_INIT(fn) OS_INIT_EXPORT(fn, "5")

#define OS_APP_INIT(fn) OS_INIT_EXPORT(fn, "6")
例如shell初始化函数,定义如下:

OS_APP_INIT(sh_system_init);

将宏定义展开:

/* 含义是函数指针 __os_call_sh_system_init
*  其指向sh_system_init函数,且该指针编译后放在".init_call.6"段
*/
const os_init_fn_t __os_call_sh_system_init
    __attribute__((section((".init_call.6")))) = sh_system_init


系统自身也有自定义函数,用来标记起止点函数

OS_INIT_EXPORT(os_init_start, "0");//段起点__start
OS_INIT_EXPORT(os_board_init_start, "0.end");
OS_INIT_EXPORT(os_board_init_end, "1.end");
OS_INIT_EXPORT(os_init_end, "6.end");//段终点__stop

最终生成的map文件如下图:





//系统底层在合适的时机调用如下两函数,将指定段区间内的所有函数自动执行

void os_board_auto_init(void)
{
    const os_init_fn_t *fn_ptr_board_init_start;
    const os_init_fn_t *fn_ptr_board_init_end;
    const os_init_fn_t *fn_ptr;

    fn_ptr_board_init_start = &__os_call_os_board_init_start + 1;
    fn_ptr_board_init_end   = &__os_call_os_board_init_end - 1;

    //将段首尾区间内的函数全部遍历执行
    for (fn_ptr = fn_ptr_board_init_start; fn_ptr <= fn_ptr_board_init_end; fn_ptr++)
    {
        (void)(*fn_ptr)();
    }
    return;
}

static void os_other_auto_init(void)
{
    const os_init_fn_t *fn_ptr_other_init_start;
    const os_init_fn_t *fn_ptr_other_init_end;
    const os_init_fn_t *fn_ptr;

    fn_ptr_other_init_start = &__os_call_os_board_init_end + 1;
    fn_ptr_other_init_end   = &__os_call_os_init_end - 1;

    for (fn_ptr = fn_ptr_other_init_start; fn_ptr <= fn_ptr_other_init_end; fn_ptr++)
    {
        (void)(*fn_ptr)();
    }
    return;
}
系统执行os_other_auto_init时实现了sh_system_init的自动执行,即使应用层没有显示的去调用它。使用符号段的方式实现初始化函数自动执行,应用层修改软件,增加功能启动或者裁剪,对底层代码无需任何改动。更多信息请关注微信公众号【嵌入式系统】。

注意,段中函数类型都是一样的,范例是同一类函数指针,也可以是结构体,需要确保每个成员占用的大小相同,这样才能逐个遍历。

不同编译器对section属性的定义略有差异,但效果相同。

/* Compiler Related Definitions */
    #if defined(__CC_ARM) || defined(__CLANG_ARM)       /* ARM Compiler */
    #define SECTION(x)    __attribute__((section(x)))
#elif defined (__IAR_SYSTEMS_ICC__)                     /* for IAR Compiler */
    #define SECTION(x)     @ x
#elif defined (__GNUC__)                                /* GNU GCC Compiler */
    #define SECTION(x)    __attribute__((section(x)))
#elif defined (__ADSPBLACKFIN__)                        /* for VisualDSP++ Compiler */
    #define SECTION(x)     __attribute__((section(x)))
#elif defined (_MSC_VER)
    #define SECTION(x)
#elif defined (__TI_COMPILER_VERSION__)
    /*
     * The way that TI compiler set section is different from other(at least
     * GCC and MDK) compilers. See ARM Optimizing C/C++ Compiler 5.9.3 for more
     * details.
     */
    #define SECTION(x)
#else
    #error not supported tool chain
#endif
配合C关键字,对代码的安全校验、扩展移植都会有很好的效果,可参考前文 C语言关键字技巧。也许小型项目、独立开发看不出效果,但对复杂的多人合作的项目,合适的关键字对代码的稳定性和架构是锦上添花。

关于:

(22条消息) Pragma section_#pragma section_mirandali的博客-CSDN博客

设置对象section的另一种方法是使用 pragma section。 通过使用编译指示section,可以通过为整组对象设置编译指示section,轻松地将多个对象定位到用户定义的section中。 该组必须包含在 pragma section 指令中才能正确设置该section和该section的属性:

#pragma section "<name>" [<flags>] [<alignment>]
<objects>
#pragma section

<name>——指定对象(变量、类型或函数)应位于的section的名称。
<flags> ——可以通过该参数设置其他标志。 这些 pragma section的标志可用:
a allocatable. 可定位的,该项总是置位This is always set

B uninitialized 未初始化的

w writable 可写的
x executable 可执行的

s using small addressing  使用小地址

z using absolute addressing 使用绝对寻址

<alignment> ——section的对齐。 它必须是 2 的幂值(例如 1, 2, 4, 8 ...)。 设置标志或编译指示section的对齐是可选的。
默认对齐方式<alignment>取决于定义的数据,而默认标志<flags>是 a 和 w。

要在 pragma section 指令定义的section中分配代码,必须设置 x 标志<flags>。

可执行意味着只有代码将位于此section中,而编译指示section中的所有数据将位于默认数据section中。 没有 x 标志的编译指示section用于分配数据。

如果要将数据放入小的或绝对可寻址的内存区域,对于小型可寻址内存区域使用 .sdata 或者 . sbss 开头,绝对可寻址的内存区域以.zdata. 或 . zbss开头 。 这是告诉链接器将数据放入适当的内存区域的唯一方法。

【注意】代码和数据编译指示可以嵌套。

例子

在这两个示例中,#pragma section“.text”中的所有代码都将放在 .text section中。 命令#pragma section ".data.mysection" 将所有全局未/初始化的数据放在.data.mysection的section中。

例1

#pragma section ".text" ax
#pragma section ".data.mysection" aw
int foo data;
#pragma section
int
foo(void)
{
int i;
...
return (foo data + i);
}
#pragma section


Example 2
#pragma section ".data.mysection" aw
#pragma section ".text" ax
int foo data;
int
foo(void)
{
int i;
...
return (foo data + i);
}
#pragma section
#pragma section


限制

在 1 字节section对齐的section中,整数可能位于奇数地址。 这将导致程序出现故障,因为编译器使用 st .w 和 ld .w 指令来访问整数。 但是 st .w 和 ld .w 指令仅对访问 16 位对齐的内存位置有效。

示例 3

如果您使用也在中断中访问的全局变量,则这些变量的原子加载修改和存储将是必不可少的。 为了解决这个问题,用户应该在一个 4 字节section对齐的绝对可寻址区域中定义这些变量。 在以下示例中,位域 Bits 是通过 #pragma section“.zdata”分配的。 对应于 TriCore EABI,位域的对齐取决于其大小。 如果大小为 4 字节section,则位域的对齐方式为 4 字section。

volatile struct {

unsigned int bit0 :1;
unsigned int bit1 :1;
unsigned int bit2 :1;
unsigned int field :3;
unsigned int dummy :16;

} T Bits;

#pragma section ".zdata" awz
T_Bits Bits;
#pragma section

选项 -funsigned-bitfields 是根据 TriCore EABI 规范默认设置的。

例子

以下示例显示如何使用 pragma section定义变量的section。

1.整型变量 a 位于名为.sdata. relative  的 2 字section对齐section中。 此section是可分配的 (a)、可写的 (w) 和可小寻址的 (s):

请注意该section名称中的前导点。

#pragma section ".sdata.relative" aws 2 //opening pragma section
int a;
#pragma section //closing pragma section


2.在下例中,编译指示section用于定位用户定义section .fastram 中的函数 int foo (void)。

#pragma section ".fastram" ax
int foo (void);
#pragma section

3.在下例子,函数 int foo (void) 和 int bar(void) 位于 .code section。 由于此 pragma section设置了 x 标志,因此除函数外的所有对象都将忽略它。所以变量 a 被定位为common symbol 。

#pragma section ".fastram" ax
int a;
int foo (void);
int bar (void);
#pragma section

4.在此示例中,函数声明包含在没有设置 x 标志的编译指示section中。

此 pragma section仅对变量有效。 变量 a 和 b 位于 .sdata.relative 段中。 函数 int foo(void) 的编译指示section被忽略,该函数位于默认段. text 。

#pragma section ".sdata.relative" aws
int a;
int b;
int foo (void);
#pragma section

5.编译器支持静态变量的编译指示

#pragma section ".sdata.foo" aws
static unsigned short s1;
#pragma section

输入section .sdata.foo 将由默认链接描述文件定位在输出section .sdata 中。

要获取有关 s1 的符号信息,请使用以下命令之一“. tricore-objdump” 显示起始地址、输出section、大小和符号名称。

tricore-objdump --syms test.o | grep s1
tricore-nm -f sysv test.o | grep s1

条目 d 将变量 s1 标记为静态变量。 全局符号用 D 标记。

【注意】静态变量不会显示在链接器的 Mapfile 中。

-Wno-deprecated-pragma-section-name, -Wdeprecated-pragma-section-name

选型 -Wdeprecated-pragma-section-name默认是置位的,如果section name没有被引号括起来,它会发出警告。

选项-Wno-deprecated-pragma-section-name 将允许使用旧语法并迁移遗留代码。

#pragma section .foodata aw
short z = 1;
#pragma section
————————————————
版权声明:本文为CSDN博主「IOT-Power」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a15236617777/article/details/131889198

使用特权

评论回复

相关帖子

沙发
tpgf| | 2023-11-6 16:11 | 只看该作者
这个指定的地方会被其他代码占用吗

使用特权

评论回复
板凳
wowu| | 2023-11-6 16:48 | 只看该作者
section是不是必须要搭配__attribute__来使用呢

使用特权

评论回复
地板
xiaoqizi| | 2023-11-6 21:13 | 只看该作者
section主要作用是将函数或者变量放在指定段中,既可以是RAM内存和可以是ROM内存,这样就可在指定的位置取出

使用特权

评论回复
5
wakayi| | 2023-11-6 21:51 | 只看该作者
在什么情况下我们需要将变量放在指定位置呢

使用特权

评论回复
6
木木guainv| | 2023-11-7 08:19 | 只看该作者
使用section修饰的函数和变量在自定义的片段,而且是连续存放,这样可根据变量的地址得出与其同段变量的地址,为后续自动初始化等功能提供了基础

使用特权

评论回复
7
磨砂| | 2023-11-7 23:23 | 只看该作者
可以将同类函数指针全部使用同一个段名修饰,然后开机后系统自动检索段内函数指针,逐个执行,对上层应用就是无需主动调用,系统自动初始化

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

101

主题

3307

帖子

3

粉丝