0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Goの豆知識――天地開闢のGmain

Last updated at Posted at 2025-10-10

Goランタイムが「動き始める瞬間」を覗く

はじめに

func main() {
    fmt.Println("hello world")
}

func main() が実行される前、Go
プログラムはすでに密かにランタイムの世界を立ち上げています。
Gmain はこの混沌とした世界に送り込まれた最初の「エンジニア」です、スケジューラの起動、GC(ガーベジコレクション)の準備、パッケージ初期化を担当し、最終的に制御を
main() に引き渡す使命を持っています。

本記事では、「hello world」が実行される前に何が起きているのか、Goランタイムの起動プロセスを遡って追っていきます。


起動とスケジューラ初期化

Go プログラムの最も低レベルなエントリポイントは、アセンブリで実装された _rt0_amd64() です。
以下の初期化処理を行います。

  • スタックの初期化
  • TLS(スレッドローカルストレージ)の設定
  • 引数の受け渡し

その後、runtime.rt0_go()
が呼び出され、Go層のランタイム処理に入ります。
この時点で Go はスケジューラの中核をなす3つの構造体を生成します:

  • M(Machine):OSスレッド。最初の M は m0(メインスレッド)
  • G(Goroutine):実行単位。最初に g0(システムgoroutine)と
    Gmain(最初のユーザーgoroutine)が生成される
  • P(Processor):論理プロセッサ。スケジューラキューとタイマーを保持

img.png


runtime.main() の初期化フロー解析

runtime.main() は Gmain
が実行する最重要関数であり、ユーザーコードを動かすために必要なすべての準備がここで行われます。

func main() {
    // 現在の Gmain が属する M を取得
    mp := getg().m
    mp.g0.racectx = 0

    // スタック最大値設定(64bitで1GB、32bitで250MB)
    if goarch.PtrSize == 8 {
        maxstacksize = 1000000000
    } else {
        maxstacksize = 250000000
    }
    // 安全上限。スタックが過剰に拡張して異常を起こすのを防ぐためです
    maxstackceiling = 2 * maxstacksize

    // ユーザーコード実行の準備開始フラグ
    mainStarted = true

    // システムモニタスレッド起動(GC、スケジューラ維持、タイマー管理など)
    if haveSysmon {
        systemstack(func() {
            newm(sysmon, nil, -1)
        })
    }

    // Gmain を M0 に固定
    lockOSThread()

    // M0 以外ならエラー
    if mp != &m0 {
        throw("runtime.main not on m0")
    }

    // 起動時間記録
    runtimeInitTime = nanotime()
    if runtimeInitTime == 0 {
        throw("nanotime returning zero")
    }

    // トレース機能の有効化確認
    if debug.inittrace != 0 {
        inittrace.id = getg().goid
        inittrace.active = true
    }

    // runtime パッケージ内部の初期化(メモリアロケータ、スケジューラ、GMP構造体など)
    doInit(runtime_inittasks)

    // 終了時にスレッドロックを解除するよう defer 設定
    defer func() {
        unlockOSThread()
    }()

    // GCを有効化
    gcenable()
    // 初期化完了同期用チャネル
    main_init_done = make(chan bool)

    // すべてのモジュールデータを走査し、各パッケージの init() を実行
    for m := &firstmoduledata; m != nil; m = m.next {
        doInit(m.inittasks)
    }
    // 初期化完了を通知
    close(main_init_done)
    // スレッドロック解除
    unlockOSThread()

    // ユーザーの main 関数を呼び出す
    fn := main_main
    fn()

    // 終了フック(defer や os.Exit 前の処理)を実行
    runExitHooks(0)
    // プロセス終了
    exit(0)
    // 安全策。ここに到達したら panic
    for {
        var x *int32
        *x = 0
    }
}

主要フェーズの概要

  • グローバル設定maxstacksizemainStarted
  • sysmon 起動:GC・スケジューラ・タイマーのバックグラウンド管理
  • runtime 初期化doInit(runtime_inittasks)
  • GC 有効化gcenable()
  • 全パッケージの init() 実行
  • ユーザー main() 実行
  • 終了処理とプロセス終了
_start → rt0_go → schedinit → newproc(Gmain) → runtime.main → user main.main

まとめ

Gmain は Go プログラムの起動過程において、最も重要な節目のひとつです。

ランタイムの初期化(メモリアロケータ、スケジューラ、GC、M0/P0/G0
の構築)が完了すると、システムは Gmain を生成し、runtime.main() の実行を任せます。
ここから高レベルの初期化が始まり、ユーザーコードへと制御が渡ります。

言い換えれば、Gmain は Go ランタイム世界の境界点とも言えます
その前はランタイムの準備段階、その後はユーザーロジックの実行段階。

この設計により、Go プログラムは完全に制御された環境下で初期化を行い、すべての
goroutine、メモリ、スケジューラが整合性の取れた状態で動作を開始します。

これこそが、Go
が高速な起動と安定した並行処理を実現できる根幹の理由の一つなのです。

Gopher image

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?