per-cpu 变量

104 篇文章 24 订阅
48 篇文章 6 订阅

概述

per-cpu 变量是 2.6 内核的一个有趣特性。当建立一个 per-cpu 变量时,系统中的每个处理器都会拥有该变量的特有副本。这看起来很奇怪,但它有其优点。对 per-cpu 变量的访问(几乎)不需要锁定,因为每个处理器在其自己的副本上工作。per-cpu 变量还可以保存在对应处理器的高速缓存中,这样,就可以在频繁更新时获得更好的性能。
关于 per-cpu 变量使用的例子可见于网络子系统中。内核维护者着大量计数器,这些计数器跟踪已接收到的各类数据包数量,而这些计数器每秒可能被更新上千次。网络子系统的开发者将这些统计用的计数器放在了 per-cpu 变量中,这样,他们就不需要处理缓存和锁定问题,而更新可在不用锁的情况下快速完成。在用户空间偶尔请求这些计数器的值时,只需将每个处理器的版本相加并返回合计值即可。

示例

操作 per-cpu 变量需要使用特定的函数,本文只作为一个引文,不进行每个函数的详细介绍,需要的话请自行查阅,以下直接给出一个 per-cpu 变量的使用示例
hello.c

#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#define MAJOR_NUM 231
#define DEVICE_NAME "hello"

DEFINE_PER_CPU(long, g_usage) = 0; // 定义一个per-cpu变量

int open(struct inode *p_inode, struct file *p_file)
{
	printk(KERN_ALERT DEVICE_NAME " hello open.\n");
	return (0);
}

ssize_t write(struct file *p_file, const char __user *p_buffer, size_t n_count, loff_t *p_offset)
{
	printk(KERN_ALERT DEVICE_NAME " hello write.\n");
	return (0);
}

int close(struct inode *p_inode, struct file *p_file)
{
	int i = 0;
	unsigned long base_addr = 0;
	unsigned long offset = 0;
	long *p_usage = NULL;
	long usage_sum = 0;

	offset = (unsigned long)(&g_usage);
	for_each_online_cpu(i)
	{
		base_addr = __per_cpu_offset[i];
		p_usage = (long *)(base_addr + offset);
		usage_sum += (*p_usage);
		printk(KERN_ALERT DEVICE_NAME " p_usage = %lx, *p_usage = %ld\n", (unsigned long)p_usage, *p_usage);
	}
	printk(KERN_ALERT DEVICE_NAME " %ld\n", usage_sum);
	return (0);
}

long ioctl(struct file *p_file, unsigned int uiCmd, unsigned long ulArg)
{
	long *p_usage = NULL;
	/* printk( KERN_ALERT DEVICE_NAME ": p_usage = 0x%lx %lx %ld", (unsigned long) p_usage, (unsigned long)
	 * (&g_usage), (*p_usage) ); */
	preempt_disable();
	p_usage = this_cpu_ptr((long *)(&g_usage));
	(*p_usage)++;
	preempt_enable();
	return (0);
}

struct file_operations hello_flops = {
	.open = open,
	.write = write,
	.release = close,
	.unlocked_ioctl = ioctl
};

static int __init hello_init(void)
{
	int ret;

	ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &hello_flops);
	if (ret < 0) {
		printk(KERN_ALERT DEVICE_NAME " can't register major number.\n");
		return (ret);
	}
	printk(KERN_ALERT DEVICE_NAME " initialized.\n");
	return (0);
}

static void __exit hello_exit(void)
{
	printk(KERN_ALERT DEVICE_NAME " removed.\n");
	unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sean Depp");

test.c

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>

#define NUM_THREADS 20

int fd = 0; // 文件描述符

// 多线程调用函数
void *pthread_fx(void *args)
{
	ioctl(fd, 1, 0);
}

int main()
{
	int ret = 0;

	// 打开文件
	if ((fd = open("/dev/hello", O_RDWR)) < 0) {
		printf("%s\n", strerror(errno));
		return -1;
	}

	// 开启多线程
	pthread_t tids[NUM_THREADS];
	for (int i = 0; i < NUM_THREADS; i++) {
		ret = pthread_create(&tids[i], NULL, pthread_fx, NULL);
		if (ret != 0) {
			printf("pthread_create error: error_code = %d\n", ret);
		}
	}

	// 回收线程资源
	for (int i = 0; i < NUM_THREADS; ++i) {
		pthread_join(tids[i], NULL);
	}

	// 关闭文件
	close(fd);

	return 0;
}

Makefile

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)

obj-m	:=hello.o

all:
	make -C $(KERNELDIR) M=$(PWD) modules

clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.* .tmp_versions *.mod *.order *.symvers *.dwo

test:
	gcc test.c -o test.out -lpthread

insmod:
	sudo insmod hello.ko
	sudo mknod /dev/hello c 231 0

rmmod:
	sudo rm /dev/hello
	sudo rmmod hello

编译内核模块

$ make
make -C /lib/modules/5.15.0-70-generic/build M=/home/liyongjun/project/c/C_study/kernel/per-cpu modules
make[1]: 进入目录“/usr/src/linux-headers-5.15.0-70-generic”
  CC [M]  /home/liyongjun/project/c/C_study/kernel/per-cpu/hello.o
  MODPOST /home/liyongjun/project/c/C_study/kernel/per-cpu/Module.symvers
  CC [M]  /home/liyongjun/project/c/C_study/kernel/per-cpu/hello.mod.o
  LD [M]  /home/liyongjun/project/c/C_study/kernel/per-cpu/hello.ko
  BTF [M] /home/liyongjun/project/c/C_study/kernel/per-cpu/hello.ko
Skipping BTF generation for /home/liyongjun/project/c/C_study/kernel/per-cpu/hello.ko due to unavailability of vmlinux
make[1]: 离开目录“/usr/src/linux-headers-5.15.0-70-generic”

编译 app

$ make test
gcc test.c -o test.out -lpthread

安装内核模块,并创建设备节点文件

$ make insmod 
sudo insmod hello.ko
sudo mknod /dev/hello c 231 0

运行 app

$ sudo ./test.out

dmesg 查看

$ dmesg | tail -n 7
[282663.589797] hello initialized.
[282726.834548] hello hello open.
[282726.835184] hello p_usage = ffff9053bea33010, *p_usage = 4
[282726.835189] hello p_usage = ffff9053beab3010, *p_usage = 5
[282726.835191] hello p_usage = ffff9053beb33010, *p_usage = 8
[282726.835192] hello p_usage = ffff9053bebb3010, *p_usage = 3
[282726.835192] hello 20

程序说明:
应用程序创建了 20 个线程,这 20 个线程终归要在 4 个核上运行,最终在 4 个核上执行次数计数 4、5、8、3,总和等于 20。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Li-Yongjun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值