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

相关文章

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

在C#中调用Python代码的两种实现方式

《在C#中调用Python代码的两种实现方式》:本文主要介绍在C#中调用Python代码的两种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#调用python代码的方式1. 使用 Python.NET2. 使用外部进程调用 Python 脚本总结C#调

Linux安装MySQL的教程

《Linux安装MySQL的教程》:本文主要介绍Linux安装MySQL的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux安装mysql1.Mysql官网2.我的存放路径3.解压mysql文件到当前目录4.重命名一下5.创建mysql用户组和用户并修

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在