1 00:00:00,540 --> 00:00:05,939 In this lecture, we will set up paging and remap our kernel to the higher part of the memory locations. 2 00:00:06,660 --> 00:00:08,109 As you see in the memory map, 3 00:00:08,670 --> 00:00:12,180 the kernel is running at address 200000 so far. 4 00:00:13,120 --> 00:00:18,100 What we want to do is we are going to do map the kernel to the area shown here. 5 00:00:18,970 --> 00:00:20,830 The large chunk of memory addresses in red 6 00:00:20,830 --> 00:00:27,570 is non canonical address. The user programs will run in the lower part of the memory location. 7 00:00:28,150 --> 00:00:32,110 So the non-canonical address area separates the kernel and user space. 8 00:00:32,890 --> 00:00:33,970 So how to do it? 9 00:00:34,660 --> 00:00:41,140 This requires us to set up paging. When we enter the 64-bit mode, paging mechanism is enabled. 10 00:00:41,800 --> 00:00:44,250 But the way we setup paging is making the virtual address 11 00:00:44,590 --> 00:00:45,880 equal to physical address. 12 00:00:46,720 --> 00:00:49,420 Therefore we need to reconfigure it to do the job. 13 00:00:50,260 --> 00:00:52,930 OK, let's see what is paging and how to set it up. 14 00:00:54,780 --> 00:01:01,460 Paging allows us to remap data into physical address space using fixed size blocks called physical pages. 15 00:01:02,880 --> 00:01:09,690 The page sizes the processor supports in 64-bit mode are 1g pages, 2m pages 16 00:01:09,690 --> 00:01:10,860 and 4k pages. 17 00:01:11,370 --> 00:01:14,880 In our system, we use 1g and 2m pages. 18 00:01:16,490 --> 00:01:22,370 So the memory address can be divided into different sets of pages. And page translation will use data structure 19 00:01:22,370 --> 00:01:27,770 called page translation table to translate virtual pages into physical pages. 20 00:01:28,800 --> 00:01:35,220 As you see, we translate it into different pages, or we can translate it into the same page 21 00:01:35,220 --> 00:01:36,720 which is used in the kernel so far. 22 00:01:38,470 --> 00:01:39,990 Let's see the translation table. 23 00:01:41,500 --> 00:01:47,190 The translation table is actually a hierarchical data structure which means we will have several tables. 24 00:01:47,920 --> 00:01:53,870 The virtual address is broken into three pieces in this example. The sign extension is not used here. 25 00:01:54,300 --> 00:01:56,730 We start from Page map level 4 table. 26 00:01:58,840 --> 00:02:01,780 The table is pointed to by the register cr3. 27 00:02:02,920 --> 00:02:08,940 Then the 9-bit pml4 value is used as an index to locate the corresponding item in the table. 28 00:02:10,350 --> 00:02:15,990 Each entry here includes the address of the next lower level table. Page directory pointer table in this example 29 00:02:15,990 --> 00:02:16,740 . 30 00:02:18,120 --> 00:02:22,680 Then the value of pdp is used as an index to locate the correct entry. 31 00:02:24,150 --> 00:02:28,440 And the entry now points to the physical page which is 1g page. 32 00:02:30,070 --> 00:02:36,220 The page offset is used as the offset into the physical page and now we get physical address. 33 00:02:36,810 --> 00:02:38,310 This is a one gigabyte page translation 34 00:02:38,350 --> 00:02:39,120 . 35 00:02:40,670 --> 00:02:43,040 Let's move to 2m page translation. 36 00:02:45,310 --> 00:02:52,120 The virtual address is now divided into more fields. The process is pretty much the same. 37 00:02:52,120 --> 00:02:59,680 Register cr3 holds the address of page map level 4 table and PML4 acts as an index to the entry 38 00:02:59,680 --> 00:03:00,940 which points to the next level table. 39 00:03:02,750 --> 00:03:08,130 Then the value of pdp is used to locate the corresponding entry. 40 00:03:08,180 --> 00:03:09,590 At this point we have one more level. 41 00:03:09,780 --> 00:03:12,920 The next level table is called page directory table 42 00:03:14,940 --> 00:03:22,140 and we can find the entry using pd value. The entry points to the 2m physical page. 43 00:03:23,130 --> 00:03:26,040 And the page offset is the offset into the page. 44 00:03:28,040 --> 00:03:33,620 The 4k page translation is the same except that it requires one more level of translation table 45 00:03:33,620 --> 00:03:34,010 . 46 00:03:35,420 --> 00:03:41,600 And note that the addresses used in register cr3 and table entries are all physical addresses. 47 00:03:42,790 --> 00:03:45,970 Alright this is the simplified process of translation. 48 00:03:46,950 --> 00:03:52,230 What we are going to do next is we are going to talk about the table entries, since setting up paging in our system 49 00:03:52,230 --> 00:03:54,330 is setting those entries. 50 00:03:56,460 --> 00:04:02,040 These three table entries are used for 1g page translation. There are other attributes 51 00:04:02,040 --> 00:04:04,500 in the entry that are not shown in the slide. 52 00:04:05,010 --> 00:04:08,440 What is shown here is enough for our memory module to work properly. 53 00:04:09,150 --> 00:04:12,350 The first entry is stored in register cr3. 54 00:04:12,510 --> 00:04:17,120 So we simply save the address of the page map level 4 table to the register. 55 00:04:18,220 --> 00:04:24,450 The next entry is page map level 4 table entry which holds the address of page directory pointer table 56 00:04:24,450 --> 00:04:24,900 . 57 00:04:26,280 --> 00:04:32,600 In addition, we have some attributes in the entry. The bit p represents present. If the bit is 0, 58 00:04:32,640 --> 00:04:38,820 it means the page table or page is not in the memory and page fault exception will generate 59 00:04:38,820 --> 00:04:39,920 if we access it. 60 00:04:40,560 --> 00:04:41,880 So we set it to 1. 61 00:04:43,330 --> 00:04:50,320 The w means read write with 0 being read only and 1 being read and write. 62 00:04:50,320 --> 00:04:52,800 The bit u means user. If the bit is 0, 63 00:04:52,840 --> 00:04:57,400 it means that the user program in ring3 is not allowed to access the page. 64 00:04:58,240 --> 00:05:02,500 If the bit is 1, then the user program in ring3 can access the page. 65 00:05:04,260 --> 00:05:09,630 Ok, move to the next entry which is page directory pointer table entry. 66 00:05:09,900 --> 00:05:12,330 The table entry holds the address of the 1g physical page. 67 00:05:13,230 --> 00:05:16,650 The p w and u bits have the same meaning. 68 00:05:17,810 --> 00:05:22,580 And the bit 7 must be 1 indicating a 1g physical page translation. 69 00:05:23,910 --> 00:05:27,180 OK, let's take a look at 2mb page translation. 70 00:05:29,190 --> 00:05:36,440 The first two entries are the same as those in the 1g page translation. The third entry is pointing to 71 00:05:36,440 --> 00:05:40,340 page directory table instead of 1g physical page. 72 00:05:40,910 --> 00:05:43,070 So the bit 7 is now cleared. 73 00:05:44,420 --> 00:05:50,600 The last entry points to the 2m physical page and the bit 7 is set to 1 74 00:05:50,600 --> 00:05:53,180 indicating a 2m physical page translation. 75 00:05:55,270 --> 00:06:01,450 OK, next we will do a recap on the code of setting up paging in the loader file and then remap our kernel 76 00:06:01,540 --> 00:06:04,800 to the higher part of the memory location. Let's get started. 77 00:06:07,220 --> 00:06:08,510 Opens the loader file. 78 00:06:15,920 --> 00:06:22,580 The code here is for setting up paging. First off, we zero the 10000 bytes of memory region starting from 70000. 79 00:06:22,580 --> 00:06:27,840 The value of cr3 is 70000. 80 00:06:27,980 --> 00:06:31,790 So the address of page map level 4 table is 70000. 81 00:06:32,710 --> 00:06:41,520 Each entry in the page map level 4 table represent 512g and we only implement the low 1g. 82 00:06:42,010 --> 00:06:44,320 so we set up the first entry of the table. 83 00:06:44,840 --> 00:06:50,710 Each table takes up 4k space, since the table include 512 entries 84 00:06:50,710 --> 00:06:52,450 with each entry being 8 bytes. 85 00:06:53,780 --> 00:06:59,900 So the next table address is set to 71000 and the lower 3 bits are the attribute we need to set 86 00:06:59,900 --> 00:07:00,440 . 87 00:07:01,750 --> 00:07:07,720 We want the memory to be readable and writable and only accessed by the kernel. Also, we set present bit to 1. 88 00:07:07,720 --> 00:07:13,330 So the value is 3. The value we assign to the entry is 71003. 89 00:07:14,950 --> 00:07:22,000 The reason we set the value 7 instead of 3 is that in the previous lectures, 90 00:07:22,330 --> 00:07:28,090 we accessed the memory when we jumped to the ring3 and write the screen buffer to print message on the screen. 91 00:07:28,660 --> 00:07:30,700 If we set it to 3 at that time, 92 00:07:30,700 --> 00:07:32,380 the cpu exception will generate. 93 00:07:33,400 --> 00:07:39,080 For now, we do the initialization in the kernel, so we don’t need to make the memory accessible in ring3 94 00:07:39,100 --> 00:07:39,640 . 95 00:07:41,140 --> 00:07:44,690 Ok let’s continue. In the page directory pointer table, 96 00:07:44,710 --> 00:07:50,210 we also set up the first entry. Because each entry here points to 1g physical page. 97 00:07:50,890 --> 00:07:53,170 This is exactly the entry we want to set. 98 00:07:53,750 --> 00:07:56,650 The base address of the physical page is set to 0 99 00:07:57,010 --> 00:08:00,100 and the attribute here is set to the same value 3. 100 00:08:02,960 --> 00:08:08,300 don’t forget to set the bit 7 to 1 to indicate this is 1g physical page translation 101 00:08:08,320 --> 00:08:08,990 . 102 00:08:10,970 --> 00:08:17,120 Ok, next thing we will do is we are going to remap our kernel. The virtual address the kernel currently running at 103 00:08:17,120 --> 00:08:19,340 is 200000. 104 00:08:20,230 --> 00:08:27,470 And the address we want the kernel to remap to is the virtual address showing here as we have seen it in the slide. 105 00:08:27,470 --> 00:08:31,180 In this example we map the virtual address to the same 1g physical page. 106 00:08:31,730 --> 00:08:37,559 So the two translations we set up will eventually point to the same physical page where the kernel is stored 107 00:08:37,640 --> 00:08:38,120 . 108 00:08:39,059 --> 00:08:44,340 Which means we just change the virtual address of the kernel by simply setting up the table 109 00:08:44,340 --> 00:08:45,330 instead of copying the kernel. 110 00:08:46,020 --> 00:08:46,440 Alright, 111 00:08:46,450 --> 00:08:47,430 with the virtual address we want, 112 00:08:47,430 --> 00:08:53,430 we need to do a simple calculation. Apparently, this virtual address is way beyond 1g 113 00:08:53,430 --> 00:08:53,940 . 114 00:08:54,420 --> 00:09:00,180 So we retrieve the 9-bit page map level 4 value located at the bit 39 of the address. 115 00:09:02,270 --> 00:09:09,020 We get the index by shifting right the value 39 bits. Next we clear other bits using and instruction. 116 00:09:10,180 --> 00:09:12,680 And now we get the 9-bit index value. 117 00:09:13,190 --> 00:09:17,970 What we are going to do next is using the index to locate the corresponding entry in the table. 118 00:09:18,650 --> 00:09:20,570 Each entry takes up 8 bytes, 119 00:09:23,650 --> 00:09:25,700 so we multiply the index by 8. 120 00:09:26,320 --> 00:09:27,080 This is the entry we want. 121 00:09:27,100 --> 00:09:35,410 The value we want to assign is 72003 which means the next level table is at 72000 122 00:09:36,670 --> 00:09:39,400 and the attributes are the same as we saw before. 123 00:09:43,610 --> 00:09:48,080 As you can see the index bits of page directory pointer table entry is 0. 124 00:09:48,560 --> 00:09:54,440 So the first entry in this table is the entry we need to set. Because we want to remap the kernel to the virtual address 125 00:09:54,440 --> 00:09:57,620 without really copying the kernel elsewhere. 126 00:09:58,460 --> 00:10:03,320 So we set the 1g physical page to the same physical page where the kernel locates. 127 00:10:04,840 --> 00:10:07,170 There is one more thing to do before we finish the loader. 128 00:10:08,530 --> 00:10:13,000 Since the kernel is relocated to the new virtual address which is far away from the loader, 129 00:10:15,980 --> 00:10:19,550 we need to save it to the 64-bit register and jump to kernel. 130 00:10:22,670 --> 00:10:25,190 So we move the virtual address to rax 131 00:10:27,890 --> 00:10:29,300 And then jmp rax 132 00:10:31,260 --> 00:10:33,120 OK, let's move to the kernel file. 133 00:10:39,510 --> 00:10:40,180 In the kernel file, 134 00:10:40,230 --> 00:10:45,710 the first thing we are going to do is change the load gdt instruction. Since the kernel is running 135 00:10:45,710 --> 00:10:47,140 at the high memory location. 136 00:10:47,780 --> 00:10:51,350 The memory address of gdt pointer is also at this memory area. 137 00:10:51,830 --> 00:10:54,170 We cannot reference it using 32-bit address. 138 00:10:54,180 --> 00:10:58,820 Instead, we move the address in register rax and load rax. 139 00:11:01,430 --> 00:11:02,750 So we copy address 140 00:11:03,890 --> 00:11:05,730 of gdt pointer to rax. 141 00:11:08,350 --> 00:11:09,520 and load rax 142 00:11:10,660 --> 00:11:16,540 If you want to reference gdt pointer using rip-relative addressing here, it will be fine since our kernel is really small. 143 00:11:16,540 --> 00:11:20,530 In this example we simply move to the register. 144 00:11:21,530 --> 00:11:24,040 The same thing happens with set tss. 145 00:11:26,550 --> 00:11:35,400 We move the address of tss descriptor to rdi and access the memory locations using rdi instead. 146 00:11:39,030 --> 00:11:40,680 Next one is kernel entry. 147 00:11:43,400 --> 00:11:49,310 Because push instruction can only push 32-bit immediate value which cannot represent the address of kernel entry. 148 00:11:49,310 --> 00:11:54,530 We need to move the address to the 64-bit register and then push the value on the stack 149 00:11:54,530 --> 00:11:54,950 . 150 00:11:58,500 --> 00:12:00,780 we move the address of kernel entry to rax 151 00:12:03,640 --> 00:12:04,600 then push rax 152 00:12:09,990 --> 00:12:17,270 Next one is stack address. The stack address copied to the rsp register is at the low memory location. 153 00:12:17,310 --> 00:12:23,310 We change it to the high memory address which points to the same physical address. Remember the virtual address 154 00:12:23,310 --> 00:12:23,660 , 155 00:12:23,670 --> 00:12:28,590 and virtual address 0 we set up in the loader file point to the same physical page. 156 00:12:29,670 --> 00:12:33,820 So adding 200000 to the new virtual address is the new stack address we want 157 00:12:33,840 --> 00:12:34,290 . 158 00:12:36,410 --> 00:12:39,500 And don’t forget to change the kernel stack in the TSS. 159 00:12:43,880 --> 00:12:46,190 we also add it with the virtual address. 160 00:12:48,620 --> 00:12:50,360 Alright that’s it for the kernel file. 161 00:12:51,860 --> 00:12:53,030 In the linker script, 162 00:12:54,610 --> 00:13:00,550 we specify the linker to link the kernel at the desired address. To do it, we use . 163 00:13:00,550 --> 00:13:06,760 which means the current address. And we assign the base address to it. In the loader file, we jump to the new virtual address. 164 00:13:06,940 --> 00:13:12,850 So this is the address we use here. And now the following sections will be linked at this address. 165 00:13:14,050 --> 00:13:16,470 OK, let's build the project and test it out. 166 00:13:24,660 --> 00:13:30,120 Because we only change the memory address of the program, the message printed on the screen remains the same. 167 00:13:30,930 --> 00:13:35,070 But the virtual address for our system is different from those in previous lectures. 168 00:13:36,090 --> 00:13:37,740 OK, that's it for this lecture.