"Kernel kernel: per-cpu variable module writing"

Posted by PhpMachine on Mon, 02 Sep 2019 08:23:18 +0200

Demand:

1. Create a per-cpu variable, initialize and modify it

2.per-cpu variable is a synchronization mechanism in Linux kernel. When all CPUs in the system access and share a variable V, CPU0 modifies the value of variable V.

CPU1 also modifies variable V at the same time, which will result in the incorrect value of variable V. If atomic locks are used, CPU0 can only wait for modification.

There are two shortcomings in this way:

(1) Atomic operation is time-consuming

(2) Now that CPUs all have L1 caches, so many CPUs accessing a variable at the same time will cause cache consistency problems. When a CPU modifies the shared variable V,

The corresponding cached rows on other CPU s need to be invalid, which results in performance loss.

The per-cpu variable presents an interesting feature to solve the above problems. It assigns copies of the variable to each processor in the system. In a multiprocessor system, when the processor can only access the copy of the variable that belongs to it, it does not need to consider the competition with other processors, and it can make full use of the local hardware cache of the processor to improve performance.

3. There are two ways to declare the definition and declaration of per-cpu variables: static declaration and dynamic allocation.

The static per-cpu variable defines and declares a per-cpu variable through DEFINE_PER_CPU and DECLARE_PER_CPU macros. The difference between the static per-cpu variable and the ordinary variable is that it is placed in a special segment.

// include/linux/Percpu-def.h

#define DECLARE_PER_CPU(type, name)                    \
    DECLARE_PER_CPU_SECTION(type, name, "")

#define DEFINE_PER_CPU(type, name)                    \
    DEFINE_PER_CPU_SECTION(type, name, "")

API for dynamic allocation and release of per-cpu variables

// include/linux/percpu.h

#define alloc_percpu(type)	\
	(typeof(type) __percpu *)__alloc_percpu(sizeof(type), __alignof__(type))

void free_percpu(void __percpu *__pdata);

4. Use per-cpu variables. For statically defined per-cpu variables, you can access and modify per-cpu variables through get_cpu_var() and put_cpu_var() functions, which have built-in functions to turn off and open kernel preemption. Note that these two functions need to be paired.

#define get_cpu_var(var) (*({				\
	preempt_disable();				\
	&__get_cpu_var(var); }))

#define put_cpu_var(var) do {				\
	(void)&(var);					\
	preempt_enable();				\
} while (0)

Access Dynamic Allocation Interface

#define get_cpu_ptr(var) ({				\
	preempt_disable();				\
	this_cpu_ptr(var); })

#define put_cpu_ptr(var) do {				\
	(void)(var);					\
	preempt_enable();				\
} while (0)

 

I. Module Writing

#include <linux/module.h>
#include <linux/init.h>
#include <linux/percpu.h>
#include <linux/cpumask.h>

static DEFINE_PER_CPU(long, cpuvar) = 10;
static long __percpu *cpualloc;

static int __init my_init(void)
{
	int cpu;

	pr_info("module loaded at 0x%p\n", my_init);

	/* modify the cpuvar value */
	for_each_possible_cpu(cpu)
		per_cpu(cpuvar, cpu) = 15;

	pr_info("init: cpuvar on cpu%d  = %ld\n",
			smp_processor_id(), get_cpu_var(cpuvar)++);
	put_cpu_var(cpuvar);

	/* alloc a percpu value */
	cpualloc = alloc_percpu(long);

	/* set all cpu for this value */
	for_each_possible_cpu(cpu)
		*per_cpu_ptr(cpualloc, cpu) = 100;

	return 0;
}

static void __exit my_exit(void)
{
	int cpu;
	pr_info("exit module...\n");

	for_each_possible_cpu(cpu)
		pr_info("cpuvar cpu%d = %ld\n", cpu, per_cpu(cpuvar, cpu));

	pr_info("exit: cpualloc = %ld\n", *per_cpu_ptr(cpualloc, smp_processor_id()));
	free_percpu(cpualloc);

	pr_info("Bye: module unloaded from 0x%p\n", my_exit);
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("HarkerYX");
MODULE_LICENSE("GPL v2");

Makefile

BASEINCLUDE ?= /home/yexiang/work/runningpkernel/runninglinuxkernel_4.0
#BASEINCLUDE ?= /lib/modules/`uname -r`/build

mypercpu-objs := my-percpu.o

obj-m   :=   mypercpu.o
all :
        $(MAKE) -C $(BASEINCLUDE) M=$(PWD) modules;

clean:
        $(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(PWD) clean;
        rm -f *.ko;

Compile: make

Function:

/ # insmod /mnt/mypercpu.ko 
[  701.216587] module loaded at 0xbf00a000
[  701.221773] init: cpuvar on cpu1  = 15
/ # 
/ # 
/ # rmmod mypercpu.ko
[  704.353633] exit module...
[  704.353943] cpuvar cpu0 = 15
[  704.354118] cpuvar cpu1 = 16
[  704.354277] cpuvar cpu2 = 15
[  704.354435] cpuvar cpu3 = 15
[  704.355773] exit: cpualloc = 100
[  704.356138] Bye: module unloaded from 0xbf008000

 

Topics: Linux Makefile