Linux内核机制总结中断异常和系统调用之系统调用(三十三)

2023-05-16

文章目录

    • 1 系统调用
      • 1.1 定义系统调用
      • 1.2 执行系统调用

  • 重要:本系列文章内容摘自<Linux内核深度解析>基于ARM64架构的Linux4.x内核一书,作者余华兵。系列文章主要用于记录Linux内核的大部分机制及参数的总结说明

1 系统调用

系统调用是内核给用户程序提供的编程接口。用户程序调用系统调用,通常使用glibc库针对单个系统调用封装的函数。如果glibc库没有针对某个系统调用封装函数,用户程序可以使用通用的封装函数syscall():

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>   /* 定义 SYS_xxx */

long syscall(long number, ...);

参数number是系统调用号,后面是传递给系统调用的参数。

返回值0表示成功,返回值−1表示错误,错误号存储在变量errno中。

例如,应用程序使用系统调用fork()创建子进程,有两种调用方法:
(1)ret = fork();
(2)ret = syscall(SYS_fork);

ARM64处理器提供的系统调用指令是svc,调用约定如下:
(1)64位应用程序使用寄存器x8传递系统调用号,32位应用程序使用寄存器x7传递系统调用号。
(2)使用寄存器x0~x6最多可以传递7个参数。
(3)当系统调用执行完的时候,使用寄存器x0存放返回值。

1.1 定义系统调用

Linux内核使用宏SYSCALL_DEFINE定义系统调用,以创建子进程的系统调用fork为例:

kernel/fork.c
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
     return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
     /* 如果处理器没有内存管理单元,那么不支持 */
     return -EINVAL;
#endif
}

把宏“SYSCALL_DEFINE0(fork)”展开以后是:

asmlinkage long sys_fork(void)

“SYSCALL_DEFINE”后面的数字表示系统调用的参数个数,“SYSCALL_DEFINE0”表示系统调用没有参数,“SYSCALL_DEFINE6”表示系统调用有6个参数,如果参数超过6个,使用宏“SYSCALL_DEFINEx”。头文件“include/linux/syscalls.h”定义了这些宏。

“asmlinkage”表示这个C语言函数可以被汇编代码调用。如果使用C++编译器,“asmlinkage”被定义为extern “C”;如果使用C编译器,“asmlinkage”是空的宏。

系统调用的函数名称以“sys_”开头。

需要在系统调用表中保存系统调用号和处理函数的映射关系,ARM64架构定义的系统调用表sys_call_table如下:

arch/arm64/kernel/sys.c
#undef __SYSCALL
#define __SYSCALL(nr, sym)     [nr] = sym,

void * const sys_call_table[__NR_syscalls] __aligned(4096) = {
     [0 ... __NR_syscalls - 1] = sys_ni_syscall,
#include <asm/unistd.h>
};

对于ARM64架构,头文件“asm/unistd.h”是“arch/arm64/include/asm/unistd.h”:

arch/arm64/include/asm/unistd.h
#include <uapi/asm/unistd.h>arch/arm64/include/uapi/asm/unistd.h
#include <asm-generic/unistd.h>include/asm-generic/unistd.h
#include <uapi/asm-generic/unistd.h>include/uapi/asm-generic/unistd.h
#define __NR_io_setup 0                                    /* 系统调用号0 */
__SC_COMP(__NR_io_setup, sys_io_setup, compat_sys_io_setup) /* [0] = sys_io_setup, */#define __NR_fork 1079                /* 系统调用号1079 */
#ifdef CONFIG_MMU
__SYSCALL(__NR_fork, sys_fork)        /* [1079] = sys_fork, */
#else
__SYSCALL(__NR_fork, sys_ni_syscall)
#endif /* CONFIG_MMU */

#undef __NR_syscalls
#define __NR_syscalls (__NR_fork+1)

1.2 执行系统调用

ARM64处理器把系统调用划分到同步异常,在异常级别1的异常向量表中,系统调用的入口有两个:
(1)如果64位应用程序执行系统调用指令svc,系统调用的入口是el0_sync。
(2)如果32位应用程序执行系统调用指令svc,系统调用的入口是el0_sync_compat。

el0_sync的代码如下:

arch/arm64/kernel.c
    .align   6
   el0_sync:
    kernel_entry 0
    mrs   x25, esr_el1                 // 读异常症状寄存器
    lsr   x24, x25, #ESR_ELx_EC_SHIFT  // 异常类别
    cmp   x24, #ESR_ELx_EC_SVC64       // 64位系统调用
    b.eq el0_svc
    …

1)把当前进程的寄存器值保存在内核栈中。
2)读取异常症状寄存器esr_el1。
3)解析出异常症状寄存器的异常类别字段。
4)如果异常类别是系统调用,跳转到el0_svc。

el0_svc负责执行系统调用,其代码如下:

arch/arm64/kernel.c
   /*
    * 这些是系统调用处理程序使用的寄存器,
    * 允许我们理论上最多传递7个参数给一个函数 – x0~x6
    *
    * x7保留,用于32位模式的系统调用号
    */
   sc_nr .req x25           // 系统调用的数量
   scno      .req  x26      // 系统调用号
   stbl      .req  x27      // 系统调用表的地址
  tsk       .req  x28      // 当前进程的thread_info结构体的地址

  .align   6
  el0_svc:
   adrp    stbl, sys_call_table      // 加载系统调用表的地址
   uxtw    scno, w8         // 寄存器w8里面的系统调用号
   mov     sc_nr, #__NR_syscalls
  el0_svc_naked:               // 32位系统调用的入口
   stp   x0, scno, [sp, #S_ORIG_X0]   // 保存原来的x0和系统调用号
   enable_dbg_and_irq
   ct_user_exit 1

   ldr   x16, [tsk, #TSK_TI_FLAGS]   // 检查系统调用钩子
   tst   x16, #_TIF_SYSCALL_WORK
   b.ne      __sys_trace
   cmp     scno, sc_nr         // 检查系统调用号是否超过上限
   b.hs      ni_sys
   ldr   x16, [stbl, scno, lsl #3]   // 系统调用表表项的地址
   blr   x16                         // 调用sys_*函数
   b   ret_fast_syscall
  ni_sys:
   mov   x0, sp
   bl   do_ni_syscall
   b   ret_fast_syscall
  ENDPROC(el0_svc) 

1)把寄存器x27设置为系统调用表sys_call_table的起始地址。
2)把寄存器x26设置为系统调用号。64位进程使用寄存器x8传递系统调用号,w8是寄存器x8的32位形式。
3)把寄存器x25设置为系统调用的数量,也就是(最大的系统调用号+1)。
4)把寄存器x0和x8的值保存到内核栈中,x0存放系统调用的第一个参数,x8存放系统调用号。
5)开启调试异常和中断。
6)如果使用ptrace跟踪系统调用,跳转到__sys_trace处理。
7)如果进程传递的系统调用号等于或大于系统调用的数量,即大于最大的系统调用号,那么是非法值,跳转到ni_sys处理错误。
8)计算出系统调用号对应的表项地址(sys_call_table + 系统调用号 * 8),然后取出处理函数的地址。
9)调用系统调用号对应的处理函数。
10)从系统调用返回用户空间。

ret_fast_syscall从系统调用返回用户空间,其代码如下:

arch/arm64/kernel.c
    ret_fast_syscall:
    disable_irq
    str   x0, [sp, #S_X0]   /* DEFINE(S_X0, offsetof(struct pt_regs, regs[0])); */
    ldr   x1, [tsk, #TSK_TI_FLAGS]
    and   x2, x1, #_TIF_SYSCALL_WORK
    cbnz    x2, ret_fast_syscall_trace
    and   x2, x1, #_TIF_WORK_MASK
    cbnz    x2, work_pending
    enable_step_tsk x1, x2
   kernel_exit 0
  ret_fast_syscall_trace:
   enable_irq                       // 开启中断
   b   __sys_trace_return_skipped    // 我们已经保存了x0

  work_pending:
   mov   x0, sp                     // 'regs'
   bl   do_notify_resume
  #ifdef CONFIG_TRACE_IRQFLAGS
   bl   trace_hardirqs_on           // 在用户空间执行时开启中断
  #endif
   ldr   x1, [tsk, #TSK_TI_FLAGS]   // 重新检查单步执行
   b   finish_ret_to_user

  ret_to_user:
   …
  finish_ret_to_user:
   enable_step_tsk x1, x2
   kernel_exit 0
  ENDPROC(ret_to_user)

1)禁止中断。
2)寄存器x0已经存放了处理函数的返回值,把保存在内核栈中的寄存器x0的值更新为返回值。
3)如果使用ptrace跟踪系统调用,跳转到ret_fast_syscall_trace处理。
4)如果进程的thread_info.flags设置了需要重新调度(_TIF_NEED_RESCHED)或者有信号需要处理(_TIF_SIGPENDING)等标志位,跳转到work_pending处理。
5)如果使用系统调用ptrace设置了软件单步执行,那么开启单步执行。
6)使用保存在内核栈中的寄存器值恢复寄存器,从内核模式返回用户模式。

work_pending调用函数do_notify_resume,函数do_notify_resume的代码如下:

arch/arm64/kernel/signal.c
   asmlinkage void do_notify_resume(struct pt_regs *regs,
                    unsigned int thread_flags)
   {do {
         if (thread_flags & _TIF_NEED_RESCHED) {
              schedule();
         } else {
              local_irq_enable();
   
             if (thread_flags & _TIF_UPROBE)
                  uprobe_notify_resume(regs);
   
             if (thread_flags & _TIF_SIGPENDING)
                  do_signal(regs);
   
             if (thread_flags & _TIF_NOTIFY_RESUME) {
                  clear_thread_flag(TIF_NOTIFY_RESUME);
                  tracehook_notify_resume(regs);
             }
   
             if (thread_flags & _TIF_FOREIGN_FPSTATE)
                  fpsimd_restore_current_state();
        }
   
        local_irq_disable();
        thread_flags = READ_ONCE(current_thread_info()->flags);
   } while (thread_flags & _TIF_WORK_MASK);
  }

1)如果当前进程的thread_info.flags设置了标志位_TIF_NEED_RESCHED,那么调度进程。
2)如果设置了标志位_TIF_UPROBE,调用函数uprobe_notify_resume()处理。uprobes(user-space probes,用户空间探测器)可以在进程的任何指令地址插入探测器,收集调试和性能信息,发现性能问题。需要内核支持,编译内核时开启配置宏CONFIG_UPROBE_EVENTS。

3)如果设置了标志位_TIF_SIGPENDING,调用函数do_signal()处理信号。
4)如果设置了标志位_TIF_NOTIFY_RESUME,那么调用函数tracehook_notify_resume(),执行返回用户模式之前的回调函数。
5)如果设置了标志位_TIF_FOREIGN_FPSTATE,那么恢复浮点寄存器。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux内核机制总结中断异常和系统调用之系统调用(三十三) 的相关文章

随机推荐

  • APM-mavlink添加消息和命令

    Mavlink资源 https mavlink io en url 61 git github com ArduPilot mavlink 事先准备好消息和命令内容 1 DO OPEN SPRAY 33002 命令实例 字段 数据类型 值
  • APM多旋翼参数

    持续更新中 ABPS AB点模式 ABPS ENABLE 1 ABPS LINE NUM 1 ABPS SPRAYER EN 1 ABPS WP DELAY 1 ACRO Acro特技模式 ACRO RP P 4 5 ACRO Y EXPO
  • APM-MP日志分析参数索引

    MP日志分析参数索引 通过 ctrl 43 F 查找获取需要对应参数信息 AP Logger 64 LoggerMessage ADSB ADSB 64 Description Automatic Dependent Serveillanc
  • 百度2014校园招聘研发工程师笔试题+答案

    一 xff0c 简答题 30分 1 xff0c 当前计算机系统一般会采用层次结构存储数据 xff0c 请介绍下典型计算机存储系统一般分为哪几个层次 xff0c 为什么采用分层存储数据能有效提高程序的执行效率 xff1f 10分 xff08
  • 教大家看官方技术文档(一)- Spring MVC

    每当学习一门新技术时候 xff0c 大部分人直接就是百度Bing搜索 xff0c 借鉴别人的博客例子等 我也是一样 xff0c 但是我经常想 xff0c 这些牛人的例子如何写出来的 xff0c 如何深入扩展其它功能等等 所以 xff0c 我
  • OpenCv学习笔记(二)--Mat矩阵(图像容器)的创建及CV_8UC1,CV_8UC2等参数详解

    xff08 一 xff09 Mat矩阵 图像容器 创建时CV 8UC1 CV 8UC2等参数详解 1 Mat不但是一个非常有用的图像容器类 同时也是一个通用的矩阵类 2 创建一个Mat对象的方法很多 我们现在先看一下Mat矩阵 图像容器类在
  • IP地址和MAC地址的作用和相互关系

    IP地址和MAC地址的作用和相互关系 关于IP地址和MAC地址相互关系以及数据包在网络中的寻址过程 xff0c 这两天盘了一下 xff0c 十多年前的计算机网络课程 xff0c 还是没有完全忘记的 概念说明 xff1a MAC地址 xff1
  • git commit –amend命令修改comment

    当git commit m your comment 后 xff0c 想修改提交后的comment xff0c 可以使用git commit amend命令 1 git commit amend xff0c 会出现上一次提交时的commen
  • php 设置允许跨域请求

    php 设置允许跨域请求 跨域 xff0c 指的是浏览器不能执行其他网站的脚本 它是由浏览器的同源策略造成的 xff0c 是浏览器施加的安全限制 同源策略 xff1a 是指域名 xff0c 协议 xff0c 端口均相同 xff0c 有任一不
  • TortoiseGit-git工具

    git小乌龟 git工具 xff0c 贼好用 下载地址 Download TortoiseGit Windows Shell Interface to Git
  • FAST-LIO, ikd-Tree, FAST-LIO2, FASTER-LIO论文总结

    目录 一 FAST LIO 本文的三个创新点 xff1a FAST LIO框架 二 ikd Tree 三 FAST LIO2 四 FASTER LIO 一 FAST LIO FAST LIO三个创新点 xff1a 将IMU和雷达点特征点紧耦
  • Realsense D435i运行ORB-SLAM3

    ORB SLAM3 根目录下的CMakeList txt opencv版本改为3 xff0c 目的是与ROS下的CMakeList txt指定的opencv版本相同 xff0c 都为3 否则会段错误 build sh build ros s
  • VINS-Fusion运行相关

    如何安装VINS Fusion 根据网址安装 xff1a https github com HKUST Aerial Robotics VINS Fusion 先装ceres xff08 网址中的改成 xff1a sudo make ins
  • jetson xavier nx 上 bash: nvcc: command not found

    jetson Xavier nx上已经默认安装好了cuda cuda 10 2和cudnn等 出现这个问题是因为 我们需要把cuda目录下的bin文件添加到环境变量中 解决方案 vim bashrc 按 i 进入输入模式 xff0c 在最后
  • ROS下使用intel Realsense摄像头进行人脸检测

    使用准备条件 xff1a ROS indigo intel Realsense摄像头 xff08 我使用的依旧是R200 xff09 确保已经正常安装驱动 xff0c 安装方法见博文 http blog csdn net may0324 a
  • 【VINS-MONO】RealsenseD435i运行VINS-Mono

    1 查看需要的 camera imu的topic内容格式 xff1a roscore rosbag play lt bag gt rostopic list rostopic echo lt topic gt 2 对齐XJ2 bag中的IM
  • vsCode如何自动保存代码

    文件 勾选自动保存 xff0c 即可
  • 智能设备WIFI配网方式汇总

    当前很多物联网设备大都无没有人机交互界面 xff0c 也就没有像手机或者PC那样有wifi的配置界面 xff0c 汇总了一下设备入网的方式大概有如下几种 xff1a 1 xff1a AP 模式流程如下 1 将Dev手动设置为AP模式 xff
  • 平凡的人生or开挂的人生——对知乎相关问题的回答

    知乎原题 xff1a 为什么有些人的人生和开了挂一样 xff1f https www zhihu com question 37106162 楼上说的都特别好 xff0c 我很想补充一点 我先抛出一个背景概念 我认为开挂人生 xff0c 本
  • Linux内核机制总结中断异常和系统调用之系统调用(三十三)

    文章目录 1 系统调用1 1 定义系统调用1 2 执行系统调用 重要 xff1a 本系列文章内容摘自 lt Linux内核深度解析 gt 基于ARM64架构的Linux4 x内核一书 xff0c 作者余华兵 系列文章主要用于记录Linux内