Intels cmpxchg instruction How does the Linux kernels

  • Slides: 22
Download presentation
Intel’s ‘cmpxchg’ instruction How does the Linux kernel’s ‘cmos_lock’ mechanism work?

Intel’s ‘cmpxchg’ instruction How does the Linux kernel’s ‘cmos_lock’ mechanism work?

Review of the i 386 registers EAX CS EBX DS ECX ES EDX FS

Review of the i 386 registers EAX CS EBX DS ECX ES EDX FS ESI GS EDI SS EBP ESP General Registers (32 -bits) Segment Registers (16 -bits) EIP EFLAGS Program Control and Status Registers (32 bits)

The x 86 ‘system’ registers CR 0 DR 0 CR 1 DR 1 CR

The x 86 ‘system’ registers CR 0 DR 0 CR 1 DR 1 CR 2 DR 2 CR 3 DR 3 CR 4 DR 4 CR 5 DR 5 CR 6 DR 6 (16 -bits) CR 7 DR 7 Control Registers (32 -bits) Debug Registers (32 -bits) LDTR TR means ‘unimplemented’ GDTR IDTR (48 -bits)

How often is ‘cmpxchg’ used? $ cat vmlinux. asm | grep cmpxchg c 01046

How often is ‘cmpxchg’ used? $ cat vmlinux. asm | grep cmpxchg c 01046 de: c 0105591: c 01055 d 9: c 010 b 895: c 010 b 949: c 0129 a 9 f: c 0129 acf: c 012 d 377: c 012 d 41 a: c 012 d 968: c 012 e 57 a: c 012 e 58 a: c 012 e 83 f: c 012 e 931: c 012 ea 94: c 012 ecf 4: c 012 f 08 e: c 012 f 163: c 013 cb 60: c 0148 b 3 c: c 0150 d 0 f: c 0150 d 87: c 0199 c 5 e: c 024 b 06 f: c 024 b 2 fe: c 024 b 321: c 024 b 34 b: c 024 b 960: f 0 f 0 f 0 f 0 f 0 f 0 f 0 f 0 0 f 0 f 0 f 0 f 0 f 0 f 0 f 0 f b 1 b 1 b 1 b 1 b 1 b 1 b 1 b 1 15 15 15 11 0 b 0 b 0 b 0 e 0 e 16 2 e 2 e 2 e 13 0 a 11 13 4 b 11 0 e 29 3 b 31 0 b 0 b 51 51 4 b 53 3 c 99 30 18 18 18 lock lock lock lock lock lock lock lock cmpxchg cmpxchg cmpxchg cmpxchg cmpxchg cmpxchg cmpxchg cmpxchg %edx, 0 xc 030993 c %edx, (%ecx) %ecx, (%ebx) %ecx, (%esi) %edx, (%esi) %ebp, (%esi) %edx, (%ebx) %ecx, (%edx) %edx, (%ecx) %edx, (%ebx) %ecx, 0 x 18(%ebx) %edx, (%ecx) %ecx, (%esi) %ebp, (%ecx) %edi, (%ebx) %esi, (%ecx) %ecx, (%ebx) %edx, 0 x 18(%ecx) %ecx, 0 x 18(%ebx) %edx, 0 x 18(%ebx) Here’s the occurrence that we studied in the ‘rtc_cmos_read()’ kernel-function… …plus 28 other times!

Intel’s documentation • You can find out what any of the Intel x 86

Intel’s documentation • You can find out what any of the Intel x 86 instructions does by consulting the official software developer’s manual, online at: http: //www. intel. com/products/processor/manuals/index. htm • Our course-webpage has a link to this site that you can just click (under ‘Resources’) • The instruction-set reference is two parts: – Volume 2 A: for opcodes A through M – Volume 2 B: for opcodes N through Z

Example: ‘cmpxchg’ • Operation of the ‘cmpxchg’ instruction is described (on 3 pages) in

Example: ‘cmpxchg’ • Operation of the ‘cmpxchg’ instruction is described (on 3 pages) in Volume 2 A • There’s an English-sentence description, and also a description in ‘pseudo-code’ • You probably do not want to print out this complete volume (. pdf) – over 700 pages! • (You could order a printed copy from Intel)

Instruction format • Intel’s assembly language syntax differs from the GNU/Linux syntax (known as

Instruction format • Intel’s assembly language syntax differs from the GNU/Linux syntax (known as ‘AT&T syntax’ with roots in UNIX history) • When AT&T syntax is used, the ‘cmpxchg’ instruction has this layout: [lock] cmpxchg reg, reg/mem optional ‘prefix’ (used for SMP) mnemonic opcode source operand destination operand

‘effects’ and ‘affects’ • According to Intel’s manual, the ‘cmpxchg’ instruction also uses two

‘effects’ and ‘affects’ • According to Intel’s manual, the ‘cmpxchg’ instruction also uses two ‘implicit’ operands (i. e. , operands not mentioned in the instruction) – The CPU’s accumulator register – The CPU’s EFLAGS register • The accumulator-register (EAX) is both a sourceoperand a destination-operand • The six status-bits in the EFLAGS register will get modified, as a ‘side-effect’ this instruction

‘cmpxchg’ description • This instruction compares the accumulator with the destination-operand (so the ZF-bit

‘cmpxchg’ description • This instruction compares the accumulator with the destination-operand (so the ZF-bit in EFLAGS gets assigned accordingly) • Then: – If (accumulator == destination) { ZF 1; destination source; } – If (accumulator != destination) { ZF 0; accumulator destination; }

An instruction-instance • In our recent disassembly of Linux’s kernel function ‘rtc_cmos_read()’, this ‘cmpxchg’

An instruction-instance • In our recent disassembly of Linux’s kernel function ‘rtc_cmos_read()’, this ‘cmpxchg’ instruction-instance was used: lock cmpxchg prefix opcode %edx, cmos_lock source-operand destination-operand Note: Keep in mind that the accumulator %eax will affect what happens! So we need to consider this instruction within it’s surrounding context

The complete function c 0105574 <rtc_cmos_read>: c 0105574: 53 c 0105575: 9 c c

The complete function c 0105574 <rtc_cmos_read>: c 0105574: 53 c 0105575: 9 c c 0105576: 5 b c 0105577: fa c 0105578: 64 8 b 15 08 c 010557 f: 0 f b 6 c 8 c 0105582: 42 c 0105583: c 1 e 2 08 c 0105586: 09 ca c 0105588: a 1 3 c 99 30 c 010558 d: 85 c 010558 f: 75 f 7 c 0105591: f 0 0 f b 1 15 c 0105598: c 0105599: 85 c 010559 b: 75 eb c 010559 d: 88 c 010559 f: e 6 70 c 01055 a 1: e 6 80 c 01055 a 3: e 4 71 c 01055 a 5: e 6 80 c 01055 a 7: c 7 05 3 c 99 c 01055 ae: 00 00 00 c 01055 b 1: 53 c 01055 b 2: 9 d c 01055 b 3: 0 f b 6 c 01055 b 6: 5 b c 01055 b 7: c 3 20 30 c 0 3 c 99 30 30 c 0 00 push %ebx pushf pop %ebx cli mov %fs: 0 xc 0302008, %edx movzbl %al, %ecx inc %edx shl $0 x 8, %edx or %ecx, %edx mov 0 xc 030993 c, %eax test %eax, %eax jne c 0105588 <rtc_cmos_read+0 x 14> lock cmpxchg %edx, 0 xc 030993 c test jne mov out in out movl %eax, %eax c 0105588 <rtc_cmos_read+0 x 14> %cl, %al, $0 x 70 %al, $0 x 80 $0 x 71, %al, $0 x 80 $0 x 0, 0 xc 030993 c push %ebx popf movzbl %al, %eax pop %ebx ret

The ‘preparation’ steps • The instructions that preceed ‘cmpxchg’ will setup register EDX (source

The ‘preparation’ steps • The instructions that preceed ‘cmpxchg’ will setup register EDX (source operand) and register EAX (the x 86 ‘accumulator’) • Several instructions are used to set up a value in EDX, and result in this layout: 31 EDX: 8 7 The current processor’s value for ‘per_cpu_number’ plus 1 0 CMOS register’s index this might be zero… …but this part is guaranteed to be non-zero!

The ‘cmos_lock’ variable • This global variable is initialized to zero, meaning that access

The ‘cmos_lock’ variable • This global variable is initialized to zero, meaning that access to CMOS memory locations is not currently ‘locked’ • If some CPU stores a non-zero value in this variable’s memory-location, it means that access to CMOS memory is ‘locked’ • The kernel needs to insure that only one CPU at a time can set this ‘lock’

The ‘most likely’ senario • One of the CPUs wishes to access CMOS memory

The ‘most likely’ senario • One of the CPUs wishes to access CMOS memory – so it needs to test ‘cmos_lock’ to be sure that access is now ‘unlocked’ (i. e. , cmos_lock == 0 is true) • The CPU copies the ‘cmos_lock’ variable into the EAX, where it can then be tested using the ‘test %eax, %eax’ instruction • A conditional-jump follows the test

The ‘busy-wait’ loop # Here is a ‘busy-wait’ loop, used to wait for the

The ‘busy-wait’ loop # Here is a ‘busy-wait’ loop, used to wait for the CMOS access to be ‘unlocked’ spin: mov cmos_lock, %eax # copy lock-variable to accumulator test %eax, %eax # was CMOS access ‘unlocked’? jnz spin # if it wasn’t, then check it again # A CPU will fall through to here if ‘unlocked’ access was detected, # and that CPU will now attempt to set the ‘lock’ – in other words, it # will try to assign a non-zero value to the ‘cmos_lock’ variable. # But there’s a potential ‘race’ here – the ‘cmos_lock’ might have been # zero when it was copied, but it could have been changed by now… # … and that’s why we need to execute ‘lock cmpxchg’ at this point

Busy-waiting will be brief spin: # see if the lock-variable is clear mov cmos_lock,

Busy-waiting will be brief spin: # see if the lock-variable is clear mov cmos_lock, %eax test %eax, %eax jnz spin # ok, now we try to grab the lock cmpxchg %edx, cmos_lock # did another CPU grab it first? test %eax, %eax jnz spin If our CPU wins the ‘race’, the (non-zero) value from source-operand EDX will have been stored into the (previously zero) ‘cmos_lock’ memory-location, but the (previously zero) accumulator EAX will not have been modified; hence our CPU will not jump back, but will fall through and execute the ‘critical section’ of code (just a few instructions), then will promptly clear the ‘cmos_lock’ variable.

The ‘less likely’ case spin: # see if the lock-variable is clear mov cmos_lock,

The ‘less likely’ case spin: # see if the lock-variable is clear mov cmos_lock, %eax test %eax, %eax jnz spin # ok, now we try to grab the lock cmpxchg %edx, cmos_lock # did another CPU grab it first? test %eax, %eax jnz spin If our CPU loses the ‘race’, because another CPU changed ‘cmos_lock’ to some non-zero value after we had fetched our copy of it, then the (now non-zero) value from the ‘cmos_lock’ destination-operand will have been copied into EAX, and so the final conditional-jump shown above will take our CPU back into the spin-loop, where it will resume busy-waiting until the ‘winner’ of the race clears ‘cmos_lock’.

start flowchart Setup nonzero value in EDX no EAX is zero? EAX cmos_lock yes

start flowchart Setup nonzero value in EDX no EAX is zero? EAX cmos_lock yes no EAX equals cmos_lock ? ZF 1 cmos_lock EDX ZF 0 EAX cmos_lock no EAX is zero? yes critical section cmos_lock 0 finish

‘btr’/’bts’ versus ‘cmpxchg’ • In an earlier lesson we used the ‘btr’/’bts’ instructions to

‘btr’/’bts’ versus ‘cmpxchg’ • In an earlier lesson we used the ‘btr’/’bts’ instructions to achieve ‘mutual exclusion’, whereas Linux uses ‘cmpxchg’ to do that • We think ‘btr’/’bts’ is easier to understand, so why do you think the Linux developers would prefer to use ‘cmpxchg’ instead? <allow some class discussion here>

In-class exercise #1 • Was it really necessary to insert a second ‘test %eax,

In-class exercise #1 • Was it really necessary to insert a second ‘test %eax, %eax’ following ‘cmpxchg’? • Can you design a simple LKM that would verify your answer to that question?

EFLAGS • The Intel documentation does not state precisely how other EFLAGS status-bits (besides

EFLAGS • The Intel documentation does not state precisely how other EFLAGS status-bits (besides ZF) are affected by ‘cmpxchg’, only that they reflect the comparison of ‘accumulator’ and ‘destination’ operands 31 11 10 AVR CMF N T IOPL 9 8 7 6 5 4 3 2 1 0 OD I T S Z A P C 0 0 1 FFFFFF F • Usually the CPU implements comparisonof-operands by performing a subtraction

In-class exercise #2 • Can you decide what Intel means by “the comparison operation”,

In-class exercise #2 • Can you decide what Intel means by “the comparison operation”, by writing suitable code that examines the effect on EFLAGS of ‘cmpxchg opnd 1, opnd 2’ and these two plausable alternatives: cmp opnd 1, opnd 2 cmp opnd 2, opnd 1