我所理解的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 <ucontext.h> and the four functions getcontext(), setcontext(), makecontext(3), and swapcontext(3) that allow user-level context switching between multiple threads of control within a process.
The mcontext_t type is machine-dependent and opaque. The ucontext_t type is a structure that has at least the following fields:

typedef struct ucontext {
struct ucontext *uc_link;
sigset_t         uc_sigmask;
stack_t          uc_stack;
mcontext_t       uc_mcontext;
...
} ucontext_t;

with sigset_t and stack_t defined in <signal.h>. Here uc_link points to the context that will be resumed when the current context terminates (in case the current context was created using makecontext(3)), uc_sigmask is the set of signals blocked in this context (see sigprocmask(2)), uc_stack is the stack used by this context (see sigaltstack(2)), and uc_mcontext is the machine-specific representation of the saved context,that includes the calling thread's machine registers.

The function getcontext() initializes the structure pointed at by ucp to the currently active context.

The function setcontext() restores the user context pointed at by ucp. A successful call does not return. The context should have been obtained by a call of getcontext(), or makecontext(3), or passed as third argument to a signal handler.

If the context was obtained by a call of getcontext(), program execution continues as if this call just returned.

If the context was obtained by a call of makecontext(3), program execution continues by
a call to the function func specified as the second argument of that call to makecontext(3). When the function func returns, we continue with the uc_link member of the structure ucp specified as the first argument of that call to makecontext(3). When this member is NULL, the thread exits.

If the context was obtained by a call to a signal handler, then old standard text says
that "program execution continues with the program instruction following the instruction interrupted by the signal". However, this sentence was removed in SUSv2, and the present verdict is "the result is unspecified".

RETURN VALUE
When successful, getcontext() returns 0 and setcontext() does not return. On error,
both return -1 and set errno appropriately.

ERRORS
None defined.

关于另外的一组函数在man手册上的说明:

NAME
makecontext, swapcontext - manipulate user context

SYNOPSIS

#include <ucontext.h>
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);

DESCRIPTION
In a System V-like environment, one has the type ucontext_t defined in <ucontext.h> and the four functions getcontext(3), setcontext(3), makecontext() and swapcontext() that allow user-level context switching between multiple threads of control within a process.

For the type and the first two functions, see getcontext(3).

The makecontext() function modifies the context pointed to by ucp (which was obtained from a call to getcontext(3)). Before invoking makecontext(), the caller must allocate a new stack for this context and assign its address to ucp->uc_stack, and define a successor context and assign its address to ucp->uc_link.

When this context is later activated (using setcontext(3) or swapcontext()) the function func is called, and passed the series of integer (int) arguments that follow argc;the caller must specify the number of these arguments in argc. When this function returns, the successor context is activated. If the successor context pointer is NULL,the thread exits.

The swapcontext() function saves the current context in the structure pointed to by oucp, and then activates the context pointed to by ucp.

RETURN VALUE
When successful, swapcontext() does not return. (But we may return later, in case oucp is activated, in which case it looks like swapcontext() returns 0.) On error, swapcontext() returns -1 and sets errno appropriately.

ERRORS
ENOMEM Insufficient stack space left.

上面的东西大家读一读就好了,我这里稍微来说一下我的感受吧!

这一族函数在使用感受上非常类似于我们使用过的goto语句。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 140,386评论 1 295
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 60,319评论 1 254
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 92,325评论 0 206
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 40,558评论 0 171
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 48,200评论 1 249
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,496评论 1 167
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,191评论 2 265
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 28,971评论 0 161
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 28,688评论 6 223
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,330评论 0 211
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,098评论 2 211
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,412评论 1 222
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,116评论 0 31
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 26,937评论 2 206
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,314评论 3 200
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,558评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 25,879评论 0 162
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,275评论 2 226
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,386评论 2 228

推荐阅读更多精彩内容