zig语言代替C语言进行裸机开发的尝试-2023年笔记

Zig是一种具有内存安全特性的编程语言,相比C和C++更易学,支持编译期代码检查、单元测试集成和异步编程。它基于LLVM,可用于交叉编译多种CPU架构,且与C和汇编兼容。文章提供了Zig用于aarch64裸机开发的示例,展示了其与C代码的交互、构建过程以及使用QEMU进行模拟运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

接触rust的时候,无意中认识了zig,目前版本是zig 0.10.0,还没有正式的1.0版本。

初步使用的感受:
1). 用zig写出的代码更防崩,不会像C那样出现很多内存非法访问的情况
   (比如这些情形:栈保护、整数溢出、下标越界、OOM、DF、Leak。。。)
2). 拥有“类”语法,相同功能的项目开发过程中,所需编写的总代码量肯定会比C少
3). 比Rust/C++容易学,但是比C稍难学一点点
4). 基于LLVM开发的编译器,天生支持交叉编译多种cpu架构的目标代码
5). 可与C、asm进行混合开发,兼容C/C++,完全可以拿来当另一个C编译器用
6). 支持编译期代码
7). 可以源码中集成单元测试代码
8). 自带基于自身脚本的构建系统,不需要makefile之类的
9). 支持 try catch exception处理
10). 编译速度快(网传)
11). MIT许可(相当宽松)
12). 原生支持异步
 

缺点及风险:
1). 小团队开发维护的编程语言,目前网上能看到的只有跟Uber一家有合作。
2). 未成熟、定型,还在变,比如三年前的zig项目,现在已经不能直接编译了。(构建脚本已变)
3). 小众,国内用的人更加少,文档不全,中文的资料更是少得可怜。

安装过程很简单
官网有编译好的,下载解压,添加路径即可。

下载 ⚡Zig Programming Language

只有一个可执行文件:zig
项目创建、构建都是用这个指令。

下面是用zig做aarch64(armv8a)裸机开发的内容,展示了zig的基本用法:
包括基本语法、类的静态成员函数、类实例的成员函数、
与C/asm互调、混合编译、链接、构建脚本、交叉编译、MMIO读写。。。

源码目录结构:

 编译指令:

zig  build         #生成elf格式文件,体积为130Kb上下
zig  build  bin  #生成bin格式文件,40Kb,不调用C的话27Kb左右

在build.zig中将编译模式调整为 "safety off" 后,编译体积甚至比C还要小,bin文件输出不到1kb!

    elf.setBuildMode(std.builtin.Mode.ReleaseSmall);//mode);

 src/main.zig:


const std = @import("std");
const io = std.io;
const os = std.os;

const a_number: i32 = 1234;

export fn main() void {
	const uart = UART.init();
	const out = uart.getWriter();
	out.print("Hello1, {s}!\r\n", .{"world"}) catch return;
	UART.putc(0x63);
	UART.putc('a');
	UART.putc('\n');
	out.print("Hello2, {s}  {}==0x{X}!\r\n", .{"世界", a_number, a_number}) catch return;
	out.print("Hello3, {}\r\n", .{cEng.c_func(3, 8)}) catch return;
	while(true) 
	{
		var c = UART.getc();
		out.print("-->{c}!\r\n", .{c}) catch return;
	}
	out.print("Hello4, {s}!\r\n", .{"world"}) catch return;
}

const UART0DR = @intToPtr(*volatile u8, 0x09000000);
const UART0FR = @intToPtr(*volatile u8, 0x09000018);

pub const UART = struct {

	inited: i32 = 0,

    pub fn init() UART {
        return UART {
            .inited = 1,
        };
    }
	
	pub fn putc(ch:u8) void
	{
		while (  ((UART0FR.*) & (1 << 5)) != 0  ) {}
		UART0DR.* = ch;
	}

	pub fn getc() u8
	{
		while (  ((UART0FR.*) & (1 << 4)) != 0   ) {}
		var ch = UART0DR.*;
		return ch;
	}

	pub fn doWrite(self:UART, bytes: []const u8) WriteError!usize {
		if(self.inited > 0) {}

		var i:usize=0;
		for (bytes) |c| {
			putc(c);
			i += 1;
		}
		
		return i;
	}

    pub const WriteError = os.WriteError;
    pub const Writer = io.Writer(UART, WriteError, doWrite);
	pub fn getWriter(uart: UART) Writer {
        return .{ .context = uart };
	}
};

export fn zig_func(a: i32, b: i32) i32 {
    return a + b;
}

const cEng = @cImport({
    @cDefine("ZIG_WITH_C", "1");
    @cInclude("coWork.h");
});

src/boot.S:

.section ".text.boot"

.global _start
_start:
  mrs x1, mpidr_el1
  and x1, x1, #3
  cbz x1, 2f
1:
  wfe
  b 1b
2:
	ldr x0, =stack_top
	bic	sp, x0, #0xf				/* 16-byte alignment for ABI compliance */
	bl main
	bl .

src/linker.ld:


#include <Platform_def.h>

/*
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
*/


ENTRY(_start)

SECTIONS {
	. = 0x40000000;
	. = ALIGN(8);
    __text_start = .;
    .text : 
	{ 
		KEEP(*(.text.boot))
		STARTOBJ(.text)
		*(.text) 
	}

	/* shell_cmd_item 保存在这个段 */
    . = ALIGN(8);
    __shell_cmd_list_start = .;
     .ShellCmdList   ALIGN(8)  : { *(.ShellCmdList) }
    __shell_cmd_list_end = .;

	/* resource_item 保存在这个段 */
    . = ALIGN(8);
    __resource_item_start = .;
     .resource_item   ALIGN(8)  : { *(.ResourceList) }
    __resource_item_end = .;

    .rodata ALIGN(8) : {*(.rodata*)} 
    .data   ALIGN(8) : { *(.data) }

    . = ALIGN(8);
    __bss_start = .;
     .bss   ALIGN(8)  : { *(.bss)  *(COMMON) }
    __bss_end = .;

	/* resource 实际保存在这个段 */
    . = ALIGN(8);
	.extdata  ALIGN(8): {*(.extdata*)}

	/* 栈内存 */
	. = ALIGN(16);
	. = . + 1M; /* 4kB of stack memory => 1MB */
	stack_top = .;

	/DISCARD/ : { *(.dynsym) }
	/DISCARD/ : { *(.dynstr*) }
	/DISCARD/ : { *(.dynamic*) }
	/DISCARD/ : { *(.plt*) }
	/DISCARD/ : { *(.interp*) }
	/DISCARD/ : { *(.gnu*) }
}

#ifdef TPL
	/* tpl 运行在64kb 的iRam 中,体积不能超了,况且iRam分出来4kb做栈内存了,实际可用的内存就更少了,所以定个58kb的可用内存 */
	ASSERT( (__bss_start - __text_start) < (58*1024), 
	"
	编译出来的 tpl.bin 太大,iRam 装不下了,请编译为 spl => make spl
	");
#endif

src/coWork.h:

int c_func(int a, int b);

src/coWork.c:


extern int zig_func(int a, int b);

int c_func(int a, int b) {
	return zig_func(a, a) + b;
}

 startQemu.ps1:


[System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8
& "d:\Program Files\qemu\qemu-system-aarch64.exe" -nographic -machine virt-6.2,gic-version=3,secure=on,virtualization=on -cpu cortex-a53 -m 1024 -semihosting -kernel ".\zig-out\bin\out.elf"

build.zig:

const Builder = @import("std").build.Builder;
const builtin = @import("builtin");
const std = @import("std");

pub fn build(b: *Builder) void {
    const target = .{
        .cpu_arch = .aarch64,
        .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.cortex_a53 },
        .os_tag = .freestanding,
        .abi = .none,
    };

    // Standard release options allow the person running `zig build` to select
    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
    const mode = b.standardReleaseOptions();

    const elf = b.addExecutable("out.elf", "src/main.zig");
    elf.addAssemblyFile("src/boot.S");
	elf.addIncludePath("src/"); // addIncludeDir 已经被官方标为deprecated,不建议使用
	elf.addCSourceFile("src/coWork.c", &[_][]const u8{"-std=c99"});
    elf.setTarget(target);
    elf.setBuildMode(mode);
    elf.setLinkerScriptPath(.{ .path = "src/linker.ld" });
	// --script

    const bin = b.addInstallRaw(elf, "out.bin", .{});
    const bin_step = b.step("bin", "Generate binary file to be flashed");
    bin_step.dependOn(&bin.step);

    b.default_step.dependOn(&elf.step);
    b.installArtifact(elf);
}

qemu运行截图:

通过 struct {} 定义类时,大括号里面的成员函数,
如果它的第一个参数的类型是类本身,
则此函数被当成类的实例的成员函数(比如代码中的 uart.getWriter ),
否则被当成类的静态函数,要通过 “类名.函数名的方式调用”,(比如代码中的 UART.putc )

zig 支持的目标cpu架构,可参考此目录:$(zig_install_dir)/lib/std/target/
也可能通过运行这条指令列出:zig  targets

碰到两个坑:

LTO (Link Time Optimization) 默认会启动,编译模式为 release* 时,export linksection 全局常量不会被放入bin文件的指定数据段,即会被“优化掉”,解法是不让它优化:build.zig 里面加上 exe.want_lto = false; 即可。

link.ld 中预留栈空间没有在最尾处,然后llvm又在后面补了got 数据段,导致预留栈段全被0填充,然后生成的bin文件体积变大

参考:
GitHub - rbino/zig-stm32-blink: Use Zig to blink some LEDs

 Documentation - The Zig Programming Language

Documentation - Zig

0.10.0 Release Notes ⚡ The Zig Programming Language

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值