0%

Linux内核riscv entry.S分析

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
/* n: 在非抢占的情况下,下面两个符号等价 */
#if !IS_ENABLED(CONFIG_PREEMPTION)
.set resume_kernel, restore_all
#endif

/* n: 在head.S里配置给CSR_TVEC */
ENTRY(handle_exception)
/*
* If coming from userspace, preserve the user thread pointer and load
* the kernel thread pointer. If we came from the kernel, the scratch
* register will contain 0, and we should continue on the current TP.
*/
/*
* n: 交换tp寄存器和scratch寄存器里的值。如果exception从用户态进来,scratch
* 的值应该是task_struct(下面在离开内核的时候,会把用户进程的task_struct
* 指针赋给tp),如果exception从内核态进来,tp应该是当前线程的task_struct
* 指针,scratch应该是0。
*
* 所以,下面紧接着的处理中,如果从内核进来,要恢复下tp。
*
* 总结下在用户态和内核态时tp和scratch的值是什么:
*
* +----------+--------------+---------------+
* | | user space | kernel |
* +----------+--------------+---------------+
* | tp: | tls base | task_struct |
* +----------+--------------+---------------+
* | scratch: | task_struct | 0 |
* +----------+--------------+---------------+
*
* 注意:配置tp为tls地址的函数是:copy_thread,如果用户态没有使用TLS,tp
* 在用户态的值是?
*/
csrrw tp, CSR_SCRATCH, tp
/* n: tp不为0,异常来自用户态,直接跳到上下文保存的地方 */
bnez tp, _save_context

_restore_kernel_tpsp:
/*
* n: csrr伪指令,把scratch寄存器的值写入tp,上面为了判断是否在内核把tp
* 的值和CSR_SCRATCH的值做了交换。这里恢复tp寄存器。
*/
csrr tp, CSR_SCRATCH
/* n: 把内核sp保存到内核thread_info上 */
REG_S sp, TASK_TI_KERNEL_SP(tp)
_save_context:
/*
* n: 保存sp到thread_info用户态指针的位置,如果是就在内核态,那么把内核栈
* sp保存到了thread_info用户态栈指针的位置。
*
* 异常或中断来自内核或者用户态,再下面合并处理。当来自用户态时,tp的值和
* scratch寄存器的值是一样的,所以这里不需要恢复tp。
*/
REG_S sp, TASK_TI_USER_SP(tp)
/* n: sp换成内核栈 */
REG_L sp, TASK_TI_KERNEL_SP(tp)
/*
* n: 扩大栈的范围,扩大的范围用来保存相关的寄存器。移动sp其实就相当于在栈上分配空间。
* sp移动之前的值是中断或者异常打断的上下文,也就是中断或异常处理完后要恢复的值。
*/
addi sp, sp, -(PT_SIZE_ON_STACK)
/*
* n: 下面的一段代码把各个系统寄存器保存到栈上刚刚开辟出来的空间, 注意需要
* 特殊处理的是sp(x2)和tp(x4)。当前的sp,由于上面的变动已经不是需要保存的sp,
* 但是,之前我们已经把需要保存的sp放到了thread_info里,所以下面把thread_info
* 里的sp取出后再入栈。
*/
REG_S x1, PT_RA(sp)
REG_S x3, PT_GP(sp)
REG_S x5, PT_T0(sp)
REG_S x6, PT_T1(sp)
REG_S x7, PT_T2(sp)
REG_S x8, PT_S0(sp)
REG_S x9, PT_S1(sp)
REG_S x10, PT_A0(sp)
REG_S x11, PT_A1(sp)
REG_S x12, PT_A2(sp)
REG_S x13, PT_A3(sp)
REG_S x14, PT_A4(sp)
REG_S x15, PT_A5(sp)
REG_S x16, PT_A6(sp)
REG_S x17, PT_A7(sp)
REG_S x18, PT_S2(sp)
REG_S x19, PT_S3(sp)
REG_S x20, PT_S4(sp)
REG_S x21, PT_S5(sp)
REG_S x22, PT_S6(sp)
REG_S x23, PT_S7(sp)
REG_S x24, PT_S8(sp)
REG_S x25, PT_S9(sp)
REG_S x26, PT_S10(sp)
REG_S x27, PT_S11(sp)
REG_S x28, PT_T3(sp)
REG_S x29, PT_T4(sp)
REG_S x30, PT_T5(sp)
REG_S x31, PT_T6(sp)

/*
* Disable user-mode memory access as it should only be set in the
* actual user copy routines.
*
* Disable the FPU to detect illegal usage of floating point in kernel
* space.
*/
/* n: 配置先放到t0寄存器里,SR_SUM为1容许S mode接入U mode地址,反之不容许 */
li t0, SR_SUM | SR_FS

/*
* n: 上面已经保存了寄存器现场,下面可以使用系统寄存器了,s0保存用户态sp。
* 把task_struct里保存的用户态栈指针提取出来,然后在后面保存到内核栈上。
*/
REG_L s0, TASK_TI_USER_SP(tp)
/* n: 把中断异常相关的csr寄存器读出来, ~t0 & csr_status旧值,然后写入csr_status,相当于清SR_SUM和SR_FS */
csrrc s1, CSR_STATUS, t0
csrr s2, CSR_EPC
csrr s3, CSR_TVAL
csrr s4, CSR_CAUSE
csrr s5, CSR_SCRATCH
/* n: 把中断异常相关的csr寄存器以及用户态sp保存到栈上 */
REG_S s0, PT_SP(sp)
REG_S s1, PT_STATUS(sp)
REG_S s2, PT_EPC(sp)
REG_S s3, PT_BADADDR(sp)
REG_S s4, PT_CAUSE(sp)
REG_S s5, PT_TP(sp)

/*
* Set the scratch register to 0, so that if a recursive exception
* occurs, the exception vector knows it came from the kernel
*/
csrw CSR_SCRATCH, x0

/* Load the global pointer */
.option push
.option norelax
la gp, __global_pointer$
.option pop

/* n: todo */
#ifdef CONFIG_TRACE_IRQFLAGS
call trace_hardirqs_off
#endif

/* n: todo */
#ifdef CONFIG_CONTEXT_TRACKING
/* If previous state is in user mode, call context_tracking_user_exit. */
li a0, SR_PP
and a0, s1, a0
bnez a0, skip_context_tracking
call context_tracking_user_exit
skip_context_tracking:
#endif

/*
* MSB of cause differentiates between
* interrupts and exceptions
*/
/* n: 最高位为1会解释为一个负数,如果是异常最高位为0,会跳到lable 1f */
bge s4, zero, 1f

/* n: 如果是中断,会走到这里,先把返回地址保存到ra */
la ra, ret_from_exception

/* Handle interrupts */
/* n: 上面可以知道sp就是pt_regs的地址,a0存放pt_regs的地址 */
move a0, sp /* pt_regs */
/* n: a1存handle_arch_irq的地址 */
la a1, handle_arch_irq
/* n: handle_arch_irq看起来像一个数组,这个在哪里定义?*/
REG_L a1, (a1)
/* n: 返回地址已经配置到ra里。这里是中断处理函数的入口,下面的处理都和异常有关了 */
jr a1
1:
/*
* Exceptions run with interrupts enabled or disabled depending on the
* state of SR_PIE in m/sstatus.
*/
/* n: s1之前被存入sstatus的值,这里把SR_PIE的值存入t0 */
andi t0, s1, SR_PIE
/*
* n: PIE表示进入中断或者异常之前IE的配置情况,如果之前就是关中断,现在
* 继续关中断跑,如果之前是开中断的,需要打开中断继续跑,但是ebreak异常中
* 不能开中断,所以,如果之前是开中断的,我们继续检测是不是ebreak异常,
* 如果是ebreak异常,在关中断下继续跑,如果不是ebreak异常,会走到csrs CSR_STATUS, SR_IE,
* 这里会把中断打开。
*
* 注意,这里是在处理异常。硬件在处理中断或异常时会改变SPIE/SPP/SIE的值。
* SPP会保存中断或异常时CPU的mode,SPIE会保存sstatus里的SIE值,SIE表示S
* mode中断是否被enable,最后硬件会关中断就是把SIE配置成0。
*/
beqz t0, 1f
/* kprobes, entered via ebreak, must have interrupts disabled. */
li t0, EXC_BREAKPOINT
beq s4, t0, 1f
/* n: todo */
#ifdef CONFIG_TRACE_IRQFLAGS
call trace_hardirqs_on
#endif
/*
* n: csrs是csrrs x0, csr, rs, 意思是把csr的值存入x0, csr的旧值和rs做或运算后写入csr。
* 这里是把S mode全局中断打开。注意,上面的逻辑是如果检测到是ebreak异常是直接跳过开中断的。
*/
csrs CSR_STATUS, SR_IE

/* n: 开始处理异常,先配置返回地址。非ebreak,且之前是开中断的,异常处理前开中断 */
1:
la ra, ret_from_exception
/* Handle syscalls */
li t0, EXC_SYSCALL
/* n: 如果是ecall异常就跳到系统调用的地方处理 */
beq s4, t0, handle_syscall

/* Handle other exceptions */
/* n: 向左移动3bit就是得到函数指针的偏移,其他异常的入口函数排到了excp_vect_table这个表里 */
slli t0, s4, RISCV_LGPTR
la t1, excp_vect_table
la t2, excp_vect_table_end
move a0, sp /* pt_regs */
/* n: t1是异常向量表的基地址,再加上对应异常处理函数的偏移,t0的值就是对应异常处理函数的地址 */
add t0, t1, t0
/* Check if exception code lies within bounds */
/* n: 计算的异常处理函数的地址超出了异常向量表的结尾 */
bgeu t0, t2, 1f
/* n: t0是存放函数指针的地址,这里把t0更新成函数指针 */
REG_L t0, 0(t0)
/* n: 跳到对应异常处理函数执行,返回地址还是之前配置的ret_from_exception */
jr t0
1:
tail do_trap_unknown

/* 这个是handle_syscall这个函数的独立代码了, 异常的处理被分为系统调用和其他异常 */
handle_syscall:
/* n: todo */
#ifdef CONFIG_RISCV_M_MODE
/*
* When running is M-Mode (no MMU config), MPIE does not get set.
* As a result, we need to force enable interrupts here because
* handle_exception did not do set SR_IE as it always sees SR_PIE
* being cleared.
*/
csrs CSR_STATUS, SR_IE
#endif
/* n: todo */
#if defined(CONFIG_TRACE_IRQFLAGS) || defined(CONFIG_CONTEXT_TRACKING)
/* Recover a0 - a7 for system calls */
REG_L a0, PT_A0(sp)
REG_L a1, PT_A1(sp)
REG_L a2, PT_A2(sp)
REG_L a3, PT_A3(sp)
REG_L a4, PT_A4(sp)
REG_L a5, PT_A5(sp)
REG_L a6, PT_A6(sp)
REG_L a7, PT_A7(sp)
#endif
/* save the initial A0 value (needed in signal handlers) */
/* n: 把a0保存到栈的PT_ORIG_A0这个位置,a0为什么要存都这里?(a0会作为系统调用的返回值,估计这里是为了做区分) */
REG_S a0, PT_ORIG_A0(sp)
/*
* Advance SEPC to avoid executing the original
* scall instruction on sret
*/
addi s2, s2, 0x4
REG_S s2, PT_EPC(sp)
/* Trace syscalls, but only if requested by the user. */
/* n: 读一个thread_info里的flag,判断是否要走syscall trace */
REG_L t0, TASK_TI_FLAGS(tp)
andi t0, t0, _TIF_SYSCALL_WORK
bnez t0, handle_syscall_trace_enter
check_syscall_nr:
/* Check to make sure we don't jump to a bogus syscall number. */
li t0, __NR_syscalls
/* n: ni的意思是no implemented,下面可以看出如果系统调用号大于等于最大值了,就会进入这个函数 */
la s0, sys_ni_syscall
/*
* Syscall number held in a7.
* If syscall number is above allowed value, redirect to ni_syscall.
*/
bgeu a7, t0, 1f
/* Call syscall */
/*
* n: sys_call_table这个表定义在arch/riscv/kernel/syscall_table.c,又include
* 到了其他地方,最终系统调用都在linux/include/uapi/asm-generic/unistd.h
* 里定义。
*
* 注意riscv系统调用的参数使用a0-a7传递,a7里放系统调用号。
*/
la s0, sys_call_table
slli t0, a7, RISCV_LGPTR
add s0, s0, t0
REG_L s0, 0(s0)
/* n: 从这里返回,返回到ret_from_syscall, 之前的几个跳转返回,会返回到之前配置的ra,就是ret_from_exception */
1:
jalr s0

ret_from_syscall:
/* Set user a0 to kernel a0 */
/* n: a0保存返回值,这里把a0存入内核栈保存的寄存器是为了后面恢复?*/
REG_S a0, PT_A0(sp)
/*
* We didn't execute the actual syscall.
* Seccomp already set return value for the current task pt_regs.
* (If it was configured with SECCOMP_RET_ERRNO/TRACE)
*/
ret_from_syscall_rejected:
/* Trace syscalls, but only if requested by the user. */
REG_L t0, TASK_TI_FLAGS(tp)
andi t0, t0, _TIF_SYSCALL_WORK
bnez t0, handle_syscall_trace_exit

ret_from_exception:
/* n: 把sstatus的值加载到s0里 */
REG_L s0, PT_STATUS(sp)
/* n: 关中断。处理异常的时候,除了ebreak异常,其他异常处理可能是开中断的 */
csrc CSR_STATUS, SR_IE
#ifdef CONFIG_TRACE_IRQFLAGS
call trace_hardirqs_off
#endif
#ifdef CONFIG_RISCV_M_MODE
/* the MPP value is too large to be used as an immediate arg for addi */
li t0, SR_MPP
and s0, s0, t0
#else
/* n: 恢复进入S mode之前的mode, 并保存到s0 */
andi s0, s0, SR_SPP
#endif
/* n: s0不是0,即不是user mode,那只恢复内核的上下文就好, 否则要先恢复用户态上下文 */
bnez s0, resume_kernel

/* n: 用户态和内核态恢复唯一的区别就在resume_userspace这一段 */
resume_userspace:
/* Interrupts must be disabled here so flags are checked atomically */
REG_L s0, TASK_TI_FLAGS(tp) /* current_thread_info->flags */
/* n: todo: 哪里会配置这个值?(这个估计要结合调度看下) 定义在arch/riscv/include/asm/thread_info.h */
andi s1, s0, _TIF_WORK_MASK
bnez s1, work_pending

#ifdef CONFIG_CONTEXT_TRACKING
call context_tracking_user_enter
#endif
/* n: 这里把相当于sp向上移动,相当于退栈,释放保存寄存器上下文的栈空间 */
/* Save unwound kernel stack pointer in thread_info */
addi s0, sp, PT_SIZE_ON_STACK
/* n: 把当前内核栈sp保存到task_struct */
REG_S s0, TASK_TI_KERNEL_SP(tp)

/*
* Save TP into the scratch register , so we can find the kernel data
* structures again.
*/
/*
* n: 每次离开内核的时候把当前进程的tp存入scratch寄存器。注意,只有进入
* 用户态的时候才这样,这里也和一开始的分析呼应。
*/
csrw CSR_SCRATCH, tp

restore_all:
#ifdef CONFIG_TRACE_IRQFLAGS
REG_L s1, PT_STATUS(sp)
andi t0, s1, SR_PIE
beqz t0, 1f
call trace_hardirqs_on
j 2f
1:
call trace_hardirqs_off
2:
#endif
/* n: 注意,前面虽然已经"退栈",但是sp的指向还没有变,这里依然可以继续使用sp */
REG_L a0, PT_STATUS(sp)
/*
* The current load reservation is effectively part of the processor's
* state, in the sense that load reservations cannot be shared between
* different hart contexts. We can't actually save and restore a load
* reservation, so instead here we clear any existing reservation --
* it's always legal for implementations to clear load reservations at
* any point (as long as the forward progress guarantee is kept, but
* we'll ignore that here).
*
* Dangling load reservations can be the result of taking a trap in the
* middle of an LR/SC sequence, but can also be the result of a taken
* forward branch around an SC -- which is how we implement CAS. As a
* result we need to clear reservations between the last CAS and the
* jump back to the new context. While it is unlikely the store
* completes, implementations are allowed to expand reservations to be
* arbitrarily large.
*/
/* n: 处理和lr/sc相关的逻辑? */
REG_L a2, PT_EPC(sp)
REG_SC x0, a2, PT_EPC(sp)

csrw CSR_STATUS, a0
csrw CSR_EPC, a2

REG_L x1, PT_RA(sp)
REG_L x3, PT_GP(sp)
REG_L x4, PT_TP(sp)
REG_L x5, PT_T0(sp)
REG_L x6, PT_T1(sp)
REG_L x7, PT_T2(sp)
REG_L x8, PT_S0(sp)
REG_L x9, PT_S1(sp)
REG_L x10, PT_A0(sp)
REG_L x11, PT_A1(sp)
REG_L x12, PT_A2(sp)
REG_L x13, PT_A3(sp)
REG_L x14, PT_A4(sp)
REG_L x15, PT_A5(sp)
REG_L x16, PT_A6(sp)
REG_L x17, PT_A7(sp)
REG_L x18, PT_S2(sp)
REG_L x19, PT_S3(sp)
REG_L x20, PT_S4(sp)
REG_L x21, PT_S5(sp)
REG_L x22, PT_S6(sp)
REG_L x23, PT_S7(sp)
REG_L x24, PT_S8(sp)
REG_L x25, PT_S9(sp)
REG_L x26, PT_S10(sp)
REG_L x27, PT_S11(sp)
REG_L x28, PT_T3(sp)
REG_L x29, PT_T4(sp)
REG_L x30, PT_T5(sp)
REG_L x31, PT_T6(sp)

REG_L x2, PT_SP(sp)

#ifdef CONFIG_RISCV_M_MODE
mret
#else
sret
#endif

/* n: 开抢占的时候,在恢复上下文之前要先处理下抢占 */
#if IS_ENABLED(CONFIG_PREEMPTION)
resume_kernel:
/*
* n: 取thread_info里的preempt_count, 如果是非0,那么是禁止抢占的, 就直接
* 跳到restore_all, 不做后面的调度, 否则就继续往下走,查看是否需要调度,
* 需要做调度,就执行下调度。
*
* 所谓抢占就是一个线程执行被被动的中断,然后CPU上换另一个线程的上下文
* 执行,所以,一个线程主动放弃CPU,换另一个线程上CPU执行肯定不是抢占,
* 所以,抢占具体实施的点就是像在中断或异常的时候,处理完中断或异常,
* 然后调度一下,这个时候就可能调度其他的线程到CPU上跑。
*
* 内核里又加了一个禁止抢占的标记,就是上面preempt_count这个变量,当
* 这个变量是0的时候,是可以抢占的,当这个变量大于0,是不能抢占的。
* 这样,就有可能出现,中断处理完但是不能抢占的情况。
*/
REG_L s0, TASK_TI_PREEMPT_COUNT(tp)
bnez s0, restore_all
REG_L s0, TASK_TI_FLAGS(tp)
andi s0, s0, _TIF_NEED_RESCHED
beqz s0, restore_all
call preempt_schedule_irq
j restore_all
#endif

work_pending:
/* Enter slow path for supplementary processing */
la ra, ret_from_exception
andi s1, s0, _TIF_NEED_RESCHED
/* n: 异常或中断返回时主动调度 */
bnez s1, work_resched
work_notifysig:
/* Handle pending signals and notify-resume requests */
csrs CSR_STATUS, SR_IE /* Enable interrupts for do_notify_resume() */
move a0, sp /* pt_regs */
move a1, s0 /* current_thread_info->flags */
tail do_notify_resume
work_resched:
tail schedule

/* Slow paths for ptrace. */
handle_syscall_trace_enter:
move a0, sp
call do_syscall_trace_enter
move t0, a0
REG_L a0, PT_A0(sp)
REG_L a1, PT_A1(sp)
REG_L a2, PT_A2(sp)
REG_L a3, PT_A3(sp)
REG_L a4, PT_A4(sp)
REG_L a5, PT_A5(sp)
REG_L a6, PT_A6(sp)
REG_L a7, PT_A7(sp)
bnez t0, ret_from_syscall_rejected
j check_syscall_nr
handle_syscall_trace_exit:
move a0, sp
call do_syscall_trace_exit
j ret_from_exception

END(handle_exception)

ENTRY(ret_from_fork)
la ra, ret_from_exception
tail schedule_tail
ENDPROC(ret_from_fork)

ENTRY(ret_from_kernel_thread)
call schedule_tail
/* Call fn(arg) */
la ra, ret_from_exception
move a0, s1
jr s0
ENDPROC(ret_from_kernel_thread)


/*
* Integer register context switch
* The callee-saved registers must be saved and restored.
*
* a0: previous task_struct (must be preserved across the switch)
* a1: next task_struct
*
* The value of a0 and a1 must be preserved by this function, as that's how
* arguments are passed to schedule_tail.
*/
/*
* n: sched/core.c里的context_switch会调用到这里。__switch_to这个函数的第一个入参
* 是前task_struct, 后一个是要switch到的线程的task_struct。task_struct里有一个架
* 构相关的结构thread_struct。riscv的定义在arch/riscv/include/asm/processor.h,
* TASK_THREAD_RA是这个结构在task_struct里的偏移,所以a3、a4得到thread_struct在
* 切出、切入线程task_struct里thread_struct的地址。
*
*/
ENTRY(__switch_to)
/* Save context into prev->thread */
li a4, TASK_THREAD_RA
add a3, a0, a4
add a4, a1, a4
/* n: 保存这些就够了,剩下的是caller save寄存器,已经保存到对应的栈了 */
REG_S ra, TASK_THREAD_RA_RA(a3)
REG_S sp, TASK_THREAD_SP_RA(a3)
REG_S s0, TASK_THREAD_S0_RA(a3)
REG_S s1, TASK_THREAD_S1_RA(a3)
REG_S s2, TASK_THREAD_S2_RA(a3)
REG_S s3, TASK_THREAD_S3_RA(a3)
REG_S s4, TASK_THREAD_S4_RA(a3)
REG_S s5, TASK_THREAD_S5_RA(a3)
REG_S s6, TASK_THREAD_S6_RA(a3)
REG_S s7, TASK_THREAD_S7_RA(a3)
REG_S s8, TASK_THREAD_S8_RA(a3)
REG_S s9, TASK_THREAD_S9_RA(a3)
REG_S s10, TASK_THREAD_S10_RA(a3)
REG_S s11, TASK_THREAD_S11_RA(a3)
/* Restore context from next->thread */
REG_L ra, TASK_THREAD_RA_RA(a4)
REG_L sp, TASK_THREAD_SP_RA(a4)
REG_L s0, TASK_THREAD_S0_RA(a4)
REG_L s1, TASK_THREAD_S1_RA(a4)
REG_L s2, TASK_THREAD_S2_RA(a4)
REG_L s3, TASK_THREAD_S3_RA(a4)
REG_L s4, TASK_THREAD_S4_RA(a4)
REG_L s5, TASK_THREAD_S5_RA(a4)
REG_L s6, TASK_THREAD_S6_RA(a4)
REG_L s7, TASK_THREAD_S7_RA(a4)
REG_L s8, TASK_THREAD_S8_RA(a4)
REG_L s9, TASK_THREAD_S9_RA(a4)
REG_L s10, TASK_THREAD_S10_RA(a4)
REG_L s11, TASK_THREAD_S11_RA(a4)
/* Swap the CPU entry around. */
/* n: 交换两个task_struct里的thread_info里的cpu域段,cpu的语意是?*/
lw a3, TASK_TI_CPU(a0)
lw a4, TASK_TI_CPU(a1)
sw a3, TASK_TI_CPU(a1)
sw a4, TASK_TI_CPU(a0)
/* The offset of thread_info in task_struct is zero. */
/* n: tp指向新的task_struct */
move tp, a1
ret
ENDPROC(__switch_to)

#ifndef CONFIG_MMU
#define do_page_fault do_trap_unknown
#endif

.section ".rodata"
.align LGREG
/* Exception vector table */
ENTRY(excp_vect_table)
RISCV_PTR do_trap_insn_misaligned
RISCV_PTR do_trap_insn_fault
RISCV_PTR do_trap_insn_illegal
RISCV_PTR do_trap_break
RISCV_PTR do_trap_load_misaligned
RISCV_PTR do_trap_load_fault
RISCV_PTR do_trap_store_misaligned
RISCV_PTR do_trap_store_fault
RISCV_PTR do_trap_ecall_u /* system call, gets intercepted */
RISCV_PTR do_trap_ecall_s
RISCV_PTR do_trap_unknown
RISCV_PTR do_trap_ecall_m
RISCV_PTR do_page_fault /* instruction page fault */
RISCV_PTR do_page_fault /* load page fault */
RISCV_PTR do_trap_unknown
RISCV_PTR do_page_fault /* store page fault */
excp_vect_table_end:
END(excp_vect_table)

#ifndef CONFIG_MMU
ENTRY(__user_rt_sigreturn)
li a7, __NR_rt_sigreturn
scall
END(__user_rt_sigreturn)
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
       -+- task_struct           low address      <--- tp
| (thread_info)
|
| struct thread_struct thread
stack |
size |
|
|
|
|
|
|
|
|
|
+-+-
| | ^
| | PT_SIZE_ON_STACK |
| | |
-+-+- sp end high address |

如上是一个线程对应的内核栈和task_struct在内存中的存储格式。栈底在sp end的位置,
task_struct在sp end - 栈大小的位置,内核栈会分出PT_SIZE_ON_STACK的大小,用来保存
中断异常时的寄存器上下文,PT_XX的宏就是这个区里的偏移。thread_info放在task_struct
的一开始,用来暂存体系结构相关的一些数据。thread也在task_struct里,在__switch_to
里使用,用TASK_THREAD_XX_XX表示相关的偏移。