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

推荐订阅源

aimingoo的专栏
aimingoo的专栏
S
Securelist
博客园 - Franky
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
IT之家
IT之家
GbyAI
GbyAI
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
云风的 BLOG
云风的 BLOG
N
News and Events Feed by Topic
AI
AI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Schneier on Security
Schneier on Security
Attack and Defense Labs
Attack and Defense Labs
Vercel News
Vercel News
腾讯CDC
Google DeepMind News
Google DeepMind News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
M
MIT News - Artificial intelligence
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
量子位
S
Schneier on Security
Hacker News: Ask HN
Hacker News: Ask HN
Cyberwarzone
Cyberwarzone
S
Security Affairs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News and Events Feed by Topic
T
Tenable Blog
PCI Perspectives
PCI Perspectives
MyScale Blog
MyScale Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
W
WeLiveSecurity
N
News | PayPal Newsroom
P
Proofpoint News Feed
O
OpenAI News
C
CERT Recently Published Vulnerability Notes
B
Blog
Cisco Talos Blog
Cisco Talos Blog
Microsoft Security Blog
Microsoft Security Blog
V
Visual Studio Blog
MongoDB | Blog
MongoDB | Blog
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
Y
Y Combinator Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Spread Privacy
Spread Privacy

KuangjuX(狂且)

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

在本次实验中我们需要实现内存页懒加载机制,也就是当用户向内核申请内存的时候,内核不需要实际分配内存,而只是增长当前进程的 size,也就是说我们需要修改 sys_sbrk() 系统调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 懒加载,在这里仅仅改变进程记录的内存大小的值
// 而并不实际分配
uint64
sys_sbrk(void)
{
int addr;
int n;

if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
if (n < 0) {
if(growproc(n) < 0)
return -1;
}else{
if(addr + n >= MAXVA){
return -1;
}
myproc()->sz = addr + n;
}
return addr;
}

由于我们并没有实际向进程分配内存,因此当进程访问未分配的内存时会发生也错误,我们需要在 usertrap 中拿到页错误的地址并分配内存地址并进行映射:

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
void
usertrap(void)
{
int which_dev = 0;

if((r_sstatus() & SSTATUS_SPP) != 0)
panic("usertrap: not from user mode");

// send interrupts and exceptions to kerneltrap(),
// since we're now in the kernel.
w_stvec((uint64)kernelvec);

struct proc *p = myproc();

// save user program counter.
p->tf->epc = r_sepc();

uint64 scause = r_scause();
if(scause == 8){
// system call

if(p->killed)
exit(-1);

// sepc points to the ecall instruction,
// but we want to return to the next instruction.
p->tf->epc += 4;

// an interrupt will change sstatus &c registers,
// so don't enable until done with those registers.
intr_on();

syscall();
}else if((which_dev = devintr()) != 0){
// ok
} else {
if(scause == 0xf || scause == 0xd) {
// 如果读取的是用户栈下的保护页则不需要进行分配
uint64 err_addr = PGROUNDDOWN(r_stval());
uint64 new_addr = err_addr + PGSIZE;

if(err_addr > p->sz || new_addr >= MAXVA || err_addr <= p->tf->sp) {
p->killed = 1;
}else{
pagetable_t pgt = p->pagetable;
// 页错误,懒加载时当需要执行代码的时候需要触发此中断
// 获取到发生地址错误的虚拟地址
// 每次只分配一页
char* page = kalloc();
if(page == 0){
uvmdealloc(pgt, err_addr, new_addr);
p->killed = 1;
}else {
if(mappages(
pgt,
err_addr,
PGSIZE,
(uint64)page,
PTE_W|PTE_X|PTE_R|PTE_U
) != 0){
kfree(page);
uvmdealloc(pgt, err_addr, new_addr);
}
}
}
}else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
}

if(p->killed)
exit(-1);

// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield();

usertrapret();
}

这里注意,我们对于发生错误的地址有一定的限制要求,首先发生错误的地址不能超过使用 proc->size,否则就是访问了不允许的地址,其次错误地址不可以超过系统最大虚拟地址 MAXVA,最后不允许访问用户栈下面的 guard page,即不能为其分配地址,而是要按正常的异常的处理走,否则会触发难以想象的后果。

除此之外,当我们调用 fork 系统调用的时候,系统会遍历该进程所有的地址并进行分配映射,然后有些地址实际上是并没有被分配的,对于这部分地址我们选择忽略而并非 panic,我们修改 uvmcopy 如下:

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
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
pte_t *pte;
uint64 pa, i;
uint flags;
char *mem;

for(i = 0; i < sz; i += PGSIZE){
if((pte = walk(old, i, 0)) == 0){
continue;
}
if((*pte & PTE_V) == 0){
continue;
}
pa = PTE2PA(*pte);
flags = PTE_FLAGS(*pte);
if((mem = kalloc()) == 0)
goto err;
memmove(mem, (char*)pa, PGSIZE);
if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
kfree(mem);
goto err;
}
}
return 0;

err:
uvmunmap(new, 0, i, 1);
return -1;
}

当系统调用 sys_read 或者 sys_write 来进行读写文件时涉及到内存的拷贝,因此当我们处理到文件内存拷贝时遇到为被分配的页时需要重新分配映射后再进行拷贝,例如我们修改 writei 如下所示:

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
int
writei(struct inode *ip, int user_src, uint64 src, uint off, uint n)
{
uint tot, m;
struct buf *bp;

if(off > ip->size || off + n < off)
return -1;
if(off + n > MAXFILE*BSIZE)
return -1;

for(tot=0; tot<n; tot+=m, off+=m, src+=m){
bp = bread(ip->dev, bmap(ip, off/BSIZE));
m = min(n - tot, BSIZE - off%BSIZE);
if(either_copyin(bp->data + (off % BSIZE), user_src, src, m) == -1) {
// brelse(bp);
// break;
uint64 addr = PGROUNDDOWN(src);
void* page = kalloc();
if(mappages(
myproc()->pagetable,
addr,
PGSIZE,
(uint64)page,
PTE_W|PTE_X|PTE_R|PTE_U
) != 0){
kfree(page);
uvmdealloc(myproc()->pagetable, addr, addr + PGSIZE);
}
either_copyin(bp->data + (off % BSIZE), user_src, src, m);
}
log_write(bp);
brelse(bp);
}

if(n > 0){
if(off > ip->size)
ip->size = off;
// write the i-node back to disk even if the size didn't change
// because the loop above might have called bmap() and added a new
// block to ip->addrs[].
iupdate(ip);
}

return n;
}

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