Task Structure array (or linked list) Implementation The structure of our task is in the form of a linked list, but its length is limited. The header pointer is a global pointer variable ( The pointer variable is an unsigned integer pointer. Its pointer address is in the BSS segment, but it points Memory allocated to the heap). Use kmalloc to allocate the kernel memory. You must write the kmalloc function by yourself. For simplicity, this function only accepts one parameter, that is, the size to be allocated. This function is very easy to do. First There is a global needle that points to the starting position of the entire heap during initialization and has a fixed size, which is called the kernel. Stack, after the kernel stack, is the user stack. Because there are a total of 10 tasks, of course, excluding the tasks of the kernel itself, Therefore, the entire stack is evenly divided into eleven parts. Note: After all tasks are initialized, another step is Moving the kernel task to the user State is equivalent to modifying the stack pointer of the task structure ), To determine whether the size exceeds the allocable range of the kernel heap, you need to maintain the heap of the Kernel Heap and other tasks, You need to partition and have a global memory usage identifier. Use an array. It is simple. 0 indicates the corresponding memory. If some items are not occupied, 1 indicates that they are occupied, and the corresponding kfree is equivalent to setting the flag to 0 ), Memory maintenance is complicated. For simplicity, it is set to 4 kb and cannot be applied for memory larger than 4 kb because After 4 K, because there is no virtual address concept, it is impossible to achieve continuous Address allocation on the stack. Of course, it is allocated on the stack. It can be larger than 4 K, and the stack is determined by the compiler and CPU. The task structure includes: 1. The remaining time slice 2. Memory Address of the code segment pointed to by this task, which is also the function entry address. 3. The data segment address pointed to by this task. The data segment here is included in the entire kernel, so it is useless and reserved. 4. Whether the function body of this task exists or is scheduled 5. The stack pointer used by this task 6. Heap pointer used by this task 7. ID of the task. 0 indicates idle, and 1 indicates other processes. 8. values of all registers 9. The current Pc value is set to the function entry address during initialization. First, we will explain the initialization of the task array structure: Define a global pointer first, then forcibly convert the pointer to a task structure pointer, and use the kmalloc function in the kernel The occupied heap (previously speaking, the starting point of the kernel heap is the starting point of the entire heap) allocates the memory occupied by ten task structures. Here It will never exceed 4 K and assign values to the ten task structures. Set the first task to idle, the time slice to 20, the memory address of the code segment to the address of the main function, and the data segment address is ignored, the function body exists and can be scheduled. The position pointed by the stack pointer is calculated as follows: Assuming that each stack can be used for each task is set to 64 K, and the starting position of the entire heap is 0x20030000, the first heap Pointer Points to 0x20030000, the stack is 0x20030000 + 64 K, and the second is later, and so on. Note: Before initializing the task structure, the system is not allowed to use the heap, but the stack can be used, so the kernel Task Stack is divided Two, before scheduling, the stack is set in step 2 on the previous page, so you have to note when setting the stack on the previous page. It is necessary to set the stack space to ten 64kb, and use the previously largest possible stack space in this step. Next, let's explain what to do during Task Switching: When entering the entire interrupt processing entry, all registers will be pushed into the IRQ stack, and the value will be copied to the corresponding fields of the current task structure, extract the current Pc value of the interrupted process and store it in the corresponding field in the current task structure. Next, identify the interrupt type to enter the corresponding interrupt processing function, here we will enter the do_timer function, and the following is the process after entering this function: There is also a global pointer in the kernel, which is the current task pointer, which is also in the system BSS segment. Its definition is the same as the global pointer in the previous step. When the system clock is interrupted, the global pointer is taken out. After the initialization is complete in the previous step, the pointer is directed to the position where the first task structure is located, that is, 0x20030000. Then, the time slice field in the task structure is taken out, if it is 0 or not, save the stack pointer in user mode to the current task structure and save the heap pointer, search for the structure of the task that can be scheduled, assign the structure to the current task pointer, and set the identifier for Task Switching. This identifier is also a global variable, however, it is assigned an initial value and will be placed in the Data Segment of the entire system. The do_timer function is returned. If the value is not 0, perform the following operations: Subtract one time slice, return to the do_timer function, and then judge the task switching identifier. If it is 0, perform the following operations: No task switching is required. All registers exit the stack (the stack here refers to the IRQ stack), enable the interrupt again, switch to the user mode, and load the current Pc value field in the current task structure, to exit the interrupt handler. If this ID is 1, perform the following operations: Task switching is required to let all registers go out of the stack (here the stack refers to the IRQ stack) and send all The memory value is restored to the corresponding register, and the stack pointer in the user State is restored to the stack pointer of the current task structure. The heap pointer of the current task structure, restores the ID of the task to be switched to 0, re-enables the interruption, and switches to user mode. Task switching is implemented by loading the Pc value, that is, by loading the current Pc value field in the current task structure to exit the interrupt handler Implementation of system calls This system does not implement system calls. Because kernel and user mode are not protected In your own C library, all functions are implemented like kmalloc. You can directly write the function prototype in the kernel. Extended, let's talk about the system call, which is implemented using the malloc system call. There is also a heap pointer (there is a heap pointer in front of kmalloc, but that heap pointer is a kernel task The heap pointer is used for user State. It assigns the initial value before the system initialization is complete. The value is the starting position of the heap used by the first task structure, that is, the 64 K position is added to the heap used by the kernel. The implementation steps of the malloc function in the function library are as follows: 1. First, check whether the application size exceeds 4 K. If the application size exceeds 4 K, an error is returned. 2. Call the system (here _ syscall1 is used and only one parameter is passed (the size to be allocated) Implementation of system call function _ syscall1: 1. Press the Register into the stack (the stack here points to the stack of the current task) 2. Set system call number 1 to R0 and the parameter to R1. 3. Send a SWI command to generate a SWI interrupt (that is, a soft interrupt, a trap) When the system is interrupted, it will enter the SWI interrupt processing entry. The following describes the implementation of the SWI entry function. 1. Retrieve the R0 value, determine its value, and enter the corresponding branch processing code segment. 2. Enter the _ malloc processing code segment here, retrieve the value of R1, get the current heap pointer mentioned above, and apply for the corresponding number The data block size is used in the corresponding field of the memory usage identifier. It puts the current heap pointer into r0, moves the current heap pointer, and changes the current Switch the heap pointer of the service structure to the user State and return the processing result of SWI Interrupt System Call _ syscall1: For simplicity, when the user State is returned from the kernel state, the task is no longer rescheduled, so the above steps are relatively simple. 1. When the return result is interrupted from SWI, the system runs in the user State. At this time, the R0 value is taken and assigned to the pointer to apply for memory. 2. The register pops up in the user State and returns to the previous function layer. The return of the malloc function. Now the malloc function returns the pointer directly, and the entire malloc process is over. Unified calling is similar to this process So far, this operating system has been initially implemented, but it seems that nothing can be done. If you want to enable it to support serial port interruptions, you may be able to do something a little bit, for example, for functions like single-chip microcomputer, the difficulty of the entire system is Interrupt Processing and task switching. In this example, arm does not support CPU-level protection modes like 0x86, so when you perform task switching, You have to load the Pc value by yourself, it is the protection of register inbound and outbound stack. When an interrupt occurs, the Register must be protected. However, if you need to re-schedule the register, you must switch from the interrupt context to the process context, how can I switch from the interrupt context to the process context ?? The method I used here is clumsy: 1. First let the Register into the stack 2. Store the Register to the current task structure array, and save the Pc value of the interrupted process to the task structure. 3. Handle timer interruptions 4. If you want to switch between tasks, find the next schedulable process, and specify the structure of the current task. Allows the Register to exit the stack, restores the values in the current task structure to the register, restores the stack pointer, and switches to the user State, resume the suspended process by loading the Pc value of the current task structure. Here, the task structure is used in the interrupt context, which is not used in Linux, the interrupt context and process context are two different concepts. The interrupt context cannot access the task structure in the process context. I really cannot find any way to implement process scheduling, so please refer to my article for a better method. |