今天,我要寫一篇文章,好好來說一下我所理解的ucontext族函數(shù)。
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 typesmcontext_tanducontext_tdefined in <ucontext.h> and the four functionsgetcontext(),setcontext(),makecontext(3), andswapcontext(3) that allow user-level context switching between multiple threads of control within a process.
Themcontext_ttype is machine-dependent and opaque. Theucontext_ttype 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_tandstack_tdefined in <signal.h>. Hereuc_linkpoints to the context that will be resumed when the current context terminates (in case the current context was created usingmakecontext(3)), uc_sigmask is the set of signals blocked in this context (seesigprocmask(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 ofgetcontext(), ormakecontext(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 tomakecontext(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 isNULL, 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 andsetcontext()does not return. On error,
both return -1 and set errno appropriately.ERRORS
None defined.
關(guān)于另外的一組函數(shù)在man手冊上的說明:
NAME
makecontext,swapcontext- manipulate user contextSYNOPSIS
#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 typeucontext_tdefined in <ucontext.h> and the four functionsgetcontext(3),setcontext(3),makecontext() andswapcontext() 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 toucp->uc_stack, and define a successor context and assign its address toucp->uc_link.When this context is later activated (using
setcontext(3) orswapcontext()) 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 likeswapcontext()returns 0.) On error,swapcontext()returns -1 and sets errno appropriately.ERRORS
ENOMEM Insufficient stack space left.
上面的東西大家讀一讀就好了,我這里稍微來說一下我的感受吧!
這一族函數(shù)在使用感受上非常類似于我們使用過的goto語句。
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);
通過swapcontext函數(shù),可以十分方便地實現(xiàn)從這個執(zhí)行點跳躍到另外一個執(zhí)行點??墒沁@個東西確實和goto語句有非常大的不同。因為它們可以記錄下跳躍點的上下文,這就非常有意思了,我舉一個不恰當(dāng)?shù)谋扔?,這就是魔法世界中的時間靜止的魔法,巫師揮一揮魔杖,在這個點的一切信息都保留了下來,然后巫師跑到另外一個世界繼續(xù)冒險。厭倦了之后,回到這個世界,一切又從原來靜止的點開始執(zhí)行。
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
makecontext這個函數(shù)是用來干什么的呢?其實這個函數(shù)和構(gòu)造線程的函數(shù)非常類似,不過,它構(gòu)造的是一個線程,而你是制造一個新的context。這個context是你將來要進入的一個世界,所以,在進入之前,你首先要準(zhǔn)備一些什么,func是你你要執(zhí)行的函數(shù)(你的任務(wù)),argc是這個函數(shù)的參數(shù)個數(shù),而后面的可變部分,是你要傳遞給func的實參。在調(diào)用makecontext函數(shù)之前,我們首先要初始化ucp指向的ucontext_t結(jié)構(gòu)體,正如前面所說的,ucontext_t結(jié)構(gòu)只是一個規(guī)范而已,不同平臺下定義的域都有可能有所不同,所以我們只能用getcontext函數(shù)來初始化這樣一個結(jié)構(gòu)體。另外一個比較重要的在ucp上要設(shè)置的一個域叫做uc_link,這個玩意也指向一個ucontext_t結(jié)構(gòu),這個玩意是在詢問我們,這個context運行完成后,你要回到哪里?(當(dāng)這個世界湮滅后,你要去哪里?)如果你不設(shè)置,相當(dāng)于說,我們結(jié)束吧(你要和這個世界共同進退)。不運行了。如果設(shè)置了,那么會跳轉(zhuǎn)到uc_link指向的context繼續(xù)運行。
另外需要注意的一點是,我們還需要設(shè)置ucontext_t的uc_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;
}
上面的程序是一個循環(huán)的調(diào)用,假設(shè)我們不斷地在子程序里調(diào)用子程序(當(dāng)然,上面調(diào)用的層次還很淺),我們知道,這樣很快就會將調(diào)用棧耗盡,拋出Segmental Fault。
而一般,我們使用ucontext族函數(shù),就不會出現(xiàn)這種情況。
#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]); // 保存當(dāng)前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]);// 保存當(dāng)前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繼續(xù)運行
uc[1].uc_stack.ss_sp = st1; // 設(shè)置新的堆棧
uc[1].uc_stack.ss_size = sizeof st1;
makecontext (&uc[1], ping, 0);
uc[2].uc_link = &uc[0]; // 這個玩意表示uc[2]運行完成后,會跳至uc[0]指向的context繼續(xù)運行
uc[2].uc_stack.ss_sp = st2; // 設(shè)置新的堆棧
uc[2].uc_stack.ss_size = sizeof st2;
makecontext (&uc[2], pong, 0);
// start ping-pong
swapcontext(&uc[0], &uc[1]); // 將當(dāng)前context信息保存至uc[0],跳轉(zhuǎn)至uc[1]保存的context去執(zhí)行
// 這里我稍微要多說幾句,因為我迷惑過,我曾經(jīng)困惑的一點在于uc[0],為什么uc[0]不需要設(shè)置堆棧的信息?因為swapcontext已經(jīng)幫我們做好了一切,swapcontext函數(shù)會將當(dāng)前點的信息保存在uc[0]中,當(dāng)然我們沒有設(shè)置的話,默認(rèn)的堆棧一定是主堆棧啦
return 0;
}
我們現(xiàn)在應(yīng)該可以了解設(shè)置uc_stack的緣由了,因為跳轉(zhuǎn)至uc[1]或者uc[2]的context繼續(xù)運行時的數(shù)據(jù)會保存在我們所指定的堆棧中,并不會占用原來堆棧的空間,所以不會出現(xiàn)主堆棧一般不會出現(xiàn)溢出的情況。
當(dāng)然,還有setcontext函數(shù)。
int setcontext(const ucontext_t *ucp);
這個函數(shù)其實很簡單啦。那就是到ucp指向的那個context去執(zhí)行。
借用一個很經(jīng)典的例子:
#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;
}
這個函數(shù)會不斷地打印Hello,world。原因也很簡單,因為上面的getcontext函數(shù)將那個點的上下文信息保存到了context中,下面調(diào)用setcontext會返回到記錄的點處繼續(xù)執(zhí)行,因此也就出現(xiàn)了不斷地輸出。
利用這組函數(shù),我們可以實現(xiàn)一個coroutine,感興趣的,可以看一下我添加了注釋的coroutine庫: