以下步骤均在ubuntu20.04下实现
如果要研究一个系统,首先要先让这个系统跑起来。所以我们的第一步是将这个系统移植到适当的平台上,大家也可以自行移植到自己手头上的开发板。
由于本人懒得弄开发板bootloader,加上调试也不太方便,就先在qemu上做实验。
先安装交叉编译链:
sudo apt-get install gcc-arm-linux-gnueabi
再安装qemu、gdb-multiarch:
sudo apt install qemu-system-arm
sudo apt install gdb-multiarch
基本上环境就搭建好了。现在下载μCOS-II源码。我的源码是直接在光盘拷贝出来的古董源码,2.52版本。
今天的任务就是先把和平台无关的代码先编译出来。明天再把需要的代码补充完整。
在上图我们可以看到,只有3个文件的代码是和CPU相关的,别的都不相关。那我们今天就先把不相关的代码编译了
先新建三个文件夹,然后把文件薅进去:
.
├── arm9
│ ├── os_cpu_a.s
│ └── os_cpu_c.c
├── include
│ ├── includes.h
│ ├── os_cfg.h
│ ├── os_cpu.h
│ └── ucos_ii.h
└── ucosii
├── os_core.c
├── os_flag.c
├── os_mbox.c
├── os_mem.c
├── os_mutex.c
├── os_q.c
├── os_sem.c
├── os_task.c
├── os_time.c
└── ucos_ii.c
这是我的布局,不一定要按照这样布局。我们可以看到,上图所有的文件都扔进去了。接下来是删代码。主要删的是 os_cpu_a.s、os_cpu_c.c、os_cpu.h的代码。为什么?因为他们和平台相关,一般文档会告诉你的。如果一个系统不告诉你这些,那他就不是一个拿来给你移植的系统。
接下来把这样
删成这样:
因为那些是内联汇编代码。既然是汇编,那就是肯定和平台相关。文件里的这些数据类型也可以改了,改成你所对应的平台的。
os_cpu_c.c这个文件就简单了,里面的所有的函数都像这样挖空就好:
接下来就是 os_cpu_a.s。把;全部改成#,因为两个汇编器的注释的符号不一样。as的注释符号是#。接下来把
改成这样
具体的.global和.extern什么意思可以直接百度,修改这里主要也是因为汇编器识别的符号不同。
接下来就是删汇编代码:
一共四个函数,每一个都删成这样就OK
新的到时候再填充
删代码已经OK了,接下来是改一下includes.h,然后写一下makefile。如果你是用ide的话就不用写makefile了。makefile是指定这些文件怎么编译的,用ide的话全部把文件加进去就OK了。includes.h主要是把那些和库相关的都删了
我是把这样
改成这样
因为那个pc.h也只是在x86平台上用的函数,有点像stm32的hal库,所以这里就用不上了。
接下来编译的时候就会发现有不少错误,因为没有那些头文件。但是这个系统用的头文件里面的函数的具体实现和平台无关,我们可以自己写,主要是memcpy和memset这两个函数。
接下来就是新建一个文件,写makfile。这个直接复制粘贴就好。makefile一般是拿一些通用模板来改的,不懂语法的就查一下就行,一般来说不用专门地学,像是脚本一样。还有链接脚本。我们暂时还不管内存布局,所以也是复制粘贴就好
CC = arm-linux-gnueabi-gcc
LD = arm-linux-gnueabi-ld
AR = arm-linux-gnueabi-ar
OBJCOPY = arm-linux-gnueabi-objcopy
OBJDUMP = arm-linux-gnueabi-objdump
DIRS = arm9
objs := $(foreach dir,$(DIRS),$(patsubst %.c,%.o,$(wildcard $(dir)/*.c)))
objs += ucosii/ucos_ii.o
objs += $(foreach dir,$(DIRS),$(patsubst %.s,%.o,$(wildcard $(dir)/*.s)))
INCLUDEDIR := $(shell pwd)/include
CFLAGS := -Wall -O2 -o
CPPFLAGS := -I$(INCLUDEDIR) -g -c -fno-stack-protector
#-nostdinc
ucos.bin : $(objs)
@echo $(objs)
$(LD) -g -Tucos.lds -o ucos.elf $^
$(OBJCOPY) -O binary -S ucos.elf $@
$(OBJDUMP) -D -m arm ucos.elf > ucos.dis
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) $@ $<
%.o:%.s
${CC} $(CPPFLAGS) $(CFLAGS) $@ $<
clean:
rm -f ucos.bin ucos.elf ucos.dis $(objs)
make一下,接下来就是改错:
错误:
把ucos_ii.c全部改成
接下来就是:
我们其实可以看到,都编译完汇编了,就差链接了。说实在的今天的任务也算完成了。
但是我们可以进一步把elf和二进制文件搞出来。
elf是带了程序信息的,比如说加载位置、入口这些。bin文件需要直接烧录进内存,还要在内存布好局。elf布局就自由一点,但是要bootloader把他的布局搞好,因为他只是有信息,不代表他天生就OK,而且因为他多了信息,还不能直接烧录进去内存,不然CPU会以为多出来的信息也是指令。但是用QEMU的话,我们就不用bootloader,QEMU直接帮我们弄好,就像是天生就好了一样。
直接复制粘贴链接脚本:
命名为ucos.lds,想改名记得同时改makefile就行
SECTIONS {
. = 0x00;
.text : { *(.text) }
.rodata ALIGN(4) : {*(.rodata)}
.data ALIGN(4) : { *(.data) }
.bss ALIGN(4) : { *(.bss) *(COMMON) }
}
make。接下来果然不出所料:
没有memset、memcpy,还有意料之外的__aeabi_uidiv。
工程目录下新建一个lib文件夹,我们自己给补上。
先解决__aeabi_uidiv,这个错误主要是arm芯片没有实现除法的硬件结构,因此需要采用软件去实现除法。我们就要先找到他的库文件。
去/usr搜索libgcc.a就行。一般库文件都在/usr下,.a文件则是和.o文件一样的东西。实现不重要,能拿来链接就行。一定要找对应的编译链的,别链接到别的CPU的代码上了。
复制放到lib下。然后新建一个文件:lib.c,现在布局如下:
.
├── arm9
│ ├── os_cpu_a.s
│ └── os_cpu_c.c
├── include
│ ├── includes.h
│ ├── os_cfg.h
│ ├── os_cpu.h
│ └── ucos_ii.h
├── lib
│ ├── lib.c
│ └── libgcc.a
├── makefile
├── ucosii
│ ├── os_core.c
│ ├── os_flag.c
│ ├── os_mbox.c
│ ├── os_mem.c
│ ├── os_mutex.c
│ ├── os_q.c
│ ├── os_sem.c
│ ├── os_task.c
│ ├── os_time.c
│ └── ucos_ii.c
└── ucos.lds
写代码
lib.c
#define NULL ((void*)0)
void * memset(void *dest, int set, unsigned int len)
{
if (dest == NULL || len < 0)
{
return NULL;
}
char *pdest = (char *)dest;
while (len-->0)
{
*pdest++ = set;
}
return dest;
}
void* memcpy(void* dest,const void* src, unsigned int num)
{
void* ret = dest;
if (dest == NULL)
{
return NULL;
}
if (src == NULL)
{
return NULL;
}
//有多少个字节执行多少次
char* dest1,*src1;
while (num--)
{
//一个字节一个字节进行赋值
*dest1 = *src1;
++dest1;
++src1;
}
return ret;
}
makefile 文件里的DIRS = arm9后面加一个lib:DIRS = arm9 lib
$(LD) -g -Tucos.lds -o ucos.elf $^ 也要把lib/libgcc.a加上:
$(LD) -g -Tucos.lds -o ucos.elf $^ lib/libgcc.a
体会到没,makefile是管理文件怎么编译的。每次加点东西都要改一下它,因为编译的方法不同了。
又有一个错误,管上:
lib.c末尾新起一行加上:
unsigned int raise (unsigned int signum)
{
return 0;
}
再编译:
成功了!
目录下打开终端,运行
qemu-system-arm -M vexpress-a9 -nographic -kernel ucos.elf -S -s
再打开一个终端运行:
gdb-multiarch --tui ucos.elf -ex 'target remote localhost:1234'
回车,我们就可以看到进入了一个函数OSInitHookBegin (void)运行
由于我们还没设置入口,也没写移植的代码,所以暂时不能继续往下调试。
但是我们可以看到的是,调试平台已经搭建完了,我们是能够继续调试的。
明天再进行下一步了。