彻底搞懂系统调用

在应用程序开发过程中经常会进行IO设备的操作,比如磁盘的读写,网卡的读写,键盘,鼠标的读入等,大多数应用开发人员使用高级语言进行开发,例如C,C++,java,python等,这些高级语言都提供了标准库或者API去操作IO设备,不过标准库或者API最终还是通过系统调用来实现操作IO设备的,系统调用是操作系统提供的,它是操作系统内核的一部分。

系统调用封装了对硬件操作的所有细节,而标准库或者SDK又在系统调用的基础上做了高度抽象的封装和优化,因此使得应用程序开发人员的日子好过多了,开发效率也提高了不少。

1

本篇文章主要阐述以下两部分:

1.什么是系统调用?

2.系统调用的实现?

主要以Linux 操作系统和IA-32处理器举例,高级语言以C语言为例,同时也会掺杂一些其它操作系统和处理器。

什么是系统调用?

对于现代的操作系统来说,应用程序运行的时候是没有权限去访问系统资源的,操作系统为了防止各类应用程序可能会破坏系统资源,对系统资源做了保护,阻止应用程序直接去访问这些资源,而应用程序又有访问这些系统资源的需求,因此操作系统提供了系统调用,让所有的应用程序统一通过系统调用来访问系统资源,这里所说的系统资源包括文件,网络 ,内存,各类IO设备等。

应用程序可以进行系统调用,也可以调用标准库或者API,一个系统调用的内部有很多的步骤,比如需要进行用户态模式到内核态模式的互相切换。

这里简单介绍下模式切换,我们知道一个完整的应用程序分为两部分,一部分是应用程序的代码和数据,另一部分是内核的代码和数据,切换模式就是这两部分的分水岭,意味着处理器进入了一个不同的模式,不同的模式就是不同的世界,不同的世界就有不同的权限,而内核态模式就是王者,可以掌握所有的资源,用户态模式只能掌握自己的一亩三分地。

正如上面所说,系统调用需要进行模式切换,而每个完整的应用程序都有两个栈,一个用户栈,一个内核栈,这两个栈是独立的,用户栈在用户空间,内核栈在内核空间,因此切换模式时,栈也得切换。

因此我们可以将系统调用的执行步骤分为三步:1.执行前的准备工作。2.执行处理程序(处理函数)。3.执行后的善后工作,当然内核模式切换和栈切换就是1和3的工作了,这里的三步都是在内核模式下执行的,如下图所示

62bd8ed4e909073986a6fd0fbf33e28c.png

应用程序直接系统调用步骤

从上图得知,执行一个系统调用很复杂,需要干很多的活,Linux的编译器提供了很多共享库(so文件)来提供系统调用,例如Linux的glibc库就提供了文件操作相关的系统调用,例如下面的代码:

int read(int fd,void *buf,int count);//读文件数据
int write(int fd,const void *buf,int couint);//写文件数据
int open(const char * pathname,int flags,mode_t mode);//打开文件

上面的代码只是glibc库中几个比较有代表性的例子,linux操作系统提供了几百个系统调用,这些系统调用分散在各个共享库中,这里就不再阐述。

Windows操作系统提供了API,简称Windows API或者SDK,它不是系统调用而是对系统调用做了二次封装,这些API是由各类DLL(动态链接库)提供的,开发人员导入这些DLL就可以通过Windows API来开发Windows应用程序,因此Widows应用程序执行系统调用的步骤就变成了如下图所示

99f6d8a2e2917393c8cdf43b08d36903.png

Windows应用程序系统调用步骤

正如上文所述,每个操作系统都提供它各自的系统调用,那么写一段C代码怎样能做到跨操作系统呢?答案是C语言标准库,C语言标准库的目的就是让开发人员写一段C代码,这些C代码使用的是C标准库,那么这段代码不需要进行任何修改就可以跨操作系统,前提是经过不同操作系统编译器的编译,C标准库的调用关系如下图

d3e2d1ca1a08cd4843c5bc39e881eb83.png

C标准库

由上图得知,Linux通过共享库直接提供系统调用,而Windows则通过Windows API间接进行提供系统调用,中间增加了一个C标准库,它将不同操作系统之间系统调用标准化,做了二次封装,简化了系统调用的复杂度,提供给应用程序。

标准库也有它的缺点,缺点就是只能取各个操作系统系统调用的交集,这意味着只有操作系统都有的功能才能纳入到标准库,然后有的时候,需要一些操作系统专有的功能时,还得直接进行系统调用或者调用API,这个就会出现跨系统的问题。

对于用标准库开发的应用程序,它的系统调用步骤可以总结如下图

8dc45497b16c85bec91111af16a95845.png

C标准库系统调用

好了,【什么是系统调用】的话题介绍到这里了,下面来看看系统调用具体是怎么实现的。

2

系统调用的实现?

上个环节阐述的是【什么是系统调用】以及系统调用的大致步骤,这个环节将以Linux操作系统为例来阐述系统调用的实现原理和细节,当然其它操作系统系统调用的实现原理比较相似,可以举一反三。

主流的操作系统如Linux和Windows是通过中断来实现系统调用的。

以操作系统Linux(2.5以前),处理器为Inter IA-32为例,看看fork这个系统调用是怎么实现的,其它的Linux系统调用类似,整体过程如下图

94e9a9c3fb94cb01606fde10b5997e47.png

Linux系统调用过程

上图为系统调用涉及到的9个步骤,我们逐个看起

1.应用程序调用linux库提供的fork函数,发起一个fork系统调用,这个系统调用的目的是创建一个子进程,这个子进程拷贝一份父进程的虚拟进程空间。

2.fork函数的第一步就是将2放入寄存器eax,每个系统调用都有一个编号,2就是fork系统调用的编号,eax是默认用于传递系统调用编号的寄存器。

如果系统调用有参数,则将参数传入到如下的寄存器EBX,ECX,EDX,ESI,EDI,EBP,可以看出系统调用最多支持6个参数,fork系统调用没有参数。

fork函数的第二步就是执行中断指令int 0x80,中断指令int用于发送中断信号给处理器,0x80为中断向量号,这个向量号是系统调用中断处理程序专用。

int指令同时也会将模式从用户态切换到内核态,用户栈切换到内核栈,同时会将当前被中断的应用程序,中断时的寄存器内容入栈(SS,ESP,EFLAGS,CS,EIP),这里的入栈指的是入内核栈(每一个应用程序都一个用户栈和内核栈)。

整体来看,2步骤的汇编代码如下:

push EAX,2;//设置fork系统调用的系统调用编号
mov EBX,arg1;//可选,参数1
mov ECX,arg1;//可选,参数2
mov EDX,arg1;//可选,参数3
mov ESI,arg1;//可选,参数4
mov EDI,arg1;//可选,参数5
mov EBP,arg1;//可选,参数6
int 0x80;//发送系统调用中断信号

3.处理器执行完当前的指令后,会检查处理器的中断引脚,发现有中断信号,然后检查状态寄存器(EFLAGS),发现中断屏蔽IF标志是打开的(系统调用中断信号不会被屏蔽),处理器根据中断信号,分析出中断向量号,然后根据中断向量号去查找中断描述符表,找到了该中断向量号对应的中断处理程序。

4.操作系统跳转到中断处理程序,然后开始执行中断处理程序,0x80对应的中断处理程序是系统调用中断处理程序(system_call)。

该中断处理程序首先会将EAX,EBX,ECX,EDX,ESI,EDI,EBP这几个寄存器入栈,之所以入栈,就是为了防止后续的工作覆盖这些寄存器,核心汇编指令如下:

push EAX;
push EBX;
push ECX;
push EDX;
push ESI;
push EDI;
push EBP;

5.系统调用中断处理程序紧接着根据系统调用号(这里就是fork系统调用号即2),去系统调用表进行查找,可以找到该系统调用号对应的处理程序(也可以叫处理函数),Linux操作系统的系统处理函数一般以sys开头,fork的系统处理函数就是sys_fork。

6.找到了系统处理函数后,开始执行该函数,处理函数可以从内核栈中获取函数的参数,函数执行完成后,函数的返回值,默认采用EAX寄存器进行返回。

7~8.系统处理函数执行完成后,回到了系统调用中断处理程序,中断处理程序执行iret指令,iret指令负责从内核态切换到用户态,将内核态入栈的寄存器数据出栈到SS,ESP,EFLAGS,CS,EIP这几个寄存器,然后跳转到系统调用处。

9.系统调用fork返回到应用程序。

3

Linux操作系统(2.5以前)的系统调用实现原理阐述完了,Windows操作系统的系统调用也采用类似的机制,另外要说的是,自从Linux(2.5)以上,处理器Inter 奔腾二代以后,为了提高系统调用的效率,Inter处理器提供了两个指令来进行系统调用的进入和退出即sysenter和sysexit指令。

sysenter指令代替了int中断指令发起系统调用,执行这个指令后,会直接跳转到一个系统调用的处理函数地址处,去执行系统调用,这个处理函数的地址是存储在一个指定的寄存器中,sysenter这个指令也负责模式的切换和应用程序现场寄存器的备份,这一点同int一样,处理函数参数的传递跟以前一样,还是通过寄存器的方式传递,没有变化。

sysexit指令代替了iret恢复指令,它负责模式切换和现场寄存器的恢复,这一点同iret指令相似。

其它的操作系统例如Power PC,AMD的系统调用与Linux(2.5以上)类似,不同的是,它们采用不同的指令来进行模式切换和寄存器备份,参数的传递也是采用寄存器的方式,只是寄存器个数和名称不一样罢了。

转自:一口Linux 并做整理


推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

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

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

相关文章

Kubernetes(k8s)集群部署(k8s企业级Docker容器集群管理)系列目录

0、目录 整体架构目录:ASP.NET Core分布式项目实战-目录 k8s架构目录:Kubernetes(k8s)集群部署(k8s企业级Docker容器集群管理)系列目录 一、感谢 在此感谢.net core社区的帮助。感谢。 二、系列部署目录 0、部署环境规划 1、自签T…

每天都用手机,你对麦克风了解吗?

简 介: 通过对于实际驻极体MIC进行拆解,看到其中的结构,对比起工作原理,实在令人难以想象它的工作机制是可行的,尽管现在它已经广泛应用在周围很多电子设备中。关键词: 驻极体,MIC01 驻极体话筒…

好了,我不想回深圳了~

国庆节算长假,一共七天,高速免费。如果一个人,待在家里睡上七天,可能我在第二天就会特别无聊,想找事情做,因为国庆离开深圳的人很多,我曾经有一次放假去球场打球,结果很失落&#xf…

开源微信管家平台——JeeWx 捷微4.0 微服务版本发布,全新架构,全新UI,提供强大的图文编辑器...

JeeWx捷微4.0 微服务版本发布^_^ 换代产品(全新架构,全新UI,提供强大的图文编辑器) JEEWX 从4.0版本开始,技术架构全新换代,采用微服务架构,插件式开发,每个业务模块都是独立的JAR…

手把手教用XNA开发winphone7游戏(三)

XNA Game Studio 游戏循环 在这部分中您将重点两剩余部分的游戏 — — 重写Update 和 Draw 功能。有些大大可能看过相关微软的训练包,我这里主要是帮一些初学者。希望各位大大包含,毕竟文章发出来还是有工作量的。大家觉得有用就好,要是没有耽…

我的代码很好,不需要写注释

作者 | Sheetal 译者 | 弯月 责编 | 王晓曼 有时候,我们会写一些非常有创意的注释,而有些注释确实让人不得不佩服 程序员的想象力。看到下面这些注释,相信每个人都会捧腹大笑。【1】#想了解递归,请参见文件末尾 . .(代…

陈潇冰php,webpack4.x入门到进阶

课程详情(本课程所涉及内容)1. webpack是什么?webpack的作用2. webpack的整体构成3. webpack-cli、package.json4. 开发环境(development)和生产环境(production),npm安装包的方式,-D、-S5. 跑一跑webpack6. webpack.config.js配置总览7. 入口配置形式&…

SpringBoot开发案例之整合Spring-data-jpa

什么是spring-data 为了简化程序与数据库交互的代码,spring提供了一个现成的dao层框架,spring家族提供的spring-data适用于关系型数据库和nosql数据库 什么是jpa JPA全称为Java持久性API(Java Persistence API),JPA是j…

细说路由器

介绍以太网交换机工作在第二层即数据链路层,用于在同一网络内部转发以太网帧。但是,当源和目的IP地址位于不同网络时,以太网帧必须发送给路由器。路由器负责在不同网络间传输报文,通过路由表来决定最佳转发路径。当主机将报文发送…

乔布斯,影响了一个时代的人

2011年10月5日,苹果公司的创始人史蒂夫乔布斯,因患胰腺神经内分泌肿瘤病逝,享年56岁,一代传奇人物,与世长辞乔布斯被认为是计算机业界与娱乐业界的标志性人物,同时人们也把他视作麦金塔计算机、iPod、iPhon…

C++ 版本ORM访问数据库之ODB访问oracle的Demo(三)

ODB的组成部分: 1: 操作系统的ODB编译器 2: odb核心库libodb 3: 各种数据库的相关链接库 使用ODB访问数据需要的库和头文件(不懂, 请看https://www.cnblogs.com/hul201610101100/p/9482311.html): lib库: odb-oracle-d.lib, odb-d.lib (由libodb-oracle-2.4.0编译成功后产生的l…

平均年薪60.8万,Linux开发拿下这个证书有多吃香?

互联网行业竞争一年比一年严峻,随着互联网的发展和进步,很多人都是想要进军到编程行业中去,作为工程师的我们唯有不停地学习,不断的提升自己才能保证自己的核心竞争力,打破内卷。从而拿到更好的薪水,进入心…

Linux新手必须掌握的命令(2)

一、输入输出重定向 输入重定向是指把文件导入到命令中,而输出重定向则是指把原本要输出到屏幕的数据信息写入到指定文件中。 在日常的学习和工作中,相较于输入重定向,我们使用输出重定向的频率更高。 所以又将输出重定向分为了标准输出重定向…

极限编程与敏捷开发(4)

解决方案一: 下面图1是一种最简单的解决方案,Switch对象可以轮询真实开关的状态,并且可以发送相应的turnOn和turnOff消息给Light。 图1解决方案二: 上面这个设计违反了两个设计原则:依赖倒置原则(DIP)和开放封闭原则(O…

虚拟机四种网络连接模式比较

虚拟机一直用,但选择网络时的四种模式总是搞不清楚,只知道选择bridge最好用。为了能更深入了了解,查询了些资料,总结如下 第一种 NAT模式 Vhost访问网络的所有数据都是由主机提供的,vhost并不真实存在于网络中&#xf…

CPU加了缓存后,有人急了~

Hi,我是CPU一号车间的阿Q,还记得我吗,真是好久不见了~我所在的CPU是一个八核CPU,就有八个工作车间,那运行起来速度杠杆的~虚拟地址翻译一大早,我们一号车间MMU(内存管理单…

redis -- 学习

redis 安装 就不细说了。 可以看这个 地址 https://www.cnblogs.com/feijl/p/6879929.html 配置完成之后 连接不上redis 如果报错守护模式 解决办法 1.修改redis配置 redis.conf 守护模式不启用 如下 2.第二种 启动redis后 设置密码 先查看是否设置了 config get requirepass…

一个学妹写的按键检测函数把我秀翻了!

摘要:今年实验室来了三个学妹,其中一个学妹以前是物联网专业的,进了实验室老师二话没说:先把STM32单片机过一遍上来第一个例程就是使用按键点亮一个LED灯,好家伙。点灯小师弟比较在行,毕竟32、FPGA、Linux的…

嵌入式行业需要什么样的技术人才?

关注「嵌入式大杂烩」,选择「星标公众号」一起进步!来源 | 巧学模电数电单片机嵌入式行业需要什么样的技术人才?仔细观察各种招聘的岗位要求吧,无非是两方面。1)通用要求比如什么学历,多少年工作经验&#…

消除VIM光标闪烁

2019独角兽企业重金招聘Python工程师标准>>> VIM光标闪烁比较影响人读代码的心情,消除光标闪烁,在配置文件中写下set gcra:block-blinkon0 保存并重启VIM 即可消除光标闪烁。 转载于:https://my.oschina.net/tonyyang/blog/10240