[PATCH] x86/split_lock: add split lock counter

From: Maksim Davydov
Date: Fri Dec 15 2023 - 09:04:44 EST


Provides per task split locks counter to monitor split locks rate
in the system. It can be helpful in split locks monitoring to get a clear
sense of which process causing split locks and how many of them have
happened by the moment. For instance, it might be used by cloud providers
who can't control guest executable code and want to make decisions based
on the rate value like ratelimiting or notifing the split lock origins.

To implement this functionality the per-task flag have been transformed
into the counter. But procfs interface is used to provide the counter
in machine-readable format.

Signed-off-by: Maksim Davydov <davydov-max@xxxxxxxxxxxxxx>
---
arch/x86/include/asm/cpu.h | 18 ++++++++++++++++++
arch/x86/kernel/cpu/intel.c | 35 +++++++++++++++++++++++++++++++++--
fs/proc/base.c | 7 +++++++
include/linux/sched.h | 6 +++---
kernel/fork.c | 2 +-
5 files changed, 62 insertions(+), 6 deletions(-)

diff --git a/arch/x86/include/asm/cpu.h b/arch/x86/include/asm/cpu.h
index fecc4fe1d68a..1470124e1d63 100644
--- a/arch/x86/include/asm/cpu.h
+++ b/arch/x86/include/asm/cpu.h
@@ -44,6 +44,12 @@ extern bool handle_user_split_lock(struct pt_regs *regs, long error_code);
extern bool handle_guest_split_lock(unsigned long ip);
extern void handle_bus_lock(struct pt_regs *regs);
u8 get_this_hybrid_cpu_type(void);
+extern int proc_pid_split_locks_show(struct seq_file *s,
+ struct pid_namespace *ns, struct pid *pid,
+ struct task_struct *tsk);
+extern int proc_tgid_split_locks_show(struct seq_file *s,
+ struct pid_namespace *ns, struct pid *pid,
+ struct task_struct *tsk);
#else
static inline void __init sld_setup(struct cpuinfo_x86 *c) {}
static inline bool handle_user_split_lock(struct pt_regs *regs, long error_code)
@@ -62,6 +68,18 @@ static inline u8 get_this_hybrid_cpu_type(void)
{
return 0;
}
+static inline int proc_pid_split_locks_show(struct seq_file *s,
+ struct pid_namespace *ns, struct pid *pid,
+ struct task_struct *tsk)
+{
+ return 0;
+}
+static inline int proc_tgid_split_locks_show(struct seq_file *s,
+ struct pid_namespace *ns, struct pid *pid,
+ struct task_struct *tsk)
+{
+ return 0;
+}
#endif
#ifdef CONFIG_IA32_FEAT_CTL
void init_ia32_feat_ctl(struct cpuinfo_x86 *c);
diff --git a/arch/x86/kernel/cpu/intel.c b/arch/x86/kernel/cpu/intel.c
index a927a8fc9624..20640a4b9eac 100644
--- a/arch/x86/kernel/cpu/intel.c
+++ b/arch/x86/kernel/cpu/intel.c
@@ -1160,10 +1160,10 @@ static void split_lock_warn(unsigned long ip)
struct delayed_work *work;
int cpu;

- if (!current->reported_split_lock)
+ if (!current->detected_split_locks)
pr_warn_ratelimited("#AC: %s/%d took a split_lock trap at address: 0x%lx\n",
current->comm, current->pid, ip);
- current->reported_split_lock = 1;
+ current->detected_split_locks++;

if (sysctl_sld_mitigate) {
/*
@@ -1191,6 +1191,37 @@ static void split_lock_warn(unsigned long ip)
put_cpu();
}

+static int split_locks_show(struct seq_file *s, struct task_struct *tsk,
+ int whole)
+{
+ u64 detected_split_locks = tsk->detected_split_locks;
+
+ if (whole) {
+ struct task_struct *t = tsk;
+
+ while_each_thread(tsk, t) {
+ detected_split_locks += t->detected_split_locks;
+ }
+ }
+
+ seq_put_decimal_ull(s, "", detected_split_locks);
+ seq_putc(s, '\n');
+
+ return 0;
+}
+
+int proc_pid_split_locks_show(struct seq_file *s, struct pid_namespace *ns,
+ struct pid *pid, struct task_struct *tsk)
+{
+ return split_locks_show(s, tsk, 0);
+}
+
+int proc_tgid_split_locks_show(struct seq_file *s, struct pid_namespace *ns,
+ struct pid *pid, struct task_struct *tsk)
+{
+ return split_locks_show(s, tsk, 1);
+}
+
bool handle_guest_split_lock(unsigned long ip)
{
if (sld_state == sld_warn) {
diff --git a/fs/proc/base.c b/fs/proc/base.c
index dd31e3b6bf77..3c533312dbbc 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -98,6 +98,7 @@
#include <linux/cn_proc.h>
#include <linux/ksm.h>
#include <trace/events/oom.h>
+#include <asm/cpu.h>
#include "internal.h"
#include "fd.h"

@@ -3360,6 +3361,9 @@ static const struct pid_entry tgid_base_stuff[] = {
ONE("ksm_merging_pages", S_IRUSR, proc_pid_ksm_merging_pages),
ONE("ksm_stat", S_IRUSR, proc_pid_ksm_stat),
#endif
+#ifdef CONFIG_CPU_SUP_INTEL
+ ONE("split_locks", S_IRUGO, proc_tgid_split_locks_show),
+#endif
};

static int proc_tgid_base_readdir(struct file *file, struct dir_context *ctx)
@@ -3699,6 +3703,9 @@ static const struct pid_entry tid_base_stuff[] = {
ONE("ksm_merging_pages", S_IRUSR, proc_pid_ksm_merging_pages),
ONE("ksm_stat", S_IRUSR, proc_pid_ksm_stat),
#endif
+#ifdef CONFIG_CPU_SUP_INTEL
+ ONE("split_locks", S_IRUGO, proc_pid_split_locks_show),
+#endif
};

static int proc_tid_base_readdir(struct file *file, struct dir_context *ctx)
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 292c31697248..5b9cd4524405 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -957,9 +957,6 @@ struct task_struct {
#ifdef CONFIG_IOMMU_SVA
unsigned pasid_activated:1;
#endif
-#ifdef CONFIG_CPU_SUP_INTEL
- unsigned reported_split_lock:1;
-#endif
#ifdef CONFIG_TASK_DELAY_ACCT
/* delay due to memory thrashing */
unsigned in_thrashing:1;
@@ -1027,6 +1024,9 @@ struct task_struct {
u64 stimescaled;
#endif
u64 gtime;
+#ifdef CONFIG_CPU_SUP_INTEL
+ u64 detected_split_locks;
+#endif
struct prev_cputime prev_cputime;
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
struct vtime vtime;
diff --git a/kernel/fork.c b/kernel/fork.c
index 10917c3e1f03..5a0318010dd5 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1188,7 +1188,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
#endif

#ifdef CONFIG_CPU_SUP_INTEL
- tsk->reported_split_lock = 0;
+ tsk->detected_split_locks = 0;
#endif

#ifdef CONFIG_SCHED_MM_CID
--
2.34.1