Linux - 系统调用(syscall)

2023-11-25 20:45
文章标签 linux 系统 调用 syscall

本文主要是介绍Linux - 系统调用(syscall),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

说明

  • 基于riscv64 soc + linux_5.10.4平台,通过新增一个系统调用深入了解下系统调用实现原理。

简介

  • Linux 软件运行环境分为用户空间和内核空间,默认情况下,用户进程无法访问内核,既不能访问内核所在的内存空间,也不能调用内核中的函数。
  • 为了给应用层提供系统支持,Linux提供了一组系统调用接口,用户可以通过调用它们访问linux内核的数据和函数。
  • Linux系统调用实现原理是固定,不同平台(arm64,riscv)只是切换至内核态的汇编指令不同,大致原理如下:
  1. 程序将系统调用参数填充到对应的平台通用寄存器。
  2. 调用平台特定的汇编指令,触发同步异常,切换至内核态运行。
  3. 内核初始化时已设置异常向量表,应用层触发同步异常后,CPU会跳到异常向量表对应的异常处理执行(通常是一段平台相关的汇编代码)。
  4. 异常处理代码会检查系统调用号是否超出,未超出,再根据定义的系统调用表(sys_call_table)找到相应的系统调用函数入口地址,执行后,再通过汇编指令返回应用层。
  • 新增系统调用,实现步骤,如下:
  1. 修改系统调用表(syscall_table),新增一项。
  2. 系统调用声明。
  3. 系统调用实现。

修改系统调用表

  • 系统调用表(syscall_table)定义如下:
// file: arch/riscv/kernel/syscall_table.c
#undef __SYSCALL
#define __SYSCALL(nr, call)     [nr] = (call),const void *sys_call_table[__NR_syscalls] = {[0 ... __NR_syscalls - 1] = sys_ni_syscall,
#include <asm/unistd.h> //通过unistd.h导入实际定义
};
  • asm/unistd.h最终定义,如下:
//file: include/uapi/asm-generic/unistd.h 
...
#define __NR_openat2 437     //系统调用openat2 编号
__SYSCALL(__NR_openat2, sys_openat2) //系统调用openat2 syscall_table项定义
#define __NR_pidfd_getfd 438
__SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd)
#define __NR_faccessat2 439
__SYSCALL(__NR_faccessat2, sys_faccessat2)
#define __NR_process_madvise 440
__SYSCALL(__NR_process_madvise, sys_process_madvise)#undef __NR_syscalls
#define __NR_syscalls 441 //系统调用表 项个数
...
  • 新增一项系统调用(mytest)
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index 15279e8d8..7df066dc5 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -860,8 +860,11 @@ __SYSCALL(__NR_faccessat2, sys_faccessat2)#define __NR_process_madvise 440__SYSCALL(__NR_process_madvise, sys_process_madvise)+#define __NR_mytest 441
+__SYSCALL(__NR_mytest, sys_mytest)
+#undef __NR_syscalls
-#define __NR_syscalls 441
+#define __NR_syscalls 442

系统调用声明

  • 新增系统调用需要先声明,否则内核编译时会报错(找不到新增系统调用声明)。
//file: include/linux/syscalls.h
....
asmlinkage long sys_madvise(unsigned long start, size_t len, int behavior);
asmlinkage long sys_process_madvise(int pidfd, const struct iovec __user *vec,size_t vlen, int behavior, unsigned int flags);
....
+asmlinkage long sys_mytest(int id); //新增系统调用声明

系统调用实现

  • 实现系统调用时,不能像实现普通函数一样,需要使用SYSCALL_DEFINE宏,如:系统调用madvise,定义时使用SYSCALL_DEFINE3宏,宏展开后就是sys_madvise。
//file: mm/madvise.c
SYSCALL_DEFINE3(madvise, unsigned long, start, size_t, len_in, int, behavior)
{                       return do_madvise(current->mm, start, len_in, behavior);
}int do_madvise(struct mm_struct *mm, unsigned long start, size_t len_in, int behavior)
{        ....//实际功能实现....
}
  • SYSCALL_DEFINE 宏定义
//file: include/linux/syscalls.h
#ifndef SYSCALL_DEFINE0         
#define SYSCALL_DEFINE0(sname)                                  \SYSCALL_METADATA(_##sname, 0);                          \asmlinkage long sys_##sname(void);                      \ALLOW_ERROR_INJECTION(sys_##sname, ERRNO);              \asmlinkage long sys_##sname(void)
#endif /* SYSCALL_DEFINE0 */#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)#define SYSCALL_DEFINE_MAXARGS  6#define SYSCALL_DEFINEx(x, sname, ...)                          \SYSCALL_METADATA(sname, x, __VA_ARGS__)                 \__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)#define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)
  1. 系统调用最多支持6个参数,1个参数使用SYSCALL_DEFINE1,2个参数使用SYSCALL_DEFINE2,以此类推。
  • 新系统调用
// file: mm/madvise.c ,随便找了一个文件保存代码
SYSCALL_DEFINE1(mytest, int, id)
{return id; //测试将id返回
}

应用层测试

  • 编译并运行新内核后,可运行应用层程序验证。
int main(void) 
{int id = 0;id = syscall(441, 100);printf("result : %d\n", id);return 0;
}~# ./mytest 
result : 100

标准C库

  • 程序中调用的syscall来自标准C库,根据源码可知:应用层系统调用接口是封装的syscall。
  • 当前使用的标准C库(musl)syscall源码如下:
//file: musl-1.2.1/arch/riscv64/syscall_arch.h
...
#define __asm_syscall(...) \__asm__ __volatile__ ("ecall\n\t" \: "=r"(a0) : __VA_ARGS__ : "memory"); \return a0; \static inline long __syscall0(long n)
{register long a7 __asm__("a7") = n;register long a0 __asm__("a0");__asm_syscall("r"(a7))
}
...
static inline long __syscall6(long n, long a, long b, long c, long d, long e, long f)
{register long a7 __asm__("a7") = n;register long a0 __asm__("a0") = a;register long a1 __asm__("a1") = b;register long a2 __asm__("a2") = c;register long a3 __asm__("a3") = d;register long a4 __asm__("a4") = e;register long a5 __asm__("a5") = f;__asm_syscall("r"(a7), "0"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5))
}
  • 可知:
  1. riscv64最终使用汇编指令ecall,触发同步异常,切换至内核态执行。
  2. 使用通用寄存器a7 存储系统调用编号
  3. 和内核定义一致,syscall支持0 ~ 6个参数(__syscall0 ~ __syscall6 ),使用寄存器a0 ~ a5传递参数。
  • ARM64实现原理也是一样,不同的只是触发异常的指令(svc)以及通用寄存器的使用,如下:
#define __asm_syscall(...) do { \__asm__ __volatile__ ( "svc 0" \: "=r"(x0) : __VA_ARGS__ : "memory", "cc"); \return x0; \} while (0)static inline long __syscall0(long n)
{register long x8 __asm__("x8") = n;register long x0 __asm__("x0");__asm_syscall("r"(x8));
}
...
static inline long __syscall6(long n, long a, long b, long c, long d, long e, long f)
{register long x8 __asm__("x8") = n;register long x0 __asm__("x0") = a;register long x1 __asm__("x1") = b;register long x2 __asm__("x2") = c;register long x3 __asm__("x3") = d;register long x4 __asm__("x4") = e;register long x5 __asm__("x5") = f;__asm_syscall("r"(x8), "0"(x0), "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x5));
}

总结

  1. 系统调用是安全的,执行时,应用层没有访问内核空间。
  2. 系统调用执行时,应用层暂停,切换至内核空间执行。
  3. 系统调用执行时,是通过平台相关的特定汇编指令触发同步异常,riscv64是使用ecall,aarch64是使用svc 0,Intel CPU由中断0x80实现。
  • CPU会跳转到对应的异常处理,源码如下:
//file: arch/riscv/kernel/entry.S
....
ENTRY(handle_exception) //对应的异常处理....
check_syscall_nr:/* Check to make sure we don't jump to a bogus syscall number. */li t0, __NR_syscallsla s0, sys_ni_syscall/** Syscall number held in a7.* If syscall number is above allowed value, redirect to ni_syscall.*/bgeu a7, t0, 3f
#ifdef CONFIG_COMPATREG_L s0, PT_STATUS(sp)srli s0, s0, SR_UXL_SHIFTandi s0, s0, (SR_UXL >> SR_UXL_SHIFT)li t0, (SR_UXL_32 >> SR_UXL_SHIFT)sub t0, s0, t0bnez t0, 1f/* Call compat_syscall */la s0, compat_sys_call_tablej 2f
1:
#endif/* Call syscall */la s0, sys_call_table
2:slli t0, a7, RISCV_LGPTRadd s0, s0, t0REG_L s0, 0(s0)
3:jalr s0ret_from_syscall:
....
  1. 内核态调用对应的系统调用函数,执行完后,会退出内核态切换至用户态,如上 ret_from_syscall。
  • 此过程 aarch64平台是由eret汇编指令实现,和arm trustzone机制 bl31切换至非安全world(REE)以及切换至安全world(bl32)实现流程是一样的,riscv64 平台,具体指令暂不明。

这篇关于Linux - 系统调用(syscall)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/424583

相关文章

Python调用Orator ORM进行数据库操作

《Python调用OratorORM进行数据库操作》OratorORM是一个功能丰富且灵活的PythonORM库,旨在简化数据库操作,它支持多种数据库并提供了简洁且直观的API,下面我们就... 目录Orator ORM 主要特点安装使用示例总结Orator ORM 是一个功能丰富且灵活的 python O

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

一分钟带你上手Python调用DeepSeek的API

《一分钟带你上手Python调用DeepSeek的API》最近DeepSeek非常火,作为一枚对前言技术非常关注的程序员来说,自然都想对接DeepSeek的API来体验一把,下面小编就来为大家介绍一下... 目录前言免费体验API-Key申请首次调用API基本概念最小单元推理模型智能体自定义界面总结前言最