系统调用与API

news2024/11/27 10:22:32

系统调用介绍

        什么是系统调用

                        为了让应用程序有能力访问系统资源,也为了让程序借助操作系统做一些由操作系统支持的行为,每个操作系统都会提供一套接口,以供应用程序使用。系统调用涵盖的功能很广,有程序运行所必需的支持,例如创建/退出进程和线程,进程内存管理,也有对系统资源的访问,例如文件,网络,进程间通信,硬件设备的访问,也可能有对图形界面的操作支持。

        Linux系统调用

                        下面让我们来看看Linux系统调用的定义,有一个比较直观的概念。在x86下,系统调用由0x80中断完成,各个通用寄存器用于传递参数,EAX寄存器用于表示系统调用的接口号,比如EAX=1表示退出进程(exit);EAX=2表示创建进程(fork);EAX=3表示读取文件或IO(read);EAX=4表示写文件或IO(write)等,每个系统调用都对应内核源代码中的一个函数,它们都是以"sys_"开头,比如exit调用对应内核中的sys_exit函数。当系统调用返回时,EAX又作为调用结果的返回值。

EAX名字C语言定义含义参数
1exitvoid _exit(int status)退出进程EBX表示退出码
3readssize_t read(int fd, void* buf, size_t count)读文件EBX表示文件句柄,ECX表示读取缓冲区地址,EDX表示读取大小

        系统调用的弊端

                        系统调用完成了应用程序和内核交流的工作,因此理论上只需要系统调用就可以完成一些程序,但是:

                                理论上,理论总是成立的。

                                事实上,包括Linux,大部分操作系统的系统调用都有两个特点:

                                1、使用不便。操作系统提供的系统调用接口往往过于原始,程序员需要了解很多与操作系统相关的细节。如果没有进行很好的包装,使用起来不方便。

                                2、各个操作系统之间系统调用不兼容。

系统调用原理

        特权级与中断

                        现在的CPU常常可以在多种截然不同的特权级别下执行指令,在现代操作系统中,通常也据此有两种特权级别,分别为用户模式和内核模式,也被称为用户态和内核态。由于有多种特权模式的存在,操作系统就可以让不同的代码运行在不同的模式下,以限制它们的权利,提高稳定性和安全性。普通应用程序运行在用户态的模式下,诸多操作将受到限制,这些操作包括访问硬件设备,开关中断,改变特权模式等。

                        一般来说,运行在高特权级的代码将自己降至低特权级是允许的,但反过来低特权级的代码将自己提升至高特权级则不是轻易就能进行的。在将低特权级的环境转为高特权级时,必须使用一种较为受控和安全的形式,以防止低特权模式的代码破坏高特权模式代码的执行。

                        系统调用是运行在内核态的,而应用程序基本都是运行在用户态的。用户态的程序如何运行内核态的代码呢?操作系统一般是通过中断来从用户态切换到内核态。什么是中断呢?中断是一个硬件或软件发出的请求,要求CPU暂停当前的工作转手去处理更加重要的事情。

                        中断一般具有两个属性,一个称为中断号(从0开始),一个称为中断处理程序。不同的中断具有不同的中断号,而同时一个中断处理程序一一对应一个中断号。在内核中,有一个数组称为中断向量表,这个数组的第n项包含了第n号中断的中断处理程序的指针。当中断到来时,CPU会暂停当前执行的代码,根据中断号,在中断向量表中找到对应的中断处理程序,并调用它。中断处理程序执行完成之后,CPU会继续执行之前的代码。一个简单的示意图如图1所示。

 图1 CPU中断过程

                        通常意义上,中断有两种类型,一种称为硬件中断,这种中断来自硬件的异常或其他事件的发生,如电源掉电,键盘被按下。另一种称为软件中断,软件中断通常是一条指令,带有一个参数记录中断号,使用这条指令用户可以手动触发某个中断并执行其中断处理程序。例如在i386下,int 0x80这条指令会调用第0x80号中断的处理程序。

                        由于中断号是很有限的,操作系统不会舍得用一个中断号来对应一个系统调用,而更倾向于一个或少数几个终端号来对应所有的系统调用。对于同一个中断号,操作系统如何知道是哪一个系统调用被调用呢?和中断一样,系统调用都有一个系统调用号,就像身份标识一样来表明是那个系统调用,这个系统调用号通常就是系统调用在系统调用表中的位置,例如Linux里的fork系统调用号是2。这个系统调用号在执行int指令前会被放置在某个固定的寄存器里,对应的中断代码会取得这个系统调用号,并且调用正确的函数。以Linux的int 0x80为例,系统调用号是由eax来传入。用户将系统调用号放入eax,然后使用int 0x80调用中断,中断服务程序就可以从eax里取得系统调用号,进而调用对应的函数。

        基于int的Linux的经典系统调用实现

                图2是以fork为例的Linux系统调用的执行流程。

图2 Linux系统中断流程 

                触发中断

                        首先当程序在代码里调用一个系统调用时,是以一个函数的形式调用的,例如程序调用fork:

                        int main(){ fork();}

                        fork函数是一个对系统调用fork的封装,可以用下列宏来定义它:

                        _syscall0(pid_t, fork);

                        _syscall0是一个宏函数,用于定义一个没有参数的系统调用的封装。它的第一个参数为这个系统调用的返回值类型,这里为pit_t,是一个Linux自定义类型,代表进程的id。_syscall0的第二个参数是系统调用的名称,_syscall0展开之后会形成一个与系统调用名称同名的函数。下面的代码是i386版本的syscall0定义:

                        #define _syscall0(type, name )                

                        type name(void)                                       

                        {                                                                 

                                long __res;                                          

                                __asm__ volatile("int $0x80"

                                        : "=a" (__res)                          

                                        : "0" (__NR__##name));                

                                __syscall_return(type, __res);                

                                                                            

                        对应syscall0(pid_t, fork),上面的宏将展开为:

                        pid_t fork(void)

                        {

                                long __res;

                                __asm__ volatile("int $0x80"

                                        : "=a" (__res)

                                        : "0" (__NR_fork));

                                __syscall_return(pid_t, __res);

                        }

                        首先__asm__是一个gcc的关键字,表示接下来要嵌入汇编代码。volatile

关键字告诉gcc对这段代码不进行任何优化。

                        __asm__的第一个参数是一个字符串,代表汇代码的文本。这里的汇编代码只有一句: int $0x80,这就要调用0x80号中断

                        "=a"(__res)表示用eax(a表示eax)输出返回数据并存储在__res里。

                        "0"(__NR_#name)表示__NR_#name为输入,"0"指示由编译器选择和输出相同的寄存器(eax)来传递参数。

                        更直观一点,可以把这段汇编改写为更为可读的格式:

                        main-->fork:

                        pid_t fork(void)

                        {

                                long __res;

                                $eax=__NR_fork

                                int $0x80

                                __res = $eax

                                __syscall_return(pid_t, __res);

                        }

                        如果系统调用本身由参数要如何实现呢?下面是x86Linux下的syscall1,用于带1个参数的系统调用:

                        #define _syscall2(type, name, type1, arg1)

                        type name(type1, arg1)

                        {

                                long __res;

                                __asm__ volatile("int $0x80"

                                        : "a=" (__res)

                                        : "0" (__NR_##name), "b"((long)(arg1)));

                                __syscall_return(type, __res);                  

                        }

                        这段代码和_syscall0不同的是,它多了一个"b"((long)(arg1))。这一句的意思是先把arg1强制转换为long,然后存放在EBX(b代表EBX)里作为输入。编译器还会生产相应的代码来保护原理的EBX的值不被破坏。

                        可见,如果系统调用由一个参数,那么参数通过EBX来传入。x86下Linux支持的系统调用参数至多由6个,分别使用6个寄存器来传递,它们分别是EBX,ECX,EDX,ESI,EDI和EBP。

                        当用户调用某个系统调用的时候,实际是执行了以上一段汇编代码。CPU执行到int $0x80时,会保存现场以便恢复,接着会将特权状态切换到内核态。然后CPU便会查找中断向量表中的第0x80号元素。

                切换堆栈

                        在实际执行中断向量表中的第0x80号元素所对应的函数之前,CPU首先还要进行栈的切换。在Linux中,用户态和内核态使用的是不同的栈,两者各自负责各自的函数调用,互不干扰。但在应用程序调用0x80号中断时,程序的执行流程从用户态切换到内核态,这时程序的当前栈必须也相应的从用户栈切换到内核栈。从中断处理函数返回时,程序的当前栈还要从内核栈切换回用户栈。

                        所谓的"当前栈",指的是ESP的值所在的栈空间。如果ESP的值位于用户栈的范围内,那么程序的当前栈就是用户栈,反之亦然。此外,寄存器SS的值还应该指向当前栈所在的页。所以,将当前栈由用户栈切换为内核栈的实际行为就是:

                        1、保存当前的ESP,SS的值。

                        2、将ESP,SS的值设置为内核栈的相应值。

                        反过来,将当前栈由内核栈切换为用户栈的实际行为则是:

                        1、恢复原来ESP,SS的值。

                        2、将用户态的ESP,SS的值保存在内核栈上。

                        当0x80号中断发生的时候,CPU除了切入内核态之外,还会自动完成下列几件事:

                        1、找到当前进程的内核栈(每一个进程都有自己的内核栈)。

                        2、在内核栈中依次压入用户态的寄存器SS,ESP,EFLAGS,CS,EIP。

                        而当内核从系统调用中返回的时候,需要调用iret指令来回倒用户态,iret指令则会从内核栈里弹出寄存器SS,ESP,EFLAGS,CS,EIP的值,使得栈恢复到用户态的状态。这个过程可以用图3来表示。

图3 中断时用户栈和内核栈切换

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/551311.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

leetCode刷题记录2

文章目录 hot100题560. 和为 K 的子数组581. 最短无序连续子数组 ▲617. 合并二叉树 hot100题 560. 和为 K 的子数组 560. 和为 K 的子数组 先暴力&#xff0c;过了再说 public int subarraySum(int[] nums, int k) {int ans 0;for (int i 0; i < nums.length; i) {in…

保姆级教程Windows11下安装RocketMQ

一、RocketMQ介绍 RocketMQ 是阿里巴巴开源的分布式消息中间件。支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。它里面有几个区别于标准消息中件间的概念&#xff0c;如Group、Topic、Queue等。系统组成则由Producer、Consumer、Broker、NameServer等。 二、Rock…

vector类详解【c++】

&#x1f600;博主主页 &#x1f600;博主码云 目录 &#x1f3c5;vector简介&#x1f3c5;vector使用&#x1f3c6;vector的定义&#x1f3c6;vector iterator 的使用&#x1f3c6;vector 空间函数&#x1f3c6;vector的扩容问题&#x1f3c6;vector 增删查改&#x1f3c6;vec…

Python tkintertools 模块介绍(新版)

&#x1f680;tkintertools&#x1f680; The tkintertools module is an auxiliary module of the tkinter module tkintertools 模块是 tkinter 模块的辅助模块 Installation/模块安装 Stable version/稳定版本 Version/版本 : 2.6.1Release Date/发布日期 : 2023/05/21 p…

Edge 浏览器:隐藏功能揭秘与高效插件推荐

文章目录 一、前言二、Edge 的各种奇淫巧计2.1 开启 Edge 分屏功能2.2 启动 Edge 浏览器后直接恢复上次关闭前的页面2.3 解决 Edge 浏览器无法同步账号内容2.4 开启垂直标签页&#xff08;推荐&#xff09;2.5 设置标签分组&#xff08;推荐&#xff09;2.6 设置标签睡眠时间&a…

网络管理 - 简单网络管理协议 SNMP

文章目录 1 概述1.1 结构1.2 操作 2 SNMP2.1 报文格式2.2 五大报文类型2.3 三大组件 3 扩展3.1 网工软考真题 1 概述 #mermaid-svg-xmaaQjpp1bT1axfw {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-xmaaQjpp1bT1axf…

实验篇(7.2) 01. 实验环境介绍 远程访问 ❀ Fortinet网络安全专家 NSE4

【简介】学习NSE4&#xff0c;如果只看文章而不动手做实验&#xff0c;就象耍流氓。为了有效的巩固学习到的内容&#xff0c;建议经常动手做实验。实验不怕出错&#xff0c;身经百战后&#xff0c;再在生产环境部署和配置FortiGate防火墙&#xff0c;就会做到胸有成竹。 虚拟实…

【网络协议详解】——RIP协议(学习笔记)

目录 &#x1f552; 1. IP路由协议概述&#x1f558; 1.1 路由表&#x1f558; 1.2 路由的度量尺度/度量值&#x1f558; 1.3 路由管理距离 &#x1f552; 2. RIP协议&#x1f558; 2.1 概述&#x1f558; 2.2 工作原理 &#x1f552; 3. 报文格式&#x1f558; 3.1 RIP 协议报…

【自动化测试】第一次项目实施

测试项目简介&#xff1a;基于python语言 跨平台的测试自动化工具&#xff0c;适用于后台、原生或混合型客户端应用的测试。它支持 Android、iOS、Web、后台、云服务和 Windows 端的 UI 自动化测试。 上手快&#xff0c;操作简单&#xff0c;只要有一点python基础&#xff0c…

5. 多线程并发锁

本文介绍了多线程并发下为了避免临界资源被抢占而出现的错误&#xff0c;引入了锁和原子操作 来解决。 一、问题分析 创建10个线程&#xff0c;每个线程实现往总进程加1万个数。则总进程会达到10万 #include<stdio.h> #include <unistd.h> #include<pthread.h…

路径规划算法:基于头脑风暴算法的路径规划算法- 附代码

路径规划算法&#xff1a;基于头脑风暴的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于头脑风暴的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法头脑…

强化学习-初步认识

前言 强化学习这个概念是2017年Alpha Go战胜了当时世界排名第一的柯洁而被大众知道&#xff0c;后面随着强化学习在各大游戏比如王者荣耀中被应用&#xff0c;而被越来越多人熟知。王者荣耀AI团队&#xff0c;甚至在顶级期刊AAAI上发表过强化学习在王者荣耀中应用的论文。 什么…

BEVDet4D 论文学习

1. 解决了什么问题&#xff1f; 单帧数据包含的信息很有限&#xff0c;制约了目前基于视觉的多相机 3D 目标检测方法的性能&#xff0c;尤其是关于速度预测任务&#xff0c;要远落后于基于 LiDAR 和 radar 的方法。 2. 提出了什么方法&#xff1f; BEVDet4D 将 BEVDet 方法从…

C++ Vecter

C Vecter &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解了C中vector的介绍以及相关的一些接口的使用 …

Prometheus+Grafana监控系统

一、简介 1、Prometheus简介 官网&#xff1a;https://prometheus.io 项目代码&#xff1a;https://github.com/prometheus Prometheus&#xff08;普罗米修斯&#xff09;是一个最初在SoundCloud上构建的监控系统。自2012年成为社区开源项目&#xff0c;拥有非常活跃的开发人员…

第二章 Electron自定义界面(最大化、最小化、关闭、图标等等)

一、介绍 &#x1f606; &#x1f601; &#x1f609; Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux——不需…

linux--systemd、systemctl

linux--systemd、systemctl 1 介绍1.1 发展sysvinitupstart主角 systemd 登场 1.2 简介 2 优点兼容性启动速度systemd 提供按需启动能力采用 linux 的 cgroups 跟踪和管理进程的生命周期启动挂载点和自动挂载的管理实现事务性依赖关系管理日志服务systemd journal 的优点如下&a…

GAMS建模技术案例01 求解简单的线性规划模型

目录 1.问题描述 2 GAMS代码要点 2.1 代码编写要点 2.2 案例源码 2.3 计算结果及报告解 1.问题描述 首先给出一个基本线性规划问题的计算案例 subject to: 2 GAMS代码要点 2.1 代码编写要点 使用 * 表示注释文本定义变量 Positive Variable 表示定义非负变量Negative V…

【经典论文】打通文本图像的里程碑--clip

Git&#xff5c;Paper&#xff5c;Colab&#xff5c; CLIP 论文逐段精读【论文精读】_哔哩哔哩_bilibili clip是openai团队在4亿对文本图像对上训练出来的。它的训练方法简单&#xff0c;但效果缺出奇的好。是打通图片文本的里程碑式的模型。 目录 一.模型结构​编辑 1.为…

“Shell“免交互

文章目录 一.免交互&#xff08;Here Document&#xff09;1.1Here Document 概述2.2Here Document 常规用法 二.Expect2.1Expect基本命令2.2Expect执行方式 一.免交互&#xff08;Here Document&#xff09; 1.1Here Document 概述 使用I/O重定向的方式将命今列表提供给交互式…