最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C語言使用setjmp和longjmp實現(xiàn)一個簡單的協(xié)程

 更新時間:2022年12月07日 15:17:08   作者:amjieker  
這篇文章主要為大家介紹了C語言使用setjmp和longjmp實現(xiàn)一個簡單的協(xié)程過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

協(xié)程是什么呢,有人說是輕量級線程,有人說的用戶級線程,其和線程的區(qū)別可能就是更輕量、操作系統(tǒng)無感的。 其實從根本來說的話,協(xié)程本質上就是在一個進程上的程序而已,外部感知不到它的存在。

協(xié)程其實我感覺對理解函數(shù)壓棧入棧、進程的上下文切換也是非常有幫助的。

以下內容均在 Linux的 x86_64 環(huán)境下實現(xiàn)。 這里不討論其他的實現(xiàn)。

(C/C++)函數(shù)的工作原理

對于匯編上的函數(shù)來說,就是一個過程。

匯編執(zhí)行的邏輯就是一條指令一條指令的去執(zhí)行,去處理。

這里分為兩個地方,第一個是代碼區(qū),我們的pc指針指向當前指向的指令。

在x86_64下,寄存器$rip存放著 pc 指針。

第二個是棧區(qū)(別杠,這里不討論堆區(qū)、靜態(tài)區(qū)、常量區(qū)等等等),棧是一種先進后出的結構。一般用來存放數(shù)據(jù)。

在x86_64下,棧頂指針放在$rsp的位置。

$rbp用來存放棧幀的起始。

看一個最簡單的匯編代碼:

int fun() {
    return 0;
}
fun:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $0, %eax
        popq    %rbp
        ret

pushq 指令是將 $rbp的值壓入棧中,然后$rsp指針移動。

movq 指令就是將 A 移動到 B 位置去。

popq 指令就是把東西從棧中彈出到A,棧指針移動。

函數(shù)入棧示意圖

具體函數(shù)傳參和棧幀請看這一篇文章 http://m.fzitv.net/article/269423.htm

(C/C++)內嵌匯編

C/C++支持我們內嵌匯編在代碼中。 形如:

asm volatile("", :::)

(volatile是為了防止被優(yōu)化掉)

格式為:

asm volatile("InSTructiON List" : Output : Input : Clobber/Modify);

你可以利用匯編來完成賦值操作

int a=114514, b;
asm volatile("movl %1, %%eax; 
      movl %%eax, %0;"
      :"=r"(b)        /* output */
      :"r"(a)         /* input */
      :"%eax"         /* clobbered register */
     );

(linux下)setjmp和longjmp

如何用可以看這一篇文章 http://m.fzitv.net/article/41250.htm

setjmplongjmp在本文中主要起到什么作用呢?

切換上下文

啊,好高大上啊,聽不懂。

說人話,就是保存一下當前的寄存器(因為是協(xié)程,只有寄存器夠了)

setjmp原理就是保存好當前時刻的寄存器。

然后在longjmp調用的時候,將恢復 jmp_buf所存放的寄存器的值,以達到跨函數(shù)跳轉的目的。

這兩個東西就非常適合用來做我們這個的上下文切換。

當然,你也可以用 ucontext來做這一件事情,只不過,我們這個是個簡單的例子罷了。

協(xié)程的實現(xiàn)

首先,拋開調度器不談,我們只用關心什么?

獨立的運行空間、上下文...?

對于每一個協(xié)程來說,我們自然是不希望開辟在棧上的,(當前棧幀被摧毀\從新利用怎么辦?)

我們可以動態(tài)的分配在堆上,將這一塊內存當為這個協(xié)程的棧。

當然,協(xié)程是一個函數(shù),并且可以調用另外的函數(shù),(調用另外的函數(shù)的時候分配的內存就是這個協(xié)程所在的這一塊內存)

現(xiàn)在我們要做三件事情。

  • %rsp切換到新分配的堆,而不是用原來有的棧。
  • 函數(shù)的傳參保存在哪兒。
  • 還是就是,協(xié)程執(zhí)行完了,主程序肯定不能直接退出,當前協(xié)程是應該返回主程序的地址?顯然不可行,需要hook返回地址,讓我們的協(xié)程回不去來的位置。

有點像什么呢?

正常的調用是這樣:

我們將push一個新的函數(shù)地址進去。

下面是匯編實現(xiàn):

asm volatile(
        "movq %0, %%rsp;" // 更改 %rsp 為 當前分配的堆地址 now
        "movq %2, %%rdi;" // 傳參
        "pushq %3;"       // 拆分call指令,將 自定義的新函數(shù)壓入返回地址 
        "jmp  *%1;"       // 跳轉到協(xié)程執(zhí)行
        :
        : "b"(now), "d"(func), "a"(arg), "c"(exit_)
        : "memory");

結構體定義

#define alignment16(a) ((a) & (~(16 - 1)))  // 向前對齊
#define STACK_SIZE 4096
enum co_status {
  CO_NEW = 1,
  CO_DEAD,
};
struct co {
  void (*func)(void *);
  void *arg;
  enum co_status status;
  jmp_buf context;
  uint8_t stack[STACK_SIZE];
};

上下文管理

std::vector<co *> context;
std::unordered_map<co *, int> has_context;
co main_co;
co *now_co;

輔助函數(shù)

void refresh_context(co *buf) {
  if (!has_context.count(buf)) {
    context.push_back(buf);
    has_context[buf] = context.size() - 1;
  }
}
void exit_() {
  now_co->status = CO_DEAD;
  while (1) {
    yield();
  }
}

新建協(xié)程

注意到rsp的對齊,不對齊rsp會段錯誤

注意堆和棧的增長是反的

co *coroutine(void (*func)(void *), void *arg) {
  co *cur = new co;
  cur->arg = arg;
  cur->func = func;
  cur->status = CO_NEW;
  void *now = (void *)(alignment16(((uintptr_t)cur->stack + STACK_SIZE)));
  int res = setjmp(main_co.context); // 保存當前上下文
  refresh_context(&(main_co));       // 刷新上下文
  if (res == 0) {
    now_co = cur;                    // 協(xié)程創(chuàng)建成功,立馬開始執(zhí)行,直到第一次 yield
    asm volatile(
        "movq %0, %%rsp;"
        "movq %2, %%rdi;"
        "pushq %3;"
        "jmp  *%1;"
        :
        : "b"(now), "d"(func), "a"(arg), "c"(exit_)
        : "memory");
  }
  return cur;
}

協(xié)程讓步

這里用的 0 和 1來區(qū)分是否為切換上下中的讓步和蘇醒操作。

void yield() {
  assert(now_co != NULL);
  int res = setjmp(now_co->context);             // 保存上下文
  refresh_context(now_co);
  if (res == 0) {
    now_co = context[(rand()) % context.size()]; // 挑選幸運觀眾
    longjmp(now_co->context, 1);                 // 跳轉到其他上下文繼續(xù)執(zhí)行
  }
}

協(xié)程回收

這里,當協(xié)程沒有執(zhí)行完,狀態(tài)不為 CO_DEAD 時,當前調用wait的程序就得一直讓出

直到等到 CO_DEAD時 ,將其回收掉。

void wait(co *co_) {
  while (co_->status != CO_DEAD) yield();
  for (auto v = context.begin(); v != context.end();
       v++)  // 比較慢,可改用紅黑樹引用刪除節(jié)點
    if (*v == co_) {
      context.erase(v);
      break;
    }
  has_context.erase(co_);
  delete co_;
}

總體代碼和測試代碼

#include <assert.h>
#include <setjmp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <queue>
#include <unordered_map>
#define alignment16(a) ((a) & (~(16 - 1)))  // 向前對齊
#define STACK_SIZE 4096
enum co_status {
  CO_NEW = 1,
  CO_DEAD,
};
struct co {
  void (*func)(void *);
  void *arg;
  enum co_status status;
  jmp_buf context;
  uint8_t stack[STACK_SIZE];
};
std::vector<co *> context;
std::unordered_map<co *, int> has_context;
co main_co;
co *now_co;
char __init_time__ = [] {
  srand(time(NULL));
  return 0;
}();
void refresh_context(co *buf) {
  if (!has_context.count(buf)) {
    context.push_back(buf);
    has_context[buf] = context.size() - 1;
  }
}
void exit_();
co *coroutine(void (*func)(void *), void *arg) {
  co *cur = new co;
  cur->arg = arg;
  cur->func = func;
  cur->status = CO_NEW;
  void *now = (void *)(alignment16(((uintptr_t)cur->stack + STACK_SIZE)));
  int res = setjmp(main_co.context);
  refresh_context(&(main_co));
  if (res == 0) {
    now_co = cur;
    asm volatile(
        "movq %0, %%rsp;"
        "movq %2, %%rdi;"
        "pushq %3;"
        "jmp  *%1;"
        :
        : "b"(now), "d"(func), "a"(arg), "c"(exit_)
        : "memory");
  }
  return cur;
}
void yield() {
  assert(now_co != NULL);
  int res = setjmp(now_co->context);
  refresh_context(now_co);
  if (res == 0) {
    now_co = context[(rand()) % context.size()];
    longjmp(now_co->context, 1);
  }
}
void wait(co *co_) {
  while (co_->status != CO_DEAD) yield();
  for (auto v = context.begin(); v != context.end();
       v++)  // 比較慢,可改用紅黑樹引用刪除節(jié)點
    if (*v == co_) {
      context.erase(v);
      break;
    }
  has_context.erase(co_);
  delete co_;
}
void exit_() {
  now_co->status = CO_DEAD;
  while (1) {
    yield();
  }
}
int count = 1;
void entry(void *arg) {
  for (int i = 0; i < 5; i++) {
    printf("task: [%s] seq:[%d] \n", (const char *)arg, count++);
    yield();
  }
}
int main() {
  co *co1 = coroutine(entry, (void *)"a");
  co *co2 = coroutine(entry, (void *)"b");
  co *co3 = coroutine(entry, (void *)"c");
  wait(co1);
  wait(co2);
  wait(co3);
  printf("%d over\n", count);
  return 0;
}

效果

task: [a] seq:[1] 
task: [b] seq:[2] 
task: [a] seq:[3] 
task: [a] seq:[4] 
task: [a] seq:[5] 
task: [b] seq:[6] 
task: [a] seq:[7] 
task: [c] seq:[8] 
task: [c] seq:[9] 
task: [b] seq:[10] 
task: [b] seq:[11] 
task: [c] seq:[12] 
task: [b] seq:[13] 
task: [c] seq:[14] 
task: [c] seq:[15] 
16 over

調度順序是隨機的。

總結

本文主要簡單介紹了一個一種可能的協(xié)程的實現(xiàn)方法,但是極其簡陋和不規(guī)范,如有紕漏,請指正。

通過對協(xié)程的學習和理解,可以大概明白線程的工作原理,進程的工作原理,為什么線程要比進程耗費資源。

可以了解到C/C++函數(shù)調用的基礎流程,以及如何搞一個函數(shù)讓其不返回等操作。

本文沒有涉及調度,涉及得很簡陋,協(xié)程的狀態(tài)只有 新建和死亡。中間的其他狀態(tài)沒有標注。

以上就是C語言使用setjmp和longjmp實現(xiàn)一個簡單的協(xié)程的詳細內容,更多關于C語言setjmp longjmp實現(xiàn)協(xié)程的資料請關注腳本之家其它相關文章!

相關文章

  • C語言嵌入informix基礎入門示例講解

    C語言嵌入informix基礎入門示例講解

    這篇文章主要介紹了C語言嵌入informix基礎方法,大家參考使用
    2013-11-11
  • C++實現(xiàn)將內容寫入文件的方法總結

    C++實現(xiàn)將內容寫入文件的方法總結

    本文主要總結了一下C/C++將內容寫入文件的方法,C的方法有些單調,畢竟沒有庫函數(shù)。C++則豐富些,下面我把搜集到的整理一下,供大家參考
    2023-04-04
  • C++ 中 socket編程實例詳解

    C++ 中 socket編程實例詳解

    這篇文章主要介紹了C++ 中 socket編程實例詳解的相關資料,需要的朋友可以參考下
    2017-06-06
  • 基于Qml實現(xiàn)水印工具

    基于Qml實現(xiàn)水印工具

    這篇文章主要介紹了如何在?Qml?中實現(xiàn)一個簡單但功能強大的水印工具,包括水印文本的透明度、顏色、字體大小、旋轉角度等自定義功能,需要的可以參考下
    2024-12-12
  • Qt實現(xiàn)將qsqlite數(shù)據(jù)庫中的數(shù)據(jù)導出為Excel表格

    Qt實現(xiàn)將qsqlite數(shù)據(jù)庫中的數(shù)據(jù)導出為Excel表格

    這篇文章主要為大家詳細介紹了如何通過Qt實現(xiàn)將qsqlite數(shù)據(jù)庫中的數(shù)據(jù)導出為Excel表格,文中的示例代碼簡潔易懂,有需要的小伙伴可以了解一下
    2024-12-12
  • OpenCV實現(xiàn)輪廓的發(fā)現(xiàn)

    OpenCV實現(xiàn)輪廓的發(fā)現(xiàn)

    這篇文章主要為大家詳細介紹了OpenCV如何實現(xiàn)輪廓的發(fā)現(xiàn),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • Microsoft Visual Studio 2022的安裝與使用詳細教程

    Microsoft Visual Studio 2022的安裝與使用詳細教程

    Microsoft Visual Studio 2022是Microsoft Visual Studio軟件的一個高版本,能夠編寫和執(zhí)行C/C++代碼,具有強大的功能,是開發(fā)C/C++程序的主流軟件,這篇文章主要介紹了Microsoft Visual Studio 2022的安裝與使用詳細教程
    2024-01-01
  • C++基于隨機數(shù)實現(xiàn)福彩雙色球的方法示例

    C++基于隨機數(shù)實現(xiàn)福彩雙色球的方法示例

    這篇文章主要介紹了C++基于隨機數(shù)實現(xiàn)福彩雙色球的方法,結合完整實例形式分析了C++隨機數(shù)算法的實現(xiàn)與使用技巧,需要的朋友可以參考下
    2017-06-06
  • cmake添加一個庫的實現(xiàn)步驟

    cmake添加一個庫的實現(xiàn)步驟

    本文主要介紹了cmake添加一個庫的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-06-06
  • C語言數(shù)據(jù)結構與算法之鏈表(一)

    C語言數(shù)據(jù)結構與算法之鏈表(一)

    鏈表是線性表的鏈式存儲方式。鏈表的內存是不連續(xù)的,前一個元素存儲地址的下一個地址中存儲的不一定是下一個元素。小編今天就將帶大家深入了解一下鏈表,快來學習吧
    2021-12-12

最新評論

永平县| 开江县| 丹寨县| 黔江区| 兰坪| 宁夏| 屯留县| 来宾市| 吴川市| 馆陶县| 肇庆市| 镇雄县| 余庆县| 石景山区| 新田县| 宁城县| 大关县| 昔阳县| 安岳县| 岢岚县| 邮箱| 凤庆县| 榆社县| 屏南县| 会宁县| 西宁市| 大冶市| 沙河市| 巍山| 巴彦县| 什邡市| 朝阳区| 濉溪县| 潮州市| 剑川县| 通化市| 株洲县| 年辖:市辖区| 旺苍县| 海淀区| 鹤壁市|