aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Documentation/kernel-hacking
diff options
context:
space:
mode:
Diffstat (limited to 'Documentation/kernel-hacking')
-rw-r--r--Documentation/kernel-hacking/false-sharing.rst206
-rw-r--r--Documentation/kernel-hacking/hacking.rst55
-rw-r--r--Documentation/kernel-hacking/index.rst1
-rw-r--r--Documentation/kernel-hacking/locking.rst62
4 files changed, 268 insertions, 56 deletions
diff --git a/Documentation/kernel-hacking/false-sharing.rst b/Documentation/kernel-hacking/false-sharing.rst
new file mode 100644
index 000000000000..eb0596734e55
--- /dev/null
+++ b/Documentation/kernel-hacking/false-sharing.rst
@@ -0,0 +1,206 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=============
+False Sharing
+=============
+
+What is False Sharing
+=====================
+False sharing is related with cache mechanism of maintaining the data
+coherence of one cache line stored in multiple CPU's caches; then
+academic definition for it is in [1]_. Consider a struct with a
+refcount and a string::
+
+ struct foo {
+ refcount_t refcount;
+ ...
+ char name[16];
+ } ____cacheline_internodealigned_in_smp;
+
+Member 'refcount'(A) and 'name'(B) _share_ one cache line like below::
+
+ +-----------+ +-----------+
+ | CPU 0 | | CPU 1 |
+ +-----------+ +-----------+
+ / |
+ / |
+ V V
+ +----------------------+ +----------------------+
+ | A B | Cache 0 | A B | Cache 1
+ +----------------------+ +----------------------+
+ | |
+ ---------------------------+------------------+-----------------------------
+ | |
+ +----------------------+
+ | |
+ +----------------------+
+ Main Memory | A B |
+ +----------------------+
+
+'refcount' is modified frequently, but 'name' is set once at object
+creation time and is never modified. When many CPUs access 'foo' at
+the same time, with 'refcount' being only bumped by one CPU frequently
+and 'name' being read by other CPUs, all those reading CPUs have to
+reload the whole cache line over and over due to the 'sharing', even
+though 'name' is never changed.
+
+There are many real-world cases of performance regressions caused by
+false sharing. One of these is a rw_semaphore 'mmap_lock' inside
+mm_struct struct, whose cache line layout change triggered a
+regression and Linus analyzed in [2]_.
+
+There are two key factors for a harmful false sharing:
+
+* A global datum accessed (shared) by many CPUs
+* In the concurrent accesses to the data, there is at least one write
+ operation: write/write or write/read cases.
+
+The sharing could be from totally unrelated kernel components, or
+different code paths of the same kernel component.
+
+
+False Sharing Pitfalls
+======================
+Back in time when one platform had only one or a few CPUs, hot data
+members could be purposely put in the same cache line to make them
+cache hot and save cacheline/TLB, like a lock and the data protected
+by it. But for recent large system with hundreds of CPUs, this may
+not work when the lock is heavily contended, as the lock owner CPU
+could write to the data, while other CPUs are busy spinning the lock.
+
+Looking at past cases, there are several frequently occurring patterns
+for false sharing:
+
+* lock (spinlock/mutex/semaphore) and data protected by it are
+ purposely put in one cache line.
+* global data being put together in one cache line. Some kernel
+ subsystems have many global parameters of small size (4 bytes),
+ which can easily be grouped together and put into one cache line.
+* data members of a big data structure randomly sitting together
+ without being noticed (cache line is usually 64 bytes or more),
+ like 'mem_cgroup' struct.
+
+Following 'mitigation' section provides real-world examples.
+
+False sharing could easily happen unless they are intentionally
+checked, and it is valuable to run specific tools for performance
+critical workloads to detect false sharing affecting performance case
+and optimize accordingly.
+
+
+How to detect and analyze False Sharing
+========================================
+perf record/report/stat are widely used for performance tuning, and
+once hotspots are detected, tools like 'perf-c2c' and 'pahole' can
+be further used to detect and pinpoint the possible false sharing
+data structures. 'addr2line' is also good at decoding instruction
+pointer when there are multiple layers of inline functions.
+
+perf-c2c can capture the cache lines with most false sharing hits,
+decoded functions (line number of file) accessing that cache line,
+and in-line offset of the data. Simple commands are::
+
+ $ perf c2c record -ag sleep 3
+ $ perf c2c report --call-graph none -k vmlinux
+
+When running above during testing will-it-scale's tlb_flush1 case,
+perf reports something like::
+
+ Total records : 1658231
+ Locked Load/Store Operations : 89439
+ Load Operations : 623219
+ Load Local HITM : 92117
+ Load Remote HITM : 139
+
+ #----------------------------------------------------------------------
+ 4 0 2374 0 0 0 0xff1100088366d880
+ #----------------------------------------------------------------------
+ 0.00% 42.29% 0.00% 0.00% 0.00% 0x8 1 1 0xffffffff81373b7b 0 231 129 5312 64 [k] __mod_lruvec_page_state [kernel.vmlinux] memcontrol.h:752 1
+ 0.00% 13.10% 0.00% 0.00% 0.00% 0x8 1 1 0xffffffff81374718 0 226 97 3551 64 [k] folio_lruvec_lock_irqsave [kernel.vmlinux] memcontrol.h:752 1
+ 0.00% 11.20% 0.00% 0.00% 0.00% 0x8 1 1 0xffffffff812c29bf 0 170 136 555 64 [k] lru_add_fn [kernel.vmlinux] mm_inline.h:41 1
+ 0.00% 7.62% 0.00% 0.00% 0.00% 0x8 1 1 0xffffffff812c3ec5 0 175 108 632 64 [k] release_pages [kernel.vmlinux] mm_inline.h:41 1
+ 0.00% 23.29% 0.00% 0.00% 0.00% 0x10 1 1 0xffffffff81372d0a 0 234 279 1051 64 [k] __mod_memcg_lruvec_state [kernel.vmlinux] memcontrol.c:736 1
+
+A nice introduction for perf-c2c is [3]_.
+
+'pahole' decodes data structure layouts delimited in cache line
+granularity. Users can match the offset in perf-c2c output with
+pahole's decoding to locate the exact data members. For global
+data, users can search the data address in System.map.
+
+
+Possible Mitigations
+====================
+False sharing does not always need to be mitigated. False sharing
+mitigations should balance performance gains with complexity and
+space consumption. Sometimes, lower performance is OK, and it's
+unnecessary to hyper-optimize every rarely used data structure or
+a cold data path.
+
+False sharing hurting performance cases are seen more frequently with
+core count increasing. Because of these detrimental effects, many
+patches have been proposed across variety of subsystems (like
+networking and memory management) and merged. Some common mitigations
+(with examples) are:
+
+* Separate hot global data in its own dedicated cache line, even if it
+ is just a 'short' type. The downside is more consumption of memory,
+ cache line and TLB entries.
+
+ - Commit 91b6d3256356 ("net: cache align tcp_memory_allocated, tcp_sockets_allocated")
+
+* Reorganize the data structure, separate the interfering members to
+ different cache lines. One downside is it may introduce new false
+ sharing of other members.
+
+ - Commit 802f1d522d5f ("mm: page_counter: re-layout structure to reduce false sharing")
+
+* Replace 'write' with 'read' when possible, especially in loops.
+ Like for some global variable, use compare(read)-then-write instead
+ of unconditional write. For example, use::
+
+ if (!test_bit(XXX))
+ set_bit(XXX);
+
+ instead of directly "set_bit(XXX);", similarly for atomic_t data::
+
+ if (atomic_read(XXX) == AAA)
+ atomic_set(XXX, BBB);
+
+ - Commit 7b1002f7cfe5 ("bcache: fixup bcache_dev_sectors_dirty_add() multithreaded CPU false sharing")
+ - Commit 292648ac5cf1 ("mm: gup: allow FOLL_PIN to scale in SMP")
+
+* Turn hot global data to 'per-cpu data + global data' when possible,
+ or reasonably increase the threshold for syncing per-cpu data to
+ global data, to reduce or postpone the 'write' to that global data.
+
+ - Commit 520f897a3554 ("ext4: use percpu_counters for extent_status cache hits/misses")
+ - Commit 56f3547bfa4d ("mm: adjust vm_committed_as_batch according to vm overcommit policy")
+
+Surely, all mitigations should be carefully verified to not cause side
+effects. To avoid introducing false sharing when coding, it's better
+to:
+
+* Be aware of cache line boundaries
+* Group mostly read-only fields together
+* Group things that are written at the same time together
+* Separate frequently read and frequently written fields on
+ different cache lines.
+
+and better add a comment stating the false sharing consideration.
+
+One note is, sometimes even after a severe false sharing is detected
+and solved, the performance may still have no obvious improvement as
+the hotspot switches to a new place.
+
+
+Miscellaneous
+=============
+One open issue is that the kernel has an optional data structure
+randomization mechanism, which also randomizes the situation of cache
+line sharing among data members.
+
+
+.. [1] https://en.wikipedia.org/wiki/False_sharing
+.. [2] https://lore.kernel.org/lkml/CAHk-=whoqV=cX5VC80mmR9rr+Z+yQ6fiQZm36Fb-izsanHg23w@mail.gmail.com/
+.. [3] https://joemario.github.io/blog/2016/09/01/c2c-blog/
diff --git a/Documentation/kernel-hacking/hacking.rst b/Documentation/kernel-hacking/hacking.rst
index eed2136d847f..0042776a9e17 100644
--- a/Documentation/kernel-hacking/hacking.rst
+++ b/Documentation/kernel-hacking/hacking.rst
@@ -76,8 +76,8 @@ handler is never re-entered: if the same interrupt arrives, it is queued
fast: frequently it simply acknowledges the interrupt, marks a 'software
interrupt' for execution and exits.
-You can tell you are in a hardware interrupt, because
-:c:func:`in_irq()` returns true.
+You can tell you are in a hardware interrupt, because in_hardirq() returns
+true.
.. warning::
@@ -112,8 +112,7 @@ time, although different tasklets can run simultaneously.
.. warning::
The name 'tasklet' is misleading: they have nothing to do with
- 'tasks', and probably more to do with some bad vodka Alexey
- Kuznetsov had at the time.
+ 'tasks'.
You can tell you are in a softirq (or tasklet) using the
:c:func:`in_softirq()` macro (``include/linux/preempt.h``).
@@ -121,7 +120,7 @@ You can tell you are in a softirq (or tasklet) using the
.. warning::
Beware that this will return a false positive if a
- :ref:`botton half lock <local_bh_disable>` is held.
+ :ref:`bottom half lock <local_bh_disable>` is held.
Some Basic Rules
================
@@ -290,8 +289,8 @@ userspace.
Unlike :c:func:`put_user()` and :c:func:`get_user()`, they
return the amount of uncopied data (ie. 0 still means success).
-[Yes, this moronic interface makes me cringe. The flamewar comes up
-every year or so. --RR.]
+[Yes, this objectionable interface makes me cringe. The flamewar comes
+up every year or so. --RR.]
The functions may sleep implicitly. This should never be called outside
user context (it makes no sense), with interrupts disabled, or a
@@ -346,8 +345,8 @@ routine.
Before inventing your own cache of often-used objects consider using a
slab cache in ``include/linux/slab.h``
-:c:func:`current()`
--------------------
+:c:macro:`current`
+------------------
Defined in ``include/asm/current.h``
@@ -588,7 +587,7 @@ Defined in ``include/linux/export.h``
Similar to :c:func:`EXPORT_SYMBOL()` except that the symbols
exported by :c:func:`EXPORT_SYMBOL_GPL()` can only be seen by
-modules with a :c:func:`MODULE_LICENSE()` that specifies a GPL
+modules with a :c:func:`MODULE_LICENSE()` that specifies a GPLv2
compatible license. It implies that the function is considered an
internal implementation issue, and not really an interface. Some
maintainers and developers may however require EXPORT_SYMBOL_GPL()
@@ -601,7 +600,7 @@ Defined in ``include/linux/export.h``
This is the variant of `EXPORT_SYMBOL()` that allows specifying a symbol
namespace. Symbol Namespaces are documented in
-:doc:`../core-api/symbol-namespaces`
+Documentation/core-api/symbol-namespaces.rst
:c:func:`EXPORT_SYMBOL_NS_GPL()`
--------------------------------
@@ -610,7 +609,7 @@ Defined in ``include/linux/export.h``
This is the variant of `EXPORT_SYMBOL_GPL()` that allows specifying a symbol
namespace. Symbol Namespaces are documented in
-:doc:`../core-api/symbol-namespaces`
+Documentation/core-api/symbol-namespaces.rst
Routines and Conventions
========================
@@ -645,8 +644,9 @@ names in development kernels; this is not done just to keep everyone on
their toes: it reflects a fundamental change (eg. can no longer be
called with interrupts on, or does extra checks, or doesn't do checks
which were caught before). Usually this is accompanied by a fairly
-complete note to the linux-kernel mailing list; search the archive.
-Simply doing a global replace on the file usually makes things **worse**.
+complete note to the appropriate kernel development mailing list; search
+the archives. Simply doing a global replace on the file usually makes
+things **worse**.
Initializing structure members
------------------------------
@@ -723,14 +723,14 @@ Putting Your Stuff in the Kernel
In order to get your stuff into shape for official inclusion, or even to
make a neat patch, there's administrative work to be done:
-- Figure out whose pond you've been pissing in. Look at the top of the
- source files, inside the ``MAINTAINERS`` file, and last of all in the
- ``CREDITS`` file. You should coordinate with this person to make sure
- you're not duplicating effort, or trying something that's already
- been rejected.
+- Figure out who are the owners of the code you've been modifying. Look
+ at the top of the source files, inside the ``MAINTAINERS`` file, and
+ last of all in the ``CREDITS`` file. You should coordinate with these
+ people to make sure you're not duplicating effort, or trying something
+ that's already been rejected.
- Make sure you put your name and EMail address at the top of any files
- you create or mangle significantly. This is the first place people
+ Make sure you put your name and email address at the top of any files
+ you create or modify significantly. This is the first place people
will look when they find a bug, or when **they** want to make a change.
- Usually you want a configuration option for your kernel hack. Edit
@@ -748,15 +748,14 @@ make a neat patch, there's administrative work to be done:
can usually just add a "obj-$(CONFIG_xxx) += xxx.o" line. The syntax
is documented in ``Documentation/kbuild/makefiles.rst``.
-- Put yourself in ``CREDITS`` if you've done something noteworthy,
- usually beyond a single file (your name should be at the top of the
- source files anyway). ``MAINTAINERS`` means you want to be consulted
- when changes are made to a subsystem, and hear about bugs; it implies
- a more-than-passing commitment to some part of the code.
+- Put yourself in ``CREDITS`` if you consider what you've done
+ noteworthy, usually beyond a single file (your name should be at the
+ top of the source files anyway). ``MAINTAINERS`` means you want to be
+ consulted when changes are made to a subsystem, and hear about bugs;
+ it implies a more-than-passing commitment to some part of the code.
- Finally, don't forget to read
- ``Documentation/process/submitting-patches.rst`` and possibly
- ``Documentation/process/submitting-drivers.rst``.
+ ``Documentation/process/submitting-patches.rst``
Kernel Cantrips
===============
diff --git a/Documentation/kernel-hacking/index.rst b/Documentation/kernel-hacking/index.rst
index f53027652290..79c03bac99a2 100644
--- a/Documentation/kernel-hacking/index.rst
+++ b/Documentation/kernel-hacking/index.rst
@@ -9,3 +9,4 @@ Kernel Hacking Guides
hacking
locking
+ false-sharing
diff --git a/Documentation/kernel-hacking/locking.rst b/Documentation/kernel-hacking/locking.rst
index 6ed806e6061b..dff0646a717b 100644
--- a/Documentation/kernel-hacking/locking.rst
+++ b/Documentation/kernel-hacking/locking.rst
@@ -94,16 +94,10 @@ primitives, but I'll pretend they don't exist.
Locking in the Linux Kernel
===========================
-If I could give you one piece of advice: never sleep with anyone crazier
-than yourself. But if I had to give you advice on locking: **keep it
-simple**.
+If I could give you one piece of advice on locking: **keep it simple**.
Be reluctant to introduce new locks.
-Strangely enough, this last one is the exact reverse of my advice when
-you **have** slept with someone crazier than yourself. And you should
-think about getting a big dog.
-
Two Main Types of Kernel Locks: Spinlocks and Mutexes
-----------------------------------------------------
@@ -118,11 +112,11 @@ spinlock, but you may block holding a mutex. If you can't lock a mutex,
your task will suspend itself, and be woken up when the mutex is
released. This means the CPU can do something else while you are
waiting. There are many cases when you simply can't sleep (see
-`What Functions Are Safe To Call From Interrupts? <#sleeping-things>`__),
+`What Functions Are Safe To Call From Interrupts?`_),
and so have to use a spinlock instead.
Neither type of lock is recursive: see
-`Deadlock: Simple and Advanced <#deadlock>`__.
+`Deadlock: Simple and Advanced`_.
Locks and Uniprocessor Kernels
------------------------------
@@ -179,7 +173,7 @@ perfect world).
Note that you can also use spin_lock_irq() or
spin_lock_irqsave() here, which stop hardware interrupts
-as well: see `Hard IRQ Context <#hard-irq-context>`__.
+as well: see `Hard IRQ Context`_.
This works perfectly for UP as well: the spin lock vanishes, and this
macro simply becomes local_bh_disable()
@@ -230,7 +224,7 @@ The Same Softirq
~~~~~~~~~~~~~~~~
The same softirq can run on the other CPUs: you can use a per-CPU array
-(see `Per-CPU Data <#per-cpu-data>`__) for better performance. If you're
+(see `Per-CPU Data`_) for better performance. If you're
going so far as to use a softirq, you probably care about scalable
performance enough to justify the extra complexity.
@@ -301,7 +295,7 @@ Pete Zaitcev gives the following summary:
- If you are in a process context (any syscall) and want to lock other
process out, use a mutex. You can take a mutex and sleep
- (``copy_from_user*(`` or ``kmalloc(x,GFP_KERNEL)``).
+ (``copy_from_user()`` or ``kmalloc(x,GFP_KERNEL)``).
- Otherwise (== data can be touched in an interrupt), use
spin_lock_irqsave() and
@@ -947,8 +941,7 @@ lock.
A classic problem here is when you provide callbacks or hooks: if you
call these with the lock held, you risk simple deadlock, or a deadly
-embrace (who knows what the callback will do?). Remember, the other
-programmers are out to get you, so don't do this.
+embrace (who knows what the callback will do?).
Overzealous Prevention Of Deadlocks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -958,8 +951,6 @@ grabs a read lock, searches a list, fails to find what it wants, drops
the read lock, grabs a write lock and inserts the object has a race
condition.
-If you don't see why, please stay the fuck away from my code.
-
Racing Timers: A Kernel Pastime
-------------------------------
@@ -976,7 +967,7 @@ you might do the following::
while (list) {
struct foo *next = list->next;
- del_timer(&list->timer);
+ timer_delete(&list->timer);
kfree(list);
list = next;
}
@@ -990,7 +981,7 @@ the lock after we spin_unlock_bh(), and then try to free
the element (which has already been freed!).
This can be avoided by checking the result of
-del_timer(): if it returns 1, the timer has been deleted.
+timer_delete(): if it returns 1, the timer has been deleted.
If 0, it means (in this case) that it is currently running, so we can
do::
@@ -999,7 +990,7 @@ do::
while (list) {
struct foo *next = list->next;
- if (!del_timer(&list->timer)) {
+ if (!timer_delete(&list->timer)) {
/* Give timer a chance to delete this */
spin_unlock_bh(&list_lock);
goto retry;
@@ -1014,9 +1005,12 @@ do::
Another common problem is deleting timers which restart themselves (by
calling add_timer() at the end of their timer function).
Because this is a fairly common case which is prone to races, you should
-use del_timer_sync() (``include/linux/timer.h``) to
-handle this case. It returns the number of times the timer had to be
-deleted before we finally stopped it from adding itself back in.
+use timer_delete_sync() (``include/linux/timer.h``) to handle this case.
+
+Before freeing a timer, timer_shutdown() or timer_shutdown_sync() should be
+called which will keep it from being rearmed. Any subsequent attempt to
+rearm the timer will be silently ignored by the core code.
+
Locking Speed
=============
@@ -1283,11 +1277,11 @@ Manfred Spraul points out that you can still do this, even if the data
is very occasionally accessed in user context or softirqs/tasklets. The
irq handler doesn't use a lock, and all other accesses are done as so::
- spin_lock(&lock);
+ mutex_lock(&lock);
disable_irq(irq);
...
enable_irq(irq);
- spin_unlock(&lock);
+ mutex_unlock(&lock);
The disable_irq() prevents the irq handler from running
(and waits for it to finish if it's currently running on other CPUs).
@@ -1344,7 +1338,7 @@ lock.
- kfree()
-- add_timer() and del_timer()
+- add_timer() and timer_delete()
Mutex API reference
===================
@@ -1358,7 +1352,19 @@ Mutex API reference
Futex API reference
===================
-.. kernel-doc:: kernel/futex.c
+.. kernel-doc:: kernel/futex/core.c
+ :internal:
+
+.. kernel-doc:: kernel/futex/futex.h
+ :internal:
+
+.. kernel-doc:: kernel/futex/pi.c
+ :internal:
+
+.. kernel-doc:: kernel/futex/requeue.c
+ :internal:
+
+.. kernel-doc:: kernel/futex/waitwake.c
:internal:
Further reading
@@ -1406,7 +1412,7 @@ bh
half will be running at any time.
Hardware Interrupt / Hardware IRQ
- Hardware interrupt request. in_irq() returns true in a
+ Hardware interrupt request. in_hardirq() returns true in a
hardware interrupt handler.
Interrupt Context
@@ -1418,7 +1424,7 @@ SMP
(``CONFIG_SMP=y``).
Software Interrupt / softirq
- Software interrupt handler. in_irq() returns false;
+ Software interrupt handler. in_hardirq() returns false;
in_softirq() returns true. Tasklets and softirqs both
fall into the category of 'software interrupts'.