IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux内核系统调用原理与实现 -> 正文阅读

[系统运维]Linux内核系统调用原理与实现

解决什么问题

Linux系统调用主要是操作系统实现的应用编程接口,简单的说就是linux内核提供对外(对于应用程序)的接口函数,进程通过调用系统调用完成自身的功能。
在这里插入图片描述
系统调用在每个平台的实现方式都不同相同,例如x86通过int 0x80中断来实现,arm通过其它的指令实现。

具体原理与实现

1 系统调用过程
对于内核提供给应用程序的接口,例如open接口,在内核中有对应的一个函数sys_open.
在这里插入图片描述
我们在内核增加一个系统调用,这里就是函数声明。
具体实现是通过系统调用相关的辅助宏来定义的。
在这里插入图片描述
内核中提供了一组宏来定义的,通过系统调用的参数个数来定义
open的3个参数,即就是SYSCALL_DEFINE3.
在这里插入图片描述
里面就是open的具体实现了。可以自己添加printk函数来编译通过查看打印信息。

对于系统调用的定义,我们可以将SYSCALL_DEFINE3宏展开,会得到代码
在这里插入图片描述
编译器自动生成代码比较复杂,但是主要就是生成两个gcc内置函数的安全性检查等,就跟sizeof和typeof一样只在编译时起作用。

type __builtin_choose_expr (const_exp, exp1, exp2)
__builtin_types_compatible_p(typeof(a), typeof(b))

把编译器生成的代码化简后并拼接上open实现代码就形成了open系统调用的最终代码。
在这里插入图片描述
2 为何可以调用
系统中维护了一个全局系统调用列表sys_call_table,以4k对齐
在这里插入图片描述
上面的是以arm64系统为例的,其它的每个硬件平台都有对应的一个全局系统调用表。
在这里插入图片描述
这样sys_open系统调用的入口就写入到sys_call_table列表中。

系统调用全过程

应用程序调用C库的open函数,C库执行80中断进入CPU异常模式,然后内核找到系统调用函数并执行它,最后返回到用户空间的一个过程。
1 程序调用C库执行open系统调用

#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
        FILE *fp = fopen(".", "r");
        if (fp) fclose(fp);
        return 0;
}

执行编译命令:
在这里插入图片描述
执行反编译命令:
在这里插入图片描述
生成asm文件可以查看到.
c库将fopen自动转成了对openat的系统调用.
通过读汇编代码可以看到设置寄存器的系统调用号0x38存放在寄存器中,执行完系统调用后,程序将继续执行下一行代码。等待软中断触发。

2 cpu进入异常模式
程序执行c库的指令后,cpu进入异常模式。内核根据异常类型以及中断向量表里面的地址,调用同步异常处理例程。现在看看el0_sync代码
在这里插入图片描述
1 kernel_entry是一个汇编宏代码,做进入系统调用前的准备工作,包括保存程序执行的现场,载入与CPU核相关的线程数据,保存异常返回地址等。
2 读取系统寄存器esr_el1的值。异常不单单只有系统调用会触发,内存缺页、指令错误等也会触发,因此,esr_el1[26:31]就保存了异常发生的原因。
3 取出esr_el1中产生异常的原因,保存在x24里。
4 ARM定义系统调用的原因为ESR_ELx_EC_SVC64,把它与x24与比较,如果相等则执行系统调用的代码,如果不相等则需继续往下走,表明异常是由其他原因触发的,比如内存缺页等。
5 执行el0_svc系统调用的代码

3 找到系统调用函数并执行它
el0_sync函数是用来处理系统调用的,首先从系统全局唯一的系统调用表里,取得C库调用svc时准备的系统调用号,然后根据调用号索引找到系统调用表里相应的函数地址,最后执行它。
在这里插入图片描述
1 载入全局系统调用列表sys_call_table的地址。
2 取出系统调用号(w8即是x8的低32位寄存器),保存到scno(x26)里。系统调用号是C库代码在每个系统调用前写入x8寄存器里的。
3 获得全部系统调用的最大值并保存到sc_nr(x25)里。
4 比较系统调用号与最大值,结果保存在状态寄存器里。
5 比较的结果大于最大值,则跳到ni_sys处执行错误处理。
6 把系统调用号作为索引,取出sys_call_table表中相应的函数地址,这里保存的就是sys_open的地址。sys_open函数地址如何设置到sys_call_table表里请参考前一篇文章。
7 调用sys_open函数,完成此系统调用的功能。
4 返回用户空间
程序调用完sys_open函数后,系统调用的功能就执行完毕了,剩下的任务就是返回到程序的用户空间里,继续执行剩下的代码。
在这里插入图片描述
ret_fast_syscall函数,执行流返回到的用户空间里去。
1 关闭中断
2 系统调用的返回值保存进堆栈里。
3 kernel_exit是一个汇编宏与kernel_entry相对应,恢复之前程序执行的现场,设置异常返回地址,执行eret指令,返回到用户空间里去。

总结

1、应用程序调用
当程序调用C库打开一个文件的时候,把系统调用的参数放入x1-x6寄存器(系统调用最多用到6个参数),把系统调用号放在x8寄存器里,然后执行SVC指令,CPU进入EL1。

2 保持线程存入系统调用表 CPU把当前程序指针寄存器PC放入ELR_EL1里,把PSTATE放入SPSR_EL1里,把系统调用的原因放在ESR_EL1里,然后通过VBAR_EL1加上偏移量取得异常向量的入口地址,接着开始执行入口的第一行代码。这一过程是CPU自动完成的,不需要程序干预。

3 执行具体调用函数
内核保存异常发生时程序的执行现场,然后通过异常的原因及系统调用号找到系统调用的具体函数,接着执行函数,把返回值放入x0寄存器里。这一过程是内核实现的,每种操作系统可以有不同的实现。

4 返回应用程序
系统调用完成后,程序需要主动设置ELR_EL1和SPSR_EL1的值,原因是异常会发生嵌套,一旦发生异常嵌套ELR_EL1和SPSR_EL1的值就会随之发生改变,所以当系统调用返回时,需要恢复之前保存的ELR_EL1和SPSR_EL1的值。最后内核调用ERET命令,CPU自动把ELR_EL1写回PC,把SPSR_EL1写回PSTATE,并返回到EL0里。这时程序就返回到用户态继续运行了。
在这里插入图片描述

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-06 16:28:46  更:2022-04-06 16:30:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 21:23:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码