惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News | PayPal Newsroom
I
InfoQ
小众软件
小众软件
T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
V
V2EX
G
Google Developers Blog
罗磊的独立博客
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
N
Netflix TechBlog - Medium
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Proofpoint News Feed
M
MIT News - Artificial intelligence
IT之家
IT之家
J
Java Code Geeks
L
LangChain Blog
D
DataBreaches.Net
F
Fortinet All Blogs
B
Blog
博客园 - 叶小钗
人人都是产品经理
人人都是产品经理
aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
P
Privacy & Cybersecurity Law Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
K
Kaspersky official blog
博客园 - 【当耐特】
T
Tenable Blog
AWS News Blog
AWS News Blog
V
Visual Studio Blog
T
Tor Project blog
阮一峰的网络日志
阮一峰的网络日志
H
Heimdal Security Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
S
Secure Thoughts
Security Archives - TechRepublic
Security Archives - TechRepublic
I
Intezer
Attack and Defense Labs
Attack and Defense Labs
Webroot Blog
Webroot Blog
Latest news
Latest news
TaoSecurity Blog
TaoSecurity Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Know Your Adversary
Know Your Adversary
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
Threatpost
SecWiki News
SecWiki News
S
Security Affairs
H
Help Net Security

KuangjuX(狂且)

提高 Directory Cache 性能 高速缓存技术(Cache) Chisel 学习笔记 随笔(一) 指令级并行技术 TLB MMU 笔记(基于 MIPS) Verilator 学习笔记 MIT-6.S081-mmap MIT-6.S081 Networking MIT-6.S081 Lazy Page Allocation MIT-6.S081 Copy on Write Fork 2021年终总结 xv6-riscv中的KPTI机制 RISC -V N扩展 io_uring 阅读笔记 在Ubuntu中升级Linux内核 飞书Rust实习面试 Rust并发安全总结 Rust String 用法总结
MIT-6.S081 User Level Threads
KuangjuX · 2022-01-31 · via KuangjuX(狂且)

Warmup: RISC-V Assembly

  1. Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf?

main 函数中 a0, a1, a2 寄存器保存着调用参数, 13 放在 a2 中。

  1. Where is the function call to f from main? Where is the call to g? (Hint: the compiler may inline functions.)

main 函数中直接将调用 f 的结果计算了出来:

1
2
24:	4635                	li	a2,13
26: 45b1 li a1,12
  1. At what address is the function printf located?
1
2
30:	00000097          	auipc	ra,0x0
34: 620080e7 jalr 1568(ra) # 650 <printf>

printf 函数的地址为: 48 + 1568 = 1616 = 650(16)

  1. What value is in the register ra just after the jalr to printf in main?

返回地址是 0x38

实现

用户态线程,又叫做协程,是相对内核态线程更加轻量化的线程。而用户态线程也分为有栈协程和无栈协程,在本实验中我们将去实现有栈协程。大部分代码已经给好了,我们需要做的就是去实现线程调度和上下文切换的过程。在实现之前,我们需要为 thread 结构体添加上下文切换所需的寄存器,在上下文切换中我们需要保存 callee saved registers,这是由于 caller saved registers 由调用者来保存,一般被保存在栈中,而 callee saved registers 由被调用者进行保存,因此在上下文切换我们需要去保存这些寄存器,除了 callee saved registers,我们还需要去保存 spra 寄存器,这是由于在上下文切换之后需要根据 ra 寄存器的值进行保存,如果不进行切换,则无法进行函数的切换,而 sp 表示栈指针,也是在函数调用时必要的地址。

我们线程上下文的结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 线程上下文,用于保存寄存器
struct thread_context{
uint64 ra;
uint64 sp;

// callee-saved
uint64 s0;
uint64 s1;
uint64 s2;
uint64 s3;
uint64 s4;
uint64 s5;
uint64 s6;
uint64 s7;
uint64 s8;
uint64 s9;
uint64 s10;
uint64 s11;
};

线程的结构如下所示:

1
2
3
4
5
6
struct thread {
struct thread_context context; // 线程上下文
char stack[STACK_SIZE]; /* the thread's stack */
int state; /* FREE, RUNNING, RUNNABLE */
char name[10]; // For Debug
};

而上下文切换的过程则只需要将当前的寄存器堆存入旧的上下文中,并将新的上下文中的寄存器读入寄存器堆即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* Switch from current_thread to next thread_thread, and make
* next_thread the current_thread. Use t0 as a temporary register,
* which should be caller saved. */

.globl thread_switch
thread_switch:
/* YOUR CODE HERE */
// a0, a1分别为老进程与新进程
sd ra, 0(a0)
sd sp, 8(a0)
sd s0, 16(a0)
sd s1, 24(a0)
sd s2, 32(a0)
sd s3, 40(a0)
sd s4, 48(a0)
sd s5, 56(a0)
sd s6, 64(a0)
sd s7, 72(a0)
sd s8, 80(a0)
sd s9, 88(a0)
sd s10, 96(a0)
sd s11, 104(a0)

ld ra, 0(a1)
ld sp, 8(a1)
ld s0, 16(a1)
ld s1, 24(a1)
ld s2, 32(a1)
ld s3, 40(a1)
ld s4, 48(a1)
ld s5, 56(a1)
ld s6, 64(a1)
ld s7, 72(a1)
ld s8, 80(a1)
ld s9, 88(a1)
ld s10, 96(a1)
ld s11, 104(a1)

ret /* return to ra */

在用户态线程初始化的过程中,我们只需要初始化线程的状态以及 rasp 寄存器的值即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建线程, 参数为函数指针
void
thread_create(void (*func)())
{
struct thread *t;

for (t = all_thread; t < all_thread + MAX_THREAD; t++) {
if (t->state == FREE) break;
}
t->name[0] = name;
name++;
// 将线程状态设置为可运行
t->state = RUNNABLE;
// 将上下文的函数返回值设置为函数指针的地址
t->context.ra = (uint64)func;
// 设置上下文的栈地址
t->context.sp = (uint64)&t->stack[STACK_SIZE];
}

这里要注意, 一定要将 sp 设置为栈的最后一个字节的地址,因为在编译调用函数的时候会将栈寄存器减去一部分,如果设置为第一个字节的地址则会发生栈溢出,这样就会破坏其他内存地址的值。

最后进行线程调度时我们只需要顺序找到一个 RUNNABLE 的线程并进行上下文切换即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 线程调度
void
thread_schedule(void)
{
struct thread *t, *next_thread;

// thread_dump();
/* Find another runnable thread. */
next_thread = 0;
t = current_thread + 1;
for(int i = 0; i < MAX_THREAD; i++){
if(t >= all_thread + MAX_THREAD)
t = all_thread;
if(t->state == RUNNABLE) {
next_thread = t;
break;
}
t = t + 1;
}

if (next_thread == 0) {
printf("thread_schedule: no runnable threads\n");
exit(-1);
}

if (current_thread != next_thread) { /* switch threads? */
// 找到下一个调度的线程并执行上下文切换
next_thread->state = RUNNING;
t = current_thread;
current_thread = next_thread;
// 进行上下文切换
thread_switch((uint64)&t->context, (uint64)&current_thread->context);
} else{
next_thread = 0;
}
}

Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.