Mac OS X Internals: A Systems Approach

5.2. Low-Level Processor Initialization

As shown in Figure 52, BootX launches the kernel by calling the _start symbol in the kernel. In a multiprocessor system, the kernel begins execution on one processor that was chosen by Open Firmware. For the purposes of kernel startup, we consider this the master processor and the rest, if any, as slave processors.

Figure 52. Low-level processor initialization

We will use the terms CPU and processor interchangeably unless the terms have specific meanings in some context. In Mach parlance, a processor is typically a hardware-independent entity, whereas a CPU represents the underlying hardware entity.

5.2.1. Per-Processor Data

_start() first initializes a pointer to the current per-processor data area. The kernel maintains a table of such per-processor data structures. The tablePerProcTableis an array of per_proc_entry structures. A per_proc_entry structure consists of a per_proc_info structure, which holds data for one processor. The per_proc_info structure for the master processor is specially labeled as BootProcInfo. These structures reside in aligned memory. Note that a thread's machine-specific context includes a pointer to the current per_proc_info structure. Figure 53 shows an excerpt from the declaration of the per_proc_info structure.

Figure 53. The kernel's per-processor data table

// osfmk/ppc/exception.h struct per_proc_info { // This processor's number unsigned short cpu_number; // Various low-level flags unsigned short cpu_flags; // Interrupt stack vm_offset_t istackptr; vm_offset_t intstack_top_ss; ... // Special thread flags unsigned int spcFlags; ... // Owner of the FPU on this processor struct facility_context *FPU_owner // VRSave associated with live vector registers unsigned int liveVRSave; // Owner of the VMX on this processor struct facility_context *VMX_owner; ... // Interrupt related boolean_t interrupts_enabled; IOInterruptHandler interrupt_handler; void *interrupt_nub; unsigned interrupt_source; ... // Processor features procFeatures pf; ... // Copies of general-purpose registers used for temporary save area uint64_t tempr0; ... uint64_t tempr31; ... // Copies of floating-point registers used for floating-point emulation double emfp0; ... double emfp31; ... // Copies of vector registers used both for full vector emulation or // save areas while assisting denormals unsigned int emvr0[4]; ... unsigned int emvr31[4]; ... // Hardware exception counters hwCtrs hwCtr; // Processor structure unsigned int processor[384]; }; extern struct per_proc_info BootProcInfo; #define MAX_CPUS 256 struct per_proc_entry { addr64_t ppe_paddr; unsigned int ppe_pad4[1]; struct per_proc_info *ppe_vaddr; }; extern struct per_proc_entry PerProcTable[MAX_CPUS-1];

The pf member of the per_proc_info structure is a structure of type procFeatures. It holds per-processor features such as the reported processor type, which processor facilities are available, various cache sizes, supported power-saving modes, and the maximum physical address supported.

// osfmk/ppc/exception.h struct procFeatures { unsigned int Available; /* 0x000 */ #define pfFloat 0x80000000 #define pfFloatb 0 #define pfAltivec 0x40000000 #define pfAltivecb 1 ... #define pfValid 0x00000001 #define pfValidb 31 unsigned short rptdProc; /* 0x004 */ unsigned short lineSize; /* 0x006 */ unsigned int l1iSize; /* 0x008 */ unsigned int l1dSize; /* 0x00C */ ... unsigned int pfPowerTune0; /* 0x080 */ unsigned int pfPowerTune1; /* 0x084 */ unsigned int rsrvd88[6]; /* 0x088 */ }; ... typedef struct procFeatures procFeatures;

5.2.2. Reset Types

Several types of processor initializations can be performed by Mac OS X. The kernel distinguishes between these by setting or clearing certain bits of the Condition Register (CR). For example, if it is the first processor coming up in a given context, the CR bit specified by the bootCPU variable is set. If it is the first time that particular processor is being initialized, the CR bit specified by the firstInit variable is set. The logical AND of bootCPU and firstInit is called firstBoot. It will be nonzero if it is the first processor starting up during kernel initialization (as opposed to a processor waking up from sleep, say). If the processor indeed is in a first-ever initialization, _start() performs one-time general low-level initialization before control flows to the allstart label in osfmk/ppc/start.s. As Figure 52 shows, other code paths also lead to this point in the code, depending on the type of reset the processor is going through. Unlike in the case when BootX directly calls _start(), other reset operations are handled by a designated reset handler.

Recall from Table 51 that 0x0100 is the vector offset for the system reset exception, which could be a result of a hard or soft processor reset. A structure variable called ResetHandler, which is of type resethandler_t, resides in memory at offset 0xF0just before the 0x0100 exception handler.

// osfmk/ppc/exception.h typedef struct resethandler { unsigned int type; vm_offset_t call_paddr; vm_offset_t arg__paddr; } resethandler_t; ... extern resethandler_t ResetHandler; ... #define RESET_HANDLER_NULL 0x0 #define RESET_HANDLER_START 0x1 #define RESET_HANDLER_BUPOR 0x2 #define RESET_HANDLER_IGNORE 0x3 ... // osfmk/ppc/lowmem_vectors.s . = 0xf0 .globl EXT(ResetHandler) EXT(ResetHandler): .long 0x0 .long 0x0 .long 0x0 . = 0x100 .L_handler100: mtsprg 2,r13 /* Save R13 */ mtsprg 3,r11 /* Save R11 */ /* * Examine the ResetHandler structure * and take appropriate action. */ ...

When the 0x0100 handler runs to handle a reset exception, it examines the ResetHandler structure to determine the type of reset. Note that the 0x0100 handler will never be run because of a true hard resetsuch a reset will be seen only by Open Firmware. For other types of resets, namely start, BUPOR,[1] and ignore, the kernel will set up the ResetHandler structure appropriately before a reset exception is generated.

[1] BUPOR stands for bring-up power-on reset.

A RESET_HANDLER_START is generated when the system is waking up from sleep. In this case, the 0x0100 handler clears the reset type by setting it to RESET_HANDLER_NULL, loads the arg__paddr field of the ResetHandler structure to GPR3, loads the call_paddr field to LR, and finally branches through LR to call the function pointed to by call_paddr. The cpu_start() [osfmk/ppc/cpu.c] and cpu_sleep() [osfmk/ppc/cpu.c] functions use this mechanism by setting ResetHandler fields. Specifically, they set call_paddr to point to _start_cpu() [osfmk/ppc/start.s]. _start_cpu() clears the bootCPU and firstInit fields, sets the current per-processor data pointer, sets the processor's Timebase Register using values from another processor, and branches to the allstart label. In doing so, it bypasses some initial instructions that only the boot processor executes.

A RESET_HANDLER_BUPOR is used to bring up a processor when starting directly from a power-on reset (POR). For example, the startCPU() method of the platform-dependent processor driver can generate a soft reset. In the specific case of the 970FX, the startCPU() method implemented in the MacRISC4CPU class (which inherits from the IOCPU class) performs a reset by strobing the processor's reset line. The 0x0100 handler calls resetPOR() [osfmk/ppc/start.s] to handle this type of reset. resetPOR() sets the type field of ResetHandler to RESET_HANDLER_NULL, ensures that the processor is in 32-bit mode, loads GPR3 with a pointer to the boot arguments structure, and branches to _start().

In a multiprocessor system, each CPU's Processor ID Register (PIR) is set to a unique value during a POR.

Finally, if the reset type is RESET_HANDLER_IGNORE, the kernel ignores the reset. This is used for software debouncingfor example, when a nonmaskable interrupt (NMI) is used to enter a debugger.

Both ResetHandler and the exception routines reside in physically addressed memory. The kernel uses special machine-dependent routinesimplemented in osfmk/ppc/machine_routines_asm.sto read from and write to such locations. These routines handle the necessary preprocessing and postprocessing while performing I/O to physical addresses. For example, on the 970FX, this preprocessing makes the floating-point and vector-processing units unavailable, delays recognition of external exceptions and decrementer exception conditions, and disables data translation. Postprocessing reverses the changes made by preprocessing.

5.2.3. Processor Types

The initial kernel code in osfmk/ppc/start.s uses a table of processor typesprocessor_typesthat maps specific processor types to their relevant features. The table contains entries for numerous PowerPC processor models: 750CX (version 2.x), 750 (generic), 750FX (version 1.x and generic), 7400 (versions 2.0 through 2.7 and generic), 7410 (version 1.1 and generic), 7450 (versions 1.xx, 2.0, and 2.1), 7455 (versions 1.xx, 2.0, and 2.1), 7457, 7447A, 970, and 970FX.[2] The entries in this table are ordered: A more specific entry appears before a less restrictive entry. Figure 54 shows an annotated version of the table entry for the 970FX processor.

[2] The 970MP and the 970FX are considered identical processor types. Unless otherwise noted, the discussion in this chapter applies to the 970FX and the 970MP alike.

Figure 54. The entry for the PowerPC 970FX in the processor-type table

; osfmk/ppc/start.s ; 970FX ; Always on word boundary .align 2 ; ptFilter ; Mask of significant bits in the processor Version/Revision code ; 0xFFFF0000 would match all versions .long 0xFFFF0000 ; ptVersion ; Version bits from the Processor Version Register (PVR) ; PROCESSOR_VERSION_970FX is 0x003C .short PROCESSOR_VERSION_970FX ; ptRevision ; Revision bits from the PVR. A zero value denotes generic attributes .short 0 ; ptFeatures ; Processor features that are available (defined in osfmk/ppc/exception.h) .long pfFloat |\ ; FPU pfAltivec |\ ; VMX pfSMPcap |\ ; symmetric multiprocessing capable pfCanSleep |\ ; can go to sleep pfCanNap |\ ; can nap pf128Byte |\ ; has 128-byte cache lines pf64Bit |\ ; GPRs are 64-bit pfL2 ; has L2 cache ; ptCPUCap ; Default value for _cpu_capabilities (defined in osfmk/ppc/cpu_capabilities.h) .long \ ; has VMX kHasAltivec |\ ; GPRs are 64-bit k64Bit |\ ; has 128-byte cache lines kCache128 |\ ; dst, dstt, dstst, dss, and dssall available, but not recommended, ; unless the "Recommended" flag is present too kDataStreamsAvailable |\ ; enhanced dcbt instruction available and recommended kDcbtStreamsRecommended |\ ; enhanced dcbt instruction available (but may or may not be recommended) kDcbtStreamsAvailable |\ ; has fres, frsqrt, and fsel instructions kHasGraphicsOps |\ ; has stfiwx instruction kHasStfiwx |\ ; has fsqrt and fsqrts instructions kHasFsqrt ; ptPwrModes ; Available power management features. The 970FX is the first processor used by ; Apple to support IBM's PowerTune Technology .long pmPowerTune ; ptPatch ; Patch features .long PatchLwsync ; ptInitRout ; Initialization routine for this processor. Can modify any of the other ; attributes. .long init970 ; ptRptdProc ; Processor type reported. CPU_SUBTYPE_POWERPC_970 is defined to be ; ((cpu_subtype_t)100). In contrast, note that CPU_SUBTYPE_POWERPC_7450 ; is defined to be ((cpu_subtype_t)11)! .long CPU_SUBTYPE_POWERPC_970 ; ptLineSize ; L1 cache line size in bytes .long 128 ; ptl1iSize ; L1 I-cache size in bytes (64KB for the 970FX) .long 64*1024 ; ptl1dSize ; L1 D-cache size in bytes (32KB for the 970FX) .long 32*1024 ; ptPTEG ; Number of entries in a page table entry group (PTEG) .long 128 ; ptMaxVAddr ; Maximum virtual address (bits) .long 65 ; ptMaxPAddr ; Maximum physical address (bits) .long 42

The kernel uses the contents of the current CPU's Processor Version Register (PVR) to find a matching entry in processor_table by looping through the table and examining the ptFilter and ptVersion fields of each candidate entry. Once a matching entry is found, a pointer to ptInitRout(), the processor-specific initialization routine, is also saved.

At this point, if the master processor is booting for the first time, a variety of processor features and capabilities are set in the CPU capabilities vector, which is an integer variable called _cpu_capabilities [osfmk/ppc/commpage/commpage.c] and whose bits represent CPU capabilities. Since the processors in a multiprocessor system have identical features, this step is bypassed for a secondary processorthe master's feature information is simply copied for the others.

5.2.4. Memory Patching

Although a given version of Mac OS X uses the same kernel executable regardless of the computer model, the kernel may alter itself at boot time, based on the underlying hardware. During an initial boot, the master processor consults one or more patch tables built into the kernel and examines their entries to determine whether any of them are applicable. Figure 55 shows the structure of a patch-table entry.

Figure 55. Data structure and related definitions of a patch-table entry

// osfmk/ppc/exception.h struct patch_entry { unsigned int *addr; // address to patch unsigned int data; // data to patch with unsigned int type; // patch type unsigned int value; // patch value (for matching) }; #define PATCH_INVALID 0 #define PATCH_PROCESSOR 1 #define PATCH_FEATURE 2 #define PATCH_END_OF_TABLE 3 #define PatchExt32 0x80000000 #define PatchExt32b 0 #define PatchLwsync 0x40000000 #define PatchLwsyncb 1 ...

The kernel's patch table is defined in osfmk/ppc/ppc_init.c. Figure 56 shows an annotated excerpt from this table.

Figure 56. The kernel's patch table

// osfmk/ppc/ppc_init.c patch_entry_t patch_table[] = { // Patch entry 0 { &extPatch32, // address to patch 0x60000000, // data to patch with PATCH_FEATURE, // patch type PatchExt32, // patch value (for matching) } // Patch entry 1 { &extPatchMCK, 0x60000000, PATCH_PROCESSOR, CPU_SUBTYPE_POWERPC_970, } ... // Patch entry N { &sulckPatch_eieio, 0x7c2004ac, PATCH_FEATURE, PatchLwsync, } ... { NULL, 0x00000000, PATCH_END_OF_TABLE, 0 } };

As the kernel examines each entry in the patch table, it checks the entry's type. If the type is PATCH_FEATURE, the kernel compares the patch value with the ptPatch field from the current processor's processor_types table entry. If there is a match, the kernel applies the patch by writing the patch data to the location specified by the patch address. If the entry is of type PATCH_PROCESSOR instead, the kernel compares it with the ptRptdProc field (processor type reported) of its processor_types table entry to check for a potential match. Let us look at specific examples.

The first patch entry shown in Figure 56 has a patch value PatchExt32. This value appears as the ptPatch value in the processor_types entries of all 32-bit processors that Mac OS X supports. Therefore, it will match on all 32-bit processors but will not match on 64-bit processors such as the 970 and the 970FX. The address to patch, extPatch32, is in the osfmk/ppc/lowmem_vectors.s file:

.L_exception_entry: ... .globl EXT(extPatch32) LEXT(extPatch32) b extEntry64 ... /* 32-bit context saving */ ... /* 64-bit context saving */ extEntry64: ...

Since the patch value will not match on 64-bit processors, the code fragment will remain as shown on these processors. On a 32-bit processor, however, the instruction that branches to extEntry64 will be replaced by the patch entry's data, 0x60000000, which is the PowerPC no-op instruction (nop).

Patch entry 1 in Figure 56 will match on a 970 or a 970FX, causing the instruction at address extPatchMCK to be turned into a no-op instruction. By default, the instruction at extPatchMCK is a branch that bypasses 64-bit-specific code in the Machine Check Exception (MCE) handler [osfmk/ppc/lowmem_vectors.s].

Patch entry N in Figure 56 replaces an eieio instruction with an lwsync instruction on matching systems.

Uniprocessor Patch Table

There is another patch table (patch_up_table[] [osfmk/ppc/machine_routines.c]) used only on a uniprocessor system. When the CPU interrupt controller is initialized, it calls ml_init_cpu_max() [osfmk/ppc/machine_routines.c], which applies the patches contained in this table if there is only one logical processor on the system. The patches convert isync and eieio instructions in several synchronization routines to no-op instructions.

5.2.5. Processor-Specific Initialization

The ptInitRout field of a processor_types table entry, if valid, points to a function for model-specific initialization of the processor. This field points to init970() [osfmk/ppc/start.s] for both the 970 and the 970FX. init970() clears the "deep nap" bit of HID0[3] during all types of processor initialization: during the boot processor's first initialization, when a slave processor is started, or when a processor wakes up from sleep. In the case of the boot processor initializing for the first time, init970() synthesizes a dummy L2 cache register (L2CR), with its value set to the actual L2 cache size on the 970FX (512KB).

[3] Recall from Chapter 3 that the 970FX contains four Hardware-Implementation-Dependent (HID) registers: HID0, HID1, HID4, and HID5.

At this point, the kernel sets the valid bit (pfValid) in the Available field of the processor features member (pF) of the per_proc_info structure.

Next, the kernel performs initialization based on whether the processor is 32-bit or 64-bit. For example, on a 32-bit processor, the BAT registers are cleared and the contents of the HID0 register are adjusted to clear any sleep-related bits. Thereafter, the code branches to startcommon() [osfmk/ppc/start.s]. On a 64-bit processor, the kernel sets the value of HID0 appropriately, prepares a machine status value in the SRR1 register, and loads the continuation point (the startcommon() routine) in SRR0. It then executes an rfid instruction, which results in the machine state in SRR1 to be restored to the Machine State Register (MSR). Execution then continues in startcommon().

5.2.6. Other Early Initialization

The kernel checks whether the floating-point facility is available on the processor, and if so, it loads FPR0 with a known floating-point initialization value, which is then copied to the rest of the FPRs. Floating-point is then turned off for the time being. The initialization value is defined in osfmk/ppc/aligned_data.s:

.globl EXT(FloatInit) .align 3 EXT(FloatInit): .long 0xC24BC195 /* Initial value */ .long 0x87859393 /* of floating-point registers */ .long 0xE681A2C8 /* and others */ .long 0x8599855A

After booting, the value can be seen in an FPR as long as that FPR has not been used. For example, you can debug a simple program using GDB and view the contents of the FPRs.

$ cat test.c main() { } $ gcc -g -o test test.c $ gdb ./test ... (gdb) break main Breakpoint 1 at 0x2d34: file test.c, line 1. (gdb) run ... Breakpoint 1, main () at test.c:1 1 main() { } (gdb) info all-registers ... f14 -238423838475.15292 (raw 0xc24bc19587859393) f15 -238423838475.15292 (raw 0xc24bc19587859393) f16 -238423838475.15292 (raw 0xc24bc19587859393) ...

Similarly, the kernel checks whether AltiVec is availableif it is, the kernel sets the VRSAVE register to zero, indicating that no VRs have been used yet. It sets the non-Java (NJ) bit and clears the saturate (SAT) bit in the VSCRwe discussed these bits in Chapter 3. A special vector initialization value is loaded into VR0, which is then copied to the other VRs. AltiVec is then turned off for the time being. The initialization value, labeled QNaNbarbarian, is defined in osfmk/ppc/aligned_data.s. It is a sequence of long integers, each with a value of 0x7FFFDEAD. Again, you can potentially see this value in untouched VRs while debugging a program.

(gdb) info all-registers ... v0 { uint128 = 0x7fffdead7fffdead7fffdead7fffdead, v4_float = {nan(0x7fdead), nan(0x7fdead), nan(0x7fdead), nan(0x7fdead)}, v4_int32 = {2147475117, 2147475117, 2147475117, 2147475117}, v8_int16 = {32767, -8531, 32767, -8531, 32767, -8531, 32767, -8531}, v16_int8 = "\177??\177??\177??\177??" } (raw 0x7fffdead7fffdead7fffdead7fffdead) ...

Quiet NaNs

A floating-point number's value is considered a "Not a Number" (NaN) if the number's exponent is 255 and its fraction is nonzero. Moreover, the NaN is a signaling NaN (SNaN) or a quiet NaN (QNaN) depending on whether the most significant bit of its fraction field is zero or nonzero, respectively. Whereas a signaling NaN signals exceptions when it is specified as an arithmetic operand, a quiet NaN is propagated through most floating-point operations.

The kernel then initializes all caches by calling cacheInit() [osfmk/ppc/machine_routines_asm.s], which first ensures that a variety of features are turned off.[4] For example, it turns off data and instruction address translation, external interrupts, floating-point, and AltiVec. It also initializes various caches via steps such as the following:

[4] Many of these features are likely to be turned off already.

  • It stops all data streams using the dssall instruction.

  • It purges and syncs the TLBs using the tlbie and tlbsync instructions, respectively.

  • On the 64-bit PowerPC, it syncs the page table entries using ptesync.

  • On the 32-bit PowerPC, it initializes the L1, L2, and L3 caches. If a cache was enabled earlier, its contents are first flushed to memory. Hardware-assisted cache flush is used when available. Thereafter, the caches are invalidated and eventually turned on.

On 64-bit processors, cache management is rather different than on 32-bit processors. For example, the L2 cache cannot be disabled on the 970FX. Consequently, the kernel performs a different set of operations to initialize the caches.

  • It disables instruction prefetching by clearing bits 7 and 8 of HID1.

  • It disables data prefetching by setting bit 25 of HID4.

  • It enables L1 D-cache[5] flash invalidation by setting bit 28 of HID4. Flash invalidation is a special mode that allows complete invalidation of the L1 D-cache by simply setting a bit and executing the sync instruction.

    [5] Recall from Chapter 3 that the L1 D-cache on the 970FX is a store-through cacheit never stores modified data. There may be pending stores in the store queue above the L1 D-cache, which may require the sync instruction to be executed to ensure global coherency.

  • It disables the L1 D-cache by setting bits 37 and 38 of HID4.

  • It manipulates the L2 cache to use direct-mapped mode instead of set-associative mode. It uses the processor's Scan Communications facility (see the sidebar "The SCOM Facility") to make this change. Thereafter, victims to evict from the cache are selected based on a simple address decode of real address bits 42 through 44.

  • It performs a memory flush of the entire L2 cache through a sequence of loads of 4MB cacheable regions of memory from addresses that increment according to the algorithm shown in Figure 57.

Figure 57. Flushing the L2 cache on the PowerPC 970FX

// pseudocode offset = 0; do { addr = 0x400000; // 4MB cacheable memory region addr = addr | offset; load_to_register_from_address(addr); for (i = 1; i < 8; i++) { // increment the direct map field (bits [42:44]) of the load address addr += 0x80000; // load a line load_to_register_from_address(addr); } // increment the congruence address field (bits [48:56]) of the load address offset += 128; } while (offset < 0x10000);

The SCOM Facility

The 970FX provides a Scan Communications (SCOM) facility that is accessible through a special-purpose register interface consisting of the SCOMC (control) and SCOMD (data) registers. Internally, SCOM designates address ranges to registers representing processor functionality. Examples of such SCOM registers include an Instruction Address Breakpoint Register, a Power Management Control Register, and the Storage Subsystem Mode Register. The kernel sets a bit in the latter to enable L2 cache direct-mapped mode.

After cache initialization, the kernel configures and starts address translation, unless the underlying processor is the boot processor, in which case it is not yet time for virtual memory. Address translation is configured by calling hw_setup_trans() [osfmk/ppc/hw_vm.s], which first marks the segment registers (SRs) and segment table entries (STEs) invalid by appropriately setting the validSegs field of the per-processor structure (struct per_proc_info). It further sets the structure's ppInvSeg field to 1 to force the complete invalidation of SRs and the segment lookaside buffer (SLB). It also sets the ppCurSeg field to 0 to specify that the current segment is a kernel segment.

If running on 32-bit hardware, the kernel invalidates BAT mappings by loading the data and instruction BAT register with zeros. Next, it retrieves the base address (hash_table_base) and size (hash_table_size) of the page hash table and loads it into the SDR1 register. Note that these variables are not initialized yet during the initial boot of the master processor. However, they are defined for a slave processor, which is the scenario in which the kernel calls hw_setup_trans() at this point. The kernel then loads each SR with the predefined invalid segment value of 1.

If running on 64-bit hardware, the setup is somewhat simpler, since there are no BAT registers. The kernel sets up the page hash table as in the 32-bit case and invalidates all SLB entries via the slbia instruction. It also ensures that 64-bit mode is turned off.

After configuring address translation, the kernel starts address translation by calling hw_start_trans() [osfmk/ppc/hw_vm.s], which sets the data relocate (DR) and instruction relocate (IR) bits of the MSR to enable data and instruction address translation, respectively.

At this point, the kernel is almost ready to continue execution in higher-level code; the boot processor will execute the ppc_init() [osfmk/ppc/ppc_init.c] function, whereas a nonboot processor will execute the ppc_init_cpu() [osfmk/ppc/ppc_init.c] function. Both these functions do not return. Before calling either of these functions, the kernel fabricates a C language call frame. It initializes GPR1 with a pointer to an interrupt stack, stores a zero as a null frame backpointer on the stack, and loads GPR3 with a pointer to the boot arguments. Thereafter, it calls ppc_init() or ppc_init_cpu() as appropriate. The kernel guards the calls to these functions by placing a breakpoint trapa tw instructionafter each call. Consequently, in the unlikely case that either call returns, a trap will be generated.

Категории