【C协程】ucontext入解

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

在系统V环境中,我们可以通过getcontext,setcontext,makecontext,swapcontext函数族,在单进程中实现用户态上下文的切换。

一、认识ucontext

首先我们来认识一下这个结构中的文键变量,结构如下,需要关注到的是uc_stack,以及uc_link,uc_stack指向上下文切换需要保存的栈空间,需要用户分配。uc_link指向当时上下文执行函数成功退出后,指定激活的上下文,如果为uc_link为NULL时则直接退出。

typedef struct ucontext
{
    unsigned long int uc_flags;
    struct ucontext *uc_link;
    stack_t uc_stack;
    mcontext_t uc_mcontext;
    __sigset_t uc_sigmask;
    struct _fpstate __fpregs_mem;
} ucontext_t;

二、认识函数族

int getcontext(ucontext_t *ucp);

getcontext(2) gets the current context of the calling process, storing it in the ucontext struct  pointed  to  by ucp.

getcontext把当前的上下文保存在ucp中

int setcontext(const ucontext_t *ucp);

setcontext(2)  sets  the  context of the calling process to the state stored in the ucontext struct pointed to by ucp. The struct must either have been created by getcontext(2) or have been passed as the third parameter of  the sigaction(2) signal handler.

setcontext把当前进程恢复到ucp指针指向的上下文,ucp必须由getcontext创建

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

The  makecontext()  function  modifies  the  context pointed to by ucp (which was obtained from a call to getcon-text(2)).  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(2) 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  argu-ments in argc.  When this function returns, the successor context is activated.  If the successor context pointer is NULL, the thread exits.

makecontext修改ucp指针指向的上下文,在调用makecontext之前,必须为ucp对象分配一个新栈并赋为ucp->uc_stack,同时定义一个成功返回后恢复的上下文并把地址赋给ucp->uc_link。当ucp被激活后(通过setcontext或swapcontext激活),func函数会被调用,并把一系统参数传凝视给func函数。当func函数返回原来,uc_link会被激活,uc_link为NULL时,线程退出。

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

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

swapcontext把当前上下文保存在oucp中,并激活ucp。

三、示例

3.1 MAN手册示例

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

static ucontext_t uctx_main, uctx_func1, uctx_func2;

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

    static void
func1(void)
{
    printf("func1: started\n");
    printf("func1: swapcontext(&uctx_func1, &uctx_func2)\n");
    if (swapcontext(&uctx_func1, &uctx_func2) == -1)
        handle_error("swapcontext");
    printf("func1: returning\n");
}

    static void
func2(void)
{
    printf("func2: started\n");
    printf("func2: swapcontext(&uctx_func2, &uctx_func1)\n");
    if (swapcontext(&uctx_func2, &uctx_func1) == -1)
        handle_error("swapcontext");
    printf("func2: returning\n");
}

    int
main(int argc, char *argv[])
{
    char func1_stack[16384];
    char func2_stack[16384];

    if (getcontext(&uctx_func1) == -1)
        handle_error("getcontext");
    uctx_func1.uc_stack.ss_sp = func1_stack;
    uctx_func1.uc_stack.ss_size = sizeof(func1_stack);
    //uctx_func1.uc_link = &uctx_func2;
    uctx_func1.uc_link = &uctx_main;
    makecontext(&uctx_func1, func1, 0);

    if (getcontext(&uctx_func2) == -1)
        handle_error("getcontext");
    uctx_func2.uc_stack.ss_sp = func2_stack;
    uctx_func2.uc_stack.ss_size = sizeof(func2_stack);
    /*  Successor context is f1(), unless argc > 1 */
    uctx_func2.uc_link = (argc > 1) ? NULL : &uctx_func1;
    makecontext(&uctx_func2, func2, 0);

    printf("main: swapcontext(&uctx_main, &uctx_func2)\n");
    if (swapcontext(&uctx_main, &uctx_func2) == -1)
        handle_error("swapcontext");

    printf("main: exiting\n");
    exit(EXIT_SUCCESS);
}

输出如下:

func2: started
func2: swapcontext(&uctx_func2, &uctx_func1)
func1: started
func1: swapcontext(&uctx_func1, &uctx_func2)
func2: returning
func1: returning
main: exiting

3.2 生产者消费者

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

static ucontext_t uc_loop, uc_main, uc_consume, uc_produce;
static int ci, pi, loop;

static void cb_consume()
{
    printf("consume %d\n", ci);
    ci++;
    sleep(1);
}

static void cb_produce()
{
    printf("produce %d\n", pi);
    pi++;
}

int main(void)
{
    char  consume_stack[1024], produce_stack[1024];

    getcontext(&uc_loop);

    getcontext(&uc_produce);
    uc_produce.uc_stack.ss_sp = produce_stack;
    uc_produce.uc_stack.ss_size = sizeof(produce_stack);
    uc_produce.uc_link = &uc_consume;
    makecontext(&uc_produce, cb_produce, 0);
    printf("finish make produce\n");

    getcontext(&uc_consume);
    uc_consume.uc_stack.ss_sp = consume_stack;
    uc_consume.uc_stack.ss_size = sizeof(consume_stack);
    uc_consume.uc_link = &uc_main;
    makecontext(&uc_consume, cb_consume, 0);
    printf("finish make consume\n");

    printf("swap main to produce\n");
    swapcontext(&uc_main, &uc_produce);
    printf("swap back to main\n");

    sleep(1);
    loop++;
    if (loop < 2) setcontext(&uc_loop);

    return 0;
}

输出如下:

finish make produce
finish make consume
swap main to produce
produce 0
consume 0
swap back to main
finish make produce
finish make consume
swap main to produce
produce 1
consume 1
swap back to main

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏liuchengxu

在 Shell 脚本中调用另一个 Shell 脚本的三种方式

fork 是最普通的, 就是直接在脚本里面用 path/to/foo.sh 来调用 foo.sh 这个脚本,比如如果是 foo.sh 在当前目录下,就是 ./...

1912
来自专栏云霄雨霁

内存可见性

2212
来自专栏程序你好

Java虚拟机JVM架构解析

每个Java开发人员都知道字节码将由JRE (Java运行时环境)运行。但是许多人不知道JRE是Java虚拟机(JVM)的实现,它分析字节码、解释并执行代码。作...

762
来自专栏IT技术精选文摘

深入理解 Java 并发之 synchronized 实现原理

线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据。因此为了...

4697
来自专栏顶级程序员

你真的了解 volatile 关键字吗?

作者:Ruheng, www.jianshu.com/p/7798161d7472 一、Java内存模型 想要理解volatile为什么能确保可见性,就要先理...

3477
来自专栏大闲人柴毛毛

深入剖析Spring(一)——IoC的基本概念(从面向对象角度介绍)

IoC与DI IoC和DI是Spring的两个核心概念,很多人都把它们视为相同的东西,但事实并非如此。 IoC(Inversion of Control)...

3575
来自专栏北京马哥教育

十分钟带你了解 Python3 多线程核心知识

每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 每个线程都有他自...

2705
来自专栏java 成神之路

JVM 类加载机制深入浅出

27211
来自专栏Urahara Blog

Web For Pentester - Directory traversal & File Include Part Tips

2886
来自专栏Java 源码分析

synchronized 原理分析

synchronized 原理分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读起来没有 IDE 方...

2573

扫码关注云+社区

领取腾讯云代金券