cargo build 命令和 build.rs 脚本的关系?

a. 位于项目根目录下
b. 如果存在 cargo build 命令执前自动编译build.rs 脚本并执行
c. 脚本可以使用 println! 的方式跟 Cargo 进行通信:通信内容是以 cargo: 开头的格式化字符串
d. 如果需要传递参数则需要使用环境变量进行传递,例如: MY_CUSTOM_ARG=42 cargo build
e. 编译脚本中所有的 println 打印都将输出到 ~/target/debug/build/test-1-ba40594839af4be9/output
f. 作用:

  1. 构建 C 依赖库
  2. 在操作系统中寻找指定的 C 依赖库
  3. 根据某个说明描述文件生成一个 Rust 模块
  4. 执行一些平台相关的配置

build.rs 中 println! 为什么都输入到了 target/debug/build/<pkg>/output 文件?

  在项目构建过程中,build.rs 脚本使用 println 的方式跟 Cargo 进行通信:以 cargo: 开头的格式化字符串将会被传递给 Cargo,其他则会忽略。构建脚本打印输出的所有内容将保存在文件 target/debug/build//中的 output 和stderr中,默认对用户隐藏,如果需要查看可以使用 -vv 来显示所有输出。

rust 中如何引用 C 函数?

  对于库中的函数,如果需要在rust 中使用,首先需要在 rust 中对函数的参数类型,返回类型进行重新定义,然后需要在extern "C" { } 块中对C/C++的函数以rust风格进行重新申明,申明完成后就可以在rust 项目中使用此函数了,在函数使用时由于C/C++的函数来自外部属于不安全范围,所以在使用时必须要将C函数放在 unsafe {} 块中,一般的做法是将C函数使用unsafe {}重新封装为 rust 函数。
  例如在rust 中引用C库函数 int get_sum(int a, int b):

pub type Int32Rs = i32;
extern "C" {
    pub fn get_sum(a:Int32Rs, b:Int32Rs) -> Int32Rs;
}

fn main() {
    unsafe { let sum = get_sum(100, 20);
        println!("sum {}", sum);
    }
}

rust 项目中如何将存在的 C 文件编译成库?

  如果需要在rust项目中编译C/C++的文件,则必须要使用到 build.rs 脚本,在然后在脚本中使用 cc::Build::new()来编译C文件,并打包成对应的库。
  Cargo.toml 文件中添加一下内容:

[package]
# ...
build = "build.rs"

# 为 build.rs 编译脚本添加依赖
[build-dependencies]
# ...
cc = "1.0.52"

build.rs 脚本:

use cc::Build;

fn main() {

    // 指明需要连接的外表库
    println!("cargo:rustc-link-lib=m");
    // 这里无需显示指明hello库,因为c源码在项目中被编译会自动添加到rust执行文件的编译链接中
    // println!("cargo:rustc-link-lib=static=hello");

    // 以下代码告诉 Cargo ,一旦指定的文件 `src/hello.c` 发生了改变,就重新运行当前的构建脚本
    println!("cargo:rerun-if-changed=src/hello.c");
    // 使用 `cc` 来构建 C 文件,然后进行静态链接,打包成 libhello.a
    Build::new()
        .opt_level(2)
        .file("src/hello.c")
        .try_compile("hello").unwrap();
}

rust 文件如何与 C 库一起编译?

  对于 rust 项目中存在C文件,并且在项目构建时打包成C库被使用的情况,则在 build.rs 中无需使用 rustc-link-lib 显示指明链接库,因为C源码在项目中被编译会自动添加到rust执行文件的编译链接中。
  对于引入的外部 C/C++ 库则需要在 build.rs 中使用 rustc-link-lib 显示的指明需要连接的库名称。
  例如链接 winmm 库。

println!("cargo:rustc-link-lib=static=winmm");

rust 和 C 项目混合编译代码大小优化?

  优化 rust 和 C的混合项目需要从三部分进行优化:

  • 首先需要对C部分设置编译优化等级
  • 设置rust 编译时的优化等级
  • 去除可执行文件的符号表,这样转换成 Hex 文件的体积才会真正降下来

C语言编译优化

  在build.rs 中 cc Build 使用opt_level() 设置优化等级。

    cc::Build::new()
        .opt_level(2)
        .file("src/hello.c")
        .try_compile("hello").unwrap();

Rust语言编译优化

  在 Cargo.toml 中设置 release 时的代码优化情况。

[profile.release]
codegen-units = 1 # better optimizations
lto = true 				# better optimizations

  编译时使用使用 --release 选项则会默认使用以上设置进行优化。

cargo build --package freertos-rust-examples --example stm32-cortex-m3 --target thumbv7m-none-eabi --release

去除符号表

  在编译出ELF可移植性文件后对可执行文件去除符号表后生成最终hex文件。

 cargo strip --target thumbv7m-none-eabi --example stm32-cortex-m3 --release
 cargo build --package freertos-rust-examples --example stm32-cortex-m3 --target thumbv7m-none-eabi --release

rust 如何生成 rlib 库?

  如果 cargo.toml 文件中已经定义了 [lib] 部分,或者如果项目中有一个 src/lib.rs 文件,cargo 会默认将项目作为库 crate 来构建,并生成 .rlib 文件。如果没有 src/lib.rs,则可以在 [lib] 项下定义程序文件入口,然后通过 path 申明。如果有多个源文件,则可以在 src/lib.rs 或指明的入口文件中做配置来决定启用哪些模块。这里的模块使用 mod 关键字 + src下的文件/文件夹名称组成,例如:mod delays;

[lib]
name = "test" 			# name
path = "src/lib.rs"		# src file

lib.rs 文件中

#[cfg(feature = "time")]
mod delays;

Rust 和C代码大小对比?

  Tokio v1.5.0 中tokio模块(高性能异步运行时框架)的代码(NBNC)有36,473行,使用tokei工具进行统计的结果。
使用 cargo build --release 编译后删除 strip 和 .rustc 段后剩余大小:为 1341680 大小为1.3M 左右。表格中C采用-O2优化等级。
rust1.jpg

.gcc_except_table

  该段和 try-catch-finally 控制流块的异常处理相关,用于处理异常,包括栈回溯时堆栈展开,清理异常处理过程中的资源。

.dynsym 符号表

  这一节存储的是关于动态链接的符号表,每一个表项占24字节,tokio总共有2099个动态符号,相比较于C,Rust会存在更多的库函数、数据结构和异常处理等。
Rust dynsym符号表:

Symbol table '.dynsym' contains 2099 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZN3std3net3tcp9TcpStream
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZN3std2fs8DirEntry9file_
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZN4core3fmt3num53_$LT$im
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZN4core3fmt3num53_$LT$im
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZN51_$LT$$RF$std..fs..Fi
     6: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND _ZN3std10std_detect6detec
     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZN3std3sys4unix6thread6T
     8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZN4core3fmt3num52_$LT$im
     9: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pipe2@GLIBC_2.9 (2)
    10: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZN91_$LT$std..io..cursor
    11: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZN74_$LT$std..fs..DirEnt
    ...

C dynsym 符号表

   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __isoc99_fscanf@GLIBC_2.7 (3)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND clock_gettime@GLIBC_2.17 (4)
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fclose@GLIBC_2.2.5 (2)
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __assert_fail@GLIBC_2.2.5 (2)
     8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     9: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND feof@GLIBC_2.2.5 (2)
    10: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    11: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@GLIBC_2.14 (5)
    ...

.dynstr

  dynstr段用来存储dysym符号表中的符号,本次测试使用的是 rustc 1.48.0,组名规则为legacy,类似于C++的组名规则,符号名中间会加上crate、mod、struct等信息,想比于C语言的组名要大很多。
//Rust 字符串表

String dump of section '.dynstr':
  [     1]  libstd-f14aca24435a5414.so
  [    1c]  _ITM_deregisterTMCloneTable
  [    38]  __gmon_start__
  [    47]  _Jv_RegisterClasses
  [    5b]  _ITM_registerTMCloneTable
  [    75]  _ZN58_$LT$std..io..error..Error$u20$as$u20$core..fmt..Debug$GT$3fmt17heb882e9e5723aaeaE
  [    cd]  _ZN244_$LT$std..error..$LT$impl$u20$core..convert..From$LT$alloc..string..String$GT$$u20$for$u20$alloc..boxed..Box$LT$dyn$u20$std..error..Error$u2b$core..marker..Send$u2b$core..marker..Sync$GT$$GT$..from..StringError$u20$as$u20$core..fmt..Display$GT$3fmt17h0381a183d16c0bdbE
  [   1e0]  _ZN3std2rt19lang_start_internal17h73711f37ecfcb277E

C 字符串表

String dump of section '.dynstr':
  [     1]  libc.so.6
  [     b]  fopen
  [    11]  puts
  [    16]  __assert_fail
  [    24]  printf
  [    2b]  feof
  [    30]  __isoc99_fscanf
  [    40]  memcpy

.text段

  .text段大概也是 C的3倍左右大小,通过汇编指令打开查看,Rust比C多出点在 异常处理、调用栈、析构函数、泛型实例化、Vec,Result,Box,String,Map等结构的处理、运行时边界校验等,这些也是保证rust 可用性和内存安全性的保证。

总结

  Rust 项目代码相对于C的代码体积膨胀约为 3倍左右,增加的代码主要是为rust 的内存安全、灵活和可用性提供保证,在非资源受限上,相对来说还是可以接受的。

文章作者: 路西法
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 以梦为马
rust rust 交叉编译
喜欢就支持一下吧