嵌入式之Linux驅(qū)動(dòng)(五)

姓名:鄭煜爍? 學(xué)號(hào):19029100010? 學(xué)院:電子工程學(xué)院

轉(zhuǎn)自:https://blog.csdn.net/u012142460/article/details/79017329

【嵌牛導(dǎo)讀】簡單介紹相關(guān)的控制和命令

【嵌牛鼻子】linux設(shè)備驅(qū)動(dòng)中的并發(fā)控制

【嵌牛提問】何為并發(fā)控制。為什么會(huì)出現(xiàn)并發(fā)控制。

【嵌牛正文】

在應(yīng)用層學(xué)習(xí)時(shí),我們學(xué)習(xí)過多個(gè)進(jìn)程處理共享資源的情況。實(shí)際上在驅(qū)動(dòng)中也有類似的情況,并且相對(duì)于應(yīng)用層,并發(fā)的情況會(huì)更多。

? ? 并發(fā)(concurrency)指的是多個(gè)執(zhí)行單元同時(shí)、并行被執(zhí)行,而并發(fā)的執(zhí)行單元對(duì)共享資源。(硬件資源和軟件上的全局變量、靜態(tài)變量等)的訪問則很容易導(dǎo)致競態(tài)(race conditions)

競態(tài)發(fā)生的原因主要有以下幾點(diǎn):

? ? 1,對(duì)稱多處理器的cpu

? ? ? 2,單CPU內(nèi)進(jìn)程與搶占它的進(jìn)程

? ? ? 3,中斷(硬中斷、軟中斷、Tasklet、底半部)與進(jìn)程之間

我們?cè)趹?yīng)用層的學(xué)習(xí)中,對(duì)臨界資源的保護(hù)主要有信號(hào)量、互斥量等等。內(nèi)核中的并發(fā)處理也有類似的機(jī)制,并且除此之外還有其他的一些機(jī)制。我們來詳細(xì)看一看;

1、中斷屏蔽

local_irq_disable() /* 屏蔽中斷 */

. . .

critical section /* 臨界區(qū)*/

. . .

local_irq_enable()? /* 開中斷*/

中斷屏蔽的使用很簡單,進(jìn)入臨界區(qū)使用屏蔽中斷函數(shù),出臨界區(qū)再打開中斷函數(shù)。但是有一點(diǎn)要注意,屏蔽的中斷只是該CPU中斷,其他CPU的中斷是無法屏蔽,所以在多核的CPU中起到的作用有限。

2、原子操作

原子操作可以保證對(duì)一個(gè)整型數(shù)據(jù)(注意只有整型數(shù)據(jù))的修改是排他性的。Linux提供了一系列API來實(shí)現(xiàn)內(nèi)核的原子操作。這些API分為兩類,一類是對(duì)整型數(shù)據(jù)的操作。一類是對(duì)位的原子操作。原子操作最終都是靠硬件保證的。因此與CPU的架構(gòu)有密切關(guān)系。在ARM架構(gòu)中,底層最終使用LDREX和STREX指令。

2.1 整型原子操作

在使用上還是比較簡單的,內(nèi)核已經(jīng)給我們寫好了API函數(shù),我們參照使用即可

? 1.設(shè)置原子變量的值

? void atomic_set(v, i)? ? ? ? ? ? //設(shè)置原子變量為i

? atomic_t ATOMIC_INIT(i)? ? //定義原子變量v并初始化為0

2. 獲取原子變量的值

? atomic_read(v)? ? ? ? ? ? ? ? ? //返回原子變量的值

3. 原子變量加/減?

void atomic_add(int i, atomic_t *v)? ? //原子變量加i

void atomic_sub(int i, atomic_t *v)? ? //原子變量減i

4.原子變量自增/自減?

void atomic_inc(atomic_t *v)? ? ? ? ? //原子變量自增1

void atomic_dec(atomic_t *v)? ? ? ? //原子變量自減1

5. 操作并測試

atomic_sub_and_test(i, v) 原子減i并測試

atomic_dec_and_test(v) 原子變量減1并測試

atomic_inc_and_test(v) 原子變量加1并測試

上述操作對(duì)原子變量執(zhí)行減i、自減、自增操作后,測試其是否為0,為0返回true,否則返回返回false

6 操作并返回

int atomic_add_return(int i, atomic_t *v);

int atomic_sub_return(int i, atomic_t *v);

int atomic_inc_return(atomic_t *v);

int atomic_dec_return(atomic_t *v);

上述操作對(duì)原子變量進(jìn)行加/減和自增/自減操作,并返回新的值

總結(jié)一下使用原子操作的步驟(可以想一想在應(yīng)用層使用信號(hào)量的步驟):

1、初始化一個(gè)原子變量,一般為0或1,(1表示第一次獲取時(shí)可以成功,0表示只等待釋放后才能使用,我們以初始化為1)

2、操作并測試,其實(shí)就是嘗試獲取臨界資源,所以也就是用自減測試或減i測試,自加并測試很少會(huì)用到。

3、操作臨界資源

4、釋放原子變量

我們使用驅(qū)動(dòng)中動(dòng)態(tài)創(chuàng)建設(shè)備號(hào)、設(shè)備節(jié)點(diǎn)文章中的例程,添加相應(yīng)程序,是該驅(qū)動(dòng)程序在同一時(shí)刻只能被打開一次。(增加的程序后面用+++++++++++表示一下,沒辦法,CSDN的編輯器依舊那么渣,單獨(dú)修改某一行程序的顏色或者字體大小無法顯示)

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/cdev.h>

#include <linux/fs.h>

#include <linux/slab.h>

#include <linux/device.h>

#include <asm/atomic.h>

MODULE_LICENSE("GPL");

dev_t devno;

int major = 0;

int minor = 0;

int count = 1;

struct cdev *pdev;

struct class * pclass;

struct device * pdevice;

atomic_t? v = ATOMIC_INIT(1);? //初始化一個(gè)原子變量+++++++++++++++++

int demo_open(struct inode * inodep, struct file * filep)

{

if(!atomic_sub_and_test(1, &v))? //獲取原子變量+++++++++++++++++

{

printk("v:%d\n", atomic_read(&v));

atomic_add(1, &v);

return -EBUSY;

}

printk("%s,%d\n", __func__, __LINE__);

return 0;

}

int demo_release(struct inode *inodep, struct file *filep)

{

printk("%s,%d\n", __func__, __LINE__);

atomic_inc(&v);? ? //釋放原子變量++++++++++++++

return 0;

}

struct file_operations? fops = {

.owner =THIS_MODULE,

.open = demo_open,

.release = demo_release,

};

static int __init demo_init(void)

{

int ret = 0;

printk("%s,%d\n", __func__, __LINE__);

ret = alloc_chrdev_region(&devno,minor,count, "xxx");

if(ret)

{

printk("Failed to alloc_chrdev_region.\n");

return ret;

}

printk("devno:%d , major:%d? minor:%d\n", devno, MAJOR(devno), MINOR(devno));

pdev = cdev_alloc();

if(pdev == NULL)

{

printk("Failed to cdev_alloc.\n");

goto err1;

}

cdev_init(pdev, &fops);

ret = cdev_add(pdev, devno, count);

if(ret < 0)

{

? ? printk("Failed to cdev_add.");

goto err2;

}

pclass = class_create(THIS_MODULE, "myclass");

if(IS_ERR(pclass))

{

printk("Failed to class_create.\n");

ret = PTR_ERR(pclass);

goto err3;

}

pdevice = device_create(pclass, NULL, devno, NULL, "hello");

if(IS_ERR(pdevice))

{

printk("Failed to device_create.\n");

ret = PTR_ERR(pdevice);

goto err4;

}

return 0;

err4:

class_destroy(pclass);

err3:

cdev_del(pdev);

err2:

kfree(pdev);

err1:

unregister_chrdev_region(devno, count);

return ret;

}

static void __exit demo_exit(void)

{

printk("%s,%d\n", __func__, __LINE__);

device_destroy(pclass, devno);

class_destroy(pclass);

cdev_del(pdev);

kfree(pdev);

unregister_chrdev_region(devno, count);

}

module_init(demo_init);

module_exit(demo_exit);

2.2 位原子操作

? ? ? ? 1、設(shè)置位

? ? ? ? void set_bit(int nr, volatile void *addr)

? ? ? ? 設(shè)置addr地址的第nr位,將nr位寫1


? ? ? ? 2、清楚位

? ? ? ? void clear_bit(int nr, unsigned long *addr)

? ? ? ? 清楚addr的第nr位

? ? ? ? 3、改變位

? ? ? ? void change_bit(unsigned long nr, volatile void *addr)

? ? ? ? 反轉(zhuǎn)地址addr處的第nr位

? ? ? ? 4、測試位

? ? ? ? int test_bit(unsigned int nr, const unsigned long *addr)

? ? ? ? 上述操作返回addr地址的第nr位

? ? ? 5、測試并操作位

? ? ? ? int test_and_set_bit(unsigned nr, volatile unsigned long *addr)

? ? ? int test_and_clear_bit(unsigned nr, volatile unsigned long *addr)

? ? ? int test_and_change_bit(unsigned nr,? volatile unsigned long *addr)

? ? ? 上述操作等同于執(zhí)行test后再執(zhí)行操作位相關(guān)函數(shù)

3、自旋鎖

? ? ? 自旋鎖是一種典型的對(duì)臨界資源進(jìn)行互斥訪問的手段,從字面上就很好理解這種機(jī)制,我們可以理解成不斷的輪詢某個(gè)變量,變量沒有被釋放就一直輪詢,知道變量被釋放獲得了臨界資源的訪問權(quán)。

? ? ? linux中與自旋鎖相關(guān)的操作有下面幾個(gè)“

? ? ? 1、定義自旋鎖

? ? ? spinlock_t? lock


? 2、初始化自旋鎖

? ? ? spin_lock_init(spinlock_t? *_lock)

? ? 3、獲得自旋鎖

? ? ? void spin_lock(spinlock_t *lock)? 獲取自旋鎖,如果不成功,則一直獲取直到成功

? ? ? void spin_lock_irq(spinlock_t *lock) 獲取自旋鎖,成功后關(guān)閉中斷,相當(dāng)于spin_lock +local_irq_disable

? ? ? spin_lock_irqsave(lock, flags)? 循環(huán)等待直到自旋鎖解鎖(置為1),然后,將自旋鎖鎖上(置為0)。關(guān)中斷,將狀態(tài)寄存器值存入flags。

? ? ? spin_lock_bh(lock)

int spin_trylock(spinlock_t *lock)? ? ? ? ? 循環(huán)等待直到自旋鎖解鎖(置為1),然后,將自旋鎖鎖上(置為0)。阻止軟中斷的底半部的執(zhí)行。

? ? ? 上述獲得自旋鎖的過程是,若獲得不成功,則直接返回FALSE,成功返回TRUE


? 4、釋放自旋鎖

? ? ? void spin_unlock(spinlock_t *lock)? ? ? ?

? ? ? void spin_unlock_irq(spinlock_t *lock)? 相當(dāng)于spin_unlock+local_irq_enable

? ? ? spin_unlock_irqrestore(lock, flags)? 將自旋鎖解鎖(置為1)。開中斷,將狀態(tài)寄存器值從flags存入狀態(tài)寄存器。

? ? ? ? s pin_unlock_bh(lock)? ? ? ? ? ? ? ? ? ? 將自旋鎖解鎖(置為1)。開啟底半部的執(zhí)行。

自旋鎖的使用過程

? ? ? /*定義一個(gè)自旋鎖*/

? ? ? ? spinlock_t? lock;

? ? ? /*初始化該自旋鎖*/

? ? ? spin_lock_init(&lock);

? ? ? /*獲取自旋鎖*/

? ? spin_lock(&lock);


? ? /*執(zhí)行臨界操作*/

? ? ......

? ? /*釋放自旋鎖*/

? ? spin_unlock(&lock);

在有中斷搶占資源的情況下,我們一般在進(jìn)程中調(diào)用spin_lock_irqsave/spin_unlock_irqrestore,在中斷中調(diào)用spin_lock/spin_unlock來配合使用

我們?cè)谑褂米孕i時(shí)要非常謹(jǐn)慎,主要因?yàn)橐韵聨c(diǎn)

1、自選鎖相當(dāng)于在不斷輪詢,在等待自旋鎖時(shí),當(dāng)前CPU只是在無意義的等待,無法做其他事情。所以自旋鎖內(nèi)的臨界區(qū)一定要盡量短。

2、自旋鎖可能導(dǎo)致死鎖,例如在獲取了鎖之后,再次獲取一下鎖,該CPU將會(huì)死鎖。

3、在自旋鎖期間,不能調(diào)用可能引起進(jìn)程調(diào)度的函數(shù),如果進(jìn)程獲得自旋鎖之后在阻塞,則可能引起內(nèi)核的崩潰

例程:驅(qū)動(dòng)文件不能同時(shí)打開

#include <linux/init.h>?

#include <linux/module.h>?

#include <linux/kernel.h>?

#include <linux/cdev.h>?

#include <linux/fs.h>?

#include <linux/slab.h>?

#include <linux/device.h>?



MODULE_LICENSE("GPL");?


dev_t devno;?

int major = 0;?

int minor = 0;?

int count = 1;?

int open_count =0;

struct cdev *pdev;?


struct class * pclass;?

struct device * pdevice;?

static? spinlock_t open_lock;? //+++++++++++++++++++++++

int demo_open(struct inode * inodep, struct file * filep)?

{?

spin_lock(&open_lock);? ? ? ? ? //+++++++++++++++++++++

if(open_count){? ? ? ? ? ? ? ? //++++++++++++++++++++

spin_unlock(&open_lock);//++++++++++++++++

return -EBUSY;? ? ? ? ? //++++++++++++++++++

}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //+++++++++++++++++++

open_count++;? ? ? ? ? ? ? ? ? //++++++++++++++++++++++

spin_unlock(&open_lock);? ? ? //+++++++++++++++++++++

? ? printk("%s,%d\n", __func__, __LINE__);?


? ? return 0;?

}?


int demo_release(struct inode *inodep, struct file *filep)?

{?

spin_lock(&open_lock);? ? ? //++++++++++++++++++++

open_count--;? ? ? ? ? ? ? ? //+++++++++++++++++++

spin_unlock(&open_lock);? ? //++++++++++++++++++

? ? printk("%s,%d\n", __func__, __LINE__);?


? ? return 0;?

}?


struct file_operations? fops = {?

? ? .owner =THIS_MODULE,?

? ? .open = demo_open,?

? ? .release = demo_release,?

};?


static int __init demo_init(void)?

{?

? ? int ret = 0;?


? ? printk("%s,%d\n", __func__, __LINE__);?


? ? ret = alloc_chrdev_region(&devno,minor,count, "xxx");?

? ? if(ret)?

? ? {?

? ? ? ? printk("Failed to alloc_chrdev_region.\n");?

? ? ? ? return ret;?

? ? }?

? ? printk("devno:%d , major:%d? minor:%d\n", devno, MAJOR(devno), MINOR(devno));?


? ? pdev = cdev_alloc();?

? ? if(pdev == NULL)?

? ? {?

? ? ? ? printk("Failed to cdev_alloc.\n");?

? ? ? ? goto err1;?

? ? }?


? ? cdev_init(pdev, &fops);?


? ? ret = cdev_add(pdev, devno, count);?

? ? if(ret < 0)?

? ? {?

? ? ? ? printk("Failed to cdev_add.");?

? ? ? ? goto err2;?

? ? }?


? ? pclass = class_create(THIS_MODULE, "myclass");?

? ? if(IS_ERR(pclass))?

? ? {?

? ? ? ? printk("Failed to class_create.\n");?

? ? ? ? ret = PTR_ERR(pclass);?

? ? ? ? goto err3;?

? ? }?


? ? pdevice = device_create(pclass, NULL, devno, NULL, "hello");?

? ? if(IS_ERR(pdevice))?

? ? {?

? ? ? ? printk("Failed to device_create.\n");?

? ? ? ? ret = PTR_ERR(pdevice);?

? ? ? ? goto err4;?

? ? }?



? ? return 0;?

err4:?

? ? class_destroy(pclass);?

err3:?

? ? cdev_del(pdev);?

err2:?

? ? kfree(pdev);?

err1:?

? ? unregister_chrdev_region(devno, count);?

? ? return ret;?

}?


static void __exit demo_exit(void)?

{?

? ? printk("%s,%d\n", __func__, __LINE__);?


? ? device_destroy(pclass, devno);?

? ? class_destroy(pclass);?

? ? cdev_del(pdev);?

? ? kfree(pdev);?

? ? unregister_chrdev_region(devno, count);?


}?



module_init(demo_init);?

module_exit(demo_exit);

4、信號(hào)量

信號(hào)量和應(yīng)用層中思路是一樣的,原理就不再詳細(xì)講了,主要還是PV操作

1、定義信號(hào)量

struct semaphore sem;

2、初始化信號(hào)量

void sema_init(struct semaphore *sem, int val)

初始化信號(hào)量值為val

3、獲取信號(hào)量P操作

void down(struct semaphore *sem);

int? down_interruptible(struct semaphore *sem);

int down_trylock(struct semaphore *sem)

前兩個(gè)的區(qū)別是,第一個(gè)函數(shù)獲取信號(hào)量不成功后,此時(shí)沒有信號(hào)(不是信號(hào)量)要打斷執(zhí)行,就進(jìn)入休眠,直到被cup喚醒。這中間誰都無法打斷。第二個(gè)函數(shù)則不同,獲取信號(hào)量不成功后,此時(shí)沒有信號(hào)打斷,就進(jìn)入休眠,休眠期間可以被信號(hào)打斷。網(wǎng)上看到一個(gè)例子很形象,天黑了,就睡覺,直到天亮了再醒來,這就是down函數(shù)。天黑了,睡覺,天還沒亮,鬧鐘響了,那就醒來把。這樣做是為了防止出現(xiàn)信號(hào)量死鎖,整個(gè)程序掛掉了。就比如北極冬天出現(xiàn)極夜現(xiàn)象,但不能就此一睡不醒,還是可以被鬧鐘叫醒的。

第三個(gè)是不引起阻塞的方式,如果獲取不到,返回非0錯(cuò)誤值,繼續(xù)向下執(zhí)行。

4、釋放信號(hào)量V操作

void up(struct semaphore *sem);

5、互斥體

互斥我們?cè)趹?yīng)用層也用過,思路也沒什么好說的了,直接上函數(shù)把

1、定義互斥體

struct mutex my_mutex;

2、初始化互斥體

mutex_init(mutex)?

3、獲取互斥體

void? mutex_lock(struct mutex *lock)

mutex_lock_interruptible(struct mutex *lock)

int? mutex_trylock(struct mutex *lock)

與信號(hào)量類似

4、釋放互斥體

void? mutex_unlock(struct mutex *lock)

信號(hào)量和互斥體功能很類似,對(duì)于保護(hù)臨界資源,我們一般使用互斥體就行,對(duì)于生產(chǎn)/消費(fèi)的類似問題,我們可以用信號(hào)量來解決。

我們來對(duì)比一下原子操作、自旋鎖、互斥體這三者的區(qū)別。

? ? ? ? 這三者我們都可以理解成設(shè)置一個(gè)標(biāo)志位、標(biāo)志位自增、標(biāo)志位自減,獲取標(biāo)志位這幾個(gè)過程。他們最大的區(qū)別在于獲取不成功時(shí)下一步動(dòng)作。

? ? 原子操作獲取不成功,跳過向下執(zhí)行,就像if(flag) else的過程。

? ? 自旋鎖獲取不成功會(huì)一直等待,就是if(flag)--->if(flag)--->if(flag)--->if(flag),或者再直接點(diǎn),while(!flag);獲取不成功就不走了。

? ? 互斥體獲取不成功,相當(dāng)于if(flag),else{ 切換進(jìn)程 } (當(dāng)然,互斥體也是存在獲取不成功,直接返回,執(zhí)行下面其他程序的,跟原子操作很像)

我們主要來區(qū)別一下自旋鎖和互斥體選用的原則。

1、當(dāng)鎖不能被獲取時(shí),使用互斥體的開銷是進(jìn)程上下文的切換時(shí)間,自旋鎖則是等待獲取自旋鎖的時(shí)間。若臨界區(qū)很小,自旋鎖的等待時(shí)間我們是可以接受的,如果臨界區(qū)很大,使用互斥體較好

2、若臨界區(qū)包含會(huì)引起阻塞的代碼,就必須用互斥體了,在自旋鎖的情況下包含阻塞代碼將引起程序死鎖。

3、在中斷中或軟中斷中是不允許出現(xiàn)休眠情況的,所以如果在中斷或軟中斷中保護(hù)臨界資源,那就使用自旋鎖或者是不引起阻塞的互斥體mutex_trylock。

————————————————

版權(quán)聲明:本文為CSDN博主「念念有余」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/u012142460/article/details/79017329

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容