最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

又來聊協(xié)程,這次我們手擼一個,C語言的

2021-07-14 21:54 作者:NewtonCY  | 我要投稿

上次寫了一篇用協(xié)程掃描SSH協(xié)議的文。兩周過去了沒人看~

人都是有分享欲的~即使沒人看,也想寫。

又不喜歡CSDN,就只有b站還勉強對我胃口的樣子。

話不多說,本文看點:

  • C++有棧協(xié)程。純手擼不調(diào)庫。實現(xiàn)又小巧

  • 看完對棧幀的理解又增加了

  • 又名:把棧放進堆區(qū)要分幾步

  • 要是再有人說局部變量一定在棧里面,就用這篇文章糊他臉(滑稽)

文末附上源碼的github,歡迎白嫖。點贊的都是我好兄弟


思路

嗯,理解這篇文章需要對調(diào)用棧有個基本的了解~

作者懶,不會從開天辟地講起,再加上有好多講得不錯的

貼個連接:

[知乎]x86-64 下函數(shù)調(diào)用及棧幀原理——冷風(fēng)寒雨宿天涯

https://zhuanlan.zhihu.com/p/27339191

當(dāng)時吧,我也是瞎看網(wǎng)頁,看到大家講棧幀,就想~我可以為每一個協(xié)程在堆區(qū)創(chuàng)建一個內(nèi)存區(qū)域,然后將指向棧頂?shù)膔sp寄存器指向堆區(qū),接下來,這個函數(shù)壓入棧中的數(shù)據(jù)就都在堆區(qū)了。

函數(shù)的棧移到了堆區(qū),那么每個協(xié)程的調(diào)用棧就可以互不干擾了。每個函數(shù)有獨立的調(diào)用棧,這樣我們只需要切換不同的調(diào)用棧和寄存器狀態(tài)即可在函數(shù)間跳來跳去了。

臥槽,我可真是個天才,要是我早生幾年——不扯遠了,讓我們詳細的捋捋需要干的事情:

  • 暫停一個任務(wù)(yield)

  • 恢復(fù)一個暫停中的任務(wù)

  • 建立一個新任務(wù)

建立一個任務(wù)

來看看我用來描述任務(wù)的結(jié)構(gòu)體

協(xié)程控制塊

記得控制進程的叫PCB,那么我這里保存協(xié)程狀態(tài)的就叫CCB好了。

  • 第一個值是指向給調(diào)用棧分配的地址的指針,我們說了,要把棧放進堆里~

  • 接下來是調(diào)用棧分配的大小,

  • 再接下來是寄存器,amd64一共有16個64位寄存器,其中rsp寄存器指向棧頂。

  • 再接下來是RIP,RIP寄存器指向下一條需要執(zhí)行的指令的地址。我們需要把這個值保存下來,恢復(fù)的時候方便跳轉(zhuǎn)過去。

  • 任務(wù)的狀態(tài),定義了三個,未開始,就緒(可以調(diào)度或正在運行),結(jié)束中(將在下一次調(diào)度循環(huán)清理掉)。當(dāng)然,理論上還應(yīng)該有個阻塞狀態(tài),以后我實現(xiàn)了同步的那些東西和異步IO之后可能會加上去。

  • 最后是函數(shù)的指針和函數(shù)參數(shù)的vector

接著再來看一個結(jié)構(gòu)體,這個結(jié)構(gòu)體表示當(dāng)前”調(diào)度循環(huán)“的上下文。每個系統(tǒng)線程應(yīng)該對應(yīng)一個。

上下文

這里面保存了:

yield_flag,協(xié)程yield的時候?qū)⑦@個標志置位,這樣我就知道他是yield了還是return了。哈哈。接下來是保存所有協(xié)程CCB的list,保存系統(tǒng)棧各個寄存器的數(shù)組,畢竟我還要切回來的嘛。最后是指向當(dāng)前正在運行的協(xié)程的迭代器。

還有一個成員函數(shù)用來建立一個新的協(xié)程。這個函數(shù)做的事情呢,在堆中分配調(diào)用棧空間,然后將這個ccb加入到list中。

接下來,在調(diào)度循環(huán)中,每次我們?nèi)〕鲆粋€協(xié)程,如果它沒有開始運行,那么就運行它,如果結(jié)束了,就釋放內(nèi)存。如果是就緒狀態(tài)(yield出來了,可以恢復(fù)運行)就恢復(fù)它。如此輪換往復(fù):

開始一個協(xié)程

可以看到,這里內(nèi)聯(lián)了vc編譯器風(fēng)格的匯編。值得一提的是:msvc編譯器并不允許在64位平臺的代碼中內(nèi)聯(lián)匯編,這導(dǎo)致我的程序必須使用clang編譯器來編譯。為了編譯出和msvc兼容的庫文件,我踩了不少坑,有空我會專門寫一篇文章講講其中的坑點。

我們來看這三行匯編。mov rsp, [rsp_] 表示把rsp_這個局部變量的值加載到rsp寄存器中。編譯的過程中,clang會自動把變量名替換成一個類似于 [rbp-0x30]這樣的形式,非常的方便。

可以看到,我們把棧頂指針rsp指向了堆區(qū),把函數(shù)的返回地址壓入了棧中,然后跳轉(zhuǎn)到了函數(shù)的地址。這樣函數(shù)的對局部變量壓棧的時候就會壓入堆中,函數(shù)運行完之后,會通過ret指令返回到我壓入棧中的FINISH_LABLE所在代碼的位置。

yield和resume

yield的過程也很簡單,依次保存每一個寄存器的值,然后跳轉(zhuǎn)到Y(jié)IELD_LABLE的位置。注意在jmp指令的下一行有一個recover label?;謴?fù)的時候?qū)⑻氐竭@里,從而無縫連接。

特別注意的是:yield函數(shù)由用戶調(diào)用,這里是從好幾層函數(shù)中跳出來,而且此時的rbp和rsp寄存器指向的是堆區(qū)中的地址,yield之后跳轉(zhuǎn)到的函數(shù)則在棧中。所以跳轉(zhuǎn)完成之后,我們首先需要恢復(fù)寄存器的狀態(tài)。

上文中我們說到,我們的”調(diào)度循環(huán)“每次取出一個任務(wù),然后執(zhí)行它。

在這個循環(huán)的開頭,我保存了每一個寄存器的狀態(tài),在這個循環(huán)的末尾,我恢復(fù)所有寄存器的狀態(tài)并跳轉(zhuǎn)到開頭,從而實現(xiàn)一個完整的循環(huán)。而YIELD_LABLE正是放在了這個循環(huán)的末尾。yield之后,能馬上讀取循環(huán)開頭的狀態(tài),來修復(fù)”破損“的棧。

需要特別注意,這種通過匯編跳轉(zhuǎn)的循環(huán)體中,不要創(chuàng)建任何對象,因為jmp指令進行跳轉(zhuǎn),編譯器并不會知道這里是”循環(huán)的結(jié)束“,所以析構(gòu)函數(shù)不會被調(diào)用,而構(gòu)造函數(shù)會被反復(fù)調(diào)用。

至于resume,就是yield的反過程,取出每一個寄存器的值,然后跳轉(zhuǎn)回原來的位置。

好了,末尾附上項目地址:

https://github.com/newtoncy/cpp_coroutine



又來聊協(xié)程,這次我們手擼一個,C語言的的評論 (共 條)

分享到微博請遵守國家法律
太仓市| 扶绥县| 中超| 沙湾县| 普洱| 萍乡市| 莱州市| 江西省| 枣庄市| 辽宁省| 宁乡县| 巩留县| 乐安县| 南京市| 芮城县| 繁昌县| 东光县| 横峰县| 阿鲁科尔沁旗| 铁岭市| 汉川市| 吉木乃县| 库车县| 平阳县| 达尔| 博野县| 乾安县| 仙游县| 车致| 西贡区| 区。| 互助| 桃园县| 布尔津县| 万全县| 张家川| 伊宁县| 门源| 嘉义县| 新津县| 黄冈市|