姓名:鄭煜爍? 學(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