以time/gettimeofday系统调用为例分析ARM64 Linux 5.4.34

 一、触发 time/gettimeofday 系统调用

首先编写一个test.c程序,该程序会通过gettimeofday库函数来触发系统调用。

gettimeofday 是 C 库提供的函数,它封装了内核里的sys_gettimeofday 系统调用。

在 ARM64 架构下 Linux 系统调用由同步异常 svc 指令触发。当用户态(EL0级)程序调用库函数从而触发系统调用的时候,先把系统调用的参数依次放入X0-X5 这 6 个寄存器( Linux 系统调用最多有 6 个参数,ARM64 函数调用参数可以使用 X0-X7 这 8 个寄存器),然后把系统调用号放在 X8 寄存器 里,最后执行 svc 指令,CPU 即进入内核态(EL1级)。svc 指令一般会带一个立即数参数,一般是 0x0,但并没有被 Linux 内核使用,而是把系统调用号放到了 X8 寄存器 里。

#include <stdio.h>
#include <time.h>
#include <sys/time.h>
 
int main()
{
      time_t tt;
      struct timeval tv;
      struct tm *t;
#if 0
      gettimeofday(&tv,NULL); // 使用库函数的方式触发系统调用
#else
      asm volatile( // 使用内嵌汇编的方式触发系统调用
          "add   x0, x29, 16\n\t"  //X0寄存器用于传递参数&tv
          "mov   x1, #0x0\n\t"     //X1寄存器用于传递参数NULL
          "mov   x8, #0xa9\n\t"   //使用X8传递系统调用号169
          "svc   #0x0\n\t"            //触发系统调用
      );
#endif
      tt = tv.tv_sec;                    //tv是保存获取时间结果的结构体
      t = localtime(&tt);                //将世纪秒转换成对应的年月日时分秒
      printf("time: %d/%d/%d %d:%d:%d\n",
             t->tm_year + 1900,
             t->tm_mon,
             t->tm_mday,
             t->tm_hour,
             t->tm_min,
             t->tm_sec);
      return 0;
}

 交叉编译test.c,这里要使用静态编译,因为默认的动态链接编译产生的二进制文件并不会有 gettimeofday 系统调用的入口,只有相应的库函数。

aarch64-linux-gnu-gcc -o test test.c -static

 然后把test复制到根文件系统中,用 ARM 环境下编译的 busybox 重新制作一个根文件系统,test 可执行文件就在虚拟机的根目录下了。然后重新编译下:

make ARCH=arm64 Image -j8  CROSS_COMPILE=aarch64-linux-gnu-

 二、分析

启动qemu:

 qemu-system-aarch64 -m 512M -smp 4 -cpu cortex-a57 -machine virt -kernel arch/arm64/boot/Image -append "rdinit=/linuxrc nokaslr console=ttyAMA0 loglevel=8" -nographic -s 

在 VSCode 中启动调试。这里触发的系统调用对应的内核函数是__arm64_sys_gettimeofday。

ARM64 架构的 CPU 中的 系统调用和其他异常的处理过程大致相同。异常发生时,CPU 首先把异常的原因,这里是比如执行 svc 指令触发系统调用放在 ESR_EL1 寄存器里,然后把当前的处理器状态PSTATE放入 SPSR_EL1 寄存器里,把当前程序指针寄存器 PC 的值存入 ELR_EL1 寄存器里来保存断点,然后 CPU 通过异常向量表基地址和异常的类型计算出异常处理程序的入口地址,即 VBAR_EL1 寄存器加上偏移量取得异常处理的入口地址,接着开始执行异常处理入口的第一行代码。

以 svc 指令对应的 el0_sync 为例。

el0_sync主要分为两部分:

  • 第一部分实现从用户空间到内核空间的上下文切换: kernel_entry 0;
  • 第二部是根据异常症状寄存器esr_el1判断异常原因,然后再进入具体处理函数。

el0_sync首先执行kernel_entry 0,将通用寄存器x0~x29保存到当前进程的内核栈,然后是从SP_EL0、SPSR_EL1、ELR_EL1寄存器中读取用户栈栈顶地址、发生异常时的处理器状态和返回地址,将这三个值以及发生异常时的LR寄存器中的值都保存到当前进程的内核栈中,以struct pt_regs结构体的格式保存在当前进程内核栈的栈底,完成保存现场过程。

 系统调用是用户态执行SVC指令导致的,因此要进入el0_svc处理函数,根据ESR_EL1 寄存器中的内容跳转到 el0_svc,el0_svc 会调用 el0_svc_handler、el0_svc_common 函数,将 X8 寄存器(regs->regs[8])中存放的系统调用号传递给 invoke_syscall 函数。

 接着执行 invoke_syscall 函数,将通用寄存器中的内容传入 syscall_fn(),引出系统调用内核处理函数 __arm64_sys_gettimeofday,然后等系统调用内核处理函数执行完成,会将系统调用的返回值存放在 X0 寄存器中。

从系统调用返回前会处理一些工作(work_pending),比如处理信号、判断是否需要进程调度等,ret_to_user的最后是kernel_exit 0负责恢复现场,与保存现场kernel_entry 0相对应,kernel_exit 0的最后会执行eret指令系统调用返回。eret指令所做的工作与svc指令相对应,eret指令会将ELR_EL1寄存器里值恢复到程序指针寄存器PC中,把SPSR_EL1寄存器里的值恢复到PSTATE处理器状态中,同时会从内核态转换到用户态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在FreeRTOS系统中,由于FreeRTOS是一个嵌入式实时操作系统,它并不直接支持Linux的gettimeofday函数。但是,你可以通过以下步骤在FreeRTOS中使用类似的功能: 1. 首先,在FreeRTOS中创建一个新的任务或者添加到现有任务中。该任务将负责获取时间信息。 2. 在任务中包含头文件 `<sys/time.h>`,这样可以访问到gettimeofday函数的声明。 3. 在任务中调用gettimeofday函数来获取时间信息。 下面是一个示例代码来演示如何在FreeRTOS中使用类似于gettimeofday函数的功能: ```c #include <sys/time.h> void getTimeOfDayTask(void *pvParameters) { struct timeval tv; while (1) { // 获取时间信息 gettimeofday(&tv, NULL); // 在这里处理时间信息,例如输出到串口或者执行其他操作 // ... // 休眠一段时间,例如100ms vTaskDelay(pdMS_TO_TICKS(100)); } } int main() { // 创建一个任务来获取时间信息 xTaskCreate(getTimeOfDayTask, "getTimeOfDayTask", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); // 启动FreeRTOS调度器 vTaskStartScheduler(); // 如果一切正常,永远不会执行到这里 return 0; } ``` 在这个示例中,我们创建了一个名为`getTimeOfDayTask`的任务来获取时间信息。在任务中,我们使用`gettimeofday`函数来获取时间信息,并在任务中处理这些信息。任务使用`vTaskDelay`函数来休眠一段时间,以模拟定期获取时间信息的功能。 请注意,由于FreeRTOS是一个实时操作系统,任务的优先级和调度可能会影响时间信息的获取精度。在实际应用中,请根据需要进行适当的配置和调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值