ARMV8上实现一个OS ---(3)启动第一个Task

        我们都知道操作系统的基本功能就是对进程,任务进行管理,使用户在无感的情况下进程任务切换,本章节我们将在之前hello world程序基础上为我们的OS添加一个任务。

任务

      任务是操作系统的基本管理单元,所有的进程切换,调度等都是以任务为单位的。任务需要管理的内容非常的多。包括栈、调度策略、VMA、任务运行时间等等。本次我们只添加基础的任务需要的元素,后续随着功能的添加我们将在其中添加更多的元素。

下边是task的数据结构和结构函数:

rdy_task_list用于管理已经就绪的任务,本次实验只有一个。

hf_task_create用于创建一个任务。

//任务的运行在状态,暂时未用到
#define HF_TASK_READY           0x1UL
#define HF_TASK_SUSPEND         0x2UL
#define HF_TASK_SLEEP           0x3UL
#define HF_TASK_DELETED         0x4UL

//任务的执行函数
typedef void *(*task_func)(void *arg);
//全局的任务列表      
extern hf_list_t *rdy_task_list;

typedef struct hf_task {
    void *task_stack;           //栈地址,放在第一个位置上,方便汇编代码获取。
                                //这是个动态的指针会根据程序运行动态变化
    void *stack_start;          //记录栈的起始位置,不会变(栈向下增长,因此是个地址)
    size_t stack_size;          //栈的大小
    char task_name[64];         //任务名
    void *arg;                  //任务运行的函数参数
    task_func func;             //运行函数
    unsigned long task_state;   //当前任务的状态
    hf_list_t task_list;        //挂载的链表
} hf_task_t;

int hf_task_create(hf_task_t *task, const char *name, void *task_stak,
    size_t stack_size, task_func func, void *arg);
int hf_task_delete(hf_task_t *task);

 实现一个printk

        为了方便内核中打印,我们增加了hf_printk.函数。类似于C库的printf函数。

        在这个函数中定义了一个printk_output的函数指针,用于进行输出函数的保存。当系统初始化后就可以调用注册函数将uart的发送函数注册为输出函数,这样打印的信息就能通过串口输出了。

static printk_output_t printk_output = NULL;

/* 设置prink输出的函数 */
int hf_set_printk_output(void *output)
{
	printk_output = output;
	return 0;
}

/* 打印信息到出口 */
int hf_printk(const char *fmt, ...)
{
	char buf[512] = {0};
	va_list ap;
	int ret = 0;

    va_start(ap, fmt);
	vsnprintf(buf, 512, fmt, ap);
	va_end(ap);

	if (printk_output) {
		ret = printk_output(buf);
	}
	return ret;
}

 添加常用的组件

        双向链表:

        双向链表在内核中是最常用的数据管理结构。通常用来将task等对象关联起来,方便查找。这里提供基础的插入、添加、删除等功能。

        C库函数:

        C库中有些比较常用的函数在内核中也用得到,因此我们会将部分函数移植到内核中来(目前自己写的大部分以功能为主,并没有开率效率的问题,后续有时间再做效率优化)。

ARMV8的异常处理:

       接下来这一小节是ARMV8体系结构的内容。

       这里以EL0为例,当异常发生的时候CPU会做如下的事情(自动执行,用户无感,此处仅描述寄存器相关的操作,更具体的查看手册):

1)把PSTATE寄存的值存储到SPSR_EL1中。

2)把返回地址(当前的PC)存储到ELR_EL1中。

当调用eret时,可以从异常处理中返回:

1)从ELR_EL1中恢复PC指针

2)从SPSR_EL1中恢复PSTATE的寄存器状态

任务加载

       利用ARMV8的异常处理流程,我们可以进行任务的加载。模拟一次异常,将SPSR的值和ELR的值分别更填写上异常值和task->func,再将task->task_stack赋值给SP。最后调用eret指令,CPU就会将SPSR和ELR的值自动加载到PSTATE和PC指针中去。这样就实现了任务的加载。


.global arch_run_task
.type arch_run_task, function
.align 8
.text
arch_run_task:
	msr elr_el1, x1			//task->func
	mov x9, 0x05
	msr spsr_el1, x9		//spsr = 0x5 = el1h
	ldr x9, [x0]			
	mov sp, x9				//栈地址设置为task->task_stack
	mov x0, x2				//task->arg放到X0里

	eret
/*
	//也可以实现跳转
	ldr x9, [x0]
	mov sp, x9
	mov x0, x2
	blr x1
*/
.end

问题总结

1)代码编译链接问题

  编译的过程遇到了一个链接的问题,这里会提示找不到汇编的这个函数。

使用objdump查看是能够发现该函数的。

修改cmake的链接顺序后解决该问题。(需要查下camke的手册找下原因)

//原来的链接顺序
target_link_libraries(hfOS_${BOARD}.elf
    PRIVATE -nostdlib
    k_arch
    k_lib
    k_core
)

//修改后的链接顺序
target_link_libraries(hfOS_${BOARD}.elf
    PRIVATE -nostdlib
    k_lib
    k_core
    k_arch
)

2)arch_run_task汇编无法正常运行问题

调用eret后SP和PC的值都是0,无法正常跳转。使用GDB进行调试,观察调用eret之后的数值,发现SPSR_EL1和ELR_EL1数值都正常。但就是没有正常跳转。

 问题分析思路:

       既然没有跳转,那么需要分析跳转前后的系统状态,因此分别dump出来跳转前后的寄存器值进行对比,对比后发现eret之后SP_EL2的数值居然发生了变化,那是不是意味着我们当前运行在EL2呢?

        有了EL2的怀疑之后在boot代码中添加了汇编代码去确认,最终确定确实运行在EL2。

解决办法:

        既然是运行在EL2,需要我们将异常等级转换到EL1。参照异常的处理机制,同样使用eret实现异常等级的切换。在boot.s中添加汇编代码,最终解决该问题。

_el_process:
    mrs  x0, CurrentEL
    ubfx x0, x0, 2, 2
    cmp x0, 0x2
    b.eq  _el2_process

    ret
_el2_process:

//  (SPRS_DEBUG_MASK | SPRS_SERR_MASK | SPRS_IRQ_MASK | SPRS_FIQ_MASK | SPRS_M_AARCH64 | SPRS_M_EL1H)
    mov x2, #0x3c5
    msr spsr_el2, x2    //设置spsr_el2, eret返回后使用sp_el1

    mov x0, #(1 << 31)
    msr hcr_el2, x0     //设置EL1的运行状态为AArch64
    isb

    msr elr_el2, lr     //设置跳转到EL1后的PC
    eret

运行效果: 

git clone https://gitee.com/genglufei/hfos.git
cd hfos/day2_task/hfOS/vendor
./build_hfos.sh qemu_a57
./run_hfos.sh

下一节,我们将尝试进行任务切换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值