setcontext getcontext makecontext swapcontext

article/2025/9/12 22:36:13

Linux上下文切换以及协程

 

     上下文切换,听起来虚无缥缈,什么是上下文,切换又是指的是什么?其实上下文就可以理解为一个进程所运行的相关的寄存器值,即包括sp/bp/pc等值,换句话说,一个上下文,就是包括了能够恢复进程运行所需要的所有必要的东西。所谓的切换, 那是多进程的操作系统必要的功能,一个CPU能够运行多个进程(看起来),那么必然要在多个进程之间不停的切换,A切换到B时,必然需要保存A相关信息,这样才能从B切换回来时接着运行A,且正确的运行A。

    不过上面描述的都是OS的事情, 一个进程或者一个线程在内核都是由struct task_struct描述的,OS的调度对象就是task_struct。而在用户态想实现类似的功能,那么必然需要对应的库函数。根据上面的描述,我们已经知道,要实现所谓的调度,那么必须保存相关寄存器信息。

 

先看例test1.c

 

 

#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);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);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);return 0;//never goto here!
}


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


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

 

 

 

看到这,肯定好多人心里很是疑惑,如果执行完func,还会返回到main吗?答是不会。为什么不会?难道func不是man调用的吗?怎么会不返回到main呢?下面我们就讲讲这个问题,不过讲这个问题前,希望大家自行去了解一下x86、x64的调用规则。


我们知道一个main作为一个函数,肯定是被其他地方调用的。
我们写一个简单的main函数,然后断点打住,推栈,返现其调用者是 __libc_start_main:

 

Breakpoint 3, 0x00000000004004d8 in main () at main.c:7
7	}
(gdb) disassemble
Dump of assembler code for function main:0x00000000004004c4 <+0>:	push   %rbp0x00000000004004c5 <+1>:	mov    %rsp,%rbp0x00000000004004c8 <+4>:	mov    $0xa,%edi0x00000000004004cd <+9>:	callq  0x4003b8 <putchar@plt>0x00000000004004d2 <+14>:	mov    $0x0,%eax0x00000000004004d7 <+19>:	leaveq
=> 0x00000000004004d8 <+20>:	retq
End of assembler dump.
(gdb) i r
rax            0x0	0
rbx            0x0	0
rcx            0xffffffff	4294967295
rdx            0x7ffff77d1e10	140737345560080
rsi            0x7ffff7ff7000	140737354100736
rdi            0x0	0
rbp            0xcc	0xcc
rsp            0x7fffffffe528	0x7fffffffe528
r8             0xffffffff	4294967295
r9             0xa	10
r10            0xffffffff	4294967295
r11            0x246	582
r12            0x4003e0	4195296
r13            0x7fffffffe600	140737488348672
r14            0x0	0
r15            0x0	0
rip            0x4004d8	0x4004d8 <main+20>
eflags         0x246	[ PF ZF IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0
(gdb) x/40xg 0x7fffffffe528
0x7fffffffe528:	0x00007ffff7460d5d	0x0000000000000000
0x7fffffffe538:	0x00007fffffffe608	0x0000000100000000
0x7fffffffe548:	0x00000000004004c4	0x0000000000000000
0x7fffffffe558:	0x6fcc4be0773d5f7c	0x00000000004003e0
0x7fffffffe568:	0x00007fffffffe600	0x0000000000000000
0x7fffffffe578:	0x0000000000000000	0x9033b41fbd5d5f7c
0x7fffffffe588:	0x9033a56c6d0d5f7c	0x00007fff00000000
0x7fffffffe598:	0x0000000000000000	0x0000000000000000
0x7fffffffe5a8:	0x00000000004004f0	0x00007fffffffe608
0x7fffffffe5b8:	0x0000000000000001	0x0000000000000000
0x7fffffffe5c8:	0x0000000000000000	0x00000000004003e0
0x7fffffffe5d8:	0x00007fffffffe600	0x0000000000000000
0x7fffffffe5e8:	0x0000000000400409	0x00007fffffffe5f8
0x7fffffffe5f8:	0x000000000000001c	0x0000000000000001
0x7fffffffe608:	0x00007fffffffe805	0x0000000000000000
0x7fffffffe618:	0x00007fffffffe822	0x00007fffffffe83d
0x7fffffffe628:	0x00007fffffffe84d	0x00007fffffffe861
0x7fffffffe638:	0x00007fffffffe872	0x00007fffffffee7f
0x7fffffffe648:	0x00007fffffffee95	0x00007fffffffeea6
0x7fffffffe658:	0x00007fffffffeebb	0x00007fffffffeec7
(gdb) info symbol 0x00007ffff7460d5d
__libc_start_main + 253 in section .text of /lib64/libc.so.6


main函数返回后,执行了0x00007ffff7460d5d,也直接调用了exit,结束进程。

0x00007ffff7460d5d:

   0x00007ffff7460d56 <+246>:	mov    (%rax),%rdx0x00007ffff7460d59 <+249>:	callq  *0x18(%rsp)0x00007ffff7460d5d <+253>:	mov    %eax,%edi0x00007ffff7460d5f <+255>:	callq  0x7ffff7477a40 <exit>




说了那么多,那么我们 test3.c 的func是谁调用的?如果简单的想
其调用栈肯定是
main->setcontext->func ,但是我上面说了,并不是这样的。

 

我们在func打断点看一下:

 

Breakpoint 1, func () at test3.c:6
6		printf("in func\n");
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.166.alios6.7.x86_64
(gdb) bt
#0  func () at test3.c:6
#1  0x00007ffff74858f0 in ?? () from /lib64/libc.so.6
#2  0x0000000000000000 in ?? ()
(gdb) disassemble
Dump of assembler code for function func:0x00000000004005f4 <+0>:	push   %rbp0x00000000004005f5 <+1>:	mov    %rsp,%rbp
=> 0x00000000004005f8 <+4>:	mov    $0x400778,%edi0x00000000004005fd <+9>:	callq  0x4004a8 <puts@plt>0x0000000000400602 <+14>:	leaveq0x0000000000400603 <+15>:	retq
End of assembler dump.
(gdb) i r
rax            0x0	0
rbx            0x603710	6305552
rcx            0x0	0
rdx            0x7fffffffe618	140737488348696
rsi            0x7fffffffe608	140737488348680
rdi            0x7fffffffe170	140737488347504
rbp            0x603700	0x603700
rsp            0x603700	0x603700
r8             0x7ffff77d1300	140737345557248
r9             0x7ffff7debac0	140737351957184
r10            0x8	8
r11            0x246	582
r12            0x400510	4195600
r13            0x7fffffffe600	140737488348672
r14            0x0	0
r15            0x0	0
rip            0x4005f8	0x4005f8 <func+4>
eflags         0x246	[ PF ZF IF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0
(gdb) x/40xg 0x603700
0x603700:	0x00007fffffffe520	0x00007ffff74858f0
0x603710:	0x0000000000000000	0x0000000000000000
0x603720:	0x0000000000000000	0x00000000000208e1
0x603730:	0x0000000000000000	0x0000000000000000
0x603740:	0x0000000000000000	0x0000000000000000
0x603750:	0x0000000000000000	0x0000000000000000
0x603760:	0x0000000000000000	0x0000000000000000
0x603770:	0x0000000000000000	0x0000000000000000
0x603780:	0x0000000000000000	0x0000000000000000
0x603790:	0x0000000000000000	0x0000000000000000
0x6037a0:	0x0000000000000000	0x0000000000000000
0x6037b0:	0x0000000000000000	0x0000000000000000
0x6037c0:	0x0000000000000000	0x0000000000000000
0x6037d0:	0x0000000000000000	0x0000000000000000
0x6037e0:	0x0000000000000000	0x0000000000000000
0x6037f0:	0x0000000000000000	0x0000000000000000
0x603800:	0x0000000000000000	0x0000000000000000
0x603810:	0x0000000000000000	0x0000000000000000
0x603820:	0x0000000000000000	0x0000000000000000
0x603830:	0x0000000000000000	0x0000000000000000
(gdb) info symbol 0x00007ffff74858f0
__start_context in section .text of /lib64/libc.so.6

func的栈地址是0x603700 他是我们的main函数malloc得到的 地址+栈大小 从而得到的栈顶(还有一定的偏移)。
现在问题来了,func是由__start_context调用的 ,而且从 x/40xg 0x603700 指令可以看出,除了__start_context,栈其余全是0,也就意味着没有人调用__start_context。也意味着没有从main->setcontext  …  一路调用到func。很奇怪把,但是仔细想想,所谓栈,也就是内存,内存里是什么东西,肯定有人放的。

 

其实能够猜到__start_context是被刻意安排在栈中的。即刻意安排在0x603700中的,好让func执行 retq返回时,读取的pc指针是__start_context。

 

ss_sp就是我们在main函数中malloc的地址,sp = ss_sp + ss_size就指向了栈顶。
我们在sp[0]安排了一个地址&__start_context,而func的函数栈就是这个sp,func在最后执行ret时,会pop 这个 sp[0],然后放到自己的pc指针上,然后跳到pc处,也就是说,func的返回值就是__start_context。

好,现在看看__start_context干了些什么事情,以为从前面推栈的结果来看没人调用了__start_context,所以猜想里面肯定直接调用了exit了。

 

(gdb) disassemble __start_context
Dump of assembler code for function __start_context:0x00007ffff74858f0 <+0>:	mov    %rbx,%rsp0x00007ffff74858f3 <+3>:	pop    %rdi0x00007ffff74858f4 <+4>:	test   %rdi,%rdi0x00007ffff74858f7 <+7>:	je     0x7ffff74858fe <__start_context+14>0x00007ffff74858f9 <+9>:	callq  0x7ffff7483090 <setcontext>0x00007ffff74858fe <+14>:	mov    %rax,%rdi0x00007ffff7485901 <+17>:	callq  0x7ffff7477a40 <exit>0x00007ffff7485906 <+22>:	hlt
End of assembler dump.


func返回到__start_context后,就exit。

 

 

 

总结一下, 在执行func时,在其栈顶放了一个__start_context,这样func执行ret时,就执行到了__start_context。换句话说,并不是__start_context调用了func,而是func返回到了__start_context。

 

 

仔细看__start_context函数汇编,他有一个可能就是不执行 exit,而是执行setcontext,判断条件就是%rdi是否是0,不是0就指向setcontext了,这个又是什么逻辑呢。且看test4.c

 

 

 

#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进行切换调度,而各个线程内部,也就是用户态程序,可以用上面这些库函数进行颗粒度更小的调度(说切换更合理)。这就是所谓的协程的概念。

 

 


http://chatgpt.dhexx.cn/article/oUG5QIFx.shtml

相关文章

android之getContext和getActivity介绍

image.png getContext View类中提供的方法&#xff0c;在继承了View的类中才可以调用。 返回的是当前View运行在哪个Activity Contex中&#xff0c;获取当前context的实例。 如果使用场景是Activity则相当于 this&#xff0c;如果使用场景是一个Server 那么获取的实例就是一个A…

sws_getContext函数详细使用

成功后返回SwsContext 类型的结构体。 参数1&#xff1a;被转换源的宽 参数2&#xff1a;被转换源的高 参数3&#xff1a;被转换源的格式&#xff0c;eg&#xff1a;YUV、RGB……&#xff08;枚举格式&#xff0c;也可以直接用枚举的代号表示eg&#xff1a;AV_PIX_FMT_YUV42…

canvas中getContext(“2d“) 对象的属性和方法

HTML5中canvas标签用于绘制图像&#xff08;通过脚本&#xff0c;通常是Js&#xff09;。 也就是说&#xff0c;canvas元素本身没有绘制能力仅仅是图形容 - 您必须使用脚本来完成实际的绘图任务。 getContext() 方法可返回一个对象&#xff0c;该对象提供了用于在画布上绘图的…

Debug:无法找到 getContext() 方法

Mybatis反向生成Swagger自动注释 - SegmentFault 思否 在使用该插件时 无法找到 getContext() 方法&#xff0c;是因为 mybatis-generator-core版本高于1.4.0&#xff0c;建议使用1.4.0一下版本进行构建

Android Context解析以及getContext()、getApplication()、getApplicationContext()和getBaseContext()区别

文章目录 Context 介绍Context数量getContext()、getApplication()、getApplicationContext()和getBaseContext()区别getContextgetApplication()、getApplicationContext()getBaseContext() Context 介绍 Android程序不像Java程序一样&#xff0c;随便创建一个类&#xff0c;…

sws_getContext和sws_scale分析

struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param);创建转换上下文&#xff0c;参数分析&#xff1a;…

View Fragment Window 的 getContext() 一定返回 Activity 吗?

目录 1. 问题分析 1.1 Context 有哪些&#xff1f; 首先&#xff0c;我们回顾一下 Context 以及它的子类&#xff0c;在之前的这篇文章里&#xff0c;我们曾经讨论过&#xff1a;《Android | 一个进程有多少个 Context 对象&#xff08;答对的不多&#xff09;》。简单来说&a…

HTML5 canvas 参考手册

HTML5 <canvas> 参考手册 描述 HTML5 <canvas> 标签用于绘制图像&#xff08;通过脚本&#xff0c;通常是 JavaScript&#xff09;。 不过&#xff0c;<canvas> 元素本身并没有绘制能力&#xff08;它仅仅是图形的容器&#xff09; - 您必须使用脚本来完成实…

Unix/Linux编程:getcontext、setcontext

ucontext该结构提供了所谓的用户上下文信息&#xff0c;用于描述调用信号处理器函数前的进程状态&#xff0c;其中包括上一个进程信号掩码以及寄存器的保存值&#xff0c;例如程序计数器&#xff08;cp&#xff09;和栈指针寄存器&#xff08;sp&#xff09;&#xff0c;使用结…

Andriod getContext和getActivity

原创文章&#xff0c;如有转载&#xff0c;请注明出处&#xff1a;http://blog.csdn.net/myth13141314/article/details/62045162 MainActivity.this&#xff1a;表示MainActivity对象&#xff0c;一般用在内部类中指示外面的this&#xff0c;如果在内部类直接用this&#xff…

岭回归模型|机器学习|回归算法

目录 1.岭回归模型1.1背景1.2损失函数 2.相关代码2.1RidgeRegression类2.2求解代码2.3绘图代码 3.直接调库使用 1.岭回归模型 1.1背景 对于回归问题来说&#xff0c;它们的基本内容基本上都是相同的&#xff0c;所以岭回归模型与线性回归模型类似&#xff1a; y θ 0 x 0 …

机器学习学习笔记(13)----岭回归(Ridge回归)

在《机器学习学习笔记&#xff08;4&#xff09;----线性回归的数学解析》&#xff0c;我们通过计算线性模型的损失函数的梯度&#xff0c;得到使得损失函数为最小值的的解析解&#xff0c;被称之为普通最小二乘法&#xff1a; (1) 公式(1)能够求得的前提是是满秩矩阵&#xf…

Python机器学习教程—岭回归的原理和实现

在某些场景下&#xff0c;线性回归无法给出一个效果好的预测模型&#xff0c;那么就需要使用线性回归的升级版&#xff0c;去面对更复杂的应用场景&#xff0c;本文所记录的岭回归便是线性回归的一个升级版。 目录 强势样本对模型的影响 实例 岭回归定义 岭回归的实现 岭…

岭回归和Lasso回归

偏差和方差 机器学习算法针对特定数据所训练出来的模型并非是十全十美的&#xff0c;再加上数据本身的复杂性&#xff0c;误差不可避免。说到误差&#xff0c;就必须考虑其来源&#xff1a;模型误差 偏差&#xff08;Bias&#xff09; 方差&#xff08;Variance&#xff09; …

基于Python的岭回归模型

本文是使用Python进行岭回归模型分析&#xff0c;消除多重共线性 一、岭回归原理 自变量之间存在多重共线性&#xff0c;当时&#xff0c;加上一个正的常数矩阵(K>0)&#xff0c;岭回归估计定义公式&#xff1a; &#xff08;k为岭参数&#xff09; 的奇异程度比奇异程度小&…

用SPSS进行岭回归分析

打开SPSS将数据输入到SPSS中。 点击左上角文件->新建->语法 输入 * Encoding: UTF-8. INCLUDE D:\Program Files\IBM\SPSS\Statistics\27\Samples\Simplified Chinese\Ridge regression.sps. ridgereg enter x1 x2 x3 x4 x5 x6 x7 x8 x9 x10/depy.include内容需要自己寻找…

R语言与岭回归

岭参数的一般选择原则 选择k&#xff08;或lambda&#xff09;值&#xff0c;使得&#xff1a;各回归系数的岭估计基本稳定用最小二乘估计时符号不合理的回归系数&#xff0c;其岭回归的符号变得合理回归系数没有不合乎实际意义的绝对值残差平方和增大的不多 用R语言进行岭回归…

岭回归(Ridge Regression)及实现

岭回归(Ridge Regression)及实现 https://blog.csdn.net/google19890102/article/details/27228279 一、一般线性回归遇到的问题 在处理复杂的数据的回归问题时&#xff0c;普通的线性回归会遇到一些问题&#xff0c;主要表现在&#xff1a; 在处理复杂的数据的回归问题时&…

岭回归(R语言)

代码实现如下&#xff1a; data3.3<-read.csv("C:/Users/Administrator/Desktop/data3.3.csv",headTRUE) datas<-data.frame(scale(data3.3[,1:6])) # 对样本数据进行标准化处理并转换为数据框的存储格式 library(MASS) ridge3.3<-lm.ridge(y~.-1,datas,l…

多元线性回归-岭回归

目录 1.精确相关关系 2.高度相关关系 3.多重共线性与相关性 4.岭回归 5.linear_model.Ridge 5.1.案例1&#xff1a;加利福尼亚房屋价值数据 5.2.案例2:波士顿房价数据集 6.选取最佳正则化参数取值 1.精确相关关系 精确相关关系&#xff0c;即完全相关。如矩阵A并不是满…