实验二 Hello, miniEuler操作演示(保姆级教程)

PS:所有的批注都写在了块引用中,其他文字均为题干

print函数是学习几乎任何一种软件开发语言时最先学习使用的函数,同时该函数也是最基本和原始的程序调试手段,但该函数的实现却并不简单。本实验的目的在于理解操作系统与硬件的接口方法,并实现一个可打印字符的函数(非系统调用),用于后续的调试和开发。

一、了解virt机器

操作系统介于硬件和应用程序之间,向下管理硬件资源,向上提供应用编程接口。设计并实现操作系统需要熟悉底层硬件的组成及其操作方法。

本系列实验都会在QEMU模拟器上完成,首先来了解一下模拟的机器信息。可以通过下列两种方法:

1.查看QEMU关于 virt的描述 , 或者查看QEMU的源码,如github上的 virt.h 和 virt.c。virt.c中可见如下有关内存映射的内容

static const MemMapEntry base_memmap[] = {
    /* Space up to 0x8000000 is reserved for a boot ROM */
    [VIRT_FLASH] =              {          0, 0x08000000 },
    [VIRT_CPUPERIPHS] =         { 0x08000000, 0x00020000 },
    /* GIC distributor and CPU interfaces sit inside the CPU peripheral space */
    [VIRT_GIC_DIST] =           { 0x08000000, 0x00010000 },
    [VIRT_GIC_CPU] =            { 0x08010000, 0x00010000 },
    [VIRT_GIC_V2M] =            { 0x08020000, 0x00001000 },
    [VIRT_GIC_HYP] =            { 0x08030000, 0x00010000 },
    [VIRT_GIC_VCPU] =           { 0x08040000, 0x00010000 },
    /* The space in between here is reserved for GICv3 CPU/vCPU/HYP */
    [VIRT_GIC_ITS] =            { 0x08080000, 0x00020000 },
    /* This redistributor space allows up to 2*64kB*123 CPUs */
    [VIRT_GIC_REDIST] =         { 0x080A0000, 0x00F60000 },
    [VIRT_UART] =               { 0x09000000, 0x00001000 },
    [VIRT_RTC] =                { 0x09010000, 0x00001000 },
    [VIRT_FW_CFG] =             { 0x09020000, 0x00000018 },
    [VIRT_GPIO] =               { 0x09030000, 0x00001000 },
    [VIRT_SECURE_UART] =        { 0x09040000, 0x00001000 },
    [VIRT_SMMU] =               { 0x09050000, 0x00020000 },
    [VIRT_PCDIMM_ACPI] =        { 0x09070000, MEMORY_HOTPLUG_IO_LEN },
    [VIRT_ACPI_GED] =           { 0x09080000, ACPI_GED_EVT_SEL_LEN },
    [VIRT_NVDIMM_ACPI] =        { 0x09090000, NVDIMM_ACPI_IO_LEN},
    [VIRT_PVTIME] =             { 0x090a0000, 0x00010000 },
    [VIRT_SECURE_GPIO] =        { 0x090b0000, 0x00001000 },
    [VIRT_MMIO] =               { 0x0a000000, 0x00000200 },
    /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */
    [VIRT_PLATFORM_BUS] =       { 0x0c000000, 0x02000000 },
    [VIRT_SECURE_MEM] =         { 0x0e000000, 0x01000000 },
    [VIRT_PCIE_MMIO] =          { 0x10000000, 0x2eff0000 },
    [VIRT_PCIE_PIO] =           { 0x3eff0000, 0x00010000 },
    [VIRT_PCIE_ECAM] =          { 0x3f000000, 0x01000000 },
    /* Actual RAM size depends on initial RAM and device memory settings */
    [VIRT_MEM] =                { GiB, LEGACY_RAMLIMIT_BYTES },
};

2.通过QEMU导出设备树

①安装设备树格式转换工具

Mac下安装

brew install dtc

Linux下安装

apt-get install device-tree-compiler

在正常输入 apt-get install device-tree-compiler时,会出现下面问题:

eafefbec19ac423ab4be6ca86d50f3a3.png

这是因为apt-get命令一般需要root权限执行,所以最快捷的解决方法就是在apt-get前面加上sudo,即输入:

sudo apt-get install device-tree-compiler

然后就可以了

62874e7e30c8400aa6f1dc5f5968af5b.png

2.通过QEMU导出设备树并转成可读格式

qemu-system-aarch64 -machine virt,dumpdtb=virt.dtb -cpu cortex-a53 -nographic
dtc -I dtb -O dts -o virt.dts virt.dtb

5c0aee9b4edb4e28bd6fb0d93b1d1237.png

 输入完这两条指令后,路径下面出现virt.dtb和virt.dts这两个文件

b7d595bd854f48dea2201a150f4273ce.png

e818333f9f1f41d6a037e8b10e3460e1.png  virt.dtb转换后生成的virt.dts中可找到如下内容

pl011@9000000 {
    clock-names = "uartclk\0apb_pclk";
    clocks = <0x8000 0x8000>;
    interrupts = <0x00 0x01 0x04>;
    reg = <0x00 0x9000000 0x00 0x1000>;
    compatible = "arm,pl011\0arm,primecell";
};

chosen {
    stdout-path = "/pl011@9000000";
    kaslr-seed = <0xcbd0568d 0xb463306c>;
};

由上可以看出,virt机器包含有pl011的设备,该设备的寄存器在0x9000000开始处。pl011实际上是一个UART设备,即串口。可以看到virt选择使用pl011作为标准输出,这是因为与PC不同,大部分嵌入式系统默认情况下并不包含VGA设备。 

二、实现 PRT_Printf 函数

本系列实验每个实验均依赖前序相关实验,因此可拷贝 lab1 目录并重命名为 lab2 ,在 lab2 目录中再操作(后续实验照此操作)。

新建 src/bsp/print.c 文件,完成如下部分。

1.宏定义

在 print.c 中包含所需头文件,并定义后续将会用到的宏

#include <stdarg.h>
#include "prt_typedef.h"

#define UART_0_REG_BASE 0x09000000 // pl011 设备寄存器地址
// 寄存器及其位定义参见:https://developer.arm.com/documentation/ddi0183/g/programmers-model/summary-of-registers
#define DW_UART_THR 0x00 // UARTDR(Data Register) 寄存器
#define DW_UART_FR 0x18  // UARTFR(Flag Register) 寄存器
#define DW_UART_LCR_HR 0x2c  // UARTLCR_H(Line Control Register) 寄存器
#define DW_XFIFO_NOT_FULL 0x020  // 发送缓冲区满置位
#define DW_FIFO_ENABLE 0x10 // 启用发送和接收FIFO

#define UART_BUSY_TIMEOUT   1000000
#define OS_MAX_SHOW_LEN 0x200


#define UART_REG_READ(addr)          (*(volatile U32 *)(((uintptr_t)addr)))  // 读设备寄存器
#define UART_REG_WRITE(value, addr)  (*(volatile U32 *)((uintptr_t)addr) = (U32)value) // 写设备寄存器

 强烈建议做这个实验时候把lab1拷贝一遍,粘贴为lab2,然后在lab2基础上做实验二的任务。因为涉及到多处对于代码片段的修改,所以如果不拷贝的话,做完实验二再想做实验一还得再改回去

可以使用vim或者gedit创建print.c文件,代码拷贝进去粘贴

f4445ff0ca874ab7a56515502b18cb81.jpeg

2.串口的初始化 

0845ba9ef55b43d4ba29035b86aeecc2.png

U32 PRT_UartInit(void)
{
    U32 result = 0;
    U32 reg_base = UART_0_REG_BASE;
    // LCR寄存器: https://developer.arm.com/documentation/ddi0183/g/programmers-model/register-descriptions/line-control-register--uartlcr-h?lang=en
    result = UART_REG_READ((unsigned long)(reg_base + DW_UART_LCR_HR));
    UART_REG_WRITE(result | DW_FIFO_ENABLE, (unsigned long)(reg_base + DW_UART_LCR_HR)); // 启用 FIFO

    return OS_OK;
}

这里,把上述代码继续拷贝到print.c文件中,紧接着粘贴在下一行 

3.往串口发送字符

// 读 reg_base + offset 寄存器的值。 uartno 参数未使用
S32 uart_reg_read(S32 uartno, U32 offset, U32 *val)
{
    S32 ret;
    U32 reg_base = UART_0_REG_BASE;


    *val = UART_REG_READ((unsigned long)(reg_base + offset));
    return OS_OK;
}

// 通过检查 FR 寄存器的标志位确定发送缓冲是否满,满时返回1.
S32 uart_is_txfifo_full(S32 uartno)
{
    S32 ret;
    U32 usr = 0;

    ret = uart_reg_read(uartno, DW_UART_FR, &usr);
    if (ret) {
        return OS_OK;
    }

    return (usr & DW_XFIFO_NOT_FULL);
}

// 往 reg_base + offset 寄存器中写入值 val。
void uart_reg_write(S32 uartno, U32 offset, U32 val)
{
    S32 ret;
    U32 reg_base = UART_0_REG_BASE;

    UART_REG_WRITE(val, (unsigned long)(reg_base + offset));
    return;
}

// 通过轮询的方式发送字符到串口
void uart_poll_send(unsigned char ch)
{

    S32 timeout = 0;
    S32 max_timeout = UART_BUSY_TIMEOUT;

    // 轮询发送缓冲区是否满
    int result = uart_is_txfifo_full(0);
    while (result) {
        timeout++;
        if (timeout >= max_timeout) {
            return;
        }
        result = uart_is_txfifo_full(0);
    }

    // 如果缓冲区没满,通过往数据寄存器写入数据发送字符到串口
    uart_reg_write(0, DW_UART_THR, (U32)(U8)ch);
    return;
}

// 轮询的方式发送字符到串口,且转义换行符
void TryPutc(unsigned char ch)
{
    uart_poll_send(ch);
    if (ch == '\n') {
        uart_poll_send('\r');
    }
}

上面的代码很简单,就是通过轮询的方式向 PL011 的数据寄存器 DR 写入数据即可实现往串口发送字符,实现字符输出。 

这里继续粘贴在print.c文件的尾部 

 4.支持格式化输出

extern int  vsnprintf_s(char *buff, int buff_size, int count, char const *fmt, va_list arg);
int TryPrintf(const char *format, va_list vaList)
{
    int len;
    char buff[OS_MAX_SHOW_LEN] = {0};
    char *str = buff;

    len = vsnprintf_s(buff, OS_MAX_SHOW_LEN, OS_MAX_SHOW_LEN, format, vaList);
    if (len == -1) {
        return len;
    }

    while (*str != '\0') {
        TryPutc(*str);
        str++;
    }

    return OS_OK;
}

U32 PRT_Printf(const char *format, ...)
{
    va_list vaList;
    S32 count;

    va_start(vaList, format);
    count = TryPrintf(format, vaList);
    va_end(vaList);

    return count;
}

为了实现与 C 语言中 printf 函数类似的格式化功能,我们要用到可变参数列表 va_list 。而真正实现格式化控制转换的函数是 vsnprintf_s 函数。 

 这里又双叒叕继续粘贴在print.c文件的尾部

5.实现 vsnprintf_s 函数

新建 src/bsp/vsnprintf_s.c 实现 vsnprintf_s 函数

vsnprintf_s 函数的主要作用是依据格式控制符将可变参数列表转换成字符列表写入缓冲区。UniProton 提供了 libboundscheck 库,其中实现了 vsnprintf_s 函数,作为可选作业你可以试着使用 libboundscheck 库中的 vsnprintf_s 函数。简单起见,我们从另一个国产实时操作系统 RT-Thread 的 kservice.c 中引入了一个实现并进行了简单修改。 可以在 这里 下载 vsnprintf_s.c。

6e2a9d5cd1ed4b479a5e59535593e8e7.png

 vsnprintf_s.c文件内代码如下:

#include <stdarg.h>

static void ftoa_fixed(char *buffer, double value);
static void ftoa_sci(char *buffer, double value);

int strlen(const char *s)
{
    const char *sc = (void *)0 ;

    for (sc = s; *sc != '\0'; ++sc) /* nothing */
        ;

    return sc - s;
}

/* private function */
#define _ISDIGIT(c)  ((unsigned)((c) - '0') < 10)

/**
 * @brief  This function will duplicate a string.
 *
 * @param  n is the string to be duplicated.
 *
 * @param  base is support divide instructions value.
 *
 * @return the duplicated string pointer.
 */
#ifdef RT_KPRINTF_USING_LONGLONG
inline int divide(unsigned long long *n, int base)
#else
inline int divide(unsigned long *n, int base)
#endif /* RT_KPRINTF_USING_LONGLONG */
{
    int res;

    /* optimized for processor which does not support divide instructions. */
#ifdef RT_KPRINTF_USING_LONGLONG
    res = (int)((*n) % base);
    *n = (long long)((*n) / base);
#else
    res = (int)((*n) % base);
    *n = (long)((*n) / base);
#endif

    return res;
}

int skip_atoi(const char **s)
{
    int i = 0;
    while (_ISDIGIT(**s))
        i = i * 10 + *((*s)++) - '0';

    return i;
}


#define ZEROPAD     (1 << 0)    /* pad with zero */
#define SIGN        (1 << 1)    /* unsigned/signed long */
#define PLUS        (1 << 2)    /* show plus */
#define SPACE       (1 << 3)    /* space if plus */
#define LEFT        (1 << 4)    /* left justified */
#define SPECIAL     (1 << 5)    /* 0x */
#define LARGE       (1 << 6)    /* use 'ABCDEF' instead of 'abcdef' */

#define RT_PRINTF_PRECISION

static char *print_number(char *buf,
                          char *end,
#ifdef RT_KPRINTF_USING_LONGLONG
                          unsigned long long  num,
#else
                          unsigned long  num,
#endif /* RT_KPRINTF_USING_LONGLONG */
                          int   base,
                          int   qualifier,
                          int   s,
#ifdef RT_PRINTF_PRECISION
                          int   precision,
#endif /* RT_PRINTF_PRECISION */
                          int   type)
{
    char c = 0, sign = 0;
#ifdef RT_KPRINTF_USING_LONGLONG
    char tmp[64] = {0};
#else
    char tmp[32] = {0};
#endif /* RT_KPRINTF_USING_LONGLONG */
    int precision_bak = precision;
    const char *digits = (void *)0;
    static const char small_digits[] = "0123456789abcdef";
    static const char large_digits[] = "0123456789ABCDEF";
    int i = 0;
    int size = 0;

    size = s;

    digits = (type & LARGE) ? large_digits : small_digits;
    if (type & LEFT)
    {
        type &= ~ZEROPAD;
    }

    c = (type & ZEROPAD) ? '0' : ' ';

    /* get sign */
    sign = 0;
    if (type & SIGN)
    {
        switch (qualifier)
        {
        case 'h':
            if ((short)num < 0)
            {
                sign = '-';
                num = (unsigned short)-num;
            }
            break;
        case 'L':
        case 'l':
            if ((long)num < 0)
            {
                sign = '-';
                num = (unsigned long)-num;
            }
            break;
        case 0:
        default:
            if ((int)num < 0)
            {
                sign = '-';
                num = (unsigned int)-num;
            }
            break;
        }

        if (sign != '-')
        {
            if (type & PLUS)
            {
                sign = '+';
            }
            else if (type & SPACE)
            {
                sign = ' ';
            }
        }
    }

#ifdef RT_PRINTF_SPECIAL
    if (type & SPECIAL)
    {
        if (base == 2 || base == 16)
        {
            size -= 2;
        }
        else if (base == 8)
        {
            size--;
        }
    }
#endif /* RT_PRINTF_SPECIAL */

    i = 0;
    if (num == 0)
    {
        tmp[i++] = '0';
    }
    else
    {
        while (num != 0)
            tmp[i++] = digits[divide(&num, base)];
    }

#ifdef RT_PRINTF_PRECISION
    if (i > precision)
    {
        precision = i;
    }
    size -= precision;
#else
    size -= i;
#endif /* RT_PRINTF_PRECISION */

    if (!(type & (ZEROPAD | LEFT)))
    {
        if ((sign) && (size > 0))
        {
            size--;
        }

        while (size-- > 0)
        {
            if (buf < end)
            {
                *buf = ' ';
            }

            ++ buf;
        }
    }

    if (sign)
    {
        if (buf < end)
        {
            *buf = sign;
        }
        -- size;
        ++ buf;
    }

#ifdef RT_PRINTF_SPECIAL
    if (type & SPECIAL)
    {
        if (base == 2)
        {
            if (buf < end)
                *buf = '0';
            ++ buf;
            if (buf < end)
                *buf = 'b';
            ++ buf;
        }
        else if (base == 8)
        {
            if (buf < end)
                *buf = '0';
            ++ buf;
        }
        else if (base == 16)
        {
            if (buf < end)
            {
                *buf = '0';
            }

            ++ buf;
            if (buf < end)
            {
                *buf = type & LARGE ? 'X' : 'x';
            }
            ++ buf;
        }
    }
#endif /* RT_PRINTF_SPECIAL */

    /* no align to the left */
    if (!(type & LEFT))
    {
        while (size-- > 0)
        {
            if (buf < end)
            {
                *buf = c;
            }

            ++ buf;
        }
    }

#ifdef RT_PRINTF_PRECISION
    while (i < precision--)
    {
        if (buf < end)
        {
            *buf = '0';
        }

        ++ buf;
    }
#endif /* RT_PRINTF_PRECISION */

    /* put number in the temporary buffer */
    while (i-- > 0 && (precision_bak != 0))
    {
        if (buf < end)
        {
            *buf = tmp[i];
        }

        ++ buf;
    }

    while (size-- > 0)
    {
        if (buf < end)
        {
            *buf = ' ';
        }

        ++ buf;
    }

    return buf;
}

/**
 * @brief  This function will fill a formatted string to buffer.
 *
 * @param  buf is the buffer to save formatted string.
 *
 * @param  size is the size of buffer.
 *
 * @param  fmt is the format parameters.
 *
 * @param  args is a list of variable parameters.
 *
 * @return The number of characters actually written to buffer.
 */
int vsnprintf_s(char *buf, int size, int no_count, const char *fmt, va_list args)
{
#ifdef RT_KPRINTF_USING_LONGLONG
    unsigned long long num = 0;
#else
    unsigned long num = 0;
#endif /* RT_KPRINTF_USING_LONGLONG */
    int i = 0, len = 0;
    char *str = (void *)0, *end = (void *)0, c = 0;
    const char *s = (void *)0;

    unsigned char base = 0;            /* the base of number */
    unsigned char flags = 0;           /* flags to print number */
    unsigned char qualifier = 0;       /* 'h', 'l', or 'L' for integer fields */
    int field_width = 0;     /* width of output field */

#ifdef RT_PRINTF_PRECISION
    int precision = 0;      /* min. # of digits for integers and max for a string */
#endif /* RT_PRINTF_PRECISION */

    str = buf;
    end = buf + size;

    /* Make sure end is always >= buf */
    if (end < buf)
    {
        end  = ((char *) - 1);
        size = end - buf;
    }

    for (; *fmt ; ++fmt)
    {
        if (*fmt != '%')
        {
            if (str < end)
            {
                *str = *fmt;
            }

            ++ str;
            continue;
        }

        /* process flags */
        flags = 0;

        while (1)
        {
            /* skips the first '%' also */
            ++ fmt;
            if (*fmt == '-') flags |= LEFT;
            else if (*fmt == '+') flags |= PLUS;
            else if (*fmt == ' ') flags |= SPACE;
            else if (*fmt == '#') flags |= SPECIAL;
            else if (*fmt == '0') flags |= ZEROPAD;
            else break;
        }

        /* get field width */
        field_width = -1;
        if (_ISDIGIT(*fmt))
        {
            field_width = skip_atoi(&fmt);
        }
        else if (*fmt == '*')
        {
            ++ fmt;
            /* it's the next argument */
            field_width = va_arg(args, int);
            if (field_width < 0)
            {
                field_width = -field_width;
                flags |= LEFT;
            }
        }

#ifdef RT_PRINTF_PRECISION
        /* get the precision */
        precision = -1;
        if (*fmt == '.')
        {
            ++ fmt;
            if (_ISDIGIT(*fmt))
            {
                precision = skip_atoi(&fmt);
            }
            else if (*fmt == '*')
            {
                ++ fmt;
                /* it's the next argument */
                precision = va_arg(args, int);
            }
            if (precision < 0)
            {
                precision = 0;
            }
        }
#endif /* RT_PRINTF_PRECISION */
        /* get the conversion qualifier */
        qualifier = 0;
#ifdef RT_KPRINTF_USING_LONGLONG
        if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L')
#else
        if (*fmt == 'h' || *fmt == 'l')
#endif /* RT_KPRINTF_USING_LONGLONG */
        {
            qualifier = *fmt;
            ++ fmt;
#ifdef RT_KPRINTF_USING_LONGLONG
            if (qualifier == 'l' && *fmt == 'l')
            {
                qualifier = 'L';
                ++ fmt;
            }
#endif /* RT_KPRINTF_USING_LONGLONG */
        }

        /* the default base */
        base = 10;

        switch (*fmt)
        {
        case 'c':
            if (!(flags & LEFT))
            {
                while (--field_width > 0)
                {
                    if (str < end) *str = ' ';
                    ++ str;
                }
            }

            /* get character */
            c = (unsigned char)va_arg(args, int);
            if (str < end)
            {
                *str = c;
            }
            ++ str;

            /* put width */
            while (--field_width > 0)
            {
                if (str < end) *str = ' ';
                ++ str;
            }
            continue;

        case 's':
            s = va_arg(args, char *);
            if (!s)
            {
                s = "(NULL)";
            }

            for (len = 0; (len != field_width) && (s[len] != '\0'); len++);
#ifdef RT_PRINTF_PRECISION
            if (precision > 0 && len > precision)
            {
                len = precision;
            }
#endif /* RT_PRINTF_PRECISION */

            if (!(flags & LEFT))
            {
                while (len < field_width--)
                {
                    if (str < end) *str = ' ';
                    ++ str;
                }
            }

            for (i = 0; i < len; ++i)
            {
                if (str < end) *str = *s;
                ++ str;
                ++ s;
            }

            while (len < field_width--)
            {
                if (str < end) *str = ' ';
                ++ str;
            }
            continue;

        case 'p':
            if (field_width == -1)
            {
                field_width = sizeof(void *) << 1;
#ifdef RT_PRINTF_SPECIAL
                field_width += 2; /* `0x` prefix */
                flags |= SPECIAL;
#endif
                flags |= ZEROPAD;
            }
#ifdef RT_PRINTF_PRECISION
            str = print_number(str, end,
                               (unsigned long)va_arg(args, void *),
                               16, qualifier, field_width, precision, flags);
#else
            str = print_number(str, end,
                               (unsigned long)va_arg(args, void *),
                               16, qualifier, field_width, flags);
#endif
            continue;

        case '%':
            if (str < end)
            {
                *str = '%';
            }
            ++ str;
            continue;

        /* integer number formats - set up the flags and "break" */
        case 'b':
            base = 2;
            break;
        case 'o':
            base = 8;
            break;

        case 'X':
            flags |= LARGE;
        case 'x':
            base = 16;
            break;

        case 'd':
        case 'i':
            flags |= SIGN;
        case 'u':
            break;

        default:
            if (str < end)
            {
                *str = '%';
            }
            ++ str;

            if (*fmt)
            {
                if (str < end)
                {
                    *str = *fmt;
                }
                ++ str;
            }
            else
            {
                -- fmt;
            }
            continue;
        }

#ifdef RT_KPRINTF_USING_LONGLONG
        if (qualifier == 'L')
        {
            num = va_arg(args, unsigned long long);
        }
        else if (qualifier == 'l')
#else
        if (qualifier == 'l')
#endif /* RT_KPRINTF_USING_LONGLONG */
        {
            num = va_arg(args, unsigned long);
        }
        else if (qualifier == 'h')
        {
            num = (unsigned short)va_arg(args, int);
            if (flags & SIGN)
            {
                num = (short)num;
            }
        }
        else
        {
            num = (unsigned int)va_arg(args, unsigned long);
        }
#ifdef RT_PRINTF_PRECISION
        str = print_number(str, end, num, base, qualifier, field_width, precision, flags);
#else
        str = print_number(str, end, num, base, qualifier, field_width, flags);
#endif
    }

    if (size > 0)
    {
        if (str < end)
        {
            *str = '\0';
        }
        else
        {
            end[-1] = '\0';
        }
    }

    /* the trailing null byte doesn't count towards the total
    * ++str;
    */
    return str - buf;
}

1dcc3ed5b6624afd9f174944c9faa633.png

6.调用 PRT_Printf 函数 

main.c 修改为调用 PRT_Printf 函数输出信息。

#include "prt_typedef.h"

extern U32 PRT_Printf(const char *format, ...);
extern void PRT_UartInit(void);

S32 main(void)
{
    PRT_UartInit();

    PRT_Printf("Test PRT_Printf int format %d \n\n", 10);
}

main函数直接全部修改为上述代码。中间PRT_UartInit()和PRT_Printf("Test PRT_Printf int format %d \n\n", 10)之间留空隙是为了下一步输出字符

7.将新增文件纳入构建系统 

修改 src/bsp/CMakeLists.txt 文件加入新增文件 print.c 和 vsnprintf_s.c

set(SRCS start.S prt_reset_vector.S print.c vsnprintf_s.c)
add_library(bsp OBJECT ${SRCS})  # OBJECT类型只编译生成.o目标文件,但不实际链接成库

和刚刚一样。这里也是把CMakeLists.txt全部修改为上述代码

8.启用 FPU 

构建项目并执行发现程序没有任何输出。 需启用 FPU (src/bsp/start.S)。

Start:
    LDR    x1, =__os_sys_sp_end // ld文件中定义,堆栈设置
    BIC    sp, x1, #0xf

//参考: https://developer.arm.com/documentation/den0024/a/Memory-Ordering/Barriers/ISB-in-more-detail
Enable_FPU:
    MRS X1, CPACR_EL1
    ORR X1, X1, #(0x3 << 20)
    MSR CPACR_EL1, X1
    ISB

    B      OsEnterMain

注意:这里不是把start.s文件内内容全部替换,而是把上述代码插入到OsEl2Entry和OsEnterReset之间,保存。如下图所示:

6655bde9b27b475f9f68658c6a98f7c8.png

9.Hello, miniEuler 

再次构建项目并执行,发现已可正常输出。至此,我们获得了一个基本的输出和调试手段,如我们可以在系统崩溃时调用 PRT_Printf 函数进行输出。

我们可以利用 PRT_Printf 函数来打印一个文本 banner 让我们写的 OS 显得专业一点😁。 manytools.org 可以创建 ascii banner,选择你喜欢的样式和文字(下面选择的是 Standard 样式),然后在 main.c 的 main 函数中调用 PRT_Printf 输出。

S32 main(void)
{
    PRT_UartInit();

    PRT_Printf("            _       _ _____      _             _             _   _ _   _ _   _           \n");
    PRT_Printf("  _ __ ___ (_)_ __ (_) ____|   _| | ___ _ __  | |__  _   _  | | | | \\ | | | | | ___ _ __ \n");
    PRT_Printf(" | '_ ` _ \\| | '_ \\| |  _|| | | | |/ _ \\ '__| | '_ \\| | | | | |_| |  \\| | | | |/ _ \\ '__|\n");
    PRT_Printf(" | | | | | | | | | | | |__| |_| | |  __/ |    | |_) | |_| | |  _  | |\\  | |_| |  __/ |   \n");
    PRT_Printf(" |_| |_| |_|_|_| |_|_|_____\\__,_|_|\\___|_|    |_.__/ \\__, | |_| |_|_| \\_|\\___/ \\___|_|   \n");
    PRT_Printf("                                                     |___/                               \n");


    PRT_Printf("Test PRT_Printf int format %d \n\n", 10);
}

这里需要把PRT_Printf那7行输出插入到PRT_UartInit()和PRT_Printf("Test PRT_Printf int format %d \n\n", 10)之间,当然内容也可以根据自己爱好灵活修改

10.构建项目并执行

dc187383cc33492d94f2d691f5da889e.png

这一步的话,如果直接输入 sh runMiniEuler.sh 的话,会出现以下错误(其实也不算报错):b61dd545f2494b6aa9487a049f8ce46e.png

很巧合的是,它的输出和实验一的一样。原因就是,我们没有进行重新编译,运行的还是旧文件,这时候需要清空build文件夹,然后进入终端,在build目录下重新输入:

cmake ../src
cmake --build .

进行编译 ,

1b730cdaac9b462db4c266928d8434f4.png

编译完成后,重新输入

sh runMiniEuler.sh

运行新的程序,结果如下图所示: 

f3aa2332deee41898e0ab8b6e6bf2df8.png

到这一步就结束啦 ʕ•̼͛͡•ʕ-̺͛͡•ʔ•̮͛͡•ʔ ʕ•̫͡•ʕ*̫͡*ʕ•͓͡•ʔ-̫͡-ʕ•̫͡•ʔ*̫͡*ʔ-̫͡-ʔ

三、个性化输出 

上文提到,manytools.org可以帮助我们生成其他字符串图案,下面给大家演示一下

进入manytools.org,网址Create ASCII text banners online (manytools.org),在Banner text中输入想要个性化的单词(不支持中文),然后Font中选择字体样式,这里我选择的是Standard,自动生成后,点击Copy banner复制到剪切板

a26a4279d4244836bfab24d6b3c15f80.png

 填入main.c文件中

b4f042ed9939424dafd24327ade9d019.png

这里注意:如果粘贴后不做任何修改的话,得到的个性化文字将会是不正常的状态,类似于乱码的图案。这里需要将字符串中的所有‘\’换成‘\\’ ,避免反斜杠被解释为一个转义序列符

7a51b02136554dc4a7aa7b92208fe511.png

ca2cfd7dde464ffa9526e7bac3a522b6.png

替换后重新编译运行,就可以得到正确的结果啦

072699b97bb64f09ba2826a91ba3af05.png

  • 36
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一二爱上蜜桃猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值