
上一节我们已经配置好了交叉编译环境,并成功运行了“Hello, world!”。不过,我记得在我刚开始学习 Arduino 的时候,也有一个非常经典的程序——Blink,这似乎也是算是 “Hello, world!” ?于是,我们今天的主题是,使用 Rust 点亮 LED。这是 Arduino 的 Blink 代码:
继续阅读本文面向于对 Rust 和 Linux 有一定基础的读者。
在开始之前,请先确保你已经拥有了一个树莓派,和一台装有 Linux 的物理机或者虚拟机,装好 Rust,并且已经为树莓派刷好了系统(推荐官方系统),能够在你电脑上用 ssh
连接至树莓派。期间你可能会遇到问题,请尝试从文末给出的连接找到答案。
通常,要得到可在树莓派上运行的二进制文件有两种方式。一种是在树莓派上安装 Rust,编译源码并运行。由于树莓派硬件资源的限制,在树莓派上编译 Rust 极其缓慢,尤其是在 zero w 这类型号上,并且编写源码也是件麻烦的事。另一种是在你的电脑上编写源码,交叉编译,并通过 scp
命令发送至树莓派。在开发时,你也可以利用 rsync
工具,将编译好的二进制文件同步至树莓派。
上一节,我们已经实现了一个最小可运行版本。之所以使用Rust而不是C,是因为Rust具备了必要的抽象能力,还能获得跟C差不多的性能。这一节,我们对上一节的代码做必要的封装,顺便还能把unsafe
的代码包装成safe
的API。
我将上一节的源码放到了这里,你可以去查看。
还记得上一节,我们把使用到的libc
中的函数socket
、bind
、connect
和结构体sockaddr
、sockaddr_in
、in_addr
等,在Rust这边定义了出来。实际上,几乎libc
中的函数,libc这个crate都帮我们定义好了。你可以去这里查看。编译器和标准库本身也使用了这个crate,我们也使用这个。
首先在Cargo.toml
文件的[dependencies]
下面加入libc = "0.2"
:
[dependencies] libc = "0.2"
接着在main.rs
文件上方加入use libc;
,也可以use libc as c;
。或者你直接简单粗暴use libc::*
,并不推荐这样,除非你明确知道你使用的函数来自哪里。并将我们定义的与libc
中对用的常量、函数、结构体删除。再添加libc::
或c::
到我们使用那些常量、结构体、函数的地方。如果你是直接use libc::*
,除了直接删除那部分代码外,几乎什么都不用做。目前的代码:
虽然标准库已经封装好了 TcpListener 和TcpStream 等基础api,但作为Rust 的爱好者,我们可以去一探究竟。本文假设你已经对 Rust 和 Linux 操作系统有了一定了解。
在 Linux 上 Rust 默认会链接的系统的 libc
以及一些其他的库,这就意味着,你可以直接使用libc
中的函数。比如,你可以使用 gethostname 获取你电脑的 “hostname”:
use std::os::raw::c_char; use std::ffi::CStr; extern { pub fn gethostname(name: *mut c_char, len: usize) -> i32; } fn main() { let len = 255; let mut buf = Vec::<u8>::with_capacity(len); let ptr = buf.as_mut_ptr() as *mut c_char; unsafe { gethostname(ptr, len); println!("{:?}", CStr::from_ptr(ptr)); } }
解释一下上面的代码。
extren
表示“外部块(External blocks)”,用来申明外部非 Rust 库中的符号。我们需要使用 Rust 以外的函数,比如 libc ,就需要在 extren
中将需要用到的函数定义出来,然后就可以像使用本地函数一样使用外部函数,编译器会负责帮我们转换,是不是很方便呢。但是,调用一个外部函数是unsafe
的,编译器不能提供足够的保证,所以要放到unsafe
块中。