1 00:00:00,330 --> 00:00:06,480 Hello, in this section we use C language to implement our kernel. 2 00:00:06,480 --> 00:00:11,700 C language will be the main language to use in the following lectures. We need to do some preparation first. 3 00:00:12,860 --> 00:00:20,720 The compiler we use is GCC and the executable file we will generate is elf file. 4 00:00:20,720 --> 00:00:26,270 Since the kernel assembly code is also in the kernel, we will see how to combine the assembly and C files together. 5 00:00:27,440 --> 00:00:32,479 The nasm assembler can output elf file. We open the build script. 6 00:00:35,430 --> 00:00:41,160 In the build script, we change the options to generate the elf file instead of binary file. 7 00:00:46,720 --> 00:00:53,530 Elf64 means we generate the elf64 object file. The name of the object file is 8 00:00:56,800 --> 00:00:57,750 kernel.o. 9 00:00:59,520 --> 00:01:05,910 Then we will link the object file with other files written in c. In the kernel assembly file, 10 00:01:05,910 --> 00:01:06,790 we define two sections. 11 00:01:07,440 --> 00:01:09,600 OK, we opened the kernel file. 12 00:01:12,130 --> 00:01:15,400 We define two sections, data section and the code section. 13 00:01:16,320 --> 00:01:18,420 So we remove these two directives. 14 00:01:19,640 --> 00:01:20,450 and define 15 00:01:21,950 --> 00:01:22,790 data section 16 00:01:25,790 --> 00:01:27,050 and text section. 17 00:01:29,560 --> 00:01:35,870 The data section is used for the data defined globally such as idt, tss etc. 18 00:01:36,610 --> 00:01:39,400 Ok now we copy the data in the data section. 19 00:01:45,250 --> 00:01:51,220 The idt are not used here and will be moved to other files. We simply remove them. 20 00:01:57,600 --> 00:01:58,830 And we copied the data 21 00:02:02,080 --> 00:02:03,130 in the data section. 22 00:02:08,470 --> 00:02:14,290 In the text section, the interrupt handlers are not defined in the kernel file and will be implemented in other files, 23 00:02:14,290 --> 00:02:17,830 so we remove all the related code here, 24 00:02:19,500 --> 00:02:21,450 such as the set handler, 25 00:02:24,370 --> 00:02:26,080 load idt instruction, 26 00:02:32,460 --> 00:02:34,380 set handler function 27 00:02:36,720 --> 00:02:37,830 and the use entry 28 00:02:40,210 --> 00:02:41,620 the handler itself. 29 00:02:47,360 --> 00:02:52,400 Because we don't get to ring3 in the kernel entry, the code here is not used 30 00:02:55,930 --> 00:02:57,790 and we place the kernel entry 31 00:03:04,110 --> 00:03:04,770 at the end. 32 00:03:09,910 --> 00:03:16,360 Now the code is initializing the TSS and PIT, PIC, 33 00:03:17,900 --> 00:03:22,460 then we will enter kernel entry and jump to kernel main function in c. 34 00:03:26,190 --> 00:03:30,690 Before we call the main function, we adjust the kernel stack pointer 35 00:03:33,430 --> 00:03:35,290 to 200000, 36 00:03:36,250 --> 00:03:39,340 which is new stack pointer we will use in the c code. 37 00:03:40,450 --> 00:03:43,220 If you return from main function, we will stop here. 38 00:03:43,540 --> 00:03:48,580 Normally we will jump to the main function and never return. But we only test our c function in this lecture 39 00:03:48,580 --> 00:03:49,420 , 40 00:03:49,540 --> 00:03:51,070 so we return from the function. 41 00:03:51,610 --> 00:03:57,070 Another thing we need to do before we create our c file is that we need to declare the start of the kernel globally 42 00:03:57,070 --> 00:03:59,890 so that the linker will find it. 43 00:04:04,150 --> 00:04:05,080 We extern 44 00:04:06,790 --> 00:04:07,660 the main function, 45 00:04:10,580 --> 00:04:17,240 since the kernel main function is not defined here but in the c file. Next we make the label start globally 46 00:04:17,240 --> 00:04:17,810 , 47 00:04:18,920 --> 00:04:21,079 so we global start. 48 00:04:22,610 --> 00:04:25,760 and the linker will know that the start is the entry of the kernel. 49 00:04:26,890 --> 00:04:29,260 Alright, let's create our first C file. 50 00:04:34,240 --> 00:04:36,760 We call it main.c. 51 00:04:41,280 --> 00:04:45,810 The first thing we are going to do is add a header file. So we include 52 00:04:47,820 --> 00:04:48,870 stdint 53 00:04:51,370 --> 00:04:58,480 What we want to do here is we want to use fixed width data types. For simplicity, the system we write will assume 54 00:04:58,480 --> 00:05:01,720 the data has specific length in the c code. 55 00:05:02,290 --> 00:05:05,500 So we will define variables with fixed width data types. 56 00:05:06,220 --> 00:05:07,720 The next header file is 57 00:05:09,820 --> 00:05:11,320 stddef 58 00:05:14,540 --> 00:05:17,270 These are the two headers we import in the project. 59 00:05:18,380 --> 00:05:22,710 OK, the first function is kernel main 60 00:05:26,880 --> 00:05:32,490 which is called from the assembly file, you can name the function to other name you like. 61 00:05:32,490 --> 00:05:36,270 This is a simple function which don’t return value and have no parameters either. 62 00:05:36,510 --> 00:05:43,200 The only thing we will do in this function is print character on the screen so that we will know we are 63 00:05:43,200 --> 00:05:44,220 in the C function. 64 00:05:45,460 --> 00:05:52,570 First off, we create a pointer and initialize it with the value b8000. We define a pointer 65 00:05:52,570 --> 00:05:53,380 to type char. 66 00:06:00,660 --> 00:06:05,760 Since each character on the screen takes up two bytes. The first byte is assigned to C 67 00:06:12,080 --> 00:06:14,320 we assign the attribute a to it. 68 00:06:16,070 --> 00:06:18,500 So the character is printed in green. 69 00:06:19,020 --> 00:06:24,380 OK, we are done. When we return from the function, we will return to the infinite loop and stop there 70 00:06:24,390 --> 00:06:24,680 . 71 00:06:25,760 --> 00:06:27,470 Now, we switch to the build script. 72 00:06:29,810 --> 00:06:31,430 and compile the c file. 73 00:06:33,990 --> 00:06:39,810 So we type gcc and we use c99 standard 74 00:06:40,800 --> 00:06:42,690 so -std c99 75 00:06:44,860 --> 00:06:48,190 and then we specify we use large code model by typing 76 00:06:50,140 --> 00:06:52,900 mcmodel large 77 00:06:54,460 --> 00:07:00,640 so that the code generated is for the large mode. Otherwise we could have relocation truncated to fit error. 78 00:07:02,520 --> 00:07:04,920 Next flag we add is free standing 79 00:07:08,980 --> 00:07:13,870 which tell GCC we don’t need c standard library and other runtime features. 80 00:07:14,810 --> 00:07:20,750 In the freestanding environment, there are still some of headers available to use, such as stdint file we used in the file 81 00:07:20,750 --> 00:07:21,870 . 82 00:07:23,240 --> 00:07:27,320 Next one is no stack protector. 83 00:07:27,350 --> 00:07:30,620 To enable it, we need runtime support which we don’t provide in our code. 84 00:07:30,630 --> 00:07:32,510 So here we disable this feature. 85 00:07:36,240 --> 00:07:38,730 The next flag we add is no red zone. 86 00:07:42,750 --> 00:07:47,820 The red zone is an area of 128 bytes below the stack pointer which can be used 87 00:07:47,820 --> 00:07:50,480 by leaf functions without changing rsp register. 88 00:07:50,530 --> 00:07:58,170 The red zone is specified in system v AMD64 calling convention which we use in the code. 89 00:07:59,180 --> 00:08:05,210 We need to disable red zone in the kernel, otherwise the kernel stack could be corrupted if the interrupt occurs. 90 00:08:06,470 --> 00:08:08,720 OK, then -c 91 00:08:09,670 --> 00:08:16,900 and the name of the file main.c in this case. -c means that we only want gcc to compile the file into an object file 92 00:08:16,900 --> 00:08:20,530 without linking to an executable file. 93 00:08:22,240 --> 00:08:29,470 Ok now we have two object files kernel.o and main.o. Let’s link them to generate the kernel. 94 00:08:30,190 --> 00:08:33,580 We use the linker and specify no standard lib 95 00:08:36,320 --> 00:08:39,799 which means we don’t use startup file and library files. 96 00:08:40,960 --> 00:08:42,950 We also use our own linker script. 97 00:08:43,659 --> 00:08:45,310 We will talk about it in a minute. 98 00:08:46,310 --> 00:08:54,650 Then we write -o the name of the file we want to produce. We call it kernel. 99 00:08:54,660 --> 00:08:57,750 And the object file we want to link. kernel.o and main.o 100 00:09:02,730 --> 00:09:10,560 When we generate the kernel, it is an elf format file. What we want to do is we want to generate a binary file 101 00:09:10,560 --> 00:09:15,720 so that we can simply load it in the memory and jump to it from the loader as we did before. 102 00:09:16,440 --> 00:09:20,010 So here we use objcopy 103 00:09:21,850 --> 00:09:22,460 -O 104 00:09:24,940 --> 00:09:28,300 And the format of the file we want, here we want binary file. 105 00:09:30,560 --> 00:09:32,060 Then the file we want to convert, 106 00:09:33,020 --> 00:09:38,300 kernel, in this case. The binary file is called kernel.bin 107 00:09:40,370 --> 00:09:44,240 and we will load this file in the memory as we did in the last section. 108 00:09:45,510 --> 00:09:47,710 Now, let's take a look at the linker script. 109 00:09:48,180 --> 00:09:50,370 First off, we create the linker script. 110 00:09:54,620 --> 00:09:57,770 called linker.lds 111 00:10:02,210 --> 00:10:07,700 The first thing we will do is specify the output format. We do this by writing output 112 00:10:09,490 --> 00:10:10,060 format 113 00:10:14,310 --> 00:10:19,890 and then the format elf64-x86-64. 114 00:10:23,140 --> 00:10:25,630 Next, we type entry start 115 00:10:28,590 --> 00:10:35,640 which indicates that the label start is the entry of the kernel. You may notice that in the kernel assembly file, 116 00:10:38,530 --> 00:10:45,490 we don’t use org directive to tell the assembler we want our kernel file running in the address 200000. 117 00:10:45,490 --> 00:10:47,750 But in the loader file, we still jump to the address 200000 118 00:10:47,770 --> 00:10:49,860 after we load the kernel. 119 00:10:50,740 --> 00:10:52,870 So we use the linker script to do it. 120 00:10:55,770 --> 00:11:00,690 We have several sections define in the files. We write sections. 121 00:11:04,160 --> 00:11:07,190 The first segment we write is text segment. 122 00:11:11,200 --> 00:11:16,630 and in the curly braces we write *(.text) 123 00:11:18,120 --> 00:11:18,660 , 124 00:11:19,920 --> 00:11:26,340 which means the .text sections in the object files, kernel.o and main.o in this case, 125 00:11:26,340 --> 00:11:33,410 are added in text segment of the output file. Because we want our code running at the address 200000, 126 00:11:33,420 --> 00:11:38,210 we type .= and the address 200000. 127 00:11:39,870 --> 00:11:45,090 This means that current location is set to 200000. 128 00:11:45,990 --> 00:11:51,560 The next segment is read only data segment, just like the text segment, 129 00:11:55,820 --> 00:11:58,430 we add all the .rodata sections here. 130 00:12:01,890 --> 00:12:03,990 The next segment is data segment. 131 00:12:09,220 --> 00:12:14,740 And the last segment we care about is BSS and we write it here. 132 00:12:19,330 --> 00:12:24,370 Also we want the data section aligned to the 16-byte boundary. So we write 133 00:12:26,330 --> 00:12:31,250 . = align 16. 134 00:12:33,530 --> 00:12:35,470 Ok that’s it for the linker script. 135 00:12:37,040 --> 00:12:38,170 In the build script. 136 00:12:40,190 --> 00:12:40,820 we add 137 00:12:42,350 --> 00:12:43,160 -T 138 00:12:44,680 --> 00:12:46,600 the linker name we just created 139 00:12:49,300 --> 00:12:49,660 and we are done. 140 00:12:51,610 --> 00:12:53,620 Let's test it out. We save the files 141 00:12:57,290 --> 00:12:59,090 and build the project in the terminal. 142 00:13:04,750 --> 00:13:06,580 Now, we run the script in the terminal. 143 00:13:09,370 --> 00:13:12,010 OK, let's test out the program in bochs. 144 00:13:17,510 --> 00:13:17,970 Alright, 145 00:13:18,010 --> 00:13:20,740 We can see the character C is printed on the screen.