我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Go调度器学习之系统调用的方法是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Go调度器学习之系统调用的方法是什么

本篇内容主要讲解“Go调度器学习之系统调用的方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go调度器学习之系统调用的方法是什么”吧!

1. 系统调用

下面,我们将以一个简单的文件打开的系统调用,来分析一下Go调度器在系统调用时做了什么。

1.1 场景

package mainimport (   "fmt"   "io/ioutil"   "os")func main() {   f, err := os.Open("./file")   if err != nil {      panic(err)   }   defer f.Close()   content, err := ioutil.ReadAll(f)   if err != nil {      panic(err)   }   fmt.Println(string(content))}

如上简单的代码,读取一个名为file的本地文件,然后打印其数据,我们通过汇编代码来分析一下其调用过程:

$ go build -gcflags "-N -l" -o main main.go
$ objdump -d main >> main.i

可以发现,在main.i中,从main.main函数,对于文件Open操作的调用关系为:main.main -> os.Open -> os.openFile -> os.openFileNolog -> syscall.openat -> syscall.Syscall6.abi0 -> runtime.entersyscall.abi0,而Syscall6的汇编如下:

TEXT ·Syscall6(SB),NOSPLIT,$0-80
   CALL   runtime·entersyscall(SB)
   MOVQ   a1+8(FP), DI
   MOVQ   a2+16(FP), SI
   MOVQ   a3+24(FP), DX
   MOVQ   a4+32(FP), R10
   MOVQ   a5+40(FP), R8
   MOVQ   a6+48(FP), R9
   MOVQ   trap+0(FP), AX // syscall entry
   SYSCALL
   CMPQ   AX, $0xfffffffffffff001
   JLS    ok6
   MOVQ   $-1, r1+56(FP)
   MOVQ   $0, r2+64(FP)
   NEGQ   AX
   MOVQ   AX, err+72(FP)
   CALL   runtime·exitsyscall(SB)
   RET
ok6:
   MOVQ   AX, r1+56(FP)
   MOVQ   DX, r2+64(FP)
   MOVQ   $0, err+72(FP)
   CALL   runtime·exitsyscall(SB)
   RET

1.2 陷入系统调用

可以发现,系统调用最终会进入到runtime.entersyscall函数:

func entersyscall() {   reentersyscall(getcallerpc(), getcallersp())}

runtime.entersyscall函数会调用runtime.reentersyscall

func reentersyscall(pc, sp uintptr) {   _g_ := getg()   // Disable preemption because during this function g is in Gsyscall status,   // but can have inconsistent g->sched, do not let GC observe it.   _g_.m.locks++   // Entersyscall must not call any function that might split/grow the stack.   // (See details in comment above.)   // Catch calls that might, by replacing the stack guard with something that   // will trip any stack check and leaving a flag to tell newstack to die.   _g_.stackguard0 = stackPreempt   _g_.throwsplit = true   // Leave SP around for GC and traceback.   save(pc, sp)  // 保存pc和sp   _g_.syscallsp = sp   _g_.syscallpc = pc   casgstatus(_g_, _Grunning, _Gsyscall)   if _g_.syscallsp < _g_.stack.lo || _g_.stack.hi < _g_.syscallsp {      systemstack(func() {         print("entersyscall inconsistent ", hex(_g_.syscallsp), " [", hex(_g_.stack.lo), ",", hex(_g_.stack.hi), "]\n")         throw("entersyscall")      })   }   if trace.enabled {      systemstack(traceGoSysCall)      // systemstack itself clobbers g.sched.{pc,sp} and we might      // need them later when the G is genuinely blocked in a      // syscall      save(pc, sp)   }   if atomic.Load(&sched.sysmonwait) != 0 {      systemstack(entersyscall_sysmon)      save(pc, sp)   }   if _g_.m.p.ptr().runSafePointFn != 0 {      // runSafePointFn may stack split if run on this stack      systemstack(runSafePointFn)      save(pc, sp)   }   // 一下解绑P和M   _g_.m.syscalltick = _g_.m.p.ptr().syscalltick   _g_.sysblocktraced = true   pp := _g_.m.p.ptr()   pp.m = 0   _g_.m.oldp.set(pp)  // 存储一下旧P   _g_.m.p = 0   atomic.Store(&pp.status, _Psyscall)   if sched.gcwaiting != 0 {      systemstack(entersyscall_gcwait)      save(pc, sp)   }   _g_.m.locks--}

可以发现,runtime.reentersyscall除了做一些保障性的工作外,最重要的是做了以下三件事:

  • 保存当前goroutine的PC和栈指针SP的内容;

  • 将当前goroutine的状态置为_Gsyscall

  • 将当前P的状态置为_Psyscall,并解绑P和M,让当前M陷入内核的系统调用中,P被释放,可以被其他找工作的M找到并且执行剩下的goroutine

1.3 从系统调用恢复

func exitsyscall() {   _g_ := getg()   _g_.m.locks++ // see comment in entersyscall   if getcallersp() > _g_.syscallsp {      throw("exitsyscall: syscall frame is no longer valid")   }   _g_.waitsince = 0   oldp := _g_.m.oldp.ptr()  // 拿到开始存储的旧P   _g_.m.oldp = 0   if exitsyscallfast(oldp) {      if trace.enabled {         if oldp != _g_.m.p.ptr() || _g_.m.syscalltick != _g_.m.p.ptr().syscalltick {            systemstack(traceGoStart)         }      }      // There's a cpu for us, so we can run.      _g_.m.p.ptr().syscalltick++      // We need to cas the status and scan before resuming...      casgstatus(_g_, _Gsyscall, _Grunning)      ...      return   }   ...   // Call the scheduler.   mcall(exitsyscall0)   // Scheduler returned, so we're allowed to run now.   // Delete the syscallsp information that we left for   // the garbage collector during the system call.   // Must wait until now because until gosched returns   // we don't know for sure that the garbage collector   // is not running.   _g_.syscallsp = 0   _g_.m.p.ptr().syscalltick++   _g_.throwsplit = false}

其中,exitsyscallfast函数有以下个分支:

  • 如果旧的P还没有被其他M占用,依旧处于_Psyscall状态,那么直接通过wirep函数获取这个P,返回true;

  • 如果旧的P被占用了,那么调用exitsyscallfast_pidle去获取空闲的P来执行,返回true;

  • 如果没有空闲的P,则返回false;

//go:nosplitfunc exitsyscallfast(oldp *p) bool {   _g_ := getg()   // Freezetheworld sets stopwait but does not retake P's.   if sched.stopwait == freezeStopWait {      return false   }   // 如果上一个P没有被其他M占用,还处于_Psyscall状态,那么直接通过wirep函数获取此P   // Try to re-acquire the last P.   if oldp != nil && oldp.status == _Psyscall && atomic.Cas(&oldp.status, _Psyscall, _Pidle) {      // There's a cpu for us, so we can run.      wirep(oldp)      exitsyscallfast_reacquired()      return true   }   // Try to get any other idle P.   if sched.pidle != 0 {      var ok bool      systemstack(func() {         ok = exitsyscallfast_pidle()         if ok && trace.enabled {            if oldp != nil {               // Wait till traceGoSysBlock event is emitted.               // This ensures consistency of the trace (the goroutine is started after it is blocked).               for oldp.syscalltick == _g_.m.syscalltick {                  osyield()               }            }            traceGoSysExit(0)         }      })      if ok {         return true      }   }   return false}

exitsyscallfast函数返回false后,则会调用exitsyscall0函数去处理:

func exitsyscall0(gp *g) {   casgstatus(gp, _Gsyscall, _Grunnable)   dropg() // 因为当前m没有找到p,所以先解开g和m   lock(&sched.lock)   var _p_ *p   if schedEnabled(gp) {      _p_ = pidleget() // 还是尝试找一下有没有空闲的p   }   var locked bool   if _p_ == nil { // 如果还是没有空闲p,那么把g扔到全局队列去等待调度      globrunqput(gp)      // Below, we stoplockedm if gp is locked. globrunqput releases      // ownership of gp, so we must check if gp is locked prior to      // committing the release by unlocking sched.lock, otherwise we      // could race with another M transitioning gp from unlocked to      // locked.      locked = gp.lockedm != 0   } else if atomic.Load(&sched.sysmonwait) != 0 {      atomic.Store(&sched.sysmonwait, 0)      notewakeup(&sched.sysmonnote)   }   unlock(&sched.lock)   if _p_ != nil { // 如果找到了空闲p,那么就去执行,这个分支永远不会返回      acquirep(_p_)      execute(gp, false) // Never returns.   }   if locked {      // Wait until another thread schedules gp and so m again.      //      // N.B. lockedm must be this M, as this g was running on this M      // before entersyscall.      stoplockedm()      execute(gp, false) // Never returns.   }   stopm() // 这里还是没有找到空闲p的条件,停止这个m,因为没有p,所以m应该要开始找工作了   schedule() // Never returns. // 通过schedule函数进行调度}

exitsyscall0函数还是会尝试找一个空闲的P,没有的话就把goroutine扔到全局队列,然后停止这个M,并且调用schedule函数等待调度;如果找到了空闲P,则会利用这个P去执行此goroutine

2. 小结

通过以上分析,可以发现goroutine有关系统调用的调度还是比较简单的:

  • 在发生系统调用时会将此goroutine设置为_Gsyscall状态;

  • 并将P设置为_Psyscall状态,并且解绑M和P,使得这个P可以去执行其他的goroutine,而M就陷入系统内核调用中了;

  • 当该M从内核调用中恢复到用户态时,会优先去获取原来的旧P,如果该旧P还未被其他M占用,则利用该P继续执行本goroutine

  • 如果没有获取到旧P,那么会尝试去P的空闲列表获取一个P来执行;

  • 如果空闲列表中没有获取到P,就会把goroutine扔到全局队列中,等到继续执行。

可以发现,如果系统发生着很频繁的系统调用,很可能会产生很多的M,在IO密集型的场景下,甚至会发生线程数超过10000的panic事件。

到此,相信大家对“Go调度器学习之系统调用的方法是什么”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Go调度器学习之系统调用的方法是什么

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Go调度器学习之系统调用的方法是什么

本篇内容主要讲解“Go调度器学习之系统调用的方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go调度器学习之系统调用的方法是什么”吧!1. 系统调用下面,我们将以一个简单的文件打开的系统
2023-07-05

Go调度器学习之系统调用详解

这篇文章肿,将以一个简单的文件打开的系统调用,来分析一下Go调度器在系统调用时做了什么。文中的示例代码讲解详细,需要的可以参考一下
2023-05-14

Torch中的学习率调度器是什么

在PyTorch中,学习率调度器是一种用于动态调整优化算法中学习率的方法。学习率调度器可以根据训练过程中的不同阶段或条件来自动调整学习率,以提高训练的效果和稳定性。常见的学习率调度器包括 StepLR、MultiStepLR、Expone
Torch中的学习率调度器是什么
2024-03-14

Linux系统调整屏幕亮度的方法是什么

今天就跟大家聊聊有关Linux系统调整屏幕亮度的方法是什么,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。对于程序员来说熬夜就是是经常的使用,那么Linux系统如何如何调整屏幕亮度呢?
2023-06-28

golang调度器的用法是什么

Golang调度器是Go编程语言中的一种机制,用于协调并发执行的goroutine。调度器负责在可用的处理器上调度goroutine的执行,并管理它们的执行状态。调度器的使用方式是隐式的,开发者无需手动控制调度器的行为。当程序启动时,调度器
2023-10-20

linux调用系统内核函数的方法是什么

在Linux中,调用系统内核函数的方法主要有以下几种:1. 使用系统调用(system call):系统调用是用户程序通过软中断(软中断号为0x80)请求操作系统内核提供的服务。用户程序通过执行int 0x80指令触发软中断,将要调用的系统
2023-10-18

Golang并发编程之调度器初始化的方法是什么

本篇内容主要讲解“Golang并发编程之调度器初始化的方法是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Golang并发编程之调度器初始化的方法是什么”吧!1. 一些全局变量在proc.g
2023-07-05

Golang并发编程之main goroutine的创建与调度的方法是什么

今天小编给大家分享一下Golang并发编程之main goroutine的创建与调度的方法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们
2023-07-05

php服务器内互相调用的方法是什么

在PHP服务器内,互相调用的方法有以下几种:1. 直接调用:在一个PHP文件中,可以直接调用另一个PHP文件中的函数或方法。通过在调用文件中使用include或require语句来引入被调用文件,然后直接使用被调用文件中的函数或方法。示例:
2023-08-08

P/Invoke之C#调用动态链接库DLL的方法是什么

这篇文章主要介绍了P/Invoke之C#调用动态链接库DLL的方法是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇P/Invoke之C#调用动态链接库DLL的方法是什么文章都会有所收获,下面我们一起来看看吧
2023-07-05

搭建云服务器及调用接口的方法是什么

搭建云服务器的方法:1.选择云服务器提供商。2.注册账号并完成实名认证。3.选择合适的云服务器规格和操作系统,如Linux或Windows。4.购买云服务器并等待服务器创建完成。5.登录服务器并进行配置,包括安装所需的软件和服务。调用接口的
2023-06-11

轻量应用服务器带宽调整方法是什么样的

轻量应用服务器(LightApplicationServer)是一种轻量级的应用服务器,用于处理小型网站或应用程序。它们通常采用低端、廉价的X服务器和一些轻量化的处理器,以及一些软件模块来构建。这些服务器可以通过配置或管理软件升级来增加服务器的数量,以满足不断变化的需求。为了轻量应用服务器的带宽调整,一些方法可以包括:自动调整-轻量应用服务器通常有一个内置的应用程序控制台来监控带宽使用情况,
2023-10-26

Go语言中init函数和defer延迟调用关键词的方法是什么

这篇文章主要介绍“Go语言中init函数和defer延迟调用关键词的方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go语言中init函数和defer延迟调用关键词的方法是什么”文章能帮助大
2023-07-05

断言:模拟:我不知道要返回什么,因为方法调用是意外的 在 Go 中编写单元测试时出错

php小编小新在这篇文章中将为您介绍在Go语言中编写单元测试时出现的一种常见错误,即断言错误。当我们在编写单元测试时,有时会遇到无法确定返回值的情况,这会导致意外的方法调用错误。在本文中,我们将讨论这个问题的原因和解决方法,帮助您更好地处理
断言:模拟:我不知道要返回什么,因为方法调用是意外的 在 Go 中编写单元测试时出错
2024-02-10

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录