どのようにチェーンを延期するためにトラバースされます

昨年の書き込み記事を始めた、最初の章では、延期、比較文学と芸術の名前についてです:「簡単Golang入札トラップ延期解決するには」、またTucao。そのため、「囲碁夜の読書」に、この記事のいずれかを語りました。しかし、時間純粋にアプリケーションレベルで、まだピット囲碁ソースコードに飛び込んしていない、記事はまた、比較的新鮮な、何のソース解像度の大部分を見ていません。

曹操以来阿波張さんゴースケジューラのソースコード解析、ソースコードの様々な「囲碁夜の読書」共有の囲碁コンパイルを聴くだけでなく、記事を読んだ後、どこにでもコンパイル......

最終ヨーロッパ神が書いた「ゴーGC 20の質問」に、全体のダウン非常に楽しみとして読まソーステキストの行が含まれていません。リストを横断して実行する方法を延期:このようなアプローチをしようとするこれも、今日では、しかし、我々はスモールスタートのテーマで始まります。

記事の延期ソースコード解析について、ネットワーク上の多くのがあります。しかし、いくつかは完全に阿波張に加えて、その、このトピックを理解します。

私たちは、このような接続を解除し、ファイルを閉じるなどの機能を終了する前に、クリーンアップ操作を実行するために、リソースのいくつかを、知っています。関数内で複数の延期声明に書き込まれます、関数がで「最後のアウト」ためにdeferedされRETた命令の前に実行されるように。

関数呼び出しのチェーンでは、複数の延期文で複数の機能が表示されます。たとえば、次のa() -> b() -> c()ステートメントを保留します両方ディファーステートメント内の各機能に対応する数の作成_defer構造のリンクされたリストの形で構造goroutine構造を。次のようになります。

グラムをぶら下げ延期

祝福のコンパイラでは、延期ステートメントdeferporc最初の呼び出し機能、新しい新しい_defer構造は、グラムにリンクされています。もちろん、世界的な意志延期プールのテイクを行くために取得していない新しい優先順位はここで、Pの現在のバインディング延期プールから取得されます、ルーチンを使用して新しい、非常に精通には言葉がありません。

そうした後、関数本体を実行するための待機は、(前に戻り、ノートではない)RET命令の前に、呼び出すdeferreturn関数が終了すると_defer、すべてのこのチェーン上で実行されて、リストを横断するdefered機能(例えば、閉じたファイル、リリース接続など)。ここでの問題は、ということですdeferreturn終わり、機能を使用するjmpdefer関数にジャンプをdeferedされる前に、制御がユーザー定義関数に転送されます。これは、この鎖が施行取得する方法を、他の機能をdeferedされ、deferedされる機能だけ実行ですか?

その答えは、そのコントロールが延期リストの完全なトラバーサルに、再びランタイムと再びdeferreturn機能を実行するために引き渡されますです。すべてのことを、これはそれを行う方法ですか?

Goは話してスタックフレームからのこのコンピレーション。アセンブリ関数の宣言を見てください:

TEXT runtime·gogo(SB), NOSPLIT, $16-8

最後の二つの数字は図16Bゴーゴー関数のスタックフレームの大きさを示し、すなわち、サブルーチン呼び出しと戻り値のローカル変数およびパラメータの関数は、スタックの空間16Bを準備する必要があり、追加のサイズパラメータと戻り値は、8Bです。このような実際のステートメントゴーゴー機能:

// func gogo(buf *gobuf)

パラメータと戻り値のサイズは、「見る」、発信者にあり、呼び出し側は、この図に基づいて構造を積み重ねることができます必要が関数のパラメータと戻り値を調整する準備ができて。

関数呼び出しパラメータの典型的なシナリオでは、次の図をレイアウト:

関数呼び出しのパラメータのレイアウト

左、実行、関数のパラメータと戻り値を呼び出すサブルーチンを呼び出す準備CALL命令は、スタック上のリターンアドレス、実装に相当してPUSH IP行い、BPレジスタスタックの値の後、同等のPUSH BP次に、呼び出された関数へのJMP。

図は、return address上位レイヤ機能において実行されるサブルーチンを呼び出すための命令文にサブ機能実行リターン終了後表し、それは、発信者のスタック・フレームに属します。BPは、呼び出された関数に属する発信者のスタックフレームです。

サブルーチンの完了、実行後のRET命令:BPに割り当てられたスタックの底部の第一部分関数値CPUに登録、BP BP点の上位機能ので、次にreturn addressSPに示すように、背面左側に、次に、IPレジスタに割り当てられました場所。すべてが起こったようにサブルーチンコールのフィールド全体の削減に相当し、次に、CPUは、IPレジスタの次の命令を実行し続けています。

バックは、建設中で、実際には、最大延期する_defer時間構造、あなたはに関数ポインタをdeferedた、SPの現在の機能を保存する必要があります_defer構造。Deferedと_defer位置に隣接する構造をコピーするために必要なパラメータの関数です。関数への最終呼び出しが時間のdeferedされ、この時間は、このパラメータはポインタまたは参照タイプでない場合、そのスナップショットを使用するのと同じ値のコピーであり、それは予期しないバグの数を生成します。

これらを実行する機能をdeferedている最後に、deferreturn機能では、_deferリストが徐々に完成し、「消費」されます。

阿波張の使用記事の例では:

package main

import "fmt"

func sum(a, b int) {
    c := a + b
    fmt.Println("sum:" , c)
}

func f(a, b int) {
    defer sum(a, b)

    fmt.Printf("a: %d, b: %d\n", a, b)
}

func main() {
    a, b := 1, 2
    f(a, b)
}

実行f機能を、機能は最終的にdeferreturn入ります。

func deferreturn(arg0 uintptr) {
    gp := getg()
	d := gp._defer
	if d == nil {
		return
	}
	
	......
	
	switch d.siz {
	case 0:
		// Do nothing.
	case sys.PtrSize:
		*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
	default:
		memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz)) // 移动参数
	}
	fn := d.fn
	d.fn = nil
	gp._defer = d.link
	freedefer(d)
	
	_ = fn.fn
	jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
}

必然的に、それ以外の場合は明確に話すことは困難である、コードに依存します。

我々が通過しているため_defer、リンクされたリストを、私たちは、終了条件を持っている必要があります:

d := gp._defer
if d == nil {
		return
}

これはトラバーサルを終了し、_deferリストが空の場合、です。_deferリストが短くなるように、それぞれの完成がdeferedれる関数を実行した後に、コードの後半に表示されます、_defer構造は、リストから削除され、回収されます。

switchDO文レーンは、必要な2つのint型のパラメータ(例はSUM関数である)Bの機能をdeferedする準備ができています。パラメータはそれから来ますか?場所に隣接_defer構造から、これはdeferprocでは、過去のコピー機能で、覚えておいてください。deferArgs(d)戻り値は、宛先アドレスをコピーしています。そして今、あなたがコピーしたいどこに行きますか?答えはunsafe.Pointer(&arg0)次のとおりです私たちは、私たちが行くのコンパイルでは、パラメータの関数を用意し、その呼び出し側の関数であることを知って、arg0には、パラメータdeferreturn機能で、知っています。したがってarg0にアドレスが実際に(fは関数である)、その上位層のスタック位置パラメータの関数に置かれます。

最後にすることによって機能jmpdeferdeferedさsum関数にジャンプ:

jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))

コアは、物事がjmpdeferを行うということです。

TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16
    MOVQ	fv+0(FP), DX	// fn // defer 的函数的地址
    MOVQ	argp+8(FP), BX
    LEAQ	-8(BX), SP	// caller sp after CALL
    MOVQ	-8(SP), BP	// restore BP as if deferreturn returned (harmless if framepointers not in use)
    SUBQ	$5, (SP)	// return to CALL again
    MOVQ	0(DX), BX
    JMP	BX	// but first run the deferred function

まず、アドレスにSUM関数は、最終的にはJMP命令によって実行するために、DXを登録します。

MOVQ	argp+8(FP), BX
LEAQ	-8(BX), SP	// caller sp after CALL // 执行 CALL 指令后 f 函数的栈顶

argpは+ 8(FP)はその点関数fは単にスタックフレームをコピー(deferreturn関数で)、第2のパラメータjmpdeferが実際にあるので、これらの2つの行は、実際に、SPレジスタの電流の値を調整しますパラメータのsum関数を超えます。そして、-8(BX)それはdeferreturnアドレスF呼び出す関数への復帰は、実際に次の命令deferreturn関数のアドレスで表しています。

次に、MOVQ -8(SP), BPそれはスタックフレームを指すように命令レジスタBP、BP Fをリセットします。パラメータを呼び出すためにF deferreturnちょうど準備ができて、およびスタックに戻り値:この方法では、SPは、BPがf関数呼び出しdeferreturn前の状態に戻って登録します。等価しかし、スタックフレームdeferreturn機能を放棄し、そして実際に役に立ちません。

次いで、SUBQ $5, (SP)リターンアドレスが低減さ5Bは、命令の正確長さはCALLです。これは何を意味するのでしょうか?deferreturn機能を実行した後に実行戻りCALL deferreturn、次の命令は、この値は、それがバックに行き、5Bが減少しますCALL deferreturn「再帰」関数呼び出しdeferreturn効果を達成するために、コマンド。もちろん、スタックが成長していませんでした!

実行jmpdefer

jmpdefer 最後の機能は、それが個人的に戻り値は準備ができているパラメータとして、sum関数を呼び出す関数fのように見える、sum関数を実行します。

sum関数を実行するまでは、実行フローはにジャンプしますcall deferreturnすべての_defer完成構造を通じて、すべての機能を実行した後、実際には完全な実行deferretrun機能をdeferedされ、再入力deferreturn機能で指示。

リコールdeferreturn

ここでは、フルテキストは終わりました。私たちは、彼が戻った、その機能は、後者の実装をdeferedされるように機能バイトdeferreturn 5減少し、リターンアドレスを呼び出して、リストを横断する延期を達成するための鍵は、jmpdefer機能は、いくつかの「日陰」の作業を行いますされていることがわかりますCALL deferreturn順番に「再帰的」リストの完全なトラバーサル_defer deferreturn機能呼び出しを達成するために、という指示。

参考資料

[分析]ソース阿波張延期https://mp.weixin.qq.com/s/iEtMbRXW4yYyCG0TTW5y9g

[阿波張はパニック&復元] https://mp.weixin.qq.com/s/0JTBGHr-bV4ikLva-8ghEw

[ベース]阿波張延期https://mp.weixin.qq.com/s/QmeQTONUuWlr_sRNP8b5Tw

[解析]コンパイルhttps://segmentfault.com/a/1190000019804120?utm_medium=referral&utm_source=tuicool

[曹操]ゴーコンパイルシェアhttps://github.com/cch123/asmshare/blob/master/layout.md

[編集]ゴー曹操https://xargin.com/plan9-assembly

[曹操ダリは、アセンブラgoidに書き込まれます] https://github.com/cch123/goroutineid

おすすめ

転載: www.cnblogs.com/qcrao-2018/p/12550380.html