我所理解的ucontext族函数

今天,我要写一篇文章,好好来说一下我所理解的ucontext族函数。

NAME
getcontext, setcontext - get or set the user context
SYNOPSIS


#include <ucontext.h>

int getcontext(ucontext_t *ucp);
int setcontext(const ucontext_t *ucp);

DESCRIPTION
In a System V-like environment, one has the two types mcontext_t and ucontext_t defined in

int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);

通过swapcontext函数,可以十分方便地实现从这个执行点跳跃到另外一个执行点。可是这个东西确实和goto语句有非常大的不同。因为它们可以记录下跳跃点的上下文,这就非常有意思了,我举一个不恰当的比喻,这就是魔法世界中的时间静止的魔法,巫师挥一挥魔杖,在这个点的一切信息都保留了下来,然后巫师跑到另外一个世界继续冒险。厌倦了之后,回到这个世界,一切又从原来静止的点开始执行。

void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);

makecontext这个函数是用来干什么的呢?其实这个函数和构造线程的函数非常类似,不过,它构造的是一个线程,而你是制造一个新的context。这个context是你将来要进入的一个世界,所以,在进入之前,你首先要准备一些什么,func是你你要执行的函数(你的任务),argc是这个函数的参数个数,而后面的可变部分,是你要传递给func的实参。在调用makecontext函数之前,我们首先要初始化ucp指向的ucontext_t结构体,正如前面所说的,ucontext_t结构只是一个规范而已,不同平台下定义的域都有可能有所不同,所以我们只能用getcontext函数来初始化这样一个结构体。另外一个比较重要的在ucp上要设置的一个域叫做uc_link,这个玩意也指向一个ucontext_t结构,这个玩意是在询问我们,这个context运行完成后,你要回到哪里?(当这个世界湮灭后,你要去哪里?)如果你不设置,相当于说,我们结束吧(你要和这个世界共同进退)。不运行了。如果设置了,那么会跳转到uc_link指向的context继续运行。

另外需要注意的一点是,我们还需要设置ucontext_tuc_stack域,说白了,就是要给这个玩意配置一个栈,为什么要这么干,我们看一下下面的例子:

#include <stdio.h>

void ping();
void pong();

void ping(){
    printf("ping\n");
    pong();
}

void pong(){
    printf("pong\n");
    ping();
}

int main(int argc, char *argv[]){
    ping();
    return 0;
}

上面的程序是一个循环的调用,假设我们不断地在子程序里调用子程序(当然,上面调用的层次还很浅),我们知道,这样很快就会将调用栈耗尽,抛出Segmental Fault

而一般,我们使用ucontext族函数,就不会出现这种情况。

#include <ucontext.h>
#include <stdio.h>

#define MAX_COUNT (1<<30)

static ucontext_t uc[3];
static int count = 0;

void ping();
void pong();

void ping(){
    while(count < MAX_COUNT){
        printf("ping %d\n", ++count);
        // yield to pong
        swapcontext(&uc[1], &uc[2]); // 保存当前context于uc[1],切换至uc[2]的context运行
    }
}

void pong(){
    while(count < MAX_COUNT){
        printf("pong %d\n", ++count);
        // yield to ping
        swapcontext(&uc[2], &uc[1]);// 保存当前context于uc[2],切换至uc[1]的context运行
    }
}

char st1[8192];
char st2[8192];

int main(int argc, char *argv[]){


    // initialize context
    getcontext(&uc[1]);
    getcontext(&uc[2]);

    uc[1].uc_link = &uc[0]; // 这个玩意表示uc[1]运行完成后,会跳至uc[0]指向的context继续运行
    uc[1].uc_stack.ss_sp = st1; // 设置新的堆栈
    uc[1].uc_stack.ss_size = sizeof st1;
    makecontext (&uc[1], ping, 0);

    uc[2].uc_link = &uc[0]; // 这个玩意表示uc[2]运行完成后,会跳至uc[0]指向的context继续运行
    uc[2].uc_stack.ss_sp = st2; // 设置新的堆栈
    uc[2].uc_stack.ss_size = sizeof st2;
    makecontext (&uc[2], pong, 0);

    // start ping-pong
    swapcontext(&uc[0], &uc[1]); // 将当前context信息保存至uc[0],跳转至uc[1]保存的context去执行
  // 这里我稍微要多说几句,因为我迷惑过,我曾经困惑的一点在于uc[0],为什么uc[0]不需要设置堆栈的信息?因为swapcontext已经帮我们做好了一切,swapcontext函数会将当前点的信息保存在uc[0]中,当然我们没有设置的话,默认的堆栈一定是主堆栈啦

    return 0;
}

我们现在应该可以了解设置uc_stack的缘由了,因为跳转至uc[1]或者uc[2]context继续运行时的数据会保存在我们所指定的堆栈中,并不会占用原来堆栈的空间,所以不会出现主堆栈一般不会出现溢出的情况。

当然,还有setcontext函数。

int setcontext(const ucontext_t *ucp);

这个函数其实很简单啦。那就是到ucp指向的那个context去执行。

借用一个很经典的例子:

#include <stdio.h>
#include <ucontext.h> 
#include <unistd.h> 
int main(int argc, char *argv[]) 
{ 
  ucontext_t context; 
  getcontext(&context); 
  puts("Hello world"); 
  sleep(1); 
  setcontext(&context); 
  return 0; 
}

这个函数会不断地打印Hello,world。原因也很简单,因为上面的getcontext函数将那个点的上下文信息保存到了context中,下面调用setcontext会返回到记录的点处继续执行,因此也就出现了不断地输出。

利用这组函数,我们可以实现一个coroutine,感兴趣的,可以看一下我添加了注释的coroutine库:

coroutine

3

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值