Remember
This time is really too busy. I haven't worked for a long time. During this time, the development of PYOS is basically equal to half-stop state, just built a PYOS CVS (http: // pyos) .binghua.com), I hope that the development can be a little more efficient ~~~ :)
I have never done something, I only completed an experiment of a system called, the words retired, I still have to introduce my experiment.
Experimental review and necessity of system calls in operating systems
Each operating system has a very important component, which is system call. The operating system is called through the system to provide services, while each process also acquires operating system services through corresponding system calls. System call is a bridge that connects each application and operating system, which is the interactive interface between them, but also a direct impact on the compilation link of the program. The most common system call is the memory allocation function (such as a Malloc), which will return a plug-in pointer to the application. Simply look, it seems that the system calls are nothing separate, but if you look at it, you will find it quite a lot.
Let's take a look at another example of another system call, the screen output function (such as the Printf of the C language), and explains what the system call is different from the general function.
Let's take a look at how the general function is written:
Void MyPrint (Char CH)
{
Static int x_pos = 0; // Record the X position of the current cursor, initial equal to 0
Static int y_pos = y; // Record the Y position of the current cursor, initial equal to 0
Output (X_POS, Y_POS, CH); // On the screen's X_POS output character ch,
// Output is a virtual function, in our discussion, it can
// Used to output how it is implemented inside, we don't have
//
x_pos; // Because a character is output, the cursor should be automatically moved to move to print new
When the // is coming, it will not overwrite the original character.
}
The above code seems to have no problem, but we will consider, there are two programs A and B, which use this normal function, so when they are loaded into the memory, it will appear as shown below. situation:
From the figure above, we can see it clearly, in this case,
A
with
B
Contain
MyPrintf
This function is a separate copy, that is,
A
with
B
Use itself
X_POS
versus
Y_POS
Position the current cursor position. So this will have a problem:
Suppose A first runs, X_POS and Y_POS are initialized to 0, this is a five characters, then the value of x_pos should be 5, and this time is running to B, because B is using itself X_POS and Y_POS Therefore, for B, the value of X_POS and Y_POS is still zero, so B will start printing its character in the (0, 0) position, that is, the result of the operation of B overrides the result of the previous A operation. Then we want to think again, in the system such as DOS or Linux, you run a program first, run the second program, will the second program overwrite the first program's run results?
The cause of the above improperly generated problem is that there are two x_pos and y_pos in the system memory space, that is, it is equivalent to present two cursors, and actually there is only one cursor, so, only because there should be a x_pos With Y_POS, they are shared by all processes. So we can change the system's institution, only provide a MyPrintf function, and different processes call it through the Southtern East similar to the function pointer, then only one x_pos will appear in the memory space. Y_POS, this will not produce the above problems. So which process is from this unique MyPrintf? Is it a process or use B process?
Obviously the A and B processes have no ability to serve as this responsibility, because the only one of the only MyPrintF must always exist in memory, so that the different processes are used, and A and B are likely to be called out of memory. Second, such a process must exist in the system when the system is started, or the first one is transferred to the system memory, so that when other processes are transferred, you can use MyPrintf. Since the process containing this MyPrintf copy requires these features, we can't think of it directly to the kernel, let it become a system, which is undoubtedly the most convenient. So, when we use this model to rebuild our system, the distribution of memory space is likely to be shown below:
Thus, since there is only one MyPrintf in the system, there is only one x_pos and y_pos, so that the above situation will not occur. Since MyPrintf is currently done in the system, we can call it a system call whispering: A and B are invoked using the screen output feature provided by the system through myprintf, and output information on the screen. From the above description, we can also see that the system calls and general functions are in that they are not in the true link to the process, and in each process, only a pointer that can call it, they only exist In the system, there is only one copy. So how do each process or call entry calling this system call? How is the system to implement this system call? This is the problem that this experiment will solve. In this experiment, we implemented a system call for a screen output. Of course, we will continue to use PYOS as an example to conduct our experiment.
Call gate
From the above description, we can see that if you want myPrintf to be called by all the process, you point to its pointer, that is, the address called by this system must be fixed, so that there is such a problem, it should What kind of place to put this pointer, in order to make the operating system kernel changes, the address of this system has not changed. Carefully analyze that this seems to be an unfinished task, because the operating system has changed, it is difficult to control the layout of each part of its internal part in the memory, so how to solve What about this problem? We can think of such a space in the system, let it store the pointer called by this system, then regardless of how the system changes, as long as the program can find this space, it can get the pointer called by this system, so it can call this System call. Then now the problem is that in what space it is, can you find the program to accurately locate it? At this time, we think of the "Descriptor Table" (Note, the contents of the descriptor table, in detail in "Operating System Boot Research"), because when the system is started, the system will load the descriptor table into memory. And tell the address of its address, so the CPU is to know its address. If we specify a location specified in the Descriptor Table, such as the "No. 2 descriptor" to store the MyPrintf pointer, so Each time the program needs to tell the CPU to use the "Second Descriptor", then the CPU can get the address of myPrintf, so that the MYPRINTF can be called. In this way, the memory layout of the system will no longer be limited to could not change, and only need to do the "second descriptor" in the descriptor table pointing to myPrintf, then the operating system is upgraded, the original The application does not need to be changed. And to maintain the above, for the system, it is a light and easy task. The descriptor is stored in the Descriptor. If a descriptor saves a function of a function, then we call it "calling the door", which means that it can call a function. Let's take a look at the structure of this call gate: the above figure is the structure of the calling door, the offset refers to the offset of the function pointed to by this call gate, and the segment selector is pointed to by this call. The segment of the function used, the double word count is if the call gate is used between the different privileges, this will trigger the privilege-level conversion, and because of the different privilege levels use their own independent stack, then the stack switching is happened. When the stack is switched, you need to point out how much double-word bytes you want to copy from the previous stack (because the function's call parameters are passed through the stack, therefore need to copy the parameters in the original stack to the new stack in). The double word count pointed out the number of copies that need to be copied, but it is in double word. Type fields need to be specified as 1100 (0xc) to indicate that this is a call door. DT is the type of this description, which indicates that it is a door descriptor (the door descriptor is not only the adjustment door, but also interrupt door, trap door, etc.). P is presented bit, indicating whether it is available. From the structure of the door descriptor of the door to the door, we can see that the call door provides enough information, so we can call the door, not only between the same privilege level, but also at different privileges. Calling between different privileges, calling this is a great feature for the operating system so that we can put the application in the user privilege level, and put the system call in the kernel station.
Ok, I have been introduced about the knowledge used in this experiment. Let's take a look at our experiment. Implementation of system calls in PYOS
Let's take a look at the real system call function that is entered into the kernel: / * This function is a system function, the real handler system call * /
Void class_pyos_systemlib_video :: Print (int x_pos, int y_pos, char ch, char style)
{
IF (x_pos> = 0) {// If it is <0, it is printed at the current position.
CURSOR_X_POS = X_POS;
}
IF (y_pos> = 0) {
CURSOR_Y_POS = Y_POS;
}
Switch (ch) {
Case '/ n': // Enter
CURSOR_X_POS = 0;
Cursor_Y_POS;
Break;
Default: // Print characters
/ * Calculate the offset * / by the cursor position * /
Unsigned short offset = cursor_y_pos * 80 CURSOR_X_POS;
/ * Display characters * /
Char * video = (char *) 0xB8000 offset * 2; // Because a character accounts for two upper bytes, the offset should be * 2
* Video = CH;
* video = style;
/ / Move the cursor to the next position
Cursor_x_pos;
Break;
}
/ * Reset cursor position * /
SetCursor (CURSOR_X_POS, CURSOR_Y_POS);
}
This function completes the output specified characters on the screen, which is a function called by the system. The problem now is that we need to call the door a pointer, now this function is a C function, it is not easy to get in memory, so we can't use its pointer directly, but need a C Language functions help, because for C language functions, its function name is its pointer, and the function name we know (the C compiler extension the function name of the C function, so we can't know that it is true The function name is L), so we can provide the call gate is this C language function instead of the pointer of the C function, the following is the package function of our C language:
/ * Real system call interface functions, this function is called by the call gate, and this function calls the real system function to handle system calls * /
Extern "C" void pyos_trin_invoke_video_print (unsigned int x_pos, unsigned int y_pos, char ch, char color)
{
Class_pyos_systemlib_video :: Print (x_pos, y_pos, ch, color);
}
This C language function is very simple, it is actually the front of the C language function that is directly called. So, is it directly using this C language function? Due to the sake of the offset, the normal C language function will be close to the compilation, that is, when the call is called, it will only put the IP in the stack, but the call to the door is A far-modulation, that is, the inter-segment call, which does not use the IP press stack, and press the contents of the segment selection subregister CS into the stack, so we cannot use this C language function. And you must write a compilation function yourself, and call this C language function in this assembly function; pyos_asm_invoke_video_print:
Four parameters (Char style)
MOV EAX, [ESP 20]
A third parameter (Char CH)
MOV EBX, [ESP 16]
A second parameter (unsigned int y_pos)
MOV ECX, [ESP 12]
Go to get the first parameter (unsigned int x_pos)
Mov Edx, [ESP 8]
; Press the parameters again, supply C program call
Push EAX
Push EBX
Push ECX
Push Edx
; Call the corresponding C processing function
Call pyos_true_invoke_video_print
Add ESP, 16
;return
Retf
This assembly function, first remove the parameters from the stack, then re-press the parameter in order to prepare the C function it calls, here you need to know, C / C call habits are from right to Left parameters are stack, so this is also required in this assembly function. In addition, we can see that this assembly function returns the RETF instruction, not the usual RET instruction. The RETF instruction is a reason why we need to write instead of we need to write instead of directly using the C language. This assembly function can be used directly by the calling door, below, we first use a structure to define the structure of the call door: / * Turn gate structure * /
Struct struct_pyos_invokegate {
Unsigned short offset_0_15; / * 0 to 15 bits of the offset * /
Unsigned short segselector; / * segment selector * /
Unsigned char dwordcount: 5; / * double word counting * /
Unsigned char saved_0: 3; / * Reserved, required 0 * /
Unsigned char TYPE_1100: 4; / * Type field, call door needs to be 1100 (0xc) * /
Unsigned char dt_0: 1; / * is 0 to indicate this descriptor for a system * /
UNSIGNED Char DPL: 2; / * Privilege Level * /
Unsigned char p: 1; / * Presence bit * /
UNSIGNED SHORT OFFSET_16_31;
}
Since the calling door is existing in the Descriptor Table, we also need to declare a variable that call the door structure in the Description Table, here we install this call door in the Global Descriptor Table in:
/ * GDT table * /
Struct struct_pyos_gdt {
Struct_pyos_gdtitem gdtnull; // empty segment, Intel reserved
Struct_pyos_gdtitem gdtsystemcode; // System code segment struct_pyos_gdtitem gdtsystemdate; // system data segment
/ * System call door * /
Struct_pyos_invokegate invokegate [2];
}
As shown above, we declare a call gate array in GDT (global descriptor table), which contains two call doors (this experiment implements two call doors, one is the screen output call, one is the clear screen call The principle is the same, so only the screen output calls are described, and the clear screen calls, please visit the source code.) Next, we will invoke a function to assign these call doors in the system initialization, below, let's Take a look at the code of this function:
/ * System call class initialization * /
Void class_pyos_systeminvoke :: init ()
{
/ * Generate setup printing system call door * /
Struct_pyos_invokegate gate;
Gate.offset_0_15 = (unsigned int) pyos_asm_incoke_video_print;
Gate.dpl = 0;
Gate.dt_0 = 0;
Gate.p = 1;
Gate.saved_0 = 0;
Gate.segselector = 0x8; / * code segment * /
Gate.Type_1100 = 0xc;
Gate.offset_16_31 = (unsigned int) pyos_asm_invoke_video_print >> 16;
Class_pyos_system :: m_gdt.invokegate [video_print_invoke_number] = Gate;
/ * System call door to generate a clear screen * /
Gate.offset_0_15 = (unsigned int) pyos_asm_invoke_video_clear;
Gate.Offset_16_31 = (unsigned int) pyos_asm_invoke_video_clear >> 16;
Class_pyos_system :: m_gdt.invokegate [video_clear_invoke_number] = Gate;
}
The above procedure should be more analyzed, so we will not say more here. In this step, the system call is done, the next thing is to call this system call in other processes, then how do other programs call this system call? In Pyos, this is also achieved by a function. The following function is the bridge that is connected to the system, and the process calls it, and it calls the system call:
/ * Print character information, this function package system call, is entered into each process * /
Void class_pyos_video :: Print (Char msg, enum_pyos_color front_color, bool flash, enum_pyos_color back_color, bool add_light)
{
/ * Combination generation system call parameters * /
Struct_pyos_videoItem V;
v.char = msg;
v.frontcolor = front_color;
v.flash = flash;
v.backcolor = back_color;
v.addlight = add_light; / * Generate system call, call call door * /
// Parameter stack, press from right to left (<-)
CHAR Ch_TMP = V.Style;
__ASM __ ("MOV% 0, %% EAX": "= m" (ch_tmp)); // Pressing style parameters
__ASM __ ("push% eax");
CH_TMP = v.char;
__ASM __ ("MOV% 0, %% EAX": "= m" (ch_tmp)); // Press the character parameters
__ASM __ ("push% eax");
INT INT_TMP = -1;
__ASM __ ("MOV% 0, %% EAX": "= m" (int_TMP)); / / Pressing the Y coordinate value, the negative table is printed in the current location
__ASM __ ("push% eax");
__ASM __ ("push% eax"); // Press the X coordinate value
// call the call gate
__ASM __ ("LCALL $ 0x18, $ 0");
// Restore Stack
__ASM __ ("Add $ 16,% ESP"); // 4 * 4 = 16
}
The above code first constructs the parameters required by the system call, then put these parameters in the order from left to right, finally, use a compilation instruction call system call:
__ASM __ ("LCALL $ 0x18, $ 0");
It should be noted here that the second parameter is $ 0, which is originally offset, but for the CPU, it is useless, the CPU will use the offset in the call gate, but here is not omitted because the compiler The "LCALL" instruction requires two parameters. $ 0x18 is calculated. Take a closer look at the structure of the GDT table, you will find the first one is an empty item, the second item is the code segment descriptor, the third item is the data segment descriptor, and the fourth item is a call door, due to each One is 8b, so the front takes up 3 * 8 = 24b = 0x18b, that is, the start of the offset from 0x18 is the call gate, so this is specified as 0x18.
Let's take a look at how the process calls it:
/ * Kernel main function * /
EXTERN "C" void pyos_main ()
{
/* system initialization */
Class_pyos_system :: init ();
/ * Clean screen, print welcome information * /
Class_pyos_video :: clear ();
Class_pyos_video :: print ("Welcome to Pyos ~~");
For (;;) {
__ASM __ ("HLT");
}
}
This function is very simple. It is used by the Print function of the class_pyos_video, and the print function, the system call is called by the call gate.
The file structure and call flow of PYOS in this experiment
Writing here, this experiment is actually completed, but since the three codes of C , C, compilation in this experiment, the structure of PYOS has also been adjusted, so I The file structure and call flow are explained in detail here.
The calling process you have seen is like this:
In the above figure, there is :: is a C function, with ASM, is a compilation function, and the rest is a C function. In this experiment, system_lib is SYSTEM_LIB in this experiment, which is a file that saves the actual system call processing function library. System_invoke is some C functions that save and actually call processing function library interfaces, and the file of initialization system call gate function, system_invoke.asm is The file exists in the assembly function corresponding to each system call. These three files are only entered into the kernel, they are not subjected to a process, with only one code in the secondary guarantee system.
Video This file contains a graphics card processing class that provides some overloaded print () functions, which ultimately call the corresponding system call by calling the door, which makes the process makes the process easier without need. Every process you go to call the door, which will be subjected to a process, so that although there are many copies in the process, because they finally call the same system call, the code called by this system is in memory. It is the only thing that solves the problem proposed by this report.
Problems left by this experiment
First of all, in general, the system call involves some critical resources allocation (so-called critical resources, only by the same process only), so, in the system call, you need to do synchronization and mutual exclusion, but unfortunately Yes, this experiment did not significantly show this, and we will describe this in a later experiment.
Second, the calling door supports a variety of calls. In the previous description of the call door structure, the most obvious most commonly used is the transition of privileges when using the call gate, such as from user-level Go to the kernel level, but this experiment did not have this, interested friends, can look at the reference information of this report A "80x86 assembly language program design tutorial", there is a detailed description. In addition, in future experiments and PYOS implementations, we will also describe this.
This experiment reported that he would also be a paragraph and hope that many of the teachers have advice.
Reference:
"80x86 assembly language program design tutorial" Yang Qiwen, Tsinghua University Press, first edition, June 1998
"Pentinum Family User's Manual Volume 3: Architecture and Programmingman" Intel, 1994
Original: http://purec.binghua.com/article/showArticle.asp?articleid=147