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() againsig_switch_contextWhy 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 issig_switch_context
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_sigreturnThis 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 functionsOsArmA32SyscallHandle.OsSaveSignalContext.OsRestorSignalContextThis 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.ULRRepresents the user stack pointer and return address.
  • CPSRThe 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 (usrCommon user) and kernel-mode (sysSuperuser) 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.
  • R7Is 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().
  • countRecords 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.
  • OsArmA32SyscallHandleThe 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 entryOsArmA32SyscallHandleAfter the implementationOsRestorSignalContext.
  • seeOsSaveSignalContext, 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_sigreturnTo 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 signalssignoNote 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_osExceptSwiHdlAfter 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_sigreturnSystem 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 contextsOsArmA32SyscallHandle
@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.