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

推荐订阅源

K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
SegmentFault 最新的问题
Last Week in AI
Last Week in AI
阮一峰的网络日志
阮一峰的网络日志
Cloudbric
Cloudbric
www.infosecurity-magazine.com
www.infosecurity-magazine.com
S
Security @ Cisco Blogs
月光博客
月光博客
T
Troy Hunt's Blog
H
Help Net Security
Forbes - Security
Forbes - Security
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
L
LINUX DO - 最新话题
Hacker News - Newest:
Hacker News - Newest: "LLM"
GbyAI
GbyAI
S
Schneier on Security
Spread Privacy
Spread Privacy
Attack and Defense Labs
Attack and Defense Labs
Blog — PlanetScale
Blog — PlanetScale
N
News | PayPal Newsroom
F
Fortinet All Blogs
Latest news
Latest news
人人都是产品经理
人人都是产品经理
Recent Announcements
Recent Announcements
博客园_首页
Martin Fowler
Martin Fowler
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
O
OpenAI News
I
Intezer
S
Security Affairs
罗磊的独立博客
T
Tailwind CSS Blog
小众软件
小众软件
P
Palo Alto Networks Blog
Help Net Security
Help Net Security
V
Vulnerabilities – Threatpost
博客园 - 【当耐特】
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
J
Java Code Geeks
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - 聂微东
博客园 - 司徒正美
T
The Exploit Database - CXSecurity.com
L
Lohrmann on Cybersecurity
C
Cisco Blogs
Security Latest
Security Latest

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.