-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
393 lines (368 loc) · 20.8 KB
/
search.xml
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Jos-lab2]]></title>
<url>/2017/04/05/Jos-lab2/</url>
<content type="html"><![CDATA[<p>这次lab主要的工作是在JOS 操作系统中实现内存分页管理的功能。除了第一个challenge外其他代码都在 pmap.c 文件中完成。</p>
<p>这次的 lab(challenge除外)可以分为两部分部分:</p>
<p>1、物理页面管理和页表管理。前者强调对机器拥有的物理内存的管理,包括建立对应的数据结构、处理分配和回收动作等。</p>
<p> 需要完成以下函数:</p>
<ul>
<li>boot_alloc()</li>
<li>mem_init()</li>
<li>page_init()</li>
<li>page_alloc()</li>
<li>page_free()</li>
</ul>
<p>2、利用Intel x86 系列处理器的页式地址管理功能,完成(虚拟)线性地址到物理地址的转换,包括建立页目录、页表等。</p>
<p>需要完成以下函数:</p>
<ul>
<li>pgdir_walk()</li>
<li>boot_map_region()</li>
<li>page_lookup()</li>
<li>page_remove()</li>
<li>page_insert()</li>
<li>boot_map_region_large()</li>
</ul>
<p>对于上述所涉及的函数(boot_map_region_large除外)都只需要照着文件中的注释写即可,<br>故不多做解释。</p>
<p>接着上一个lab的内容,并调用 i386_init()函数,而 i386_init()函数在将自己的 BSS 区域清零后,调用 cons_init()函数设置好屏幕显示设备为 cprintf 的运行做好准备后就调用 mem_init()函数进入lab2。</p>
<p>lab中所涉及到的内存的分页机制包含页面管理和页表管理两个部分。对于前一个部分,主要是讨论内核如何在初始物理内存中分配空间和建立合理的数据结构来对物理页面进行管理,而后一个部分主要是讨论如何建立两级页目录和页表,从而在开启 x86 的分页管理后仍然能够进行合理的逻辑地址到物理地址的转换。</p>
<p><strong>页面管理</strong></p>
<p>这个Page是这个lab中很重要的一个单项链表结构,用来管理物理内存。其中pp_link指向链表的下一个单元的地址,pp_ref则表示页表结构中映射到该段物理页的entry数量,通过该结构可以完成这个lab的很多事情。</p>
<pre><code>struct Page {
struct Page *pp_link;
uint16_t pp_ref;
};
</code></pre><p>在 JOS 中,页面操作主要由 page_alloc()以及page_free()完成。同时,还要进行页面的初始化。由于系统的物理内存由 4KB 大小的很多物理页面组成,同时这些物理页面与页面管理单向链表中的管理结点有一一对应的关系,根据一个物理页的首地址可以计算出它所对应的管理结点的位置,可以用 pages[下标]来索引。在页面管理数据结构初试化的时候,系统会把所有管理结点加入页面管理链表,但是,由于有些物理内存已经被 x86 系统所使用或者预先放了一些系统数据。同时,有些内存已为内核本身所占用,这些内存不能把它们当自由页面分配出去。所以,在建立好页面管理链表后,需要将这些已经被使用的内存对应的页面管理结点从链表中移出。page_alloc()函数被调用后,将取页面管理双向链表中的第一个管理结点,并将该结点返回出去,需要注意的是,由于无法确定得到的物理内存页面是否是“干净”的,如果已经有数据,就会对之后的程序造成影响。所以得到页面管理结点后,要将它所对应的物理页面清零。</p>
<p><strong>页表管理</strong></p>
<p>lab中有提到逻辑地址( Virtual Address)、线性地址(Linear Address)和物理地址(Physical Address)。逻辑地址是指程序在编译连接后,变量名字等的符号地址, 在 JOS 系统中的内核部分, 该地址是以 KERNBASE(默认等于 0xF0000000,实际上可以根据具体的情况加以修改);线性地址是指经过 x86 保护模式的段地址变换后的地址,该变换的过程是 逻辑地址+段首地址;物理地址是指内存存储单元的编址,如 1GB<br>的内存,它的物理编址是从 0x00000000 到 0x40000000。</p>
<p>关于线性地址的转换,在ics课上已经讲过,就不说了。</p>
<p>关于第二部分的函数,主要就是用于处理对页目录、页表和数据页的关联。这里稍微解释一下:</p>
<p> <code>pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create)</code> 根据输入的虚拟地址va,返回它在二级页表中对应的表项。如果页不存在,根据其中的create参数,决定是否要为这个虚拟地址分配物理页。</p>
<p><code>static void boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)</code>将从va开始的size个byte映射到pa。</p>
<p><code>struct Page *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)</code>根据va,返回对应的页表的页,如果有pte_store,则将对应的pte的位置保存pte_store上。</p>
<p><code>void page_remove(pde_t *pgdir, void *va)</code>删除给定va对应的页表,同时也要清除对应的TLB。</p>
<p><code>int page_insert(pde_t *pgdir, struct Page *pp, void *va, int perm)</code>这个函数将虚拟地址va映射到pp对应的页表上,如果没有分配就直接映射va,如果页表已分配先判断是否映射va相同的虚拟地址,如果是就只删除对应的TLB,如果不是就直接remove。</p>
<p><strong>部分函数代码</strong></p>
<p>因为代码过多,只贴出其中几个。其余基本代码可以在提交的代码中查看。</p>
<p>关于boot_map_region_large()</p>
<pre><code> uint32_t cr4;
cr4 = rcr4();
cr4 |= CR4_PSE;
lcr4(cr4);
boot_map_region_large(kern_pgdir, KERNBASE, 64, 0, PTE_P | PTE_W);
static void
boot_map_region_large(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
uint32_t i=0;
while(i<size)
{
kern_pgdir[PDX(va)] = pa | perm | PTE_PS;
va=va+PTSIZE;
pa=pa+PTSIZE;
i++;
}
}
</code></pre><p>需要用这个boot_map_region_large()替代boot_map_region()。先开启CR4中的PSE位以此使用PDE的PS位。通过分配PDE,替代了二级页表的分配。通过计算可以得出只需要64的PDE表项。</p>
<p><strong>Challenge1(1)</strong></p>
<p>主要是要实现一个showmapping的命令,改命令可以打出指定范围内每个页所对应的权限位</p>
<p>主要实现如下,只需要遍历一下范围地址里在二级页表里的entry并输出权限位即可。</p>
<pre><code>int
showmappings(int argc, char **argv, struct Trapframe *tf)
{
if (argc!=3)
{
cprintf("Usage: showmappings 0xbegin_addr 0xend_addr\n");
return 0;
}
uint32_t beg_addr = atoi(argv[1]);
uint32_t end_addr = atoi(argv[2]);
while(beg_addr <= end_addr)
{
pte_t *pte = pgdir_walk(kern_pgdir, (void *) beg_addr, 1);
if (!pte)
{
panic("out of the memory");
}
else if (*pte & PTE_P)
{
cprintf("page at %x has the permission that PTE_P: %x, PTE_W: %x, PTE_U: %x\n", beg_addr, *pte&PTE_P, *pte&PTE_W, *pte&PTE_U);
}
else
{
cprintf("no that page: %x\n", beg_addr);
}
beg_addr=beg_addr+PGSIZE;
}
return 0;
}
</code></pre><p><strong>Challenge1(2)</strong></p>
<p>实现对一个地址页表权限位的设置,清零与更改,也不难。</p>
<p>得到一个地址所对应的二级页表后,根据所输入的指令更改页表里的权限位,实现如下:</p>
<pre><code>int
setmapping(int argc, char **argv, struct Trapframe *tf)
{
if (argc!=4)
{
cprintf("Usage: setmapping 0xaddr [0|1|2 : set or clear or change ] [P|W|U]\n");
return 0;
}
uint32_t addr = atoi(argv[1]);
pte_t *pte = pgdir_walk(kern_pgdir, (void *)addr, 1);
cprintf("Before setmapping :PTE_P: %x, PTE_W: %x, PTE_U: %x\n", *pte&PTE_P, *pte&PTE_W, *pte&PTE_U);
uint32_t perm = 0;
if (argv[3][0] == 'P')
{
perm = PTE_P;
}
else if (argv[3][0] == 'W')
{
perm = PTE_W;
}
else
{
perm = PTE_U;
}
if (argv[2][0] == '0')
{
*pte = *pte|perm;
}
else if(argv[2][0]=='1')
{
*pte = *pte & ~perm;
}
else if(argv[2][0]=='2')
{
*pte = *pte^perm;
}
cprintf("After setmapping :PTE_P: %x, PTE_W: %x, PTE_U: %x\n", *pte&PTE_P, *pte&PTE_W, *pte&PTE_U);
return 0;
}
</code></pre><p><strong>Challenge1(3)</strong></p>
<p>把地址范围内内存里的内容打印出来,并且支持虚拟地址与物理地址。如果是物理地址,转换为虚拟地址即可。</p>
<pre><code>int
mon_dump(int argc, char **argv, struct Trapframe *tf)
{
if (argc !=4)
{
cprintf("Usage: dump [P|V] 0xbegin_addr 0xend_addr\n");
return 0;
}
uint32_t beg_addr = atoi(argv[2]);
uint32_t end_addr = atoi(argv[3]);
if(argv[1][0]=='P')
{
if(PGNUM(beg_addr)>=npages||PGNUM(end_addr)>=npages)
{
panic("Wrong address");
}
beg_addr=(uint32_t)KADDR(((physaddr_t)beg_addr));
end_addr=(uint32_t)KADDR(((physaddr_t)end_addr));
}
void** addr = (void**) beg_addr;
int i=0;
for (; beg_addr < end_addr;beg_addr++)
{
cprintf(" contents of %x is %x\n", beg_addr, addr[i]);
i++;
}
return 0;
}
</code></pre><p><strong>Challenge2</strong></p>
<p>实现一个称作alloc_page_with_color的函数,返回一个所需要的color的页。这个函数需要遍历page_free_list直到得到所需要的color的页,并返回。</p>
<pre><code>struct Page *alloc_page_with_color(int alloc_flags, int color)
{
if(!page_free_list)
{
return NULL;
}
struct Page* page = page_free_list;
struct Page* result;
while(page)
{
if((((int)page2pa(page))&4) ==color)
{
page->pp_link = 0;
result=page;
page= page->pp_link;
if(alloc_flags & ALLOC_ZERO)
{
memset(page2kva(result), 0, PGSIZE);
}
return result;
}
page=page->pp_link;
}
return NULL;
}
</code></pre>]]></content>
<categories>
<category> os_lab </category>
</categories>
</entry>
<entry>
<title><![CDATA[Jos-lab1]]></title>
<url>/2017/03/20/Jos-lab1/</url>
<content type="html"><![CDATA[<p><strong>jos的启动过程总结如下:</strong></p>
<p>首先是BIOS加载。然后从BIOS跳转至Boot loader。Boot loader 做了一些初始化工作如禁止中断,打开 A20地址线等,然后载入GDT,并从实模式跳转到32位指令的保护模式下。在初始化了寄存器后,读取硬盘第一页上的ELF文件,将内核载入内存,最终进入内核。</p>
<p>内核首先执行 entry.S 文件,entry.S 先将寻址模式从32位段模式转为32位段页模式,然后初始化栈空间,跳转到 i386_init中,接下来的过程都能够在终端中反映出来,在exercise中也会涉及到。</p>
<p>其中,BIOS部分是由QEMU提供的,不属于JOS。</p>
<p><strong>以下是对此次lab中一些问题以及设计的回答:</strong></p>
<p><strong><em>(1) At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?</em></strong></p>
<pre><code> # Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
ljmp$PROT_MODE_CSEG, $protcseg
.code32 # Assemble for 32-bit mode
</code></pre><p> 从<code>.code32</code>开始,处理器执行32位的代码。<br><code>ljmp $PROT_MODE_CSEG, $protcseg</code>指令使得从16位模式切换至32位模式。</p>
<p><strong><em>(2) What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?</em></strong></p>
<p><code>(void (*)(void)) (ELFHDR->e_entry))();</code>这行代码是boot loader最后执行的代码,而最后一条执行的汇编指令为<code>call *0x10018</code><br>。Kernel刚加载时的第一条指令为<code>movw $0x1234,0x472</code></p>
<p><strong><em>(3) How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information?</em></strong></p>
<p>Bootloader 在内核的ELF文件头中可以获得关于需要读多少sectors的信息。</p>
<p><strong>Exercise 5.</strong></p>
<p>在从BIOS进入boot loader时,这8个words都是0。在从boot loader进入kernel后,则有真正的数据了。因为在第一个断点时还没有将内核代码载入至内存,第二个断点时则已经载入进去。在第二个断点时的内容是elf内核文件的内容。</p>
<p><strong>Exercise 6.</strong></p>
<p>可以发现,修改只对相对跳转的语句有影响。</p>
<p><strong>Exercise 7.</strong></p>
<p><code>Jmp *%eax</code> 是旧的mapping依旧存在时出错的第一条指令。</p>
<p><strong>Exercise 8.</strong></p>
<p>照着前面的写即可,具体代码如下</p>
<pre><code>case 'o':
// Replace this with your code.
// display a number in octal form and the form should begin with '0'
putch('0', putdat);
num = getuint(&ap, lflag);
base = 8;
goto number;
</code></pre><p><strong>Exercise 9.</strong></p>
<p>从<code>init.c</code>中的代码<code>cprintf("show me the sign: %+d, %+d\n", 1024, -1024);</code>中了解到,就是在有加号的情况下需要把正或负号全表示出来。所以只需加入一个对是否是加号的判断,若是,则在数字是正数时在前面加上正号。</p>
<p><strong>Exercise 10.</strong></p>
<p>首先要判断要写入的值的指针是否是空指针,并判断指针要指向的值是否已溢出,若是,则输出已提供的错误信息。最后赋值。</p>
<p><strong>Exercise 11.</strong></p>
<p>首先判断padc是否是’-‘,如果不是,按原来的函数处理,若是,进入新的函数。新的函数如下</p>
<pre><code>static void
printnum2(void (*putch)(int, void*), void *putdat,
unsigned long long num, unsigned base, int width, int padc)
{
while(num>=base)
{
putch("0123456789abcdef"[num % base], putdat);
num=num/base;
width--;
}
// then print this (the least significant) digit
putch("0123456789abcdef"[num % base], putdat);
while (--width > 0)
{
putch(' ', putdat);
}
}
</code></pre><p>即先输出数字的表示,不满的部分再用空格填补。</p>
<p><strong>Exercise 14.</strong></p>
<p>代码如下</p>
<pre><code>uint32_t *ebp = (uint32_t *) read_ebp();
while (ebp != NULL)
{
uint32_t* eip=(uint32_t*)(ebp+1);
cprintf(" eip %08x ebp %08x ",*eip, ebp);
cprintf(" args");
int i=0;
for(i=0;i<5;i++)
{
cprintf(" %08x",*((uint32_t*)(ebp+2+i)));
}
cprintf("\n");
ebp = (uint32_t *) (*ebp);
}
</code></pre><p>用<code>read_ebp()</code>函数得到当前的ebp位置后,相对ebp向上得到eip以及各参数值,最后从ebp寄存器得到外部函数的保存的ebp位置,循环指指针为空。需要注意的是打印的格式。</p>
<p><strong>Exercise 15.</strong></p>
<p>这个练习主要的部分只要在上个练习的循环中加入如下的代码:</p>
<pre><code>struct Eipdebuginfo info;
debuginfo_eip(*eip, &info);
cprintf("\t%s:%d: %.*s+%d\n", info.eip_file,
info.eip_line,info.eip_fn_namelen,
info.eip_fn_name,*eip-info.eip_fn_addr);
</code></pre><p>只是对<code>Eipdebuginfo</code>类的一些使用以及打印,应该不需要赘述。</p>
<pre><code>stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);
if (lline<=rline)
{
info->eip_line = stabs[lline].n_desc;
}
else
{
info->eip_line = -1;
}
</code></pre><p>在<code>kdebug.c</code>中也要加入如下查找行号的代码。</p>
<p>为了能直接使用<code>backtrace</code>命令而不只是通过测试,需要在<code>Command</code>中注册一下</p>
<p><strong>Exercise 16.</strong></p>
<p>为了这个练习需要把<code>start_overflow()</code>中的return address替换为<code>do_overflow()</code>的地址,但因为要保证正常退出,需要把return address+4的地方填上本来应该返回的地址,以便从<code>do_overflow()</code>返回到本来应该返回的<code>overflow_me()</code></p>
<p>代码如下:</p>
<pre><code>char str[256] = {};
int nstr = 0;
char *pret_addr;
uint32_t addr = (uint32_t) do_overflow;
pret_addr = (char *) read_pretaddr();
int i;
for(i=0;i<4;i++)
{
uint32_t addr1;
memset(str,0xd,256);
addr1=(uint32_t)((*(pret_addr+i))&0xff);
str[addr1]='\0';
cprintf("%s%n", str, pret_addr+4+i);
memset(str,0xd,256);
addr1=(uint32_t)((addr >> (8*i)) & 0xff);
str[addr1]='\0';
cprintf("%s%n", str, pret_addr+i);
}
</code></pre><p><strong>Exercise 17.</strong></p>
<p>实际上就是调用rdtsc里的函数<code>read_tsc()</code>,计算下执行命令之间的cycle数并输出。也要在Command中注册下。</p>
<p>代码如下:</p>
<pre><code>int
mon_time(int argc, char **argv, struct Trapframe *tf)
{
uint64_t start;
uint64_t end;
uint64_t time;
// Lookup and invoke the command
if (argc == 1) {
cprintf("time [command]\n");
return 0;
}
int i=0;
for (i = 0; i < NCOMMANDS; i++)
{
if (strcmp(argv[1], commands[i].name) == 0)
{
argc=argc-1;
argv=argv+1;
start=read_tsc();
commands[i].func(argc,argv,tf);
end=read_tsc();
time=end-start;
cprintf("%s cycles: %llu\n",argv[0],time);
return 0;
}
}
cprintf("Unknown command '%s'\n", argv[1]);
return 0;
}
</code></pre>]]></content>
<categories>
<category> os_lab </category>
</categories>
</entry>
<entry>
<title><![CDATA[Hello World]]></title>
<url>/2017/03/20/hello-world/</url>
<content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/" target="_blank" rel="external">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/" target="_blank" rel="external">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html" target="_blank" rel="external">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="external">GitHub</a>.</p>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo new <span class="string">"My New Post"</span></div></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/writing.html" target="_blank" rel="external">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo server</div></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/server.html" target="_blank" rel="external">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo generate</div></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/generating.html" target="_blank" rel="external">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo deploy</div></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/deployment.html" target="_blank" rel="external">Deployment</a></p>
]]></content>
</entry>
</search>