linux內核原子操作使用簡介

驅動開發住常見的一個問題:就是一個驅動可以被多個進程同時打開,使用,這樣會導致驅動功能混亂。

./app & ./app & ./app   可以同時打開這個設備,可通過文件描述符調用驅動的其他接口操作這個設備。

如果想只一個進程使用這個設備,只需要在open接口函數做打開判斷。

示例:
static int  open_flag = 0;
//打開設備時候執行的程序
static int  chrdev_open(struct inode *pinode, struct file *pfile)
{
   //打開判斷
    if(open_flag)
        return -EBUSY;
    open_flag = 1;   //設置    設備已經被打開的標誌
     
    printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);

    return 0;
}

//關閉設備時執行的程序
static int chrdev_release(struct inode *pinode, struct file *pfile)
{
    open_flag = 0;   //設置    設備已經被打開的標誌
    printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
    return 0;
}

以上的效果實現,但是不安全。
分析:
open_flag = 1;   這條語句編譯彙編 最少得3條。 只需要執行時候沒有一次完成,中途被其他進程打開,剛剛這個進程又調用open打開這個設備。這樣,新進程也可以打開成功。

怎麼樣纔可以安全做一次性執行完成這三個步驟?

關係統中斷;    -->關中斷後不會任務調度情況,這樣就是安全。
open_flag = 1;     
開系統中斷;    -->重新恢復系統調度

示例:
unsigned long flags;
raw_local_irq_save(flags);     //關中斷
open_flag = 1;                       //設置    設備已經被打開的標誌
raw_local_irq_restore(flags); //開中斷

Linux內核已經提供相關的一些,很方便實現這樣的效果。原子操作,信號量,互斥信號量,自旋鎖。

原子操作:

1.概念:

原子,是最小的可以獨立存在的物質單元,是不可分割。把一個完整的,不可中斷的操作過程稱爲原子操作。Linux系統中的原子本質就是對一個Int類型變量進行操作,但是,它整個操作操作進行控制,保證這個過程是一次性完成。

2. 數據結構  

Types.h (include\linux)

typedef struct {
	volatile int counter;
} atomic_t;

本質 就是一個int類型變量,可以這個機制很簡單,內核只提供對這個變量的+,-,讀,賦值操作相關的接口。volatile 修飾字段告訴 gcc 不要對該類型的數據做優化處理,對它的訪問都是對內存的訪問,而不是對寄存器的訪問。
 

3. 相關的API

Atomic.h (include\linux)
Atomic.h (include\asm-generic)

ATOMIC_INIT(v);

宏原型 #define ATOMIC_INIT(i) { (i) }
宏功能 初始化爲原子變量爲 i
宏參數 i: 值
所在頭文件 arch\arm\include\asm\atomic.h
宏定義文件 arch\arm\include\asm\atomic.h

atomic_read(atomic_t * v);

宏原型 #define atomic_read(v) (*(volatile int *)&(v)->counter)
宏功能 對原子類型的變量進行原子讀操作,它返回原子類型的變量 v 的值。
宏參數 v: 原子變量指針
所在頭文件 arch\arm\include\asm\atomic.h
宏定義文件 arch\arm\include\asm\atomic.h

atomic_set(atomic_t * v, int i);

函數原型 #define atomic_set(v,i)    (((v)->counter) = (i))
函數功能

給v增加值i

參數 v:原子變量指針     i:增加的值
返回值
頭文件 arch/arm/include/asm/atomic.h

void atomic_sub(int i, atomic_t *v)

函數原型 #define atomic_sub(i, v)    (void) atomic_sub_return(i, v)
函數功能

給v減值i

參數 v:原子變量指針     i:減的值
返回值
頭文件 arch/arm/include/asm/atomic.h

atomic_sub_and_test(i, v)

宏原型 #define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)
宏功能 該函數從原子類型的變量 v 中減去 1,並判斷結果是否爲 0,如果爲 0,返回真,否則返回
假。
宏參數 v: 原子變量指針;
宏返回值 非 0:相減結果爲 0; 0: 相減結果不爲 0。(因爲宏對就的是函數,所以有返回值)
所在頭文件 arch\arm\include\asm\atomic.h
宏定義文件 arch\arm\include\asm\atomic.h

atomic_inc(v);

宏原型 #define atomic_sub(i, v)    (void) atomic_sub_return(i, v)
宏功能 對原子類型變量 v 原子加 1
宏參數 v: 原子變量指針;
所在頭文件 arch\arm\include\asm\atomic.h
宏定義文件 arch\arm\include\asm\atomic.h

atomic_dec(v)

宏原型 #define atomic_sub(i, v)    (void) atomic_sub_return(i, v)
宏功能 對原子類型的變量 v 原子減 1
宏參數 v: 原子變量指針;
所在頭文件 arch\arm\include\asm\atomic.h
宏定義文件 arch\arm\include\asm\atomic.h

atomic_dec_and_test(v)

宏原型 #define atomic_dec_and_test(v)    (atomic_sub_return(1, v) == 0)
宏功能 對原子類型的變量 v 原子減 1,並判斷結果是否爲 0,如果爲 0,返回真,否則返回假。
宏參數 v: 原子變量指針;
宏返回值 非 0:相減結果爲 0; 0: 相減結果不爲 0。(因爲宏對就的是函數,所以有返回值)
所在頭文件 arch\arm\include\asm\atomic.h
宏定義文件 arch\arm\include\asm\atomic.h

atomic_inc_and_test(v)

宏原型 #define atomic_inc_and_test(v)    (atomic_add_return(1, v) == 0)
宏功能 對原子變量v增加1,並判斷結果是否爲0,如果爲0,返回真,否則返回假
宏參數 v: 原子變量指針;
宏返回值 非 0:增加 1 結果爲 0; 0: 增加 1 結果不爲 0。(因爲宏對就的是函數,所以有返回值)
所在頭文件 arch\arm\include\asm\atomic.h
宏定義文件 arch\arm\include\asm\atomic.h

atomic_add_negative(i,v)

宏原型 #define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0)
宏功能 對原子類型的變量 v 增加 i,並判斷結果是否爲負數,如果是,返回真,否則返回假。
宏參數 v: 原子變量指針; i:要加的值
宏返回值 非 0:加 i 後結果爲負數; 0: 減去 i 後結果爲不負數。(因爲宏對就的是函數,所以有返
回值)
所在頭文件 arch\arm\include\asm\atomic.h
宏定義文件 arch\arm\include\asm\atomic.h

int atomic_add_return(int i, atomic_t *v)

函數原型 static inline int atomic_add_return(int i, atomic_t *v)
函數功能 對原子類型的變量 v 增加 i,並且返回 v 的值
函數參數 v: 原子變量指針; i:要加的值
函數返回值 加 i 後的原子變量值
所在頭文件 arch\arm\include\asm\atomic.h
函數定義文件 arch\arm\include\asm\atomic.h

int atomic_sub_return(int i, atomic_t *v)

函數原型 static inline int atomic_sub_return(int i, atomic_t *v)
函數功能 原子類型的變量 v 中減去 i,並且返回 v 的值
函數參數 v: 原子變量指針; i:要減去的值
函數返回值 減去 i 後的原子變量值
所在頭文件 arch\arm\include\asm\atomic.h
函數定義文件 arch\arm\include\asm\atomic.h

atomic_inc_return(v)

宏原型 #define atomic_inc_return(v)    (atomic_add_return(1, v))
宏功能 對原子類型的變量 v 增加 1 並且返回 v 的值
宏參數 v: 原子變量指針;
宏返回值 原子變量增加 1 後的值; (因爲宏對應的就是函數,所以有返回值)
所在頭文件 arch\arm\include\asm\atomic.h
宏定義文件 arch\arm\include\asm\atomic.h

atomic_dec_return(v)

宏原型 #define atomic_dec_return(v)    (atomic_sub_return(1, v))
宏功能 對原子類型的變量 v 減 1 並且返回 v 的值。
宏參數 v: 原子變量指針;
宏返回值 原子變量減 1 後的值; (因爲宏對應的就是函數,所以有返回值)
所在頭文件 arch\arm\include\asm\atomic.h
宏定義文件 arch\arm\include\asm\atomic.h

爲了說明原子操作的作用和用法,現在舉個例子,使用原子變量實現設備只能被一個進程打開。
 

#include <linux/module.h> /* Needed by all modules */
#include <linux/init.h> /* Needed for the module-macros */
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/atomic.h>
#define LEDS_MAJOR 255
#define DEVICE_NAME "miscdev_atomic"
static atomic_t miscdev_atomic = ATOMIC_INIT(1); //定義並初始化一個原子變量
static int first_miscdev_open(struct inode *pinode, struct file *pfile)
{
printk (KERN_EMERG "Linux miscdevice:%s is call\r\n",__FUNCTION__);
if( !atomic_dec_and_test(&miscdev_atomic) ) {
printk(KERN_ERR DEVICE_NAME " is already open!\n");
atomic_inc(&miscdev_atomic);
return -EBUSY;
}
return 0;
}
int first_miscdev_release (struct inode *inode, struct file *file)
{
printk (KERN_EMERG "Linux miscdevice:%s is call\r\n",__FUNCTION__);
atomic_inc(&miscdev_atomic); //釋放原子變量
return 0;
}
static struct file_operations dev_fops =
{
.owner = THIS_MODULE,
.open = first_miscdev_open,
.release= first_miscdev_release,
};
static struct miscdevice misc =
{
.minor = LEDS_MAJOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
/* 模塊裝載執行函數 */
static int __init first_miscdev_init(void)
{
int ret;
ret = misc_register(&misc); //註冊混雜設備
if(ret <0)
printk (KERN_EMERG DEVICE_NAME"\t err\r\n");
printk (KERN_EMERG DEVICE_NAME"\tinitialized\n");
return ret;
}
/* 模塊卸載執行函數 */
static void __exit first_miscdev_exit(void)
{
misc_deregister(&misc);
printk(KERN_EMERG "Goodbye,cruel world!, priority = 0\n");
}
module_init(first_miscdev_init);
module_exit(first_miscdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XYD");
MODULE_DESCRIPTION("This the samlpe drv_test");

從上面可以看到,其實只是在原來的字符設備驅動的 open 函數中增加了原子操作判斷相關的代碼,在關閉函數中增了釋放原子操作的代碼。 其餘的代碼基本不用改動,這樣就可以保證這個驅動只能被一個進程使用,保證了驅動的安全性。當然,原子操作代碼不僅僅是可以放在 open 函數上,可以根據需要放在不同的地方,比如放在 write 函數中,可以實現驅動可以被多進程打開,但是在同時刻只能被一個進程寫操作,讀操作可以被多進程讀,因爲讀操作不會破壞數據, 而寫操作會, 所以, 有時候這處功能恰好是我們想要的結果,就可以這樣寫。原子操作提供了很多 API,以上只以使用其中一種寫法,讀者可以在理解了的基礎上使用其他 API 來實現相同的功能。











               

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章