The Master said, “Hui is not the one who will help me. He will not say anything to me.” The Analects of Confucius: Advanced chapter
A hundred blog series. This paper is: the v49. Xx HongMeng kernel source code analysis (signal) | who let CPU run for four times in the stack
Process management:
- V02. Xx HongMeng kernel source code analysis (process management) | who in the management of core resources
- V24. Xx HongMeng kernel source code analysis concept (process) | process in which resources management
- V45. Xx HongMeng kernel source code analysis (Fork) | one call, return twice
- V46. Xx HongMeng kernel source code analysis (special) | mouse son can make hole
- V47. Xx HongMeng kernel source code analysis (process recycling) | how to entrust an orphan to ancestor dying
- V48. Xx HongMeng kernel source code analysis (signal) | the year lead half hundred, still energetic
- V49. Xx HongMeng kernel source code analysis (signal) | who let CPU run for four times in the stack
- V71. Xx HongMeng kernel source code analysis (Shell editor) | two tasks, three stages
- V72. Xx HongMeng kernel source code analysis (Shell) | watching the kernel of the application window
Signal consumption
This article is about signal consumption. It is recommended to read signal production before reading it.
- V48. Xx (signal) production | the year lead half hundred, still energetic
- V49. Xx (signal consumption) | who let CPU run for four times in the stack
This section is quite difficult, involving two rounds of switching between the user stack and the kernel stack, four times of switching between the CPU stack and the register value, which will be illustrated around the following figure.
Interpretation of the
- For the convenience of this article, the figure is simplified label description:
- User: user space
- Kernel: indicates the kernel space
- source(…) : the source function
- sighandle(…) : signal processing function,
- syscall(…) : system call, the parameter is the system call number, such as SIGreturn, N(table arbitrary)
- User.source (): represents the source function running in user space
- As has been stated several times in this series, user-mode tasks have two running stacks, one user stack and one kernel stack. The stack space comes from user space and kernel space respectively. There is a strict address division between the two Spaces, and the size of the virtual address can be used to determine whether it is user space or kernel space. System calls are essentially soft interrupts, which change the CPU execution site from the user stack to the kernel stack. Sp indicates which stack is running on. When the CPU is running on the user stack, it cannot access the kernel space, but kernel-mode tasks can access the entire space, and kernel-mode tasks have no user stack.
- User.source () -> kernel.syscall(N) -> user.source(). These values are stored in the kernel stack, and recovery is also recovered from the kernel stack.
- The process of signal consumption can be simplified as: user.source() -> kernel.syscall(N) ->user.sighandle() ->kernel.syscall(sigreturn) -> user.source() A call to a signal handler is inserted in the middle of what would have been a return to user.source(). This is the core of this article’s code.
- Following this line of thinking leads to the following points, which are actually done:
- Kernel.syscall (N) must save the context of user.source() again
sig_switch_context
Why save it again after saving it once? - This is because the first time the data is stored in the kernel stack, and the kernel stack is restored to the user-state user.sighandle(). Save scene/restore scene is a team of good gay friends, note that some articles say that the entire kernel stack will be cleared, which is not true.
- The second is stored in the task structure, which is derived from the task pool and is a kernel global variable resident in memory. In both cases, the user-.source () runtime information is saved, and a review of the associated constructs. The key is
sig_switch_context
- Kernel.syscall (N) must save the context of user.source() again
typedef struct {
// ...
sig_cb sig;// Signal control block for asynchronous communication
} LosTaskCB;
typedef struct {// Signal control block (descriptor)
sigset_t sigFlag; // Unmasked signal set
sigset_t sigPendFlag; // Signal block tag set, record those signals come, the task is still blocked set. These signals do not wake up the task
sigset_t sigprocmask; /* Signals that are blocked */ // Which signals are masked by the task
sq_queue_t sigactionq; // Signal capture queue
LOS_DL_LIST waitList; Check OsTaskWait(&SIGCB ->waitList, timeout, TRUE) for a waiting list
sigset_t sigwaitmask; /* Waiting for pending signals */ // What signal is the task waiting for
siginfo_t sigunbinfo; /* Signal info when task unblocked */ // Signal information for task unlocking
sig_switch_context context; // Signal switching context, used to hold the switching scene, such as the return of a system call, involving switching between two stacks of the same task
} sig_cb;
Copy the code
- You must also change the value of the original PC/R0/R1 register. To execute user.sighandle(), the PC register must point to it, and R0, R1 are its arguments.
- After the signal processing is completed, we have to go back to the kernel state. How can we get into the kernel state again? The answer is:
__NR_sigreturn
This is also a system call. Restore after returnsig_switch_context
, that is, restore the SP/PC and other registers when user.source() was interrupted, so that it jumps back to the user stack to continue execution from where user.source() was interrupted. - With these three corollaries, it’s hard to understand the following code, which involves three key functions
OsArmA32SyscallHandle
.OsSaveSignalContext
.OsRestorSignalContext
This article one by one interpretation, thoroughly dug. First look at the signal context structuresig_switch_context
.
sig_switch_context
// Task interrupt context
#define TASK_IRQ_CONTEXT \
unsigned int R0; \
unsigned int R1; \
unsigned int R2; \
unsigned int R3; \
unsigned int R12; \
unsigned int USP; \
unsigned int ULR; \
unsigned int CPSR; \
unsigned int PC;
typedef struct {// Signal to switch context
TASK_IRQ_CONTEXT
unsigned int R7; // Store the system call ID
unsigned int count; // Record whether the signal context is saved
} sig_switch_context;
Copy the code
- Save the structure of the user.source() site,
USP
.ULR
Represents the user stack pointer and return address. CPSR
The register is used to set the WORKING mode of the CPU. There are 7 working modes of the CPU
V36. Xx (operating mode) | CPU is inspired, what are the seven wife?User state in question (usr
Common user) and kernel-mode (sys
Superuser) corresponds to only two of them. Both share the same register. Restoring it tells the CPU that the kernel has been switched to normal user mode.- The other registers are not saved because they are not used by system calls, so they do not need to be saved.
R7
Is used to record the system call number when the system call occurs, and during signal processing R0 will get the signal number as the first argument to user.sighandle().count
Records whether the signal context is saved
OsArmA32SyscallHandle Specifies the total entry for system calls
/* The SYSCALL ID is in R7 on entry. Parameters follow in R0..R6 */
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * by the assembly call, See LOs_hw_exc. S/BLX OsArmA32SyscallHandle SYSCALL is a signal that is triggered when the system call is generated. Register R7 stores the specific system call ID. //MOV R0; //MOV R0; R0 will serve as the parameters of OsArmA32SyscallHandle * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs)
{
UINT32 ret;
UINT8 nArgs;
UINTPTR handle;
UINT32 cmd = regs[REG_R7];// The C7 register records which system call was triggered
if (cmd >= SYS_CALL_NUM) {// Total number of system calls
PRINT_ERR("Syscall ID: error %d !!! \n"CMD);return regs;
}
SVC 119 #__NR_sigreturn = SVC 119 #__NR_sigreturn
if (cmd == __NR_sigreturn) {
OsRestorSignalContext(regs);// Restore the signal context and go back to the user stack.
return regs;
}
handle = g_syscallHandle[cmd];// Get the system call registration function, similar to SysRead
nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */
nArgs = (cmd & 1)? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);// Get the number of parameters
if ((handle == 0) || (nArgs > ARG_NUM_7)) {// The system call must have no more than 8 arguments
PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", CMD, nArgs); regs[REG_R0] = -ENOSYS;return regs;
}
//regs[0-6] records the parameters of the system call, which is why the R7 register holds the system call number
switch (nArgs) {// The number of arguments
case ARG_NUM_0:
case ARG_NUM_1:
ret = (*(SyscallFun1)handle)(regs[REG_R0]);// Perform a system call, similar to SysUnlink(pathname);
break;
case ARG_NUM_2:// How about a two-parameter system call? There is no problem with passing three parameters, because the called function does not fetch R2 values
caseARG_NUM_3: ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]); ARG_NUM_3: ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);// Similar to SysExecve(fileName, argv, envp);
break;
case ARG_NUM_4:
caseARG_NUM_5: ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4]); ARG_NUM_5: ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3], regs[REG_R4]).break;
default: //7 parametersRet = (*(SyscallFun7) Handle)(regs[REG_R0], regS [REG_R1], regs[REG_R2], regs[REG_R3], regS [REG_R4], regs[REG_R5], regs[REG_R6]); } regs[REG_R0] = ret;//R0 saves the system call return value
OsSaveSignalContext(regs);// If there is a signal to be processed, the PC, R0, R1 registers will be overwritten to change the path back to normal user mode, and the signal handler will be executed first.
/* Return the last value of curent_regs. This supports context switches on return from the exception. * That capability is only used with the SYS_context_switch system call. */
return regs;// Return the value of the register
}
Copy the code
Interpretation of the
- This is the general entry point for system calls, and all system calls are going to go through here and they’re going to be handled uniformly. By system number (saved in R7), locate the registration function and call back. Complete the system call process.
- About the system call to view v37. Xx (system calls) | what experienced a system call This unknown detail process of the system call, saying with the associated parts of the signal.
OsArmA32SyscallHandle
The overall understanding is that the signal save and restore two functions are sandwiched. Note that it is important to understand the process of calling both functions at run time. For the same task, it must be executed firstOsSaveSignalContext
, the second entryOsArmA32SyscallHandle
After the implementationOsRestorSignalContext
.- see
OsSaveSignalContext
, which is responsible for saving the context of user.source(), where the sp, r0/ R1 register values are changed, and the signal handler user.sighandle() is run. - At the beginning of the function, the system call number is encountered
__NR_sigreturn
To directly restore the signal context exits, because this is the operation that cuts back to user.source() to continue.
SVC 119 #__NR_sigreturn = SVC 119 #__NR_sigreturn
if (cmd == __NR_sigreturn) {
OsRestorSignalContext(regs);// Restore the signal context and go back to the user stack.
return regs;
}
Copy the code
OsSaveSignalContext holds the signal context
With that in mind, it’s easy to understand what this function does.
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * to produce a system call, also is the soft interrupt, Save the user stack register on-site information to rewrite the PC register values * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void OsSaveSignalContext(unsigned int *sp)
{
UINTPTR sigHandler;
UINT32 intSave;
LosTaskCB *task = NULL;
LosProcessCB *process = NULL;
sig_cb *sigcb = NULL;
unsigned long cpsr;
OS_RETURN_IF_VOID(sp == NULL);
cpsr = OS_SYSCALL_GET_CPSR(sp);// Get the CPSR value for the system callOS_RETURN_IF_VOID(((cpsr & CPSR_MASK_MODE) ! = CPSR_USER_MODE));Note that CPSR_USER_MODE(CPU level) and OS_USER_MODE(system level) are two different things.
SCHEDULER_LOCK(intSave);/ / if you have not understand to https://my.oschina.net/weharmony through working mode distribution / / signal processing
task = OsCurrTaskGet();
process = OsCurrProcessGet();
sigcb = &task->sig;// Get the signal control block of the task
//1. The task context task is not saved
//2. Any signal label set is not empty or the process has signals to process
if ((sigcb->context.count == 0) && ((sigcb->sigFlag ! =0) || (process->sigShare ! =0))) {
sigHandler = OsGetSigHandler();// Get the signal handler function
if (sigHandler == 0) {// The signal is not registered
sigcb->sigFlag = 0;
process->sigShare = 0;
SCHEDULER_UNLOCK(intSave);
PRINT_ERR("The signal processing function for the current process pid =%d is NULL! \n"Task - > processID);return;
}
/* One pthread do the share signal */
sigcb->sigFlag |= process->sigShare;// Extend the task's signal label set
unsigned int signo = (unsigned int)FindFirstSetedBit(sigcb->sigFlag) + 1; OsProcessExitCodeSignalSet (process, signo);// Set the process exit signal
sigcb->context.CPSR = cpsr; // Save the status register
sigcb->context.PC = sp[REG_PC]; // Get the value of the interrupted field register
sigcb->context.USP = sp[REG_SP];// The top of the user stack so that it can be cut back from the kernel stack to the user stack
sigcb->context.ULR = sp[REG_LR];// return address of user stack
sigcb->context.R0 = sp[REG_R0]; // Return value of the system call
sigcb->context.R1 = sp[REG_R1];
sigcb->context.R2 = sp[REG_R2];
sigcb->context.R3 = sp[REG_R3];
sigcb->context.R7 = sp[REG_R7];// The reason why R7 is not passed is because R7 always holds the system call number when the system call occurs.
sigcb->context.R12 = sp[REG_R12];/ / https://my.oschina.net/weharmony/blog/4967613
sp[REG_PC] = sigHandler;// Specify the signal execution function. Note that the value of the PC register in the context of the save task is changed. This function will be executed when the context is restored.
sp[REG_R0] = signo; // Signal ID
sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); 2 / / parameter
/* SIG No bits 00000100 present SIG No 3, but 1<< 3 = 00001000, so signo needs minus 1 */
sigcb->sigFlag ^= 1ULL << (signo - 1);
sigcb->context.count++; // Indicates saved
}
SCHEDULER_UNLOCK(intSave);
}
Copy the code
Interpretation of the
- First, we determine the execution conditions, and we do have signals to process, we do have processing functions. Custom handlers are installed by user processes and are shared by all processes. The parameters are signals
signo
Note that this is not a system call number. The signal number looks like this.
#define SIGHUP 1 // The terminal hangs or the control process terminates
#define SIGINT 2 // Keyboard break (CTRL + C)
#define SIGQUIT 3 // The keyboard exit key is pressed
#define SIGILL 4 // Invalid instruction
#define SIGTRAP 5 // Trace trap, start process, trace code execution
#define SIGABRT 6 // Exit instruction issued by abort(3)
#define SIGIOT SIGABRT // Abort signals
#define SIGBUS 7 // Bus error
#define SIGFPE 8 // Float exception
#define SIGKILL 9 Commonly used commands / / kill 9 123 | can't be ignored, processing, and blocking
Copy the code
The system call number looks like this, if you see some familiar functions.
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_lchown 16
#define __NR_break 17
Copy the code
- Finally, the most critical code, change the VALUE of the PC register, the value of a change in
_osExceptSwiHdl
After the context is restored in, the CPU jumps to the user-space code segment user.sighandle(R0, R1) to start execution, that is, to execute the signal processing function.
sp[REG_PC] = sigHandler;// Specify the signal execution function. Note that the value of the PC register in the context of the save task is changed. This function will be executed when the context is restored.
sp[REG_R0] = signo; // Signal ID
sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); 2 / / parameter
Copy the code
OsRestorSignalContext Restores signal context
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * context, restore signal generated by the system call __NR_sigreturn, this is an internal generated system calls. Why restore it? Because the execution of the system call is completed by the task kernel state, and the stack used is also the kernel stack. The CPU-related registers record the contents of the kernel stack, and after the system call is completed, it needs to return to the user stack of the task for execution. When the CPU registers must be returned to the scene of the user mode So the function of the function becomes a reduction register values * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void OsRestorSignalContext(unsigned int *sp)
{
LosTaskCB *task = NULL; /* Do not adjust this statement */
LosProcessCB *process = NULL;
sig_cb *sigcb = NULL;
UINT32 intSave;
SCHEDULER_LOCK(intSave);
task = OsCurrTaskGet();
sigcb = &task->sig;// Get the current task signal control block
if(sigcb->context.count ! =1) {// It must have been saved before it can be restored
SCHEDULER_UNLOCK(intSave);
PRINT_ERR("sig error count : %d\n"Sigcb - > context. Count);return;
}
process = OsCurrProcessGet();// Get the current process
sp[REG_PC] = sigcb->context.PC;// Command registerOS_SYSCALL_SET_CPSR (sp, sigcb - > context. The CPSR);// Reset the program status register
sp[REG_SP] = sigcb->context.USP;// The user stack pointer, USP refers to the user state of the stack, is going back to the user stack to continue running
sp[REG_LR] = sigcb->context.ULR;// Returns the user stack code execution position
sp[REG_R0] = sigcb->context.R0;
sp[REG_R1] = sigcb->context.R1;
sp[REG_R2] = sigcb->context.R2;
sp[REG_R3] = sigcb->context.R3;
sp[REG_R7] = sigcb->context.R7;
sp[REG_R12] = sigcb->context.R12;
sigcb->context.count--; // The number of signal contexts goes back down
process->sigShare = 0; // Return to user mode, signal sharing clear 0
OsProcessExitCodeSignalClear(process);// Clear the process exit code
SCHEDULER_UNLOCK(intSave);
}
Copy the code
Interpretation of the
- After the signal handler completes, the kernel fires one
__NR_sigreturn
System call, fell back into kernel mode, back toOsArmA32SyscallHandle
. - The process of recovery is very simple, the previously saved signal context is restored to the start of the kernel stack SP, the order of data stored in the stack can be viewed by using the stack method chapter, the most important look at this sentence.
sp[REG_PC] = sigcb->context.PC;// Command register
sp[REG_SP] = sigcb->context.USP;// The user stack pointer, USP refers to the user state of the stack, is going back to the user stack to continue running
sp[REG_LR] = sigcb->context.ULR;// Returns the user stack code execution position
Copy the code
Note that this is not really context switching, just changing existing data in the kernel stack. This data will be restored to the register. The USP and ULR point to the user stack location. Once PC, USP, and ULR pop off the stack assign to the register. The switch from the kernel stack to the user stack is really completed. Return to user.source() and continue running.
- The real switching assembly code follows, which has been annotated, sandwiched in the save and Restore contexts
OsArmA32SyscallHandle
@description: Software interrupt exception handler _osExceptSwiHdl: @ Software interrupt exception handler _osExceptSwiHdl: @ Software interrupt exception handler SUB SP, SP, #(4 * 16) @ Apply first16STMIA SP, {r0-r12} @taskContext. R[GEN_REGS_NUM] STMIA executes from left to right, starting with R0.. R12 MRS R3, spsr@ Read the value of SPSR in this mode MOV R4, LR @ Save the back register LR AND R1, R3, #CPSR_MASK_MODE @interrupted mode CMP R1, # cpsr_user_mode@user mode Whether the User mode is BNE oskernelSvChandl@branchif not@we enter from user mode @we enter from user modewe need get the values of USER mode r13(sp) and r14(lr).
@ stmia with ^ will return the user mode registers (provided that r15 is not in the register list).mov R0, sp@ get the SP value, R0 will be used as the OsArmA32SyscallHandle parameter STMFD SP! .{R3} @save CPSR => TaskContext. RegPSR ADD R3, SP, #(4 * 17) @offset to PC/CPSR storage jump to PC/CPSR storage STMFD R3! , {R4} @save the CPSRandR15 (PC) Save LR register => taskContext. PC STMFD R3, {R13, R14}^ @save user mode R13 (sp)andR14 (lr) saved from right to left => TaskContext. lr and SP SUB SP, SP, #4@ => taskcontext. resved PUSH_FPU_REGS R1 @ save interrupt mode (user mode) @ save TaskContext (TaskContext) end MOV FP, #0@init frame Pointer CPSIE I @open interrupt, indicating that interrupt BLX OsArmA32SyscallHandle can be responded to during a system call/* Passes the system call to C, with the parameter R0 pointing to the start of the TaskContext */CPSID i@ interrupt must be turned off before executing the following command @ restore TaskContext (TaskContext) start POP_FPU_REGS R1 @ pop the FPU value to R1 ADD SP, SP, #4@locate the location where the old SPSR value is saved LDMFD SP! , {R3} @fetch thereturnSPSR Displays the old SPSR value MSR SPSR_cxsf, r3@set thereturn@we are leaving to user mode, we need to restore the values of user mode R13 (sp) @we are leaving to user mode, we need to restore the values of user mode R13 (SP)and r14(lr).
@ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list) LDMFD SP! , {r0-r12} @ Restore user mode R13/R14 ADD SP, SP, #(2 * 4) @locate the location where the old PC value is saved LDMFD SP! , {PC}^ @return to user switch back to user mode run @restore TaskContext endCopy the code
Specific can also see these two:
Switch v42. Xx (interrupt) | system dynamic due to interrupt
V41. Xx (task switching) switch | see how assembly task
Intensive reading of the kernel source code
Four code stores synchronous annotation kernel source code, >> view the Gitee repository
Analysis of 100 blogs. Dig deep into the core
Add comments to hongmeng kernel source code process, sort out the following article. Content based on the source code, often in life scene analogy as much as possible into the kernel knowledge of a scene, with a pictorial sense, easy to understand memory. It’s important to speak in a way that others can understand! The 100 blogs are by no means a bunch of ridiculously difficult concepts being put forward by Baidu. That’s not interesting. More hope to make the kernel become lifelike, feel more intimate. It’s hard, it’s hard, but there’s no turning back. 😛 and code bugs need to be constantly debug, there will be many mistakes and omissions in the article and annotation content, please forgive, but will be repeatedly amended, continuous update. Xx represents the number of modifications, refined, concise and comprehensive, and strive to create high-quality content.
Compile build | The fundamental tools | Loading operation | Process management |
---|---|---|---|
Compile environment The build process Environment script Build tools Designed.the gn application Ninja ninja |
Two-way linked list Bitmap management In the stack way The timer Atomic operation Time management |
The ELF format The ELF parsing Static link relocation Process image |
Process management Process concept Fork Special process Process recycling Signal production Signal consumption Shell editor Shell parsing |
Process of communication | Memory management | Ins and outs | Task management |
spinlocks The mutex Process of communication A semaphore Incident control The message queue |
Memory allocation Memory management Memory assembly The memory mapping Rules of memory Physical memory |
Total directory Scheduling the story Main memory slave The source code comments Source structure Static site |
The clock task Task scheduling Task management The scheduling queue Scheduling mechanism Thread concept Concurrent parallel The system calls Task switching |
The file system | Hardware architecture | ||
File concept The file system The index node Mount the directory Root file system Character device VFS File handle Pipeline file |
Compilation basis Assembly and the cords Working mode register Anomaly over Assembly summary Interrupt switch Interrupt concept Interrupt management |
HongMeng station | into a little bit every day, the original is not easy, welcome to reprint, please indicate the source.