核心必須懂(五): 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函式. 沒什麼要說的.
驅動程式碼
是依據上一次的內容 核心必須懂(四): 撰寫核心驅動 擴充套件的, 變化基本在 DriverClose 和 DriverIOControl 兩個函式中.
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #defineMAJOR_NUM231 #defineDEVICE_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 ) { inti= 0; unsigned longulBaseAddr= 0; unsigned longulOffset= 0; long*pUsage= NULL; longusageSum= 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上篇文章 核心必須懂(四): 撰寫核心驅動 也說了. 這裡補充一下, 看到了警告, 這是缺了個庫, 最直接的解決方案就是, 安裝這個庫之後, 重編譯核心. 否則其他方案都過於麻煩.

這裡看到有8個cpu, 因為這是4核8執行緒的cpu, 然後一共跑了20次, 每個核跑的次數也可以看到. 所以, 實驗成功.
