Unix/Linux编程:getcontext、setcontext

ucontext该结构提供了所谓的用户上下文信息,用于描述调用信号处理器函数前的进程状态,其中包括上一个进程信号掩码以及寄存器的保存值,例如程序计数器(cp)和栈指针寄存器(sp),使用结构 ucontext_t 的其他函数有 getcontext()、makecontext()、setcontext()和 swapcontext(),分别对应的功能是允许进程去接收、创建、改变以及交换执行上下文。(这些操作有点类似于 setjmp()和 longjmp(),但更为通用。)可以使用这些函数来实现协程(coroutines),令进程的执行线程在两个(或多个)函数之间交替。SUSv3 规定了这些函数,但将它们标记为已废止。SUSv4 则将其删去,并建议使用 POSIX 线程来重写旧有的应用程序。glibc 手册页提供了关于这些函数的深入信息。

ucontext使得linux程序可以在用户态执行上下文切换,从而避免了进程或者线程切换导致的切换用户空间、切换堆栈,因此,效率相对更高。

结构体

有两个结构体,分别是mcontext_t和ucontext_t,其中mcontext_t是透明的。我们只需要关注ucontext_t就可以了
ucontext_t定义在头文件ucontext.h中,为:

/* Userlevel context.  */
typedef struct ucontext_t
  {
    unsigned long int __ctx(uc_flags);
    struct ucontext_t *uc_link;
    stack_t uc_stack;
    mcontext_t uc_mcontext;
    sigset_t uc_sigmask;
    struct _libc_fpstate __fpregs_mem;
  } ucontext_t;

关键字段说明:

  • uc_link:当前context执行结束之后要执行的下一个context,如果uc_link为空,执行完当前context后退出程序。
  • uc_stack:当前上下文所需要的stack。
  • uc_mcontext:保存具体的程序执行上下文,如PC值、堆栈指针和寄存器的值。它的实现依赖于底层,是硬件平台相关的,因此不透明。
  • uc_sigmask:执行上下文过程中,要屏蔽的信号集合,即信号掩码。

stack_t 结构参考这里

函数

int getcontext(ucontext_t *ucp);

初始化ucp结构体,将当前上下文保存在ucp中。成功时,返回0,错误返回-1,并设置errno

int setcontext(const ucontext_t *ucp);

设置当前上下文为ucp所指向的上下文。setcontext的上下文ucp应该通过getcontext或者makecontext取得,如果调用成功则不返回。

  • 如果程序是通过getcontext取得,则程序会继续执行这个调用。
  • 如果context是通过makecontext取得,则程序调用makecontext函数的第二个参数指向的函数,这时,如果函数返回,则恢复ucp->uc_link,如果func函数返回,则恢复makecontext第一个参数指向的上下文第一个参数指向的上下文context_t中指向的uc_link.如果uc_link为NULL,则线程退出。
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);

makecontext修改通过getcontext取得的上下文ucp(这意味着调用makecontext前必须先调用getcontext)。然后给该上下文指定一个栈空间ucp->stack,设置后继的上下文ucp->uc_link.

当上下文通过setcontext或者swapcontext激活后,执行func函数,argc为func的参数个数,后面是func的参数序列。当func执行返回后,继承的上下文被激活,如果继承上下文为NULL时,线程退出。

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

保存当前上下文到oucp结构体中,然后激活upc上下文。

如果执行成功,getcontext返回0,setcontext和swapcontext不返回;如果执行失败,getcontext,setcontext,swapcontext返回-1,并设置对于的errno.

简单说来, getcontext获取当前上下文,setcontext设置当前上下文,swapcontext切换上下文,makecontext创建一个新的上下文。

例子

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

int done = 0;

int main()
{
    ucontext_t context;
    getcontext(&context);
    if (done)
    {
        printf("return from getcontext,exit\n");
        return 0;
    }

    done = 1;
    setcontext(&context);
    printf("finished");
    return 0;//never goto here!
}

在这里插入图片描述
程序先调用getcontext保存当前寄存器信息到了context中,然后执行setcontext,所谓的setcontext就是把context中的寄存器信息恢复到当前的寄存器信息,也就是说,强制把context的pc bp sp等值,赋值到了当前cpu的寄存中,显而易见的是,这又跳回去到了getcontext 处。

上面这个goto一样,那用goto就行了?那看下面这个例子(test2.c):

#include <ucontext.h>
#include <stdio.h>
 
int done = 0;
 
int func1(ucontext_t *context)
{
	done = 1;
	setcontext(context);
}
int main()
{
	ucontext_t context;
	getcontext(&context);
	if (done)
	{
		printf("return from getcontext,exit\n");
		return 0;
	}
	func1(&context);
	printf("finished");
	return 0;
}

在这里插入图片描述
goto 一定做不了函数之间的跳转,只能做本地跳转。当然 getcontet 和 setcontext 肯定不仅仅只有这些功能,看下面这个例子。

//test3.c
 
#include <ucontext.h>
#include <stdio.h>
#include <malloc.h>
void func()
{
        printf("in func\n");
}
 
int main()
{
        ucontext_t context;
        getcontext(&context);
 
        //指定栈
        context.uc_stack.ss_sp = malloc(10000);
        context.uc_stack.ss_size = 10000;
        context.uc_link = NULL;
        makecontext(&context, func, 0);
 
        setcontext(&context);
         printf("finished");
        return 0;//never goto here!
}

在这里插入图片描述

先setcontext初始化context,然后makecontext,指定跳转的函数,然后再setcontext切换context到func函数。

除此之外,还为新的context指定了新的栈,为什么呢?因为如果不指定栈,那么还是getcontext时获取的sp bp指针,sp bp描述的是main函数的栈大小,如果main函数栈大小是100字节,但是你所要指定的fun的栈大小需要1000字节,显然不够用。

看到这,肯定好多人心里很是疑惑,如果执行完func,还会返回到main吗?答是不会。因为这里没有设置后继上下文。

例子:

#include <ucontext.h>
#include <stdio.h>
 
void func1(void * arg)
{
    puts("1");
    puts("11");
    puts("111");
    puts("1111");
 
}
void context_test()
{
    char stack[1024*128];
    ucontext_t child,main;
 
    getcontext(&child); //获取当前上下文
    child.uc_stack.ss_sp = stack;//指定栈空间
    child.uc_stack.ss_size = sizeof(stack);//指定栈空间大小
    child.uc_stack.ss_flags = 0;
    child.uc_link = &main;//设置后继上下文
 
    makecontext(&child,(void (*)(void))func1,0);//修改上下文指向func1函数
 
    swapcontext(&main,&child);//切换到child上下文,保存当前上下文到main
    puts("main");//如果设置了后继上下文,func1函数指向完后会返回此处
}
 
int main()
{
    context_test();
 
    return 0;
}

在context_test中,创建了一个用户线程child,其运行的函数为func1.指定后继上下文为main
func1返回后激活后继上下文,继续执行主函数。
在这里插入图片描述

例子:

#include <ucontext.h>
#include <stdio.h>
#include <malloc.h>
int did = 0;
void func()
{
	did = 1;
	printf("in func\n");
}
 
int main()
{
	ucontext_t context,rt;
	getcontext(&context);
	getcontext(&rt);
	if(did == 1)
	{
		printf("continue from func\n");
		return 0;
	}
	//指定栈
	context.uc_stack.ss_sp = malloc(10000);
	context.uc_stack.ss_size = 10000;
	context.uc_link = &rt;
	makecontext(&context, func, 0);
 
	setcontext(&context);
 
	return 0;//never goto here!
}

在这里插入图片描述
test4.c比test3.c就多个一个对uc_link的赋值(还有一些流程控制变量)。uc_link就指定了func执行完之后,接着执行的上下文,如果为空,则执行完func后,exit,就像test3.c一样;如果uc_link不为空,则执行完func后,接着执行uc_link就指定的上下文。

就如__start_context逻辑一致。

接下来,看test4.c,我们从getcontext(&rt);处返回了,但是能不能从setcontext处返回呢,当然可以,方法1就是getcontext(&rt);返回后,goto到setcontext 后面。但是glibc还提供了一个接口就是swapcontext。且看下面那个例子。

#include <ucontext.h>
#include <stdio.h>
#include <malloc.h>
int did = 0;
void func()
{
	did = 1;
	printf("in func\n");
}
 
int main()
{
	ucontext_t context,rt;
	getcontext(&context);
 
	//指定栈
	context.uc_stack.ss_sp = malloc(10000);
	context.uc_stack.ss_size = 10000;
	context.uc_link = &rt;
	makecontext(&context, func, 0);
 
	swapcontext(&rt,&context);
 
	printf("finally return\n");
	return 0;//should goto here!
}

swapcontext 两件事,一个就是保存当前的上下文,到rt中,然后切换到context。context指定的上下文执行完成之后,就跳到了swapcontext后面了。

注意:这些xxxcontext函数给予了用户态程序“调度的功能”,这和os的调度不同,os是对各个线程,即task_struct进行切换调度,而各个线程内部,也就是用户态程序,可以用上面这些库函数进行颗粒度更小的调度(说切换更合理)。这就是所谓的协程的概念。

您需要使用 JavaScript 发送 AJAX 请求来将拍照的图片上传到指定的 URL。以下是一个示例代码: ```javascript // 获取拍照按钮和视频元素 const takeBtn = document.getElementById('take'); const video = document.getElementById('v'); // 点击拍照按钮时触发 takeBtn.addEventListener('click', function() { // 获取 canvas 元素和图像元素 const canvas = document.getElementById('canvas'); const photo = document.getElementById('photo'); // 将视频的帧画面绘制到 canvas 中 canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height); // 将 canvas 转换为 base64 编码的图像数据 const imageData = canvas.toDataURL('image/jpeg'); // 发送 AJAX 请求上传图像数据 const xhr = new XMLHttpRequest(); xhr.open('POST', 'http://localhost:5001/static/picture'); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onload = function() { console.log('上传成功'); }; xhr.onerror = function() { console.error('上传失败'); }; xhr.send('image=' + encodeURIComponent(imageData)); // 将图像元素的 src 属性设置为 base64 编码的图像数据 photo.setAttribute('src', imageData); }); ``` 需要注意的是,以上代码中的 AJAX 请求使用 POST 方法上传图像数据。服务器端需要能够解析该请求,并将图像数据保存到指定的路径。另外,由于图像数据是以 base64 编码的形式上传的,因此需要在服务器端进行解码才能保存为图像文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值