123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- this_cpu operations
- -------------------
- this_cpu operations are a way of optimizing access to per cpu
- variables associated with the *currently* executing processor through
- the use of segment registers (or a dedicated register where the cpu
- permanently stored the beginning of the per cpu area for a specific
- processor).
- The this_cpu operations add a per cpu variable offset to the processor
- specific percpu base and encode that operation in the instruction
- operating on the per cpu variable.
- This means there are no atomicity issues between the calculation of
- the offset and the operation on the data. Therefore it is not
- necessary to disable preempt or interrupts to ensure that the
- processor is not changed between the calculation of the address and
- the operation on the data.
- Read-modify-write operations are of particular interest. Frequently
- processors have special lower latency instructions that can operate
- without the typical synchronization overhead but still provide some
- sort of relaxed atomicity guarantee. The x86 for example can execute
- RMV (Read Modify Write) instructions like inc/dec/cmpxchg without the
- lock prefix and the associated latency penalty.
- Access to the variable without the lock prefix is not synchronized but
- synchronization is not necessary since we are dealing with per cpu
- data specific to the currently executing processor. Only the current
- processor should be accessing that variable and therefore there are no
- concurrency issues with other processors in the system.
- On x86 the fs: or the gs: segment registers contain the base of the
- per cpu area. It is then possible to simply use the segment override
- to relocate a per cpu relative address to the proper per cpu area for
- the processor. So the relocation to the per cpu base is encoded in the
- instruction via a segment register prefix.
- For example:
- DEFINE_PER_CPU(int, x);
- int z;
- z = this_cpu_read(x);
- results in a single instruction
- mov ax, gs:[x]
- instead of a sequence of calculation of the address and then a fetch
- from that address which occurs with the percpu operations. Before
- this_cpu_ops such sequence also required preempt disable/enable to
- prevent the kernel from moving the thread to a different processor
- while the calculation is performed.
- The main use of the this_cpu operations has been to optimize counter
- operations.
- this_cpu_inc(x)
- results in the following single instruction (no lock prefix!)
- inc gs:[x]
- instead of the following operations required if there is no segment
- register.
- int *y;
- int cpu;
- cpu = get_cpu();
- y = per_cpu_ptr(&x, cpu);
- (*y)++;
- put_cpu();
- Note that these operations can only be used on percpu data that is
- reserved for a specific processor. Without disabling preemption in the
- surrounding code this_cpu_inc() will only guarantee that one of the
- percpu counters is correctly incremented. However, there is no
- guarantee that the OS will not move the process directly before or
- after the this_cpu instruction is executed. In general this means that
- the value of the individual counters for each processor are
- meaningless. The sum of all the per cpu counters is the only value
- that is of interest.
- Per cpu variables are used for performance reasons. Bouncing cache
- lines can be avoided if multiple processors concurrently go through
- the same code paths. Since each processor has its own per cpu
- variables no concurrent cacheline updates take place. The price that
- has to be paid for this optimization is the need to add up the per cpu
- counters when the value of the counter is needed.
- Special operations:
- -------------------
- y = this_cpu_ptr(&x)
- Takes the offset of a per cpu variable (&x !) and returns the address
- of the per cpu variable that belongs to the currently executing
- processor. this_cpu_ptr avoids multiple steps that the common
- get_cpu/put_cpu sequence requires. No processor number is
- available. Instead the offset of the local per cpu area is simply
- added to the percpu offset.
- Per cpu variables and offsets
- -----------------------------
- Per cpu variables have *offsets* to the beginning of the percpu
- area. They do not have addresses although they look like that in the
- code. Offsets cannot be directly dereferenced. The offset must be
- added to a base pointer of a percpu area of a processor in order to
- form a valid address.
- Therefore the use of x or &x outside of the context of per cpu
- operations is invalid and will generally be treated like a NULL
- pointer dereference.
- In the context of per cpu operations
- x is a per cpu variable. Most this_cpu operations take a cpu
- variable.
- &x is the *offset* a per cpu variable. this_cpu_ptr() takes
- the offset of a per cpu variable which makes this look a bit
- strange.
- Operations on a field of a per cpu structure
- --------------------------------------------
- Let's say we have a percpu structure
- struct s {
- int n,m;
- };
- DEFINE_PER_CPU(struct s, p);
- Operations on these fields are straightforward
- this_cpu_inc(p.m)
- z = this_cpu_cmpxchg(p.m, 0, 1);
- If we have an offset to struct s:
- struct s __percpu *ps = &p;
- z = this_cpu_dec(ps->m);
- z = this_cpu_inc_return(ps->n);
- The calculation of the pointer may require the use of this_cpu_ptr()
- if we do not make use of this_cpu ops later to manipulate fields:
- struct s *pp;
- pp = this_cpu_ptr(&p);
- pp->m--;
- z = pp->n++;
- Variants of this_cpu ops
- -------------------------
- this_cpu ops are interrupt safe. Some architecture do not support
- these per cpu local operations. In that case the operation must be
- replaced by code that disables interrupts, then does the operations
- that are guaranteed to be atomic and then reenable interrupts. Doing
- so is expensive. If there are other reasons why the scheduler cannot
- change the processor we are executing on then there is no reason to
- disable interrupts. For that purpose the __this_cpu operations are
- provided. For example.
- __this_cpu_inc(x);
- Will increment x and will not fallback to code that disables
- interrupts on platforms that cannot accomplish atomicity through
- address relocation and a Read-Modify-Write operation in the same
- instruction.
- &this_cpu_ptr(pp)->n vs this_cpu_ptr(&pp->n)
- --------------------------------------------
- The first operation takes the offset and forms an address and then
- adds the offset of the n field.
- The second one first adds the two offsets and then does the
- relocation. IMHO the second form looks cleaner and has an easier time
- with (). The second form also is consistent with the way
- this_cpu_read() and friends are used.
- Christoph Lameter, April 3rd, 2013
|