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

推荐订阅源

V
Visual Studio Blog
Project Zero
Project Zero
阮一峰的网络日志
阮一峰的网络日志
博客园 - 【当耐特】
大猫的无限游戏
大猫的无限游戏
The Register - Security
The Register - Security
C
Check Point Blog
Attack and Defense Labs
Attack and Defense Labs
L
LangChain Blog
Simon Willison's Weblog
Simon Willison's Weblog
S
Schneier on Security
Recorded Future
Recorded Future
GbyAI
GbyAI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Y
Y Combinator Blog
量子位
A
About on SuperTechFans
I
Intezer
T
Threat Research - Cisco Blogs
MongoDB | Blog
MongoDB | Blog
U
Unit 42
C
CERT Recently Published Vulnerability Notes
Scott Helme
Scott Helme
Cisco Talos Blog
Cisco Talos Blog
P
Palo Alto Networks Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Spread Privacy
Spread Privacy
M
MIT News - Artificial intelligence
雷峰网
雷峰网
博客园 - 聂微东
NISL@THU
NISL@THU
The Hacker News
The Hacker News
G
Google Developers Blog
F
Full Disclosure
博客园 - Franky
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
P
Privacy & Cybersecurity Law Blog
博客园 - 叶小钗
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Blog of Author Tim Ferriss
Security Latest
Security Latest
T
Tenable Blog
Know Your Adversary
Know Your Adversary
Stack Overflow Blog
Stack Overflow Blog
K
Kaspersky official blog
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
C
Cybersecurity and Infrastructure Security Agency CISA
Martin Fowler
Martin Fowler
Schneier on Security
Schneier on 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.