1 00:00:00,390 --> 00:00:06,240 In this lecture, we will focus on the process module and add an idle process which will be running 2 00:00:06,240 --> 00:00:12,530 when there is no processes running in the system. The idle process is the process where we initialize the kernel, 3 00:00:13,200 --> 00:00:15,000 So in the kernel assembly file, 4 00:00:20,030 --> 00:00:25,610 After it initialized the system and return from kernel main, it will be in the infinite loop 5 00:00:25,610 --> 00:00:29,510 which means if there is no other process, the system is running here. 6 00:00:30,500 --> 00:00:33,730 So the idle process is running in the infinite loop. 7 00:00:34,880 --> 00:00:40,730 In order to accept the interrupts when the idle process is running, we have to enable interrupt 8 00:00:40,730 --> 00:00:41,510 before we halt the system. 9 00:00:42,350 --> 00:00:46,460 So we use the instruction sti to enable interrupt. 10 00:00:47,400 --> 00:00:54,090 if an interrupt is fired, the processor will be wakened up from halt state. It’s worth mentioning that 11 00:00:54,090 --> 00:00:58,470 since we haven’t initialized ss register, 12 00:00:58,470 --> 00:01:04,769 it will cause exception when we return from interrupt handler. We have discussed it in the section exceptions and interrupts handling. 13 00:01:05,610 --> 00:01:08,790 So here we simple set ss to null. 14 00:01:13,080 --> 00:01:17,920 we zero out ax, and move ss ax 15 00:01:19,970 --> 00:01:23,240 OK, at this point, we have a working idle process. 16 00:01:24,690 --> 00:01:28,050 The next thing we will do is change the code in the memory module. 17 00:01:30,700 --> 00:01:34,720 Remember we have modified the memory info block address in the loader file. 18 00:01:35,320 --> 00:01:37,390 So in function initialize memory function. 19 00:01:38,950 --> 00:01:43,810 The address of memory info block count is 20000 20 00:01:44,960 --> 00:01:47,810 and the start of the memory info block array is at 21 00:01:49,100 --> 00:01:50,390 20008. 22 00:01:51,650 --> 00:01:57,650 Also, we set aside the high part of the 1g of memory for the file system. 23 00:01:57,650 --> 00:02:01,850 So the top of the free memory region we can use is set to the base of the file system. 24 00:02:02,600 --> 00:02:04,310 The following memory limits checks 25 00:02:07,100 --> 00:02:08,900 like this, need to be changed. 26 00:02:13,320 --> 00:02:14,100 and here 27 00:02:16,900 --> 00:02:21,760 we set them all to the base of file system. 28 00:02:25,990 --> 00:02:32,170 The last one is set up kvm function because memory end is not larger than the base address of the file system 29 00:02:32,170 --> 00:02:34,750 if we still use it here. 30 00:02:34,990 --> 00:02:38,440 the higher part of the 1g of memory region will not be mapped. 31 00:02:39,280 --> 00:02:42,490 So we manually set it to the 1g of memory space. 32 00:02:46,060 --> 00:02:47,980 Ok that’s it for the memory module. 33 00:02:49,580 --> 00:02:54,500 What we are going to do next is we are going to change the code in the process module to run the idle process first 34 00:02:54,500 --> 00:02:55,340 . 35 00:02:57,870 --> 00:03:03,060 So in process.c file, we first look at initialization function, 36 00:03:04,450 --> 00:03:06,620 All the code in this function is not used. 37 00:03:08,260 --> 00:03:12,520 we initialize idle process and user process. 38 00:03:16,510 --> 00:03:19,390 The user process is what the loader reads into the memory. 39 00:03:20,790 --> 00:03:22,650 We start with idle process. 40 00:03:26,470 --> 00:03:30,250 In the function, we define two variables process and process control. 41 00:03:31,620 --> 00:03:37,260 Since the idle process is the first process to run. So we will save it as the current process in process control 42 00:03:37,260 --> 00:03:38,100 . 43 00:03:39,300 --> 00:03:43,020 First off, we find an unused process. 44 00:03:45,720 --> 00:03:48,990 So we call function find unused process. 45 00:03:50,980 --> 00:03:57,280 Since we are at initialization process, we assert this operation will succeed and the process 46 00:03:57,280 --> 00:03:58,620 is the first item in the table. 47 00:03:59,990 --> 00:04:07,390 After we find a process slot, we set the process entry. The pid of the idle process is set to 0. 48 00:04:10,550 --> 00:04:18,700 The page map is initialized with current cr3 value which means we use the current stack for the idle process. 49 00:04:18,740 --> 00:04:22,340 remember the value in cr3 is physical address 50 00:04:22,730 --> 00:04:25,140 So here we use p2v to convert 51 00:04:25,140 --> 00:04:27,040 physical address to virtual address. 52 00:04:29,020 --> 00:04:32,320 Then we set the process state 53 00:04:34,700 --> 00:04:35,540 to proc running 54 00:04:42,740 --> 00:04:46,010 and then we save it to the process control. 55 00:04:49,390 --> 00:04:55,390 As you see, we didn’t set up uvm because the idle process only has kernel stack and it’s running only in the kernel mode 56 00:04:55,390 --> 00:04:56,170 . 57 00:04:56,960 --> 00:04:59,770 Ok, that's it for the idle process. 58 00:04:59,780 --> 00:05:01,360 next we go to user process. 59 00:05:03,280 --> 00:05:07,960 So we define function initialize user process 60 00:05:11,320 --> 00:05:15,610 Initializing user process is the same as we did in the previous lectures. 61 00:05:16,750 --> 00:05:20,500 The variables we need include process control, process. 62 00:05:22,610 --> 00:05:23,540 and ready list. 63 00:05:26,340 --> 00:05:29,610 We first get the ready list from process control 64 00:05:31,660 --> 00:05:33,820 and we will append it to the list for scheduling. 65 00:05:35,340 --> 00:05:40,230 Next we allocate a new process 66 00:05:41,580 --> 00:05:44,070 and we assume that we will find a new process. 67 00:05:48,750 --> 00:05:51,280 Function alloc new process will setup kvm, 68 00:05:51,690 --> 00:05:53,760 so here we only setup uvm. 69 00:05:55,100 --> 00:05:56,750 The argument is page map 70 00:06:01,320 --> 00:06:03,460 and the base of the user process. 71 00:06:04,410 --> 00:06:07,260 Remember we read it into memory 30000 72 00:06:08,200 --> 00:06:10,180 and we copy 10 sectors of data. 73 00:06:14,020 --> 00:06:18,820 Since this is the first user process, we assert operation will succeed. 74 00:06:22,070 --> 00:06:24,920 OK, in the end, we set the state to proc ready. 75 00:06:26,940 --> 00:06:28,620 and append it to the ready list. 76 00:06:33,770 --> 00:06:36,620 Alright, let's take a look at alloc new process function. 77 00:06:39,060 --> 00:06:45,000 This function just finds a new process and set the process entry. Most of the work is done in set process entry function. 78 00:06:45,000 --> 00:06:48,750 So we rename it to alloc new process. 79 00:06:52,510 --> 00:06:54,340 and it returns the process 80 00:06:57,350 --> 00:06:59,450 This function doesn't take parameters. 81 00:07:04,670 --> 00:07:06,610 We define variable process 82 00:07:10,190 --> 00:07:15,890 What we are going to do in this function is we are going to find a new process. 83 00:07:15,890 --> 00:07:23,090 If there is no process available to us, we simply return null. So we find process by function find unused process 84 00:07:26,250 --> 00:07:29,490 and we check the return value, if it is null, 85 00:07:31,750 --> 00:07:36,610 It means that there is no process available to us, so we return null. 86 00:07:41,910 --> 00:07:44,970 If the check passed, then we set process state, 87 00:07:45,960 --> 00:07:53,430 pid number, then we allocate kernel stack. Here if the allocation of kernel stack fails, 88 00:07:53,430 --> 00:07:54,510 we just return null. 89 00:07:57,640 --> 00:08:03,400 Let’s move on. Initialize kernel stack with 0 and set process context, 90 00:08:04,880 --> 00:08:08,150 initialize the trap frame so that we can return to the user mode. 91 00:08:09,660 --> 00:08:14,510 We also set up kvm and if this operation failed, we return null to the caller. 92 00:08:20,100 --> 00:08:21,810 Don’t forget to free kernel stack 93 00:08:27,850 --> 00:08:31,930 and set process to null meaning that the process slot is not used. 94 00:08:37,480 --> 00:08:40,330 In this function, set up uvm is not used. 95 00:08:42,450 --> 00:08:46,740 at the end of the function, return the process. 96 00:08:48,590 --> 00:08:53,630 So another thing I need to mention is that in our previous lectures, we assumed that the ready list 97 00:08:53,630 --> 00:08:55,830 is not empty when we do the scheduling 98 00:08:58,040 --> 00:09:05,540 So in function schedule, we use assert to do the check and now since we have idle process 99 00:09:05,540 --> 00:09:06,890 we can allow the ready list empty 100 00:09:08,780 --> 00:09:14,360 If we find that the list is empty, it means that we have no process to run. 101 00:09:15,170 --> 00:09:17,040 Therefore we run the idle process. 102 00:09:17,720 --> 00:09:18,980 Here we do another check, 103 00:09:19,490 --> 00:09:23,750 if that is the case, we assume that the current process is not idle process. 104 00:09:24,950 --> 00:09:27,660 the pid of idle process is set to zero. 105 00:09:29,870 --> 00:09:35,570 In the system, we don’t append the idle process to ready list. So we just copy the address of process 0 106 00:09:35,570 --> 00:09:37,790 to the current process. 107 00:09:40,510 --> 00:09:47,650 If the list is not empty, it’s a normal case, we use the exsiting code. 108 00:09:49,220 --> 00:09:55,160 which means we just remove the item from the ready list and then we'll switch to the process. 109 00:09:56,690 --> 00:10:00,260 As we just talked about, we don’t append the idle process to the ready list, 110 00:10:01,930 --> 00:10:05,110 so we need to add another check in the yield function. 111 00:10:08,780 --> 00:10:15,240 If the current process is idle process and it’s about to give up cpu resource, we will not append it to the list 112 00:10:15,260 --> 00:10:15,860 . 113 00:10:19,250 --> 00:10:26,450 So if we find that the process is not idle process, we will append it to the ready list. 114 00:10:29,600 --> 00:10:36,230 After we return from the main function, we are running as the idle process, so launch function 115 00:10:36,230 --> 00:10:37,070 is not used. 116 00:10:38,630 --> 00:10:45,540 Alright, that's it for the process module. In function initialize idle process, we use function read cr3. 117 00:10:45,710 --> 00:10:51,260 read cr3 is implemented in the assembly code. 118 00:10:52,220 --> 00:10:55,250 So we add it in the trap assembly file. 119 00:10:58,660 --> 00:11:01,060 Just as we did with read cr2 120 00:11:01,930 --> 00:11:05,590 we copy the value of cr3 to rax and return. 121 00:11:09,390 --> 00:11:13,680 Global read cr3 so that we can reference it in the c module 122 00:11:17,440 --> 00:11:19,030 In the trap.h file, 123 00:11:23,550 --> 00:11:26,040 we add the declaration of read cr3 124 00:11:28,160 --> 00:11:31,040 OK, now we can use function read cr3 125 00:11:32,560 --> 00:11:36,480 Before we build the project, let’s first take a look at the linker script. 126 00:11:38,850 --> 00:11:42,630 In this section, the kernel file is stored in the fat16 image, 127 00:11:43,590 --> 00:11:48,210 So we should initialize the data in bss section before we use them. 128 00:11:49,170 --> 00:11:56,670 So here we define two symbols bss start and bss end to represent the beginning and end of the bss section 129 00:11:57,030 --> 00:11:57,720 . 130 00:11:58,640 --> 00:12:01,790 Then we can references the symbols in the C file. 131 00:12:07,180 --> 00:12:11,530 the initialization of bss section is done in main.c file. 132 00:12:13,900 --> 00:12:19,750 print function is not used in this example, and we uncomment code here. 133 00:12:21,390 --> 00:12:25,520 Remember launch function is not used in this section, so we got rid of it. 134 00:12:29,040 --> 00:12:35,430 Now, let's reference those two symbols so we extern bss start 135 00:12:39,360 --> 00:12:41,130 and bss end 136 00:12:43,090 --> 00:12:45,760 Then we calculate the size of the bss section. 137 00:12:48,440 --> 00:12:54,760 after we get the size of bss section, we initialize it with function memset 138 00:12:57,650 --> 00:12:57,930 OK. 139 00:12:58,940 --> 00:13:03,470 Now we have finished the kernel part. Let's build the kernel project. 140 00:13:07,140 --> 00:13:13,680 OK, what we are going to do next is we are going to build the first user program, In the previous lectures, 141 00:13:13,680 --> 00:13:19,050 user1 is used to do the cleanup for the killed process. In this section 142 00:13:19,080 --> 00:13:22,260 we don’t use the program this way. At this point, 143 00:13:22,260 --> 00:13:25,560 the only thing we will do is print message and counter value every 1 second 144 00:13:25,590 --> 00:13:26,340 . 145 00:13:26,390 --> 00:13:26,910 Second. 146 00:13:27,870 --> 00:13:29,490 So we define variable i 147 00:13:34,320 --> 00:13:37,380 and initialize it with 0, 148 00:13:41,300 --> 00:13:44,960 then we print user process 149 00:13:47,240 --> 00:13:47,620 and the value. 150 00:13:49,090 --> 00:13:50,110 Here we sleep 151 00:13:51,110 --> 00:13:56,700 for 1 second and then we increment 152 00:13:57,120 --> 00:13:57,870 the value i. 153 00:14:00,510 --> 00:14:02,280 OK, let's build the user project. 154 00:14:08,190 --> 00:14:14,490 Instead of writing the binary file into the image, we mount the fat16 image and copy the kernel file 155 00:14:14,490 --> 00:14:16,410 and user file to the partition. 156 00:14:36,280 --> 00:14:41,500 Once we copy the binary files into the image, we unmount the partition. 157 00:14:44,720 --> 00:14:46,550 And the run bochs to see the result. 158 00:14:55,480 --> 00:14:58,960 OK, now, as you can see, the user process is printed on screen.