日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

st(state-threads) coroutine和stack分析

發布時間:2024/2/28 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 st(state-threads) coroutine和stack分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

st(state-threads)?https://github.com/winlinvip/state-threads

以及基于st的RTMP/HLS服務器:https://github.com/winlinvip/simple-rtmp-server

st是實現了coroutine的一套機制,即用戶態線程,或者叫做協程。將epoll(async,nonblocking socket)的非阻塞變成協程的方式,將所有狀態空間都放到stack中,避免異步的大循環和狀態空間的判斷。

關于st的詳細介紹,參考翻譯:http://blog.csdn.net/win_lin/article/details/8242653

我將st進行了簡化,去掉了其他系統,只考慮linux系統,以及i386/x86_64/arm/mips四種cpu系列,參考:https://github.com/winlinvip/simple-rtmp-server/tree/master/trunk/research/st
本文介紹了coroutine的創建和stack的管理。

STACK分配

Stack數據結構定義為:

[cpp]?view plaincopy
  • typedef?struct?_st_stack?{??
  • ????_st_clist_t?links;??
  • ????char?*vaddr;????????????????/*?Base?of?stack's?allocated?memory?*/??
  • ????int??vaddr_size;????????????/*?Size?of?stack's?allocated?memory?*/??
  • ????int??stk_size;??????????????/*?Size?of?usable?portion?of?the?stack?*/??
  • ????char?*stk_bottom;???????????/*?Lowest?address?of?stack's?usable?portion?*/??
  • ????char?*stk_top;??????????????/*?Highest?address?of?stack's?usable?portion?*/??
  • ????void?*sp;???????????????????/*?Stack?pointer?from?C's?point?of?view?*/??
  • }?_st_stack_t;??

  • 實際上vaddr是棧的內存開始地址,其他幾個地址下面分析。

    棧的分配是在_st_stack_new函數,在st_thread_create函數調用,先計算stack的尺寸,然后分配棧。

    [plain]?view plaincopy
  • |?REDZONE?|??????????stack?????????|??extra??|?REDZONE?|??
  • +---------+------------------------+---------+---------+??
  • |????4k???|????????????????????????|???4k/0??|????4k???|??
  • +---------+------------------------+---------+---------+??
  • vaddr?????bottom???????????????????top??

  • 上圖是棧分配后的結果,兩邊是REDZONE使用mprotect保護不被訪問(在DEBUG開啟后),extra是一個額外的內存塊,st_randomize_stacks開啟后會調整bottom和top,就是隨機的向右邊移動一點。

    總之,最后使用的,對外提供的接口就是bottom和top,st_thread_create函數會初始化sp。stack對外提供的服務就是[bottom, top]這個內存區域。

    THREAD初始化棧

    開辟Stack后,st會對stack初始化和分配,這個stack并非直接就是thread的棧,而是做了以下分配:

    [plain]?view plaincopy
  • +--------------------------------------------------------------+??
  • |?????????????????????????stack????????????????????????????????|??
  • +--------------------------------------------------------------+??
  • bottom?????????????????????????????????????????????????????????top??

  • 分配如下:

    [plain]?view plaincopy
  • +-----------------+-----------------+-------------+------------+??
  • |?stack?of?thread?|pad+align(128B+)?|thread(336B)?|?keys(128B)?|??
  • +-----------------+-----------------+-------------+------------+??
  • bottom????????????sp????????????????trd???????????ptds?????????top??
  • ???????(context[0].__jmpbuf.sp)?????????????(private_data)??

  • 也就是說:

    ptds:這個是thread的private_data,是12個指針(ST_KEYS_MAX指定),參考st_key_create()。

    trd:thread結構本身也是在這個stack中分配的。

    pad+align:在trd之后是對齊和pad(_ST_STACK_PAD_SIZE指定)。

    sp:這個就是thread真正的stack了。

    coroutine必須要自己分配stack,因為setjmp保存的只是sp的值,而沒有全部copy棧,所以若使用系統的stack,各個thread之間longjmp時會導致棧混淆。參考:http://blog.csdn.net/win_lin/article/details/40948277

    Thread啟動和切換

    st的thread如何進入到指定的入口呢?

    其實在第一次setjmp時,是初始化thread,這時候返回值是0,初始化完后就返回到調用函數繼續執行了。

    調用函數會在其他地方調用longjmp到這個thread,這時候是從setjmp地方開始執行,返回值是非0,這時進入thread的主函數:_st_thread_main。

    參考我改過的代碼:

    [cpp]?view plaincopy
  • _st_thread_t?*st_thread_create(void?*(*start)(void?*arg),?void?*arg,?int?joinable,?int?stk_size)??
  • {??
  • //?by?winlin,?expend?macro?MD_INIT_CONTEXT??
  • #if?defined(__mips__)??
  • ????MD_SETJMP((trd)->context);??
  • ????trd->context[0].__jmpbuf[0].__pc?=?(__ptr_t)?_st_thread_main;??
  • ????trd->context[0].__jmpbuf[0].__sp?=?stack->sp;??
  • #else??
  • ????int?ret_setjmp?=?0;??
  • ????if?((ret_setjmp?=?MD_SETJMP((trd)->context))?!=?0)?{??
  • ????????_st_thread_main();??
  • ????}??
  • ????MD_GET_SP(trd)?=?(long)?(stack->sp);??
  • #endif??
  • }??

  • gdb調試,第一次setjmp時,返回值是0,調用堆棧是創建線程的堆棧,62行的代碼是st_thread_t trd = st_thread_create(thread_func, NULL, 1, 0);:

    [cpp]?view plaincopy
  • (gdb)?f??
  • #0??st_thread_create?(start=0x4073fb?<thread_func>,?arg=0x0,?joinable=1,?stk_size=65536)?at?sched.c:600??
  • 600?????if?((ret_setjmp?=?MD_SETJMP((trd)->context))?!=?0)?{??
  • (gdb)?bt??
  • #0??st_thread_create?(start=0x4073fb?<thread_func>,?arg=0x0,?joinable=1,?stk_size=65536)?at?sched.c:600??
  • #1??0x00000000004074b5?in?thread_test?()?at?srs.c:62??
  • #2??0x00000000004081c3?in?main?(argc=1,?argv=0x7fffffffe4b8)?at?srs.c:344??
  • (gdb)?p?ret_setjmp???
  • $36?=?0??

  • 從其他線程切換過來時,即longjmp過來時,返回值非0,調用堆棧是longjmp的堆棧,68行的代碼是st_thread_join(trd, NULL);:

    [cpp]?view plaincopy
  • (gdb)?f??
  • #0??st_thread_create?(start=0x4073fb?<thread_func>,?arg=0x6390b0,?joinable=0,?stk_size=6599392)?at?sched.c:601??
  • 601?????????_st_thread_main();??
  • (gdb)?bt??
  • #0??st_thread_create?(start=0x4073fb?<thread_func>,?arg=0x6390b0,?joinable=0,?stk_size=6599392)?at?sched.c:601??
  • #1??0x00000000004074f4?in?thread_test?()?at?srs.c:68??
  • #2??0x00000000004081c3?in?main?(argc=1,?argv=0x7fffffffe4b8)?at?srs.c:344??
  • (gdb)?p?ret_setjmp???
  • $37?=?1??

  • 注意,雖然顯示都是thread_test這個函數過來,實際上函數行數已經不一樣了,gdb顯示的stk_size也是破壞了的,因為這個時候的棧是用的st自己開辟的棧了。

    進入到_st_thread_main中后,會調用用戶指定的線程函數(這個函數里面會調用st函數setjmp,下次longjmp是到這個位置了);從線程函數返回后,會調用st_thread_exit清理線程,然后切換到其他函數,直到完成最后一個函數就返回了。

    [cpp]?view plaincopy
  • void?_st_thread_main(void)??
  • {??
  • ????_st_thread_t?*trd?=?_ST_CURRENT_THREAD();??
  • ??????
  • ????/*?Run?thread?main?*/??
  • ????trd->retval?=?(*trd->start)(trd->arg);??
  • ??????
  • ????/*?All?done,?time?to?go?away?*/??
  • ????st_thread_exit(trd->retval);??
  • }??

  • 這個就是st的thread啟動和調度的過程。

    第一次創建線程和setjmp后,會設置sp,即設置stack。也就是說,這個函數的所有stack信息在longjmp之后都是未知的了,這就是所有st的thread結束后,必須longjmp到其他的線程,或者退出,不能直接return的原因(因為沒法return了,頂級stack就是_st_thread_main)。

    Thread退出

    在st的thread中退出后,會切換到其他thread(st創建的線程stack是重新建立的,無法返回后繼續執行)。

    st創建的thread,結束后會調用st_thread_exit,參考_st_thread_main的定義,這個就是thread執行的主要流程。

    st在初始化st_init時,會把當前的線程當作_ST_FL_PRIMORDIAL,也就是初始化線程,這個線程若調用exit,等待其他thread完成后,會直接exit。實際上是沒有線程時會切換到idle線程:

    [cpp]?view plaincopy
  • void?_st_vp_schedule(void)??
  • {??
  • ????_st_thread_t?*trd;??
  • ??????
  • ????if?(_ST_RUNQ.next?!=?&_ST_RUNQ)?{??
  • ????????/*?Pull?thread?off?of?the?run?queue?*/??
  • ????????trd?=?_ST_THREAD_PTR(_ST_RUNQ.next);??
  • ????????_ST_DEL_RUNQ(trd);??
  • ????}?else?{??
  • ????????/*?If?there?are?no?threads?to?run,?switch?to?the?idle?thread?*/??
  • ????????trd?=?_st_this_vp.idle_thread;??
  • ????}??

  • idle線程是在st_init時創建,也就是說st_init會創建一個idle線程(使用st_thread_create),以及直接創建一個_ST_FL_PRIMORDIAL線程(直接calloc)。idle線程的代碼:

    [cpp]?view plaincopy
  • void?*_st_idle_thread_start(void?*arg)??
  • {??
  • ????_st_thread_t?*me?=?_ST_CURRENT_THREAD();??
  • ??????
  • ????while?(_st_active_count?>?0)?{??
  • ????????/*?Idle?vp?till?I/O?is?ready?or?the?smallest?timeout?expired?*/??
  • ????????_ST_VP_IDLE();??
  • ??????????
  • ????????/*?Check?sleep?queue?for?expired?threads?*/??
  • ????????_st_vp_check_clock();??
  • ??????????
  • ????????me->state?=?_ST_ST_RUNNABLE;??
  • ????????_ST_SWITCH_CONTEXT(me);??
  • ????}??
  • ??????
  • ????/*?No?more?threads?*/??
  • ????exit(0);??
  • ??????
  • ????/*?NOTREACHED?*/??
  • ????return?NULL;??
  • }??

  • 所有線程完成時就exit。

    Thread初始線程

    st的初始線程,或者叫做物理線程,primordial線程,是調用st_init的那個線程。一般而言,調用st的程序都是單線程,所以這個初始線程也就是那個系統的唯一的一個線程。

    所有st的線程都是調用st_create_thread創建的,使用st自己開辟的stack;除了一種初始線程,沒有重新設置stack,這個就是初始線程(物理線程)。

    參考st_init的代碼:

    [cpp]?view plaincopy
  • /*?
  • *?Initialize?primordial?thread?
  • */??
  • trd?=?(_st_thread_t?*)?calloc(1,?sizeof(_st_thread_t)?+??
  • (ST_KEYS_MAX?*?sizeof(void?*)));??
  • if?(!trd)?{??
  • ????return?-1;??
  • }??
  • trd->private_data?=?(void?**)?(trd?+?1);??
  • trd->state?=?_ST_ST_RUNNING;??
  • trd->flags?=?_ST_FL_PRIMORDIAL;??
  • _ST_SET_CURRENT_THREAD(trd);??
  • _st_active_count++;??

  • 在分配trd對象時,分配了_st_thread_t和keys兩個對象,可以參考前面對于stack的使用。keys用來做private_data,所以后面初始化private_data時是指向下一個thread。

    創建后設置這個線程為_ST_FL_PRIMORDIAL,這個就是用來指明stack是否是st自己分配的:

    [cpp]?view plaincopy
  • void?st_thread_exit(void?*retval)??
  • {??
  • ????if?(!(trd->flags?&?_ST_FL_PRIMORDIAL))?{??
  • ????????_st_stack_free(trd->stack);??
  • ????}??
  • }??

  • 如果是初始線程(物理線程),那么stack是不釋放的,這個stack是NULL。

    在調度時,不管stack是否是自己創建的,對于調度都沒有影響。stack如果是st自己創建的,只是在setjmp之后的context中修改sp的地址,這個時候longjmp會使用新的stack而已,對于longjmp的jmp_buf到底sp是自己創建的還是系統的,其實沒有區別。

    所以初始線程(物理線程)也是作為一個st的thread被調度,沒有任何區別。

    Thread生命周期

    再整理下st整個線程的執行流程。

    第一個階段,st_init創建idle線程和創建priordial線程(初始線程,物理線程,_ST_FL_PRIMORDIAL),這時候_st_active_count是1,也就是初始線程(調用st_init,也是物理線程)在運行,idle線程不算一個active的線程,它主要是做切換和退出。

    第二個階段,可選的階段,用戶創建線程。調用st_thread_create時,會把_st_active_count遞增,并且加入線程隊列。譬如創建了一個線程;這時候st調度有兩個線程,一個是初始線程,一個是剛剛創建的線程。

    第三個階段,初始線程切換,將控制權交給st。也就是初始線程,做完st_init和創建其他線程后,這個時候還沒有任何的線程切換。初始線程(物理線程)需要將控制權切換給st,可以調用st_sleep循環和休眠,或者調用st_thread_exit(NULL)等待其他線程結束。假設這個階段物理線程不進行切換,st將無法獲取控制權,程序會直接返回。

    這么設計其實很完善,如果物理線程不exit,那么st的idle線程也不退出(認為有個初始線程還在跑)。如果初始線程直接退出,那么idle線程不會拿到控制權。如果初始線程調用st_thread_exit(NULL),認為是物理線程也退出,那么idle會等所有線程完了再exit,相當于控制權交給st了。

    或者說,可以在初始線程(物理線程)里面做各種的業務邏輯,譬如srs用初始線程更新各種數據,給api使用。或者可以直接創建線程后st_thread_exit,就等所有線程退出。

    總結

    以上是生活随笔為你收集整理的st(state-threads) coroutine和stack分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。