内核必须懂(一): 用系统调用打印Hello, world!html
内核必须懂(二): 文件系统初探node
内核必须懂(三): 重编Ubuntu18.04LTS内核4.15.0linux
- 前言
- 用户态代码
- 驱动模块代码
- per-CPU变量
- 关闭抢占
- 演示
- 最后
以前内核必须懂(四): 撰写内核驱动说到了基础的驱动模块写法. 此次目标就是计算进入驱动ioctl或者其余某个驱动函数的次数. 固然, 你可能会以为, 这弄个全局变量计数不就完了吗? 可是这里的要求是要并行进行访问, 因此统计的是多核多线程的访问次数. 是否是感受没有那么简单了? 你可能会回答, 上锁, 那基本等于串行, 太low, 这里展现真正的并行访问与计数.bash
先来看下用户态代码:多线程
#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函数. 没什么要说的.函数
是依据上一次的内容内核必须懂(四): 撰写内核驱动扩展的, 变化基本在DriverClose和DriverIOControl两个函数中.ui
#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变量 变量, 也就是 DEFINE_PER_CPU( long, gUsage ) = 0; 这一行. 能够先看看这篇文章和这篇文章, 简单来讲就是在单cpu环境下生效的变量. 那你可能会说, 我就一个核啊, 岂不是没用了, 可是这是相对于虚拟cpu来讲的, 也就是说, 若是你是4核8线程, 就能够同时有8个这样的per-CPU变量生效. 正由于有这样的变量, 计数变得很是简单, 只须要统计每一个cpu中的这个变量便可. 还有个问题须要解决, 那就是获取每一个cpu的基地址, 这里又有一个宏for_each_online_cpu(), 只须要给个变量便可循环输出活跃的cpu了. 宏真是好东西哦~this
多核状况下, 就还有一个问题, 抢占. 固然了, 这里的代码简单, 不关闭抢占大多数状况下也不会出错, 可是状况复杂的话, 出错几率就会大大提升, 甚至你不知道怎么错的. 并且这但是内核, 错了每每十分致命.google
preempt_disable();
pUsage = this_cpu_ptr( (long *) (&gUsage) );
(*pUsage)++;
preempt_enable();
复制代码
这里还有一个宏this_cpu_ptr, 获取per cpu变量的线性地址. 这是操做系统的知识了, 我就很少说了, 自行google咯. 那我来讲一下, 为何要关闭抢占. 试想一下, 获取到地址以后, 正打算++操做, 结果中断抢占, 到了另外一个核, 以前的地址就对不上了, 这时候进行++操做就彻底不对了. 因此为了避免发生这样的问题, 就须要关闭抢占, 固然, 关闭中断也行, 可是中断对操做系统影响太大了, 不推荐.
说了这么多, 万一演示不出来, 就没有任何意义, 因此跑下程序. 编译生成.ko, .out这些很少说了, mknod上篇文章内核必须懂(四): 撰写内核驱动也说了. 这里补充一下, 看到了警告, 这是缺了个库, 最直接的解决方案就是, 安装这个库以后, 重编译内核. 不然其余方案都过于麻烦.
这里看到有8个cpu, 由于这是4核8线程的cpu, 而后一共跑了20次, 每一个核跑的次数也能够看到. 因此, 实验成功.
喜欢记得点赞, 有意见或者建议评论区见哦~