首页 > 编程知识 正文

系统调用过程,linux命令调用

时间:2023-05-05 19:25:59 阅读:153744 作者:2842

前言:本文只讨论linux上的系统调用,不考虑windows等其他操作系统。

两点:

1 .对于系统调用,进程调用操作系统内核函数,而不是进程。

2 .对于系统调用,会出现上下文切换,但与进程调度时的上下文切换不同。

Part1:

早期,内存使用的是哈佛结构。 这意味着程序和数据是分开存储的,这种划分是硬件级的。 到了现代,存储器采用了冯诺伊曼结构。 也就是说,程序和数据保存在一起。 但是,在linux中,存储器在逻辑上分为内核部分和进程部分两部分。 内核的一部分由操作系统的内核使用,进程的一部分由进程使用。

Part2:

百度“系统调用”解读:

操作系统的主要功能是管理硬件资源,为APP应用程序开发人员提供合适的环境,以提高APP应用程序兼容性。 为了实现这个目的,内核通过一组称为系统调用的接口向用户提供具有特定功能的内核函数。 系统调用将对APP应用的请求传递给内核,调用相应的内核函数完成所需的处理,并将处理结果返回给APP应用。

现代操作系统通常具有多任务功能,通常通过进程实现。 由于操作系统在每个进程之间快速切换和运行,所以一切看起来都是同时的。 此外,还存在许多安全问题,例如一个进程可以轻松地更改进程内存区域中的数据,从而使另一个进程发生异常或达到目的。 因此,操作系统必须确保所有进程都能安全运行。 这个问题的解决方法是在处理器中嵌入基址寄存器和边界寄存器。 这两个寄存器的内容通过硬件限制对存储器的访问命令访问的存储器的地址。 这样,在系统切换进程时,可以将两个寄存器的内容写入分配给进程的地址范围,从而避免恶意软件。

为了防止用户程序更改基址寄存器和边界寄存器的内容而访问其他内存空间,必须用特殊指令访问这两个寄存器。 处理器通常有两种模式:“用户模式”和“内核模式”,通过一个标签位识别当前的模式。 只能在内核模式下执行更改基址寄存器内容的命令等,但在用户模式下,硬件会直接跳过该命令并继续执行下一个命令。

同样,由于安全问题,某些I/O操作指令被限制为只能在内核模式下执行。 因此,操作系统必须为APP应用程序提供接口,例如读取磁盘位置的数据。 这些接口被称为系统调用。

操作系统收到系统调用请求后,将处理器置于内核模式,执行I/O操作、更改基址寄存器内容等指令,处理系统调用内容后,操作系统将处理器返回用户模式并执行用户代码

系统调用本质上是APP请求操作系统内核完成功能时的过程调用,但它是一种特殊的过程调用,与常见的过程调用有很大的不同:

)1)在不同的系统状态下运行。 的典型过程调用在调用方和被调用方相同的状态——系统状态或用户状态下执行。 系统调用和一般调用的最大区别在于调用方在用户状态下执行,而被调用的程序在系统状态下执行。

)2)状态切换通过软中断进行。 典型的过程调用不参与系统状态转换,因此可以直接从调用过程迁移到被调用的过程。 但是,执行系统调用时,调用进程和调用进程在不同的系统状态下工作,因此不允许直接从调用进程转移到调用进程。 通常通过软中断机制,首先从用户状态转换为系统状态,经过核心分析,再过渡到相应的系统调用处理子程序。

)3)回到问题。 在采用抢占式调度方式的系统中,被调用的进程被执行后,必须优先分析系统中所有要求执行的进程。 如果调用进程仍具有最高优先级,请返回到调用进程并继续执行。 否则,将发生重新调度,以确保具有最高优先级的进程优先运行。 调用进程被排入就绪队列。

)4)嵌套调用。 和一般过程一样,系统调用也可以嵌套。 这意味着在调用的过程正在执行时,可以使用系统调用命令调用另一个系统调用。 当然,每个系统都有嵌套调用的深度限制。 例如,最大深度为6。 但一般过程对嵌套深度没有任何限制[1]。

百度“内核函数”解读:

操作系统的核心(内核)包含一组用于实现各种系统功能的子程序(过程),并提供给APP调用或内核支持函数。 这些函数是操作系统系统自身程序模块的一部分,因此为了保护操作系统程序免受用户程序的影响,一般不允许用户程序访问操作系统的程序和数据。 因此,不允许APP应用程序以一般的过程调用方式直接调用这些过程,而是为APP应用程序提供一系列系统调用指令,即程序接口,APP应用程序通过系统调用所需的内核支持函数

Part3:

Part4:

总结:

出于安全原因,某些进程的权限受到限制,无法自己执行IO等操作。 但是,linux系统提供了一种可以通过操作系统内核实现进程的机制。 这个机制就是系统调用。 例如,如果进程需要读取磁盘上的数据,则通过系统调用将数据从内核函数()磁盘读取到内核的内存空间中,并将数据从内核内存空间复制到进程的内存空间中。

流程如何实现系统调用呢? 答案将通过软中断来实现。 软中断时有上下文开关吗?

在linux上,内存为

又被分为两部分:内核空间和进程空间。内核空间归操作系统内核使用,进程空间归进程使用。这是一种笼统的说法。事实上,linux内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每个进程会有两个栈:一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。
当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。
当进程因为中断或者系统调用而陷入内核态之行时,进程所使用的堆栈也要从用户栈转到内核栈。进程陷入内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈里面的用户栈的地址恢复到cpu堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。
那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们是如何知道内核栈的地址的呢?
关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户栈,当进程陷入到内核态时,内核栈保存进程在内核态运行的相关信息,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部删除。因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。(如果不是出于某种设计理念,linux大可不必在创建进程的时候,就为进程在内核空间中开辟内核栈,选择在进程由用户态陷入内核态时在内核空间中为进程开辟内核栈也行。)
综上所述,进程从用户态陷入内核态到从内核态恢复到用户态的过程中,出现了上下文切换。不过,这和发生在进程之间的调度时的上下文切换还是有区别的。值得注意的是,在采用抢先式调度的系统中,当系统调用返回时,要重新进行调度分析是否有更高优先级的进程就绪,这时候就有可能出现进程之间的调度了。

Part5:
IO模型的发展与系统调用
BIO模型

BIO的问题:线程太多!出现线程太多的根本原因就是阻塞:每增加一个客户端,主线程就clone一个子线程去服务,而且子线程还是采用阻塞的方式,一个子线程只能服务一个客户端。当客户端数量过多时,子线程数量也跟着变多。线程过多又会引发以下问题:
1)消耗内存:线程的栈区是独享的,过多线程会消耗大量内存。
2)消耗cpu:创建线程是通过clone来实现,clone的过程需要系统调用。创建过多线程需要频繁系统调用,导致内核大量占用cpu资源。此外,过多线程频繁切换也会消耗cpu。
解决办法:只有采用非阻塞NIO才能进一步提升性能。
NIO模型

问题:
因为每询问一个客户端连接fd时,都要发生一次系统调用。当客户端连接太多时,每次循环内,就会出现大量的系统调用,这非常消耗cpu资源。而问题关键在于,只有部分客户端向服务端发送了数据,这就造成了资源浪费。
Select模型

Select模型的特征:内核增加了一个select的系统调用,该调用有一个参数fds。fds是所有客户端连接,内核收到select系统调用后,只会向程序返回向程序发送了数据的连接,从而过滤掉了无效连接。程序收到select(fds)的返回结果,再次发生系统调用recvfrom,但是和NIO相比,压力大大减轻,因为只需要处理那些发送了数据的连接。整个过程是同步的。Select又称为多路复用器。
问题:fds数据量不能太多
1)select(fds)需要传输fds,当fds数据量比较大时,从用户空间将fds拷贝至内核空间很消耗cpu资源
2)内核在接收到select后,当fds数据量比较大时,需要处理大量数据,内核因此又要大量占用cpu时间,导致cpu被浪费在内核空间。
Epoll模型

解释:
1)调用epoll_create()在linux内核中建立一个epoll数据结构,这个数据结构包含两个重要的成员:红黑树和链表。
2)调用epoll_ctl将服务端的socket添加进红黑树。
3)调用epoll_wait将链表返回给用户空间的服务端。服务端根据这个结果处理客户端的数据读写请求。
注意:
1)epoll_wait还会将新的客户端(也是socket)连接加入epoll,也就是红黑树。
2)将红黑树中的状态同步到链表是由事件驱动回调函数来完成的。
Epoll高效的原因。
1.共享内存,免去拷贝fd造成的开销。
2.基于事件驱动,不再是轮询。

小结:从BIO->NIO->select->poll->epoll,是一个逐步改进的过程,其驱动力就是来源于尽可能减少系统调用。因为无论是创建线程,还是传递数据,都需要进行系统调用。

Part6:
jvm通过内存的变迁来避免更多的系统调用,进而提高IO效率。如下:



解释:
方法区和堆一样也是各个线程的共享区域,主要包含了被虚拟机加载的类型变量,常量,静态变量,即时编译后的代码缓存等数据。
永久代和元空间最大的区别在于元空间不再使用虚拟机内存而是使用本地内存,元空间大小只受本地内存限制。参数-XX:MetaspaceSize和-XX:MaxMetaspaceSize可以调整元空间大小。之所以用元空间取代永久代,是因为永久代中存储字符串和数组容易出现性能问题和内存溢出,而且类和方法信息难以确定,要指定永久代大小不容易,太小容易导致永久代溢出,太大容易导致老年代溢出。而且,在永久代进行GC复杂度高,效率低。JDK7的HotSpot已经把原本存放在永久代的字符串常量,静态变量等移除,到JDK1.8直接放弃了永久代的概念,在本地内存中实现了元空间来代替。把JDK7中永久代还剩余的内容全部挪到了元空间。
JDK8 HotSpot JVM 使用本地内存来存储类元数据信息并称之为:元空间(Metaspace);这与Oracle JRockit 和IBM JVM’s很相似。这将是一个好消息:意味着不会再有java.lang.OutOfMemoryError: PermGen问题,也不再需要你进行调优及监控内存空间的使用,但是新特性不能消除类和类加载器导致的内存泄漏。你需要使用不同的方法以及遵守新的命名约定来追踪这些问题。

什么是直接内存?
堆外内存和本地内存含义接近,直接内存和它们还是有区别的,直接内存是位于本地内存中更特殊的一块内存。在内核态期间,调用ByteBuffer.allocateDirect()时,会分配一块直接内存区间(direct memory),jvm和操作系统可以共享该区域,IO时可以减少一次读写操作。

参考:
1
2
3

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。