Linux設(shè)備驅(qū)動(dòng)中的并發(fā)控制
并發(fā)指的是多個(gè)執(zhí)行單元同時(shí)、并行被執(zhí)行,而并發(fā)的執(zhí)行單元對(duì)共享資源的訪問(wèn)則很容易導(dǎo)致競(jìng)態(tài)。
linux內(nèi)核中主要競(jìng)態(tài)
1.多對(duì)稱處理器的多個(gè)CPU
2.單CPU內(nèi)進(jìn)程與搶占它的進(jìn)程
3.中斷(硬中斷、軟中斷、Tasklet、下半部)與進(jìn)程之間
訪問(wèn)共享內(nèi)存資源的代碼區(qū)稱為“臨界區(qū)”,臨界區(qū)需要被以某種互斥機(jī)制加以保護(hù),中斷屏蔽、原子操作、自旋鎖和信號(hào)量等,是linux設(shè)備驅(qū)動(dòng)中可采用的互斥途徑。
這幾個(gè)互斥的介紹:
1.中斷屏蔽,這個(gè)主要用于單CPU,中斷屏蔽將使得中斷和進(jìn)程之間的并發(fā)不再發(fā)生。
使用方法:
- local_irq_disable();//屏蔽中斷
- ...
- ...
- 臨界區(qū)
- ...
- local_irq_enable();//開中斷
由于linux的異步IO、進(jìn)程調(diào)度等很多重要的操作都依賴于中斷,中斷對(duì)于內(nèi)核的運(yùn)行非常重要,在屏蔽中斷期間所有的中斷都無(wú)法處理,因此長(zhǎng)時(shí)間的屏蔽中斷很危險(xiǎn),有可能導(dǎo)致數(shù)據(jù)丟失甚至系統(tǒng)崩潰。所以這個(gè)不作為重點(diǎn)討論。
**********************************************************************************************************************************************************
2.原子操作,原子操作是一系列的不能被打斷的操作。
linux內(nèi)核提供了一系列的函數(shù)來(lái)實(shí)現(xiàn)內(nèi)核中的原子操作,這些函數(shù)分為2類,分別針對(duì)位和整型變量進(jìn)行原子操作。
實(shí)現(xiàn)原子操作的步驟:
1).定義原子變量并設(shè)置變量值
- void atomic_set(atomic_t *v , int i); //設(shè)置原子變量值為i
- atomic_t v = ATOMIC_INIT(0); //定義原子變量v,初始化為0
2).獲取原子變量的值
- atomic_read(atomic_t *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).操作并測(cè)試:對(duì)原子變量執(zhí)行自增、自減后(沒(méi)有加)測(cè)試其是否為0,如果為0返回true,否則返回false。
- 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);
**********************************************************************************************************************************************************
3.自旋鎖
自旋鎖是一個(gè)忙鎖,它在一個(gè)小的循環(huán)內(nèi)不斷的重復(fù)測(cè)試并設(shè)置的操作。
自旋鎖保護(hù)臨界區(qū)的特點(diǎn):臨界區(qū)要小,并且臨界區(qū)內(nèi)不能有導(dǎo)致睡眠的操作,否則可能引起系統(tǒng)崩潰。自旋鎖可能導(dǎo)致系統(tǒng)死鎖,引發(fā)這個(gè)問(wèn)題最常見(jiàn)的情況是遞歸使用一個(gè)自旋鎖。
自旋鎖的操作步驟:
1).定義自旋鎖
- spinlock_t lock;
2).初始化自旋鎖
- spin_lock_init(lock); //這是個(gè)宏,它用于動(dòng)態(tài)初始化自旋鎖lock;
3).獲得自旋鎖
- spin_lock(lock);//該宏用于加鎖,如果能夠立即獲得鎖,它就能馬上返回,否則,他將自旋在那里,直到該自旋鎖的保持者釋放。
- spin_trylock(lock);//能夠獲得,則返回真,否則返回假,實(shí)際上是不在原地打轉(zhuǎn)而已。
4).釋放自旋鎖
- spin_unlock(lock);
與上面的兩個(gè)配對(duì)使用。
例子:
- spinlock_t lock;
- spin_lock_init(&lock);
- spin_lock(&lock); //獲取自旋鎖,保護(hù)臨界區(qū)。。。。臨界區(qū)
- spin_unlock(&lock);//釋放自旋鎖
自旋鎖不關(guān)心鎖定的臨界區(qū)究竟是如何執(zhí)行的。不管是讀操作還是寫操作,實(shí)際上,對(duì)共享資源進(jìn)行讀取的時(shí)候是應(yīng)該可以允許多個(gè)執(zhí)行單元同時(shí)訪問(wèn)的,那么這樣的話,自旋鎖就有了弊端。于是便衍生出來(lái)一個(gè)讀寫鎖。
它保留了自旋的特性,但在對(duì)操作上面可以允許有多個(gè)單元進(jìn)程同時(shí)操作。當(dāng)然,讀和寫的時(shí)候不能同時(shí)進(jìn)行。
現(xiàn)在又有問(wèn)題了,如果我第一個(gè)進(jìn)程寫共享資源,第二個(gè)進(jìn)程讀的話,一旦寫了,那么就讀不到了,可能寫的東西比較多,但是第二個(gè)進(jìn)程讀很小,那么能不能第一個(gè)進(jìn)程寫的同時(shí),我第二個(gè)進(jìn)程讀呢?
當(dāng)然可以,那么引出了順序鎖的概念。都是一樣的操作。
**********************************************************************************************************************************************************
4.信號(hào)量
是用于保護(hù)臨界區(qū)的一種常用的方法,它的使用與自旋鎖差不多,但是它不在原地打轉(zhuǎn),當(dāng)獲取不到信號(hào)量時(shí)候,進(jìn)程會(huì)進(jìn)入休眠等待狀態(tài)。
主要操作:
1).定義sem信號(hào)量
- struct semaphore sem;
2).初始化信號(hào)量
- void sema_init(struct semaphore *sem, int val);
初始化信號(hào)量,并設(shè)置sem的值為val
初始化的時(shí)候還可以這樣用,init_MUTEX(sem),這是個(gè)宏 #define init_MUTEX(sem) sema_init(sem , 1)
init_MUTEX_LOCKED(sem),這是個(gè)宏 #define init_MUTEX_LOCKED(sem) sema_init(sem , 0)
3).獲得信號(hào)量
- void down(struct semaphore * sem);
該函數(shù)用于獲得信號(hào)量sem,他會(huì)導(dǎo)致睡眠,所以不能在中斷中使用。
- int down_interruptible(struct semaphore* sem);
與上面功能類似,因?yàn)閐own進(jìn)入睡眠狀態(tài)的進(jìn)程不能被信號(hào)打斷,而它能被信號(hào)打斷,信號(hào)也會(huì)導(dǎo)致該函數(shù)返回。
- int down_trylock(struct semaphore * sem);
4).釋放信號(hào)量
- void up(struct semaphore * sem);
該函數(shù)用于釋放信號(hào)量,同時(shí)喚醒等待者。
信號(hào)量一般這樣使用:
- DECLARE_MUTEX(sem);
- down(&sem);
- .....臨界區(qū)
- up(&sem);
linux自旋鎖和信號(hào)量采用的“獲取鎖-訪問(wèn)臨界區(qū)-釋放鎖”的方式。
*************************************************************************************************************************
5.互斥體
互斥體和信號(hào)量基本上差不多。不介紹了。
總結(jié):
并發(fā)和競(jìng)態(tài)廣泛存在,這幾個(gè)機(jī)制是解決問(wèn)題的好方法,中斷屏蔽很少單獨(dú)使用,原子操作只能針對(duì)整數(shù)進(jìn)行,因此,自旋鎖和信號(hào)量應(yīng)用最為廣泛。
自旋鎖會(huì)導(dǎo)致死循環(huán),鎖定期間不允許阻塞,因此要求鎖定的臨界區(qū)要小。信號(hào)量允許臨界區(qū)阻塞,可以適用于臨界區(qū)較大的情況。讀寫自旋鎖和讀寫信號(hào)量是放寬了條件的自旋鎖和信號(hào)量,他們?cè)试S多個(gè)進(jìn)程并發(fā)的讀取共享空間。
一個(gè)fifo的綜合例子
- /*======================================================================
- A globalfifo driver as an example of char device drivers
- This example is to introduce poll,blocking and non-blocking access
- The initial developer of the original code is Baohua Song
- <author@linuxdriver.cn>. All Rights Reserved.
- ======================================================================*/#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/slab.h>#include <linux/init.h>#include <linux/cdev.h>#include <asm/io.h>#include <asm/system.h>#include <asm/uaccess.h>#include <linux/poll.h>#include <linux/time.h>#include <linux/timer.h>#include <linux/kernel.h>#include <linux/spinlock.h>#include <linux/interrupt.h>#define GLOBALFIFO_SIZE 0x1000 /*全局fifo最大4K字節(jié)*/#define FIFO_CLEAR 0x1 /*清0全局內(nèi)存的長(zhǎng)度*/#define GLOBALFIFO_MAJOR 250 /*預(yù)設(shè)的globalfifo的主設(shè)備號(hào)*/static int globalfifo_major = GLOBALFIFO_MAJOR;/*globalfifo設(shè)備結(jié)構(gòu)體*/struct globalfifo_dev
- {
- struct cdev cdev; /*cdev結(jié)構(gòu)體*/
- unsigned int current_len; /*fifo有效數(shù)據(jù)長(zhǎng)度*/
- unsigned char mem[GLOBALFIFO_SIZE]; /*全局內(nèi)存*/
- struct semaphore sem; /*并發(fā)控制用的信號(hào)量*/
- wait_queue_head_t r_wait; /*阻塞讀用的等待隊(duì)列頭*/
- wait_queue_head_t w_wait; /*阻塞寫用的等待隊(duì)列頭*/
- struct tasklet_struct tlet;
- };struct globalfifo_dev *globalfifo_devp; /*設(shè)備結(jié)構(gòu)體指針*//*文件打開函數(shù)*/int globalfifo_open(struct inode *inode, struct file *filp)
- { /*將設(shè)備結(jié)構(gòu)體指針賦值給文件私有數(shù)據(jù)指針*/
- filp->private_data = globalfifo_devp; return 0;
- }/*文件釋放函數(shù)*/int globalfifo_release(struct inode *inode, struct file *filp)
- { return 0;
- }/* ioctl設(shè)備控制函數(shù) */static int globalfifo_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg)
- { struct globalfifo_dev *dev = filp->private_data;/*獲得設(shè)備結(jié)構(gòu)體指針*/
- switch (cmd)
- { case FIFO_CLEAR:
- down(&dev->sem); //獲得信號(hào)量
- dev->current_len = 0;
- memset(dev->mem,0,GLOBALFIFO_SIZE);
- up(&dev->sem); //釋放信號(hào)量
- printk(KERN_INFO "globalfifo is set to zero\n");
- break; default: return - EINVAL;
- } return 0;
- }static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)
- {
- unsigned int mask = 0; struct globalfifo_dev *dev = filp->private_data; /*獲得設(shè)備結(jié)構(gòu)體指針*/
- down(&dev->sem);
- poll_wait(filp, &dev->r_wait, wait);
- poll_wait(filp, &dev->w_wait, wait);
- /*fifo非空*/
- if (dev->current_len != 0)
- {
- mask |= POLLIN | POLLRDNORM; /*標(biāo)示數(shù)據(jù)可獲得*/
- } /*fifo非滿*/
- if (dev->current_len != GLOBALFIFO_SIZE)
- {
- mask |= POLLOUT | POLLWRNORM; /*標(biāo)示數(shù)據(jù)可寫入*/
- }
- up(&dev->sem); return mask;
- }/*globalfifo讀函數(shù)*/static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count,
- loff_t *ppos)
- { int ret; struct globalfifo_dev *dev = filp->private_data; //獲得設(shè)備結(jié)構(gòu)體指針
- DECLARE_WAITQUEUE(wait, current); //定義等待隊(duì)列
- down(&dev->sem); //獲得信號(hào)量
- add_wait_queue(&dev->r_wait, &wait); //進(jìn)入讀等待隊(duì)列頭
- /* 等待FIFO非空 */
- if (dev->current_len == 0)
- { if (filp->f_flags &O_NONBLOCK)
- {
- ret = - EAGAIN; goto out;
- }
- __set_current_state(TASK_INTERRUPTIBLE); //改變進(jìn)程狀態(tài)為睡眠
- up(&dev->sem);
- schedule(); //調(diào)度其他進(jìn)程執(zhí)行
- if (signal_pending(current)) //如果是因?yàn)樾盘?hào)喚醒 {
- ret = - ERESTARTSYS; goto out2;
- }
- down(&dev->sem);
- } /* 拷貝到用戶空間 */
- if (count > dev->current_len)
- count = dev->current_len; if (copy_to_user(buf, dev->mem, count))
- {
- ret = - EFAULT; goto out;
- } else
- {
- memcpy(dev->mem, dev->mem + count, dev->current_len - count); //fifo數(shù)據(jù)前移
- dev->current_len -= count; //有效數(shù)據(jù)長(zhǎng)度減少
- printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count, dev->current_len);
- wake_up_interruptible(&dev->w_wait); //喚醒寫等待隊(duì)列
- ret = count;
- } out: up(&dev->sem); //釋放信號(hào)量
- out2:remove_wait_queue(&dev->w_wait, &wait); //從附屬的等待隊(duì)列頭移除 set_current_state(TASK_RUNNING); return ret;
- }/*globalfifo寫操作*/static ssize_t globalfifo_write(struct file *filp, const char __user *buf,
- size_t count, loff_t *ppos)
- { struct globalfifo_dev *dev = filp->private_data; //獲得設(shè)備結(jié)構(gòu)體指針
- int ret;
- DECLARE_WAITQUEUE(wait, current); //定義等待隊(duì)列
- down(&dev->sem); //獲取信號(hào)量
- add_wait_queue(&dev->w_wait, &wait); //進(jìn)入寫等待隊(duì)列頭
- /* 等待FIFO非滿 */
- if (dev->current_len == GLOBALFIFO_SIZE)
- { if (filp->f_flags &O_NONBLOCK) //如果是非阻塞訪問(wèn) {
- ret = - EAGAIN; goto out;
- }
- __set_current_state(TASK_INTERRUPTIBLE); //改變進(jìn)程狀態(tài)為睡眠
- up(&dev->sem);
- schedule(); //調(diào)度其他進(jìn)程執(zhí)行
- if (signal_pending(current)) //如果是因?yàn)樾盘?hào)喚醒 {
- ret = - ERESTARTSYS; goto out2;
- }
- down(&dev->sem); //獲得信號(hào)量 } /*從用戶空間拷貝到內(nèi)核空間*/
- if (count > GLOBALFIFO_SIZE - dev->current_len)
- count = GLOBALFIFO_SIZE - dev->current_len; if (copy_from_user(dev->mem + dev->current_len, buf, count))
- {
- ret = - EFAULT; goto out;
- } else
- {
- dev->current_len += count;
- printk(KERN_INFO "written %d bytes(s),current_len:%d\n", count, dev ->current_len);
- wake_up_interruptible(&dev->r_wait); //喚醒讀等待隊(duì)列
- ret = count;
- }
- tasklet_schedule(&dev->tlet);
- printk("in write jiffies=%ld\n",jiffies); out: up(&dev->sem); //釋放信號(hào)量
- out2:remove_wait_queue(&dev->w_wait, &wait); //從附屬的等待隊(duì)列頭移除 set_current_state(TASK_RUNNING); return ret;
- }/*文件操作結(jié)構(gòu)體*/static const struct file_operations globalfifo_fops ={
- .owner = THIS_MODULE,
- .read = globalfifo_read,
- .write = globalfifo_write,
- .ioctl = globalfifo_ioctl,
- .poll = globalfifo_poll,
- .open = globalfifo_open,
- .release = globalfifo_release,
- };/*初始化并注冊(cè)cdev*/static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
- { int err, devno = MKDEV(globalfifo_major, index);
- cdev_init(&dev->cdev, &globalfifo_fops);
- dev->cdev.owner = THIS_MODULE;
- dev->cdev.ops = &globalfifo_fops;
- err = cdev_add(&dev->cdev, devno, 1); if (err)
- printk(KERN_NOTICE "Error %d adding LED%d", err, index);
- }void jit_tasklet_fn(unsigned long arg)
- {
- printk("in jit_tasklet_fn jiffies=%ld\n",jiffies);
- }/*設(shè)備驅(qū)動(dòng)模塊加載函數(shù)*/int globalfifo_init(void)
- { int ret;
- dev_t devno = MKDEV(globalfifo_major, 0); /* 申請(qǐng)?jiān)O(shè)備號(hào)*/
- if (globalfifo_major)
- ret = register_chrdev_region(devno, 1, "globalfifo"); else /* 動(dòng)態(tài)申請(qǐng)?jiān)O(shè)備號(hào) */
- {
- ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
- globalfifo_major = MAJOR(devno);
- } if (ret < 0) return ret; /* 動(dòng)態(tài)申請(qǐng)?jiān)O(shè)備結(jié)構(gòu)體的內(nèi)存*/
- globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL); if (!globalfifo_devp) /*申請(qǐng)失敗*/
- {
- ret = - ENOMEM; goto fail_malloc;
- }
- memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
- globalfifo_setup_cdev(globalfifo_devp, 0);
- init_MUTEX(&globalfifo_devp->sem); /*初始化信號(hào)量*/
- init_waitqueue_head(&globalfifo_devp->r_wait); /*初始化讀等待隊(duì)列頭*/
- init_waitqueue_head(&globalfifo_devp->w_wait); /*初始化寫等待隊(duì)列頭*/
- /* register the tasklet */
- tasklet_init(&globalfifo_devp->tlet, jit_tasklet_fn, (unsigned long)globalfifo_devp); return 0;
- fail_malloc: unregister_chrdev_region(devno, 1); return ret;
- }/*模塊卸載函數(shù)*/void globalfifo_exit(void)
- {
- cdev_del(&globalfifo_devp->cdev); /*注銷cdev*/
- kfree(globalfifo_devp); /*釋放設(shè)備結(jié)構(gòu)體內(nèi)存*/
- unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); /*釋放設(shè)備號(hào)*/}
- MODULE_AUTHOR("Song Baohua");
- MODULE_LICENSE("Dual BSD/GPL");
- module_param(globalfifo_major, int, S_IRUGO);
- module_init(globalfifo_init);
- module_exit(globalfifo_exit);