內核必須懂(五): per-CPU變量

內核必須懂(一): 用系統調用打印Hello, world!html

內核必須懂(二): 文件系統初探node

內核必須懂(三): 重編Ubuntu18.04LTS內核4.15.0linux

內核必須懂(四): 撰寫內核驅動ios


目錄

  • 前言
  • 用戶態代碼
  • 驅動模塊代碼
  • 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函數. 沒什麼要說的.函數


驅動代碼

是依據上一次的內容內核必須懂(四): 撰寫內核驅動擴展的, 變化基本在DriverCloseDriverIOControl兩個函數中.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變量

可是在說代碼以前, 要說一下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上篇文章內核必須懂(四): 撰寫內核驅動也說了. 這裏補充一下, 看到了警告, 這是缺了個庫, 最直接的解決方案就是, 安裝這個庫以後, 重編譯內核. 不然其餘方案都過於麻煩.

image.png

這裏看到有8個cpu, 由於這是4核8線程的cpu, 而後一共跑了20次, 每一個核跑的次數也能夠看到. 因此, 實驗成功.

image.png


最後

喜歡記得點贊, 有意見或者建議評論區見哦~

相關文章
相關標籤/搜索