diff options
author | Ivan Maidanski <ivmai@mail.ru> | 2023-05-04 21:34:07 +0300 |
---|---|---|
committer | Ivan Maidanski <ivmai@mail.ru> | 2023-05-04 21:43:12 +0300 |
commit | d654f40deaa60b9bcaa5177963aec76974745897 (patch) | |
tree | 1be9c4b31e8f45b4c5fbff2b40ff9014d8a4ea1a | |
parent | 53f132650ac49289c4a8e2b0cebbfab5e08a205b (diff) | |
download | bdwgc-d654f40deaa60b9bcaa5177963aec76974745897.tar.gz |
Workaround a malfunction of soft-dirty bits clearing on Power9
(fix of commit c1bf1b973)
Issue #479 (bdwgc).
Make a page dirty twice in detect_soft_dirty_supported() clearing all
the soft-dirty bits in the middle. If the 2nd write to a page is not
noticeable, then fallback to mprotect-based VDB. This a workaround for
a bug observed, at least, in Fedora 36 kernel on Power9 CPU.
* os_dep.c [SOFT_VDB] (clear_soft_dirty_bits): New static function
(move part of code from GC_soft_read_dirty).
* os_dep.c [SOFT_VDB] (detect_soft_dirty_supported): Call
clear_soft_dirty_bits() and retry changing *vaddr to check whether the
latter is reflected in pagemap file; add comment.
* os_dep.c [SOFT_VDB] (GC_soft_read_dirty): Call clear_soft_dirty_bits.
-rw-r--r-- | os_dep.c | 46 |
1 files changed, 30 insertions, 16 deletions
@@ -3833,6 +3833,16 @@ GC_INLINE void GC_proc_read_dirty(GC_bool output_unneeded) } # endif /* CAN_HANDLE_FORK */ + /* Clear soft-dirty bits from the task's PTEs. */ + static void clear_soft_dirty_bits(void) + { + ssize_t res = write(clear_refs_fd, "4\n", 2); + + if (res != 2) + ABORT_ARG1("Failed to write to /proc/self/clear_refs", + ": errno= %d", res < 0 ? errno : 0); + } + /* The bit 55 of the 64-bit qword of pagemap file is the soft-dirty one. */ # define PM_SOFTDIRTY_MASK ((pagemap_elem_t)1 << 55) @@ -3841,18 +3851,28 @@ GC_INLINE void GC_proc_read_dirty(GC_bool output_unneeded) off_t fpos; pagemap_elem_t buf[1]; - *vaddr = 1; /* make it dirty */ - - /* Read the relevant PTE from the pagemap file. */ GC_ASSERT(GC_log_pagesize != 0); + *vaddr = 1; /* make it dirty */ fpos = (off_t)(((word)vaddr >> GC_log_pagesize) * sizeof(pagemap_elem_t)); - if (lseek(pagemap_fd, fpos, SEEK_SET) == (off_t)(-1)) - return FALSE; - if (PROC_READ(pagemap_fd, buf, sizeof(buf)) != (int)sizeof(buf)) - return FALSE; - /* Is the soft-dirty bit set? */ - return (buf[0] & PM_SOFTDIRTY_MASK) != 0; + for (;;) { + /* Read the relevant PTE from the pagemap file. */ + if (lseek(pagemap_fd, fpos, SEEK_SET) == (off_t)(-1)) + return FALSE; + if (PROC_READ(pagemap_fd, buf, sizeof(buf)) != (int)sizeof(buf)) + return FALSE; + + /* Is the soft-dirty bit unset? */ + if ((buf[0] & PM_SOFTDIRTY_MASK) == 0) return FALSE; + + if (0 == *vaddr) break; + /* Retry to check that writing to clear_refs works as expected. */ + /* This malfunction of the soft-dirty bits implementation is */ + /* observed on some Linux kernels on Power9 (e.g. in Fedora 36). */ + clear_soft_dirty_bits(); + *vaddr = 0; + } + return TRUE; /* success */ } # ifndef NO_SOFT_VDB_LINUX_VER_RUNTIME_CHECK @@ -4037,8 +4057,6 @@ GC_INLINE void GC_proc_read_dirty(GC_bool output_unneeded) GC_INLINE void GC_soft_read_dirty(GC_bool output_unneeded) { - ssize_t res; - GC_ASSERT(I_HOLD_LOCK()); # ifndef THREADS /* Similar as for GC_proc_read_dirty. */ @@ -4085,11 +4103,7 @@ GC_INLINE void GC_proc_read_dirty(GC_bool output_unneeded) # endif } - /* Clear soft-dirty bits from the task's PTEs. */ - res = write(clear_refs_fd, "4\n", 2); - if (res != 2) - ABORT_ARG1("Failed to write to /proc/self/clear_refs", - ": errno= %d", res < 0 ? errno : 0); + clear_soft_dirty_bits(); } #endif /* SOFT_VDB */ |