Linux設備驅動併發控制
1. 併發及競態的概念
1.1 併發與競態的概念
併發:指多個執行單元同時、併發地被執行;
競態:併發執行單元的運行,很容易會對共享資源(包含硬件、軟件的全局變量靜態變量等)的訪問造成競態。
1.2 競態發生的幾種情況
在Linux內核中,有如下幾種競態:
1)對稱多處理器的多個CPU;
2)單CPU內的進程與搶佔它的進程之間;
3)進程與中斷(硬中斷、軟中斷、tasklet、底半部)之間。
2.併發控制的具體機制
2.1 中斷屏蔽
2.2 原子操作
void atomic_set(atomic_t *v, int i); /*設置原子變量的值爲i*/
atomic_t v = ATOMIC_INIT(0);/*定義原子變量v並初始化爲0*/
atomic_read(atomic_t *v);/*返回原子變量的值*/
void atomic_add(int i, atomic_t *v); /*原子變量增加i*/
void atomic_sub(int i, atomic_t *v); /*原子變量減少i*/
void atomic_inc(atomic_t *v); /*原子變量自增1*/
void atomic_dec(tomic_t *v); /*原子變量自減1*/
5)操作並測試
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
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);
2.3 位作原子操作
void set_bit(nr, void *addr);
2) 清除位
void clear_bit(nr, void *addr);
3)改變位
void change_bit(nr, void *addr);
4)測試位
test_bit(nr, void * addr);
5)測試並操作位
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);
2.3 自旋鎖
1. 自旋鎖
理解自選鎖的最簡單的方法是把自旋鎖看成是一個變量。該變量把一個臨界區標記爲“我當前在運行,請稍等一會”或標記爲“我當前不在運行,可以被使用”。自旋鎖的相關操作由如下4種:
1)定義自旋鎖
spinlock_t lock;
2)初始化自旋鎖
spin_lock_init(lock);
3)獲得自旋鎖
spin_lock(lock);<span style="white-space:pre"> </span>/*如果能獲得自旋鎖將立即返回,否則將自旋在那裏。*/
spin_trylock(lock);<span style="white-space:pre"> </span>/*如果獲得自旋鎖將返回真,否則將返回假*/
4)釋放自旋鎖
spin_unlock(lock);
自旋鎖主要是針對SMP或單CPU內內核可被將佔的情況,無法避免中斷和底半部的影響,因而在自旋鎖的基礎上有了衍生。
spin_lock_irq();
spin_lock_irqsave();
spin_lock_bh();
注意:
1)自旋鎖實際上爲忙等待,只使用於較短時間的佔用,當臨界區很大,或臨界區內有共享設備時,長時間的佔用可能或降低系統的性能;
2)自旋鎖可能會導致死鎖,例如對自旋鎖的遞歸調用,當一個CPU試圖調用一個已經佔用的自旋鎖時就會產生死鎖;
3)自旋鎖期間不能調用系統調用的函數,如copy_from_user()、copy_to_user()、kmalloc()、msleep()等。
2. 讀寫鎖
在自旋鎖的基礎上粒度更細,在資源共享方面,運行有1個寫的執行單元,可以有多個讀的共享單元。
讀寫所一般這樣被使用:
/*定義讀寫所*/
rwlock_t lock;
/*初始化讀寫鎖*/
rwlock_init(&lock);
/*讀時獲取鎖*/
read_lock(&lock);
.../*臨界資源*/
read_unlock(&lock);
/*寫時獲取鎖*/
write_lock_irqsave(&lock, flags);
.../*臨界資源*/
write_unlock_irqrestore(&lock, flags);
3. 順序鎖
/*讀開始*/
unsigned read_seqbegin(const seqlock_t *sl);
read_seqbegin_irqsave(lock, flags);
/*釋放鎖*/
read_seqretry_irqrestore(lock, iv, flags);
/*重讀*/
do
{
seqnum = read_seqbegin(&seqlock_a);
} while(read_seqretry(&seqlock_a, seqnum) )
4. RCU(讀-拷貝-更新)
2.4 信號量
1. 信號量的概念
信號量的是在保護臨界區一種較爲常見的方法,使用同自旋鎖;與自旋鎖不同的是,自旋鎖無法獲取時系統進入忙等待,而信號量是進入休眠狀態。
信號量的使用方法:
1)定義:
<span style="font-weight: normal;">struct semaphore sem;</span>
2) 信號量的初始化
<span style="font-weight: normal;">/*信號量初始化*/
sema_init(struct semaphore *sem, int val); /*將信號量初始化爲val*/
/*一般的使用方法被定義爲宏*/
#define init_MUTEX(sem) sema_init(sem, 1) /*初始化信號量爲1*/
#define init_MUTEX_LOCKED(sem) sem_init(sem, 0) /*初始化信號量爲0*/</span>
3) 信號量的獲取
<span style="font-weight: normal;">/*用於獲取信號量sem, 會導致睡眠,因此不能在中斷上下文中使用,不能被信號打斷*/
void down(struct semaphore *sem);
/*會被信號打斷;有信號打斷則返回非0*/
int down_interruptible(struct semaphore *sem);
if(down_interruptible(&sem) )
return - ERESTARTSYS;
/*如果可以獲取信號量則立即獲取並返回0,如果不可以獲取則立即返回非0值。不會導致調用者睡眠,可被用在中斷上下文*/
int down_trylock(struct semaphore *sem);</span>
4) 信號量的釋放<span style="font-weight: normal;">void up(struct semaphore *sem);</span>
一般使用方法:
<span style="font-weight: normal;">DECLARE_MUTEX(mount_sem);
down(&mount_sem); /*獲取信號量,包含臨界區*/
...
critical section /*臨界區*/
...
up(&mount_sem); /*釋放信號量*/</span>
信號量的同步意味着代碼的順序執行,使用方法上可以將信號量初始化爲0;如下圖,執行單元A在執行代碼b前,需在執行單元b執行完代碼c之後。
3. 完成量用於同步
代碼的同步也可以用完成量來實現,功能需求同2中信號量的同步,實現過程如下圖所示。
4. 信號量VS自旋鎖
信號量和自旋鎖都是較爲常用的互斥手段,只是適用的層次不同,信號量的實現依賴於自旋鎖,爲了保證信號量結構存取的原子性,在多CPU中需要用自旋鎖來互斥;而信號量適用於進程間的競爭,因而這樣進程上下文的切換開銷很大。因此,當佔用資源時間較長時適用信號量,較短時適用自旋鎖。選擇哪個要看臨界區的性質和系統特點。
1)使用信號量的開銷是進程上下文切換時間Tsw,使用自旋鎖的開銷是等待自旋鎖Tcs,如果Tcs較小則使用自旋鎖,否則使用信號量;
2)當臨界區可能存在阻塞時,因爲阻塞會引起進程切換,如果使用自旋鎖會導致死鎖,因此這是使用信號量;
3)如果共享資源是在中斷或軟中斷下使用的,則使用自旋鎖。
使用方法:
struct rw_semaphore my_rws; /*定義讀寫信號量*/
init_rwsem(struct rw_semaphore *sem); /*初始化讀寫信號量*/
/*讀時獲取信號量*/
down_read(&rw_sem);
...
up_read(&rw_sem);
/*寫時獲取信號量*/
down_write(&rw_sem);
...
up_write(&rw_sem);
2.5 互斥體
使用方法:
struct mutex my_mutex; /*定義互斥體*/
mutex_init(&my_mutex); /*初始化互斥體*/
mutex_lock(&my_mutex); /*獲取互斥體*/
...
mutex_unlock(&my_mutex); /*釋放互斥體*/
3. 應用實例
/************************************************************************
* Filename: globalmem.c
* File Description: A virtual mem char driver
* Author: Vinvian Cheng
* Emaile:
* Ver: 1.0.0
* Date: 2014.11.11
* History:
* 1.
************************************************************************/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
/*struct mem*/
#define GLOBALMEM_SIZE<span style="white-space:pre"> </span>0x1000<span style="white-space:pre"> </span>/*全局內存最大4K字節*/
#define MEM_CLEAR 0x1 /*清0全局內存*/
#define GLOBALMEM_MAJOR 254 /*預設的globalmem的主設備號*/
static globalmem_major = GLOBALMEM_MAJOR;
/*globalmem struct*/
struct globalmem_dev{
struct cdev cdev; /*cdev struct*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局內存,私有數據*/
struct semaphore sem; /*信號量*/
};
/*dev struct piont*/
struct globalmem_dev *globalmem_devp = NULL;
/*文件打開函數*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*將設備結構體指針賦值給文件私有數據指針*/
filp->private_data = globalmem_devp;
return 0;
}
/*文件釋放函數*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ioctl設備控制函數 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*獲得設備結構體指針*/
switch (cmd)
{
case MEM_CLEAR:
{
if (down_interruptible(&dev->sem))
{
return - ERESTARTSYS;
}
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
up(&dev->sem);
break;
}
default:
return - EINVAL;
}
return 0;
}
/*讀函數*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
/*分析和獲取有效的寫長度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
if (down_interruptible(&dev->sem))
{
return - ERESTARTSYS;
}
/*內核空間->用戶空間*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p);
}
up(&dev->sem);
return ret;
}
/*寫函數*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
/*分析和獲取有效的寫長度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
if (down_interruptible(&dev->sem))
{
return - ERESTARTSYS;
}
/*用戶空間->內核空間*/
if (copy_from_user(dev->mem + p, buf, count) )
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
up(&dev->sem);
return ret;
}
/* seek文件定位函數 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0: /*相對文件開始位置偏移*/
if (offset < 0)
{
ret = - EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /*相對文件當前位置偏移*/
if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
if ((filp->f_pos + offset) < 0)
{
ret = - EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;
}
/*文件操作結構體*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/***********************************************************************
* Function Name: globalmem_setup_cdev
* Paramter:
* struct globalmem_dev *dev [INOUT];
* int index [IN];
* Function Descrition:
* cdev init and register.
* Return: void
* Author: Vinvian 2014/11/11
***********************************************************************/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err = 0;
int devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops); //dev init
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/***********************************************************************
* Function Name: globalmem_init
* Paramter: type name [IN]: void
* Function Descrition:
* Module init
* Return: 0
* Error:
* Author: Vinvian 2014/11/11
***********************************************************************/
static int globalmem_init(void)
{
printk(KERN_INFO " Globlamem Driver for init!\n");
int result;
dev_t devno = MKDEV(globalmem_major, 0); //Create det_t with major and minor devNo
/*Regsiter devNo*/
if (globalmem_major) //has major no
result = register_chrdev_region(devno, 1, "globalmem");
else //no major no
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/*malloc dev struct mem*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) /*申請失敗*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
/*init cdev*/
globalmem_setup_cdev(globalmem_devp, 0);
/*init sema*/
sema_init(&globalmem_devp->sem, 1);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/***********************************************************************
* Function Name: globalmem_exit
* Paramter: type name [IN]: void
* Function Descrition:
* Module exit
* Return: void
* Error:
* Author: Vinvian 2014/11/11
***********************************************************************/
void globalmem_exit(void)
{
/*release cdev*/
cdev_del(&globalmem_devp->cdev);
/*release struct dev mem*/
kfree(globalmem_devp);
/*release devNo*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
module_init(globalmem_init);
module_exit(globalmem_exit);
module_param(globalmem_major, int, S_IRUGO);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Vinvian");
MODULE_DESCRIPTION("A globalmem driver Module");
MODULE_ALIAS("A globalmem driver");