1 00:00:00,460 --> 00:00:06,040 In this lecture we will implement print function. The print function is really important for us. 2 00:00:06,490 --> 00:00:12,340 Because when we add new code to the kernel, we need to know if it works and what errors we could encounter. 3 00:00:13,210 --> 00:00:16,860 Printing message on screen is the easiest way to get the information. 4 00:00:17,320 --> 00:00:21,910 So we will write the simplified version of print function like the printf function we used in c language. 5 00:00:21,910 --> 00:00:29,380 In the preivous lectures, we enabled interrupt in the kernel file for testing interrupt handlers. 6 00:00:29,380 --> 00:00:30,070 In following lectures, 7 00:00:30,070 --> 00:00:33,010 we will not deal with interrupt until we get to ring3. 8 00:00:38,590 --> 00:00:43,870 So we disable interrupts and the code for setting up ss register can also be removed. 9 00:00:44,680 --> 00:00:46,040 OK, let's get started. 10 00:00:47,530 --> 00:00:49,000 We create a header file 11 00:00:51,010 --> 00:00:52,080 print.h 12 00:00:53,960 --> 00:01:00,470 In the header file, we add a guard so that the header file is included only once. 13 00:01:00,470 --> 00:01:02,930 we define a constant line size which is 160. 14 00:01:03,440 --> 00:01:10,530 Remeber we have 80 characters each line in the text mode and each characters takes up 2 bytes. 15 00:01:10,560 --> 00:01:10,910 The structure screen buffer 16 00:01:10,910 --> 00:01:13,640 is used for printing message on the screen. 17 00:01:14,590 --> 00:01:20,860 The buffer field is the address of screen buffer, b8000 in this case. The next two items are 18 00:01:20,860 --> 00:01:25,210 the column and row which represents where we will print message next. 19 00:01:26,590 --> 00:01:31,960 The print function in the system is called printk function which is used in kernel mode. 20 00:01:33,090 --> 00:01:39,450 The first parameter is the address of format string. Here we use const indicating that 21 00:01:39,450 --> 00:01:42,030 the data pointed to by format should not be modified. 22 00:01:42,840 --> 00:01:48,510 The printk function is variable argument function which means it can take a variable number of arguments. 23 00:01:49,290 --> 00:01:51,690 The variable argument function is like this, 24 00:01:52,620 --> 00:01:58,770 there is ... at the end of the parameters which indicates that there are parameters, 25 00:01:58,770 --> 00:02:00,120 but you don’t need to specify here. 26 00:02:00,780 --> 00:02:06,600 The format string tells the function how to interrupt the variable arguments. and it returns 27 00:02:06,600 --> 00:02:07,160 the count of the characters it actually prints 28 00:02:07,170 --> 00:02:08,220 . 29 00:02:09,389 --> 00:02:13,830 Ok let’s look at the implementation of printk function in print.c file. 30 00:02:16,970 --> 00:02:24,030 The header files we will use are stdint stdarg print and library files. 31 00:02:24,830 --> 00:02:28,810 The printk function uses macros in the stdarg header file. 32 00:02:29,230 --> 00:02:30,980 We will talk about it in a minute. 33 00:02:31,700 --> 00:02:37,880 Next we define a variable called screen buffer which is only used in this file. 34 00:02:37,880 --> 00:02:41,870 we initialize it with the value b8000 and the 0,0 for row and column. 35 00:02:49,460 --> 00:02:57,680 In the printk function, we define a few variables. Buffer is the array which can save a total of 1024 characters 36 00:02:57,680 --> 00:02:59,440 which will be printed on the screen. 37 00:02:59,840 --> 00:03:05,080 So we should make sure the number of characters we want to print is not larger than this value 38 00:03:05,240 --> 00:03:10,270 when we call the function. The buffer size holds the count of the characters we want to print. 39 00:03:11,060 --> 00:03:16,070 The integer is used to save the value we want to print on the screen. 40 00:03:16,070 --> 00:03:20,010 To make it simple, the integer we print in our system is assumed to be 64bits. 41 00:03:20,390 --> 00:03:23,000 So here we use fix width data type. 42 00:03:23,820 --> 00:03:29,620 Next variable is a string pointing to the characters. Then we have a variable called args, 43 00:03:29,640 --> 00:03:33,480 the type is va_list defined in the header file stdarg.h. 44 00:03:34,380 --> 00:03:41,160 Ok now we can initialize the args list using va_start macro so that the variables can be used with other macros later 45 00:03:41,160 --> 00:03:42,100 . 46 00:03:43,500 --> 00:03:51,000 The first argument is the args, then the last known argument passed to the function. Since we only have one argument passed in 47 00:03:51,000 --> 00:03:52,570 which is format, 48 00:03:53,130 --> 00:03:53,950 this is the last known argument. 49 00:03:54,020 --> 00:03:56,460 We write format here. 50 00:03:57,420 --> 00:04:02,850 At the end of the function, we also need to do cleanup using va end before we return. 51 00:04:03,950 --> 00:04:06,140 Ok now let’s parse the format string. 52 00:04:08,330 --> 00:04:14,930 The format string used in printk function is much like that in printf in c language. 53 00:04:14,930 --> 00:04:21,019 There are a few format specifiers we can use in printf function such as %d signed integer type, 54 00:04:21,019 --> 00:04:28,670 %s string, etc. For simplicity, we only support x d s and u specifiers 55 00:04:29,120 --> 00:04:30,800 and this is enough for our system. 56 00:04:31,860 --> 00:04:38,520 Ok, first off, we use for loop to loop through each character in the format string. 57 00:04:38,520 --> 00:04:44,520 The format string here is null-terminated string. So if we get the null character, it means we reach the end of the format string. 58 00:04:45,760 --> 00:04:52,060 In the for loop, we check to see if the character is %. If the character is not %, it is a regular character 59 00:04:52,060 --> 00:04:57,910 and we simply copy the character to the buffer, increment the buffer size 60 00:04:57,910 --> 00:04:58,710 and move to the next one. 61 00:05:00,050 --> 00:05:05,210 If it is %, we will check the next character. Here we use switch statement. 62 00:05:06,550 --> 00:05:15,080 We have x hexadecimal type, u unsigned integer type, d signed integer type and s string specifiers. 63 00:05:15,850 --> 00:05:22,450 As for other characters, we deal with it in the default here. Since it is not the specifier we support, 64 00:05:22,750 --> 00:05:27,880 we simply copy the character % to the buffer, decrement index and continue the loop. 65 00:05:28,900 --> 00:05:34,600 If the specifier is s, then we will get the corresponding pointer and read characters from it. 66 00:05:35,980 --> 00:05:43,990 So we use va arg macro to retrieve the pointer. The first argument is args variable, 67 00:05:43,990 --> 00:05:47,630 the next one is the type of the variable we want to retrieve which is a pointer to type char. 68 00:05:48,780 --> 00:05:54,900 And we use variable string to hold the address of the string. Then we call function read string into the buffer 69 00:05:54,900 --> 00:05:55,400 . 70 00:05:55,920 --> 00:06:01,820 The first argument is buffer, then the location from which we write the string, buffer size in this case. 71 00:06:01,920 --> 00:06:05,130 The last one is the address of the string. 72 00:06:06,790 --> 00:06:12,610 The function returns the number of characters it writes into the buffer. So we add the result to update the buffer size, 73 00:06:12,610 --> 00:06:16,270 then we break and continue the for loop. 74 00:06:16,960 --> 00:06:18,910 Ok let’s see the read string function. 75 00:06:21,590 --> 00:06:25,660 The first parameter is buffer, then the position and the address of the string. 76 00:06:26,820 --> 00:06:30,930 The first thing we will do is define a variable called index. we initialize it with the value 0. 77 00:06:32,120 --> 00:06:38,300 The for loop here is used to copy each character of the string to the buffer. 78 00:06:38,300 --> 00:06:44,630 The null character means the end of the string. We copy each character in the string to the buffer 79 00:06:44,630 --> 00:06:46,100 and update the position. 80 00:06:46,990 --> 00:06:53,800 When we finish copying characters, we return the number of characters being copied, 81 00:06:53,830 --> 00:06:56,710 the index here represents the number of characters we have copied into the buffer. 82 00:06:58,160 --> 00:06:58,660 OK. 83 00:07:00,360 --> 00:07:07,740 Next specifier we will implement is x. In our system, when we use x specifier, 84 00:07:07,950 --> 00:07:11,370 it means that we want to print 64-bit hexadecimal value. 85 00:07:12,580 --> 00:07:16,690 So we retrieve the value by specifying the type as 64-bit integer. 86 00:07:17,890 --> 00:07:19,940 The value is stored in the variable integer. 87 00:07:21,220 --> 00:07:27,910 Then we call function hex to string which convert the hex to characters and store it in the buffer. 88 00:07:28,720 --> 00:07:32,080 The function also returns the number of characters copied in the buffer. 89 00:07:32,890 --> 00:07:35,390 We pass the buffer, buffer size and the value we want to convert 90 00:07:35,410 --> 00:07:36,340 . 91 00:07:37,230 --> 00:07:39,630 Notice that this is unsigned integer type. 92 00:07:41,140 --> 00:07:44,560 When we return, we update the buffer size and continue the process. 93 00:07:45,440 --> 00:07:50,270 As you see, the parameters it takes is the same as read string except the last one. 94 00:07:54,290 --> 00:08:00,230 In function hex to string, we need to define a digits buffer to save the converted characters 95 00:08:00,230 --> 00:08:06,020 and digits map which is used to get the correct characters. Size stores the number of characters converted. 96 00:08:06,020 --> 00:08:10,070 We use do while loop to convert the value. 97 00:08:11,130 --> 00:08:15,290 We copy each of the converted characters to the buffer and update the size. 98 00:08:16,460 --> 00:08:22,520 Here the digits mod 16 will get the first digit on the right and we use it as an index 99 00:08:22,520 --> 00:08:29,030 to get the correct character. As you can see, the characters in the digit map are arranged in a way 100 00:08:29,030 --> 00:08:32,010 that the index of the character actually represents the value. 101 00:08:32,510 --> 00:08:37,380 For example, if the digits is A, then we mod 16, the result is 10, 102 00:08:37,909 --> 00:08:40,490 the value at the index 10 of the map is A. 103 00:08:41,830 --> 00:08:48,010 Ok, then we need to divide the value by 16 which will make the next digit become the first digit 104 00:08:48,010 --> 00:08:48,500 . 105 00:08:49,270 --> 00:08:52,490 Now we can repeat the process until the value is 0. 106 00:08:53,320 --> 00:08:55,190 So each digit mode 16 107 00:08:55,240 --> 00:08:59,560 and we use the result as an index to find the correct character in the digits map. 108 00:09:00,500 --> 00:09:05,960 Notice that when we finish, the characters in the buffer are in the reverse order. 109 00:09:06,130 --> 00:09:09,380 Because we convert the digits and copy them into the buffer from right to left. 110 00:09:10,410 --> 00:09:15,150 So we have to copy them to the buffer backward. We use for loop to do that. 111 00:09:16,160 --> 00:09:21,980 The index of the last character is size - 1 and the first character is 0. 112 00:09:21,980 --> 00:09:25,100 So we copy it to the buffer backward and update the position. 113 00:09:26,150 --> 00:09:31,660 At the end of the characters, we also add the character H which represents this is a hexadecimal value. 114 00:09:32,090 --> 00:09:36,590 If you like, you can add prefix 0x to the buffer instead of suffix h. 115 00:09:37,210 --> 00:09:42,020 Ok we return size+1, because H is added in the end. 116 00:09:43,830 --> 00:09:45,660 Next we get to the specifier u. 117 00:09:47,540 --> 00:09:52,940 The process is pretty much the same. We retrieve the unsigned integer value 118 00:09:54,050 --> 00:09:59,270 and call function unsigned decimal to string to convert the value to characters and copy them to the buffer 119 00:09:59,270 --> 00:09:59,940 . 120 00:10:00,320 --> 00:10:03,500 Also we update the buffer size after the function returns. 121 00:10:04,460 --> 00:10:05,900 Let's look at this function. 122 00:10:11,820 --> 00:10:15,330 This function also deals with unsigned 64-bit integers. 123 00:10:16,630 --> 00:10:22,480 As we did in function hexadecimal to string, we define a digit map, since this is decimal value, 124 00:10:22,480 --> 00:10:27,730 we only need 10 digits 0 through 9, digit buffer and the size. 125 00:10:29,660 --> 00:10:37,550 We use do while loop to convert the decimal value. Instead of mod 16, the digits mod 10 in this case 126 00:10:37,550 --> 00:10:38,970 and copy the character to the buffer. 127 00:10:39,830 --> 00:10:44,170 Then we divide the digits by 10 which makes the second digit become the first one. 128 00:10:44,870 --> 00:10:46,410 If the value is non- zero, 129 00:10:46,640 --> 00:10:49,940 we repeat the process. When it is done, 130 00:10:49,940 --> 00:10:52,610 the size holds the number of the characters being copied to the buffer. 131 00:10:53,390 --> 00:10:57,350 Remember the converted characters copied to the buffer is in the reverse order. 132 00:10:58,250 --> 00:11:04,370 We copy them to buffer from the last character to the first one using for loop. 133 00:11:04,370 --> 00:11:05,240 And then we simply return the size. 134 00:11:06,630 --> 00:11:08,350 The last specifier is d 135 00:11:10,860 --> 00:11:12,120 signed integer type. 136 00:11:13,850 --> 00:11:19,640 The function we use is called decimal to string. The arguments are the same as unsigned decimal to string 137 00:11:19,640 --> 00:11:22,730 except that the integer is signed integer. 138 00:11:24,100 --> 00:11:25,450 Let's define this function. 139 00:11:30,520 --> 00:11:35,710 The first thing we are going to do is we are going to check the value passed in is positive or negative value 140 00:11:35,710 --> 00:11:36,100 . 141 00:11:36,490 --> 00:11:39,730 If it is negative value, we convert it to the positive 142 00:11:39,730 --> 00:11:42,620 and the first character we will print is minus sign. 143 00:11:43,210 --> 00:11:46,990 So we copy the minus sign to the buffer and update the size to 1. 144 00:11:47,740 --> 00:11:53,890 Note that we will get overflow if the digits here is the minimum value. But the result should be correct 145 00:11:53,890 --> 00:11:56,320 based on the data type and systems we use. 146 00:11:57,900 --> 00:12:04,500 Alright, the actual work of converting value is done in function unsigned decimal to string. We pass the buffer, 147 00:12:05,400 --> 00:12:06,720 position and the digits we want to convert 148 00:12:06,750 --> 00:12:07,620 . 149 00:12:08,750 --> 00:12:14,600 If it is positive value we simply call the unsigned decimal to string function. The return value is 150 00:12:14,600 --> 00:12:18,950 the number of the characters it actually prints, and we update the size and return. 151 00:12:22,820 --> 00:12:30,050 Ok that’s all the formats we can support in the print function. Let’s continue. After we exit out for loop, 152 00:12:30,050 --> 00:12:34,880 we have characters ready to write into the screen buffer. So we call write screen to do that. 153 00:12:39,310 --> 00:12:45,730 In this function, we need the buffer which contains the characters, the buffer size, 154 00:12:45,730 --> 00:12:52,660 the address of the screen buffer and the character attribute. The structure includes current position 155 00:12:52,660 --> 00:12:54,650 which tells us where we will write the character. 156 00:12:55,390 --> 00:12:57,970 We copy it to the variable column and row. 157 00:12:59,530 --> 00:13:03,490 Then we use for loop to copy the character to the screen buffer. 158 00:13:04,690 --> 00:13:11,680 The first if statement is used for scrolling screen. We will discuss it in a moment. If the character is not new line, 159 00:13:11,680 --> 00:13:15,490 we just copy the character and the attribute to the buffer. 160 00:13:16,500 --> 00:13:22,860 The address is stored in the field buffer of the structure. Here we multiply row by the macro line size 161 00:13:22,960 --> 00:13:29,170 to get the address of current line and add the column value. Then Update column and check to see 162 00:13:29,170 --> 00:13:36,420 if column is larger than or equal to 80. If it is, we increment row and set column to 0 163 00:13:36,440 --> 00:13:39,460 so that characters will be printed at the beginning of the next line. 164 00:13:42,060 --> 00:13:47,730 If we find the new line in the buffer, we will change the position to the next line 165 00:13:47,730 --> 00:13:49,220 so that the next character will be printed from there. 166 00:13:50,070 --> 00:13:52,650 We increment row and set column to 0. 167 00:13:53,810 --> 00:13:58,170 Suppose we have too much info printed on the screen and there is no room to print, 168 00:13:58,610 --> 00:14:00,830 what we will do is scroll the screen. 169 00:14:03,100 --> 00:14:08,530 So if row is greater than or equal to 25, we copy the characters which is in the second line through the last line 170 00:14:08,560 --> 00:14:14,860 to the first 24 lines and leave the last line empty. It’s like scroll the page. 171 00:14:15,810 --> 00:14:20,580 We use memcpy which we have implemented in the last lecture to copy the characters. 172 00:14:21,660 --> 00:14:25,380 And then use memset to empty the last line of the screen buffer. 173 00:14:26,490 --> 00:14:31,260 And also, don’t forget to decrement row number, since we only move one line up. 174 00:14:32,260 --> 00:14:37,210 Ok, we could see the screen get scrolled up when the message is printed out of the screen. 175 00:14:38,980 --> 00:14:43,940 At the end of the function, write the current position back to the structure for later use. 176 00:14:44,700 --> 00:14:46,510 OK, back to the print function. 177 00:14:49,190 --> 00:14:53,210 we pass the buffer, buffer size and the address of the structure, 178 00:14:53,810 --> 00:15:00,140 this is the global variable screen buffer. And the attribute, here we use f which means the character is printed in white 179 00:15:00,140 --> 00:15:00,670 . 180 00:15:00,680 --> 00:15:01,280 Alright, 181 00:15:02,770 --> 00:15:07,870 return the buffer size which is the number of the characters being printed and we are done. 182 00:15:09,800 --> 00:15:16,910 In the main.c file, we include print header file and print the test message. For example, 183 00:15:17,300 --> 00:15:18,380 hello and welcome. 184 00:15:19,320 --> 00:15:26,190 and the hexadecimal value 123456789abcd 185 00:15:26,190 --> 00:15:27,570 this is 64-bit value. 186 00:15:29,400 --> 00:15:36,920 The first print function prints string. The next one prints the variable in hexadecimal value. Before we test our program, 187 00:15:36,920 --> 00:15:40,280 we need to add print file in the build script. 188 00:15:42,680 --> 00:15:48,410 So we add print.c file and in the linker command, we add print.o file. 189 00:15:49,610 --> 00:15:52,100 OK, let's build the project and test it out. 190 00:15:59,950 --> 00:16:07,030 OK, as you see, hello and welcome is printed and the hexadecimal value 123456789abcd 191 00:16:07,030 --> 00:16:07,630 , 192 00:16:07,660 --> 00:16:10,890 is printed and suffix H is 193 00:16:11,170 --> 00:16:12,460 also added here. 194 00:16:13,380 --> 00:16:14,610 That's it for this lecture. 195 00:16:14,640 --> 00:16:15,870 See you in the next video.