内核必须懂(五): per-CPU变量

简介: 内核必须懂(一): 用系统调用打印Hello, world!内核必须懂(二): 文件系统初探内核必须懂(三): 重编Ubuntu18.04LTS内核4.15.0内核必须懂(四): 撰写内核驱动目录前言用户态代码驱动模块代码per-CPU变量关闭抢占演示最后前言之前内核必须懂(四): 撰写内核驱动说到了基础的驱动模块写法.

目录

  • 前言
  • 用户态代码
  • 驱动模块代码
  • per-CPU变量
  • 关闭抢占
  • 演示
  • 最后

前言

之前内核必须懂(四): 撰写内核驱动说到了基础的驱动模块写法. 这次目标就是计算进入驱动ioctl或者其他某个驱动函数的次数. 当然, 你可能会觉得, 这弄个全局变量计数不就完了吗? 但是这里的要求是要并行进行访问, 所以统计的是多核多线程的访问次数. 是不是感觉没有那么简单了? 你可能会回答, 上锁, 那基本等于串行, 太low, 这里展示真正的并行访问与计数.

用户态代码

先来看下用户态代码:
#include <iostream>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <string>

using namespace std;

#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/hellodr", O_RDWR)) < 0) {
        cerr << strerror(errno) << endl;
        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;
}
这里就是开20个线程调用驱动的ioctl函数. 没什么要说的.

驱动代码

是依据上一次的内容内核必须懂(四): 撰写内核驱动扩展的, 变化基本在 DriverCloseDriverIOControl两个函数中.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>

#define    MAJOR_NUM    231
#define    DEVICE_NAME  "hellodr"

DEFINE_PER_CPU( long, gUsage ) = 0;

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


ssize_t DriverWrite( struct file *pslFileStruct, const char __user *pBuffer, size_t nCount, loff_t *pOffset )
{
    printk( KERN_ALERT DEVICE_NAME " hello write.\n" );
    return(0);
}


int DriverClose( struct inode *pslINode, struct file *pslFileStruct )
{
    int     i       = 0;
    unsigned long   ulBaseAddr  = 0;
    unsigned long   ulOffset    = 0;
    long        *pUsage     = NULL;
    long        usageSum    = 0;

    ulOffset = (unsigned long) (&gUsage);
    for_each_online_cpu( i )
    {
        ulBaseAddr  = __per_cpu_offset[i];
        pUsage      = (long *) (ulBaseAddr + ulOffset);
        usageSum    += (*pUsage);
        printk( KERN_ALERT DEVICE_NAME " pUsage = %lx, *pUsage = %ld\n", (unsigned long) pUsage, *pUsage );
    }
    printk( KERN_ALERT DEVICE_NAME " %ld\n", usageSum );

    return(0);
}


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


struct file_operations hello_flops = {
    .owner      = THIS_MODULE,
    .open       = DriverOpen,
    .write      = DriverWrite,
    .release    = DriverClose,
    .unlocked_ioctl = DriverIOControl
};

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" );

per-CPU变量

但是在说代码之前, 要说一下per-CPU变量
变量, 也就是 DEFINE_PER_CPU( long, gUsage ) = 0;这一行. 简单来说就是在单cpu环境下生效的变量. 那你可能会说, 我就一个核啊, 岂不是没用了, 但是这是相对于虚拟cpu来说的, 也就是说, 如果你是4核8线程, 就可以同时有8个这样的per-CPU变量生效. 正因为有这样的变量, 计数变得非常简单, 只需要统计每个cpu中的这个变量即可. 还有个问题需要解决, 那就是获取每个cpu的基地址, 这里又有一个宏for_each_online_cpu(), 只需要给个变量即可循环输出活跃的cpu了. 宏真是好东西哦~

关闭抢占

多核情况下, 就还有一个问题, 抢占. 当然了, 这里的代码简单, 不关闭抢占大多数情况下也不会出错, 但是情况复杂的话, 出错概率就会大大提高, 甚至你不知道怎么错的. 而且这可是内核, 错了往往十分致命.
preempt_disable();
pUsage = this_cpu_ptr( (long *) (&gUsage) );
(*pUsage)++;
preempt_enable();
这里还有一个宏 this_cpu_ptr, 获取per cpu变量的线性地址. 这是操作系统的知识了, 我就不多说了, 自行google咯. 那我来说一下, 为什么要关闭抢占.
试想一下, 获取到地址之后, 正打算++操作, 结果中断抢占, 到了另一个核, 之前的地址就对不上了, 这时候进行++操作就完全不对了. 所以为了不发生这样的问题, 就需要关闭抢占, 当然, 关闭中断也行, 但是中断对操作系统影响太大了, 不推荐.

演示

说了这么多, 万一演示不出来, 就没有任何意义, 所以跑下程序. 编译生成.ko, .out这些不多说了, mknod上篇文章内核必须懂(四): 撰写内核驱动也说了. 这里补充一下, 看到了警告, 这是缺了个库, 最直接的解决方案就是, 安装这个库之后, 重编译内核. 否则其他方案都过于麻烦.

image.png

这里看到有8个cpu, 因为这是4核8线程的cpu, 然后一共跑了20次, 每个核跑的次数也可以看到. 所以, 实验成功.

image.png


最后

喜欢记得点赞, 有意见或者建议评论区见哦~

目录
相关文章
|
8天前
|
缓存 Linux
揭秘Linux内核:探索CPU拓扑结构
【10月更文挑战第26天】
23 1
|
8天前
|
缓存 运维 Linux
深入探索Linux内核:CPU拓扑结构探测
【10月更文挑战第18天】在现代计算机系统中,CPU的拓扑结构对性能优化和资源管理至关重要。了解CPU的核心、线程、NUMA节点等信息,可以帮助开发者和系统管理员更好地调优应用程序和系统配置。本文将深入探讨如何在Linux内核中探测CPU拓扑结构,介绍相关工具和方法。
9 0
|
2月前
|
Linux API 调度
CPU热插拔在内核中的支持 【ChatGPT】
CPU热插拔在内核中的支持 【ChatGPT】
50 14
|
3月前
|
Linux
Linux系统如何查看版本信息,内核、发行版、cpu、所有版本
Linux系统如何查看版本信息,内核、发行版、cpu、所有版本
118 10
|
4月前
|
Linux 开发工具
CPU-IO-网络-内核参数的调优
CPU-IO-网络-内核参数的调优
72 7
|
12月前
|
存储 Linux Docker
跨cpu架构部署容器技术点:怎样修改Linux 的内核版本
在使用Docker 进行跨平台部署之后,我们还可以尝试进行跨架构部署。 从X86 架构上移植到 aarch64 上。
254 0
|
负载均衡 算法 Linux
深入理解Linux内核进程CPU负载均衡机制(上)
深入理解Linux内核进程CPU负载均衡机制
|
6月前
|
缓存 BI Linux
CPU-IO-网络-内核参数的调优(一)
【4月更文挑战第3天】本文介绍了Linux系统中调整CPU资源使用的两种方法。一是通过`nice`和`renice`命令改变进程优先级,影响进程对CPU的占用。`nice`用于设置新进程的优先级,例如将`vim`的优先级设为-5,而`renice`用于改变已运行进程的优先级。二是使用`taskset`设置进程的CPU亲和力,指定进程在特定CPU上运行,如将`vim`限制在CPU0上执行。此外,通过`vmstat`工具监控系统状态,分析CPU利用率、内存使用、IO活动和上下文切换,帮助找出系统瓶颈。
106 1
|
负载均衡 算法 Linux
深入理解Linux内核进程CPU负载均衡机制(下)
深入理解Linux内核进程CPU负载均衡机制
|
Ubuntu
【Ubuntu系统如何查看 CPU 架构、系统信息、内核版本、版本代号?】
在 Ubuntu 中,我们可以使用一些命令来查看 CPU 架构、系统信息、内核版本、版本代号等相关信息。
2033 0