Linux系统调用与API

news2024/11/17 21:28:51

        系统调用是应用程序与操作系统内核之间的接口,它决定了应用程序是如何与内核打交道的。无论程序是直接进行系统调用,还是通过运行库,最终还是会达到系统调用这个层面。

系统调用介绍

        1、什么是系统调用

                在现代的操作系统里,程序运行的时候,本身是没有权利访问多少系统资源的。由于系统有限的资源有可能被多个不同的应用程序同时访问,因此,如果不加以保护,那么各个应用程序难免产生冲突。所以现代操作系统都将可能产生冲突的系统资源给保护起来,阻止应用程序直接访问。这些系统资源包括文件,网络,IO,各种设备等。举个例子,程序员都没有机会擅自去访问硬盘的某个扇区上面的数据,而必须通过文件系统;也不能擅自修改任意文件,所有的这些操作都必须经由操作系统所规定的方式进行,比如我们使用fopen去打开一个没有权限的文件就会发生失败。

        没有操作系统的帮助,应用程序的执行可谓寸步难行。为了让应用程序有能力访问系统资源,也为了让程序借助操作系统做一些必须由操作系统支持的行为,每个操作系统都会提供一套接口,以供应用程序使用。这些接口往往通过中断来实现。

        2、Linux系统调用

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

        Linux内核版本2.6.19总共提供了319个系统调用,我们将其中一部分列在表1中。

                                表1

EAX名字C语言定义含义参数
1exitvoid _exit(int status);退出进程EBX表示退出码(Exit Code)
2forkpid_t fork(void);复制进程EBX表示复制参数
3readssize_t read(int fd, void buf, size_t count);读文件EBX表示文件句柄,ECX表示读取缓冲地址,EDX表示读取大小
4write   ssize_t write(int fd, void buf, size_t count);写文件同sys_read
5openint open(const char pathname, int flags, mode_t mode);打开文件EBX表示文件路径,ECX表示打开文件的模式(读,写,追加等),EDX也表示打开文件的模式(文件不存在是否创建)
6closeint close(int fd);关闭文件EBX表示文件句柄
7waitpidpid_t waitpid(pid_t pid, int* status, int options);等待进程退出EBX表示进程ID,ECX表示指向进程退出码的指针,EDX表示等待模式
8createint creat(const char* pathname, mode_t mode);创建文件EBX表示文件路径,ECX表示创建模式
.....

 

                         

3、 系统调用的弊端

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

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

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

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

                2、各个操作系统之间系统调用不兼容。Windows系统和Linux系统之间的系统调用就基本上完全不同。

                为了解决这个问题,有一个称为"万能法则"---解决计算机的问题可以通过增加层来实现,于是运行库挺身而出,它作为系统调用与程序之间的一个抽象层可以保持着这样的特点:

                1、使用简便

                2、形式统一。运行库有它的标准,叫做标准库,凡事所有遵循这个标准的运行库理论上都是相互兼容的,不会随着操作系统或编译器的变化而变化。

系统调用原理

        1、特权级与中断

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

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

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

                                         图2 CPU中断过程

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

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

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

                我们将了解到当应用程序调用系统调用时,程序是如何一步步进入操作系统内核调用相应的函数。图3是以fork为例的Linux系统调用的执行流程。

                                        图3 Linux系统中断流程 

                接下来我们来了解下这个过程的细节。

                1、触发中断

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

                        int main()

                        {        

                                fork();

                        }

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

                        _syscall0(pid_t, fork);

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

                        #define _syscall0(type, name)\

                        type name(void)                        \

                        {                                                \

                         long __res;                                \

                          __asm__ volate ("int $0x80"     \

                          : "=a"         (__res)                     \

                         : "0" (__NR_##name));                \

                        __syscall_return(type, __res);        \

                        }

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

                        pid_t fork()

                        {

                                long __res;

                                __asm__ volatile("int 0x80"

                                        : "=a" (__res)

                                        : "0" (__NR__fork));

                                __syscall_return(pid_t,__res);

                        }

                        1、首先__asm__是一个gcc的关键字,表示接下来要嵌入汇编代码。volatile关键字告诉GCC对这段代码不进行任何优化。

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

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

                        4、"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);

                         }

                        __NR_fork是一个宏,表示fork系统调用的调用号。

                        而__syscall_return是另外一个宏,定义如下:

                        #define __syscall_return(type, res)        \

                        do{                                                        \

                                if ((unsigned long)(res) >= (unsigned long)(-125)){\

                                errno = -(res);

                                res = -1;

                                       }

                                return (type)(res);

                        }

                        这个宏用于检查系统调用的返回值,并把它相应的转换为C语言的errno错误码。在Linux里,系统调用使用返回值传递错误码,如果返回值为负数那么表明调用失败,返回值的绝对值就是错误码。

                        如果系统调用本身有参数要如何实现呢?下面是x86Linux下的syscall,用于带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强制转换为龙,然后存放在EBX(b代表EBX)里作为输入。编译器还会生成相应的代码来保护原来的EBX的值不被破坏。

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

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

        2、切换堆栈

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

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

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

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

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

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

                2、用户态的ESP和SS的值保存在哪里呢?答案是在内核栈上。这一行为由i386中的中断指令自动地由硬件完成。

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

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

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

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

              

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

                    

                        

   

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

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

相关文章

CEC2015:动态多目标野狗优化算法求解CEC2015(提供完整MATLAB代码,含GD、IGD、HV和SP评价指标)

一、动态多目标优化问题简介 现实世界中,许多优化问题不仅具有多属性,而且与时间相关,即随着时间的变化,优化问题本身也发生改变,这类问题称为动态多目标优化问题(dynamic multi-objective optimization p…

写一个flutter程序—记录

目录 使用外部package 添加一个Stateful widget Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全…

[附源码]Python计算机毕业设计SSM精品旅游项目管理系统(程序+LW)

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

【Pytorch安装】windows下,安装了torch但是import torch失败

【Pytorch安装】windows下,安装了torch但是import torch失败问题原因解决问题 输入 python 进入python环境,输入 import torch 但是显示找不到这个包 输入 pip list 查看之前是否安装成功 但是可以看到包都在 原因 电脑中先前安装了多个 python &am…

Ajax用法总结

目录 Ajax简介 Ajax使用 xhr内部五种状态 Ajax在IE浏览器上存在的缓存问题 如何发送post请求 如何取消Ajax请求 ​编辑 jQuery封装的Ajax如何使用? Ajax简介 Ajax全称为Asynchous Javascript And XML,即异步的JS和XML,通过Ajax可以在…

Linux进程的调度

目录 调度策略与调度类 实时调度策略 普通调度策略 调度类 sched_class有几种实现: 完全公平调度算法 调度队列与调度实体 调度类是如何工作的? 调度策略与调度类 在Linux里面,进程大概可以分成两种。 一种称为实时进程,…

Redis主从复制+哨兵模式

必读 redis的主从复制是单向的,只能有主节点到从节点,主节点以写为主从节点以读为主不能写入数据!因为系统的80%的需求都是读的需求。 redis服务默认自己是主节点,一个主节点由一个或多个从节点,一个从节点只有一个主…

图信号处理论文

图信号处理并且非图神经网络的论文: Donget.al“GraphSignal Processingfor MachineLearning A Review and New Perspectives," ICASSP Tutorial, June 2021. Lorenzoet.al“Adaptiveleast mean squaresestimation ofgraph signals"IEEE Trans. Signal I…

Vue3 学习笔记 —— 破坏式更新、自定义指令 directive

目录 1. 什么叫破坏式更新? 2. Vue3 中的自定义指令 2.1 自定义指令的生命周期 2.1.1 Vue2 Vs Vue3 的自定义指令生命周期 2.1.2 自定义指令的生命周期中,接收的参数 2.2 定义一个自定义指令 2.2.1 在 setup 中定义自定义指令(此处为 …

数据结构 树练习题

目录 判断 选择 判断 1.一棵有124个结点的完全二叉树,其 叶结点个数是确定的。 【答案】正确 【解析】完全二叉树 若设二叉树的深度为h 除第 h 层外 其它各层 1~(h-1) 的结点数都达到最大个数(即1~(h-1)层为一个满二叉树) 第 h 层所有的结点都连续集…

【C++】STL

文章目录回调函数:一、STL的诞生二、STL基本概念三、STL六大组件四、STL中容器,算法,迭代器回调函数: 函数被作为参数传递到另一个函数(主要函数)的那个函数就叫做 回调函数 一、STL的诞生 C的面向对象和…

DHTMLX Diagram JavaScript/HTML5 Pro Library:5.0

Diagram — JavaScript/HTML5 Diagram Library Ω578867473 破解版DHTMLX Diagram comprises a set of interactive HTML5 UI components such as organization charts, flowcharts, decision trees, block diagrams, mind maps, etc. Consisting of nodes and connectors, di…

i.MX 6ULL 驱动开发 二十八:网络设备

一、网络设备的系统框图 MAC:工作在网络模型的数据链路层,通过 RGMII 或 RMII 接口连接 PHY,MAC 控制器中的 MDIO 控制器提供 MDIO 接口,用于访问 PHY 寄存器。 PHY:工作在网络模型的物理层,是 IEEE802.3 …

SVN项目,提交Git保留之前提交记录

📃目录跳转简介:1.创建文件2.命令下载:3.上传到远程git3.1 创建远程git工程3.2 添加一个新的远程 Git 仓库3.3 拉取远程master的数据合并3.3 推送远程git分支简介: 由于之前直接搞的项目是部署在自己搭建的SVN服务器上,平时创建的…

5. JVM调优工具详解及调优实战(这里有我的实战案例预制构件生产管理平台)

1. Jmap,Jstack,Jinfo命令详解 1.1 Jmap 此命令可以用来查看内存信息,实例个数以及占用内存大小 jps 先查看有哪些java程序 jmap -histo 16492 > ./log.txt jmap -heap 16492 查看堆的信息 查看堆年轻代老年代的使用情况 堆内存dum…

Matlab:tftb-0.2时频工具箱安装小记

Matlab:tftb-0.2时频工具箱安装小记一、安装过程记录1、解压缩:2、将解压缩后的文件夹复制到自己的Matlab安装目录工具箱下;3、打开Matlab设置路径:设置路径4、测试是否安装成功:5、小试牛刀叮嘟!这里是小啊…

【ASE+python学习】-批量识别石墨烯团簇结构中的吡啶氮,并删除与其相连的氢

批量识别石墨烯团簇结构中的吡啶氮,并删除与其相连的氢文章背景任务内容程序实现思路实现代码建立标准结构中边缘碳与氢的位置差值标准数据集读入待修改结构,识别氮与氢位置差值是否存在标准数据集代码细节剖析文章背景 在科研工作中,我的工…

STM32系列(HAL库)——串口IAP

前言 IAP(In Application Programming)即在应用编程,IAP 是用户自己的程序在运行过程中对 User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产 品中的固件程序进行更新升级。 设备具备IAP功能…

javaScript学习———变量概述 变量的使用 变量语法扩展 变量命名规范交换 变量案例

博主每篇博文的浪漫主义: 【东京girl秀场上那些甜度爆表的女孩子们。💖】 https://www.bilibili.com/video/BV1pG411F7KT/?share_sourcecopy_web&vd_source385ba0043075be7c24c4aeb4aaa73352 东京girl秀场上那些甜度爆表的女孩子们。💖…

计算机组成原理--------12.4---------开始

计算机硬件的基本组成 冯诺依曼计算机的特点 冯诺依曼首次提出“存储程序”概念 计算机由五大部件组成:I/O设备(输入输出),存储器(存放数据和程序),运算器(算术运算、逻辑运算&…