找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 127|回复: 0

【Rust】一些在no_std里开发的技巧

[复制链接]
发表于 2024-11-21 02:29:53 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
本帖最后由 tangptr@126.com 于 2024-11-21 02:37 编辑

前言

如果是在常规的用户态里玩Rust,那我极不建议放弃使用Rust的标准库。
使用no_std时,一般是因为:

  1. 你需要在内核态或其他特殊环境里使用Rust
  2. 你需要砍掉CRT依赖(注:Rust标准库几乎完全静态,仅产生CRT类的动态库依赖)

使用no_std

使用no_std时需要在你的crate里根部源码文件(main.rslib.rs)中使用#![no_std]进行声明。
声明了no_std后,意味着你放弃了Rust的std crate,但你仍然可以使用core crate,很多std里的东西也能在core里找到,但是像I/O以及线程之类的库就消失了。

堆内存

使用no_std后,堆内存类型(如Vec,String,Box等)仍然可以在alloc crate里找到,但你需要用extern crate alloc来声明使用alloc crate。
此外你还需要声明一个全局内存分配器(见后文的例子)才能使用堆内存。

调试输出

使用no_std后,控制台输出的那些宏(如print!,println!等)也就消失了。但是你仍然能自己实现调试输出的宏(见后文的例子)。

使用windows-sys

微软官方同时发布了windows库和windows-sys库。注意,前者是依赖std的!所以当你使用no_std时,请使用后者。

全局内存分配器

在声明全局内存分配器时,需要声明一个用于分配堆内存的类型,并为其实现GlobalAlloc的trait,最后用#[global_allocator]语句声明其为全局内存分配器。
以Windows为例,我们可以用系统提供的HeapAlloc(请不要用VirtualAlloc,因为它的分配粒度太大,并且还要走系统调用,每次分配内存都用这个函数就太慢太浪费内存了):

use core::alloc::GlobalAlloc;
use windows_sys::Win32::System::Memory::*;

struct SysAlloc;

unsafe impl GlobalAlloc for SysAlloc
{
    unsafe fn alloc(&self, layout: core::alloc:: Layout) -> *mut u8
    {
        HeapAlloc(GetProcessHeap(),0,layout.size()).cast()
    }

    unsafe fn dealloc(&self, ptr: *mut u8, _layout: core::alloc:: Layout)
    {
        HeapFree(GetProcessHeap(),0,ptr.cast());
    }

    unsafe fn alloc_zeroed(&self, layout: core::alloc:: Layout) -> *mut u8
    {
        HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,layout.size()).cast()
    }

    unsafe fn realloc(&self, ptr: *mut u8, _layout: core::alloc:: Layout, new_size: usize) -> *mut u8
    {
        HeapReAlloc(GetProcessHeap(),0,ptr.cast(),new_size).cast()
    }
}

#[global_allocator] static GLOBAL_ALLOCATOR:SysAlloc=SysAlloc;

注意,alloc_zeroedrealloc是可选的特征方法,可以不实现。

格式化字符串与输出

和C不同,Rust自己的ABI不允许函数有可变的参数数量,但是宏允许可变的参数数量!最常用的print!, println!等等其实都是宏,完全可以自己实现:

#[macro_export] macro_rules! print
{
    ($($args:tt)*) =>
    {
        internal_print(format_args!($($args)*))
    };
}

#[macro_export] macro_rules! println
{
    ()=>
    {
        print!("\n")
    };
    ($($args:tt)*)=>
    {
        print!("{}\n",format_args!($($args)*))
    };
}

这个internal_print函数需要自己实现。这个format_args!宏是rust内置的宏,即使是在no_std里也可以用。它会返回一个Arguments的类型。
它虽然实现了一个as_str的方法,但这个方法很鸡肋:只有当优化器可以直接格式化这个字符串的时候才能返回字符串(比如print!("{}+{}={}",1,2,3);一定会被优化器优化成输出1+2=3),否则一定会返回None
这里可以定义一个用于接收格式化字符串的类型,并为其实现Write trait

use core::fmt;

struct FormatBuffer
{
    buffer:[u8;512],
    used:usize
}

impl Default for FormatBuffer
{
    fn default()->Self
    {
        Self
        {
            buffer:[0;512],
            used:0
        }
    }
}

impl fmt::Write for FormatBuffer
{
    fn write_str(&mut self, s: &str) -> fmt::Result
    {
        let remainder=&mut self.buffer[self.used..];
        let current=s.as_bytes();
        if remainder.len()<current.len()
        {
            return Err(fmt::Error);
        }
        remainder[..current.len()].copy_from_slice(current);
        self.used+=current.len();
        Ok(())
    }
}

接下来就可以实现internal_print函数了!

fn internal_print(args:fmt::Arguments)
{
    let mut w=FormatBuffer::default();
    let r=fmt::write(&mut w,args);
    if r.is_ok()
    {
        let b=&w.buffer;
        let h=unsafe{GetStdHandle(STD_OUTPUT_HANDLE)};
        if !h.is_null()
        {
            let mut size:u32=w.used as u32;
            let _=unsafe{WriteConsoleA(h,b.as_ptr(),size,&raw mut size,null())};
        }
    }
}

注意:虽然你可以直接在实现Write trait的时候就直接输出到控制台,但你会遇到竞态条件的问题。

入口函数

如果你的程序不是库,则需要同时用#![no_main]来标记没有Rust可识别的入口函数。
在MSVC中,以控制台为例,默认的入口函数是mainCRTStartup,但你也可以给链接器加上/ENTRY参数来修改入口函数名。
声明入口函数时,需要用#[no_mangle]取消rust编译器mangle函数符号的行为,并用extern "C"标记这个函数使用C的ABI。

#[no_mangle] extern "C" fn start()
{
    // Entry point starts here.
}

与此同时,你需要在crate的根目录里创建一个build.rs文件(和Cargo.toml文件平级),来给链接器增加参数:

fn main()
{
    println!("cargo:rustc-link-arg=/ENTRY:start");
}

使用外部库

当你使用外部库时,需要确认它们是否支持no_std的环境。一般而言,支持no_std的crate会在简介里就强调自己支持no_std
此外还需要按照它们的说明来进行配置。比如指定default-featurefalse。每个crate各不相同,有的可能无需配置就可以支持no_std了。

回复

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-12 09:07 , Processed in 0.030645 second(s), 21 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表