Linux裝置驅動開發-linux並發控制中的訊號量機制

來源:互聯網
上載者:User

上一篇講了並發控制的幾種機制,比如中斷屏蔽、原子操作、自旋鎖。下面我們來說一說並發控制的另一個重要的機制:訊號量。

訊號量的使用和自旋鎖類似,得到訊號量的進程進入臨界區,不同的是當擷取不到訊號量的時候,進程不會一直等待,而是進入休眠狀態。訊號量的概念想必大家在學習作業系統原理的時候已經很熟悉了。

那麼下面我們來看一下linux為訊號量提供的操作都有哪些。

首先肯定是如何定義一個訊號量,如:

struct semaphore sem;

這樣就定義了一個訊號量。如何初始化呢?

static inline void sema_init(struct semaphore *sem, int val)

上面函數的功能就是初始化訊號量的,其設定訊號量的值為val。但是一般情況下不會設定訊號量的值大於1,那麼我們再看下面這個初始化訊號量的宏:

#define init_MUTEX(sem)         sema_init(sem, 1)

這個就是將訊號量初始化為1,成為一個互斥的訊號量。而宏:

#define init_MUTEX_LOCKED(sem)  sema_init(sem, 0)

也是初始化訊號量,只是將值設為0。

下面還有另外一種直接定義加初始化訊號量的方法:

#define DECLARE_MUTEX(name)     \        struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

他是直接定義一個訊號量name,並將其值設為1。

初始化知道了,那麼我們就該瞭解如何獲得訊號量:

void down(struct semaphore *sem)

這個函數獲得訊號量會導致睡眠,而且不能被訊號打斷。

int down_interruptible(struct semaphore *sem)

這個跟down()獲得訊號量都會進入睡眠狀態,但是不同的是使用這個進入睡眠狀態後可以被訊號打斷。在使用down_interruptible()獲得訊號量時,還會檢查傳回值,如果非0,返回-ERESTARTSYS。

獲得了訊號量如何釋放呢?與down相反,這裡使用up函數:

void up(struct semaphore *sem)

這個函數釋放訊號量並喚醒等待者。

當訊號量被初始化為0時,其就用於同步,一個執行單元的執行需另一個執行單元完成。

說了自旋鎖與訊號量機制,他們都是解決互斥的基本操作,那麼我們在寫代碼的時候怎樣選擇使用哪個呢?訊號量是進程級的,用於多個進程之間對資源的互斥,其代表進程來爭奪資源,爭取失敗的話就進行進程的環境切換,當前的進程進入休眠狀態。但是大家應該都知道,進程的切換需要花費很大的開銷,所以只有當進程佔用共用資源的時間較長時使用訊號量機制才會比較划算,因為如果時間短的話,老進行進程的切換開銷是很大的。

由上面的分析很明顯就可以看出在什麼情況下使用訊號量和自旋鎖,自旋鎖的花費時間是等待鎖的過程,那麼我們假設這個時間是T,而進程切換的時間是t,那麼當T>t時,我們就使用訊號量,相反,則使用自旋鎖。

我們說過,自旋鎖所使用的共用資源中是嚴禁包含可能引起阻塞的代碼,但是訊號量沒有你這樣的規定。而如果被保護的共用資源需要在中斷或者非強制中斷的情況下使用,那麼我們只能使用自旋鎖,前面說擷取訊號量的時候已經說過,其不能在中斷使用。下來我們看下讀取訊號量,其就和自旋鎖與讀取自旋鎖的關係一樣,讀取訊號量可能引起阻塞,它允許多個讀進程讀取共用資料單元,但是只能有一個寫進程訪問,所以讀寫訊號量機制比訊號量機制條件要寬鬆一點。其裡奴性提供的操作有:

定義和初始化:

struct semaphore my_rws;void init_rwsem(struct rw_semaphore *sem);

讀訊號量擷取:

void __sched down_read(struct rw_semaphore *sem)int down_read_trylock(struct rw_semaphore *sem)

寫訊號量擷取:

void __sched down_write(struct rw_semaphore *sem)int down_write_trylock(struct rw_semaphore *sem)

讀訊號量釋放:

void up_read(struct rw_semaphore *sem)

寫訊號量釋放:

void up_write(struct rw_semaphore *sem)

其實在linux系統中還有一個用於互斥的機制:互斥體,其使用的方法和訊號量是一樣的,這裡就不再講解了。

下面我們考慮上一篇文章所提到的globalmem裝置驅動中存在的互斥可能導致阻塞的情況,那麼我們怎樣去解決它呢?在驅動中,由於我們調用了copy_to_user和copy_from_user函數,其可能引起阻塞。,所以我們根據前面所將,這裡應採取訊號量的機制來避免竟態的發生。這裡我們將訊號量放入裝置結構體,如下:

struct globalmem_dev{        struct cdev cdev;        unsigned char mem[GLOBALMEM_SIZE];struct semaphore sem;};

初始化函數的改變:

int globalmem_init(void){        int result;        dev_t devno=MKDEV(globalmem_major,0);        if(globalmem_major)                result=register_chrdev_region(devno,1,"globalmem");        else{                result=alloc_chrdev_region(&devno,0,1,"globalmem");                globalmem_major=MAJOR(devno);        }        if(result<0)                return result;       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));        globalmem_setup_cdev(&globalmem_devp,0);init_MUTEX(&globalmem_devp->sem);        return 0;fail_malloc:unregister_chrdev_region(devno,1);return result;}

read、write和ioctl模組的改動如下:

static ssize_t globalmem_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos){unsigned long p = *ppos;int ret = 0;struct globalmem_dev *dev = filp->private_data;if(p>=GLOBALMEM_SIZE)return 0;if(count>GLOBALMEM_SIZE-p)count = GLOBALMEM_SIZE-p;if(down_interruptible(&dev->sem))return -ERESTARTSYS;if(copy_to_user(buf,dev->mem+p,count))ret = -EFAULT;else{*ppos += count;ret = count;printk(KERN_INFO "read %d bytes(s) from %d\n",count,p);}up(&dev->sem);return ret;}

static ssize_t globalmem_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos){        unsigned long p = *ppos;        int ret = 0;struct globalmem_dev *dev = filp->private_data;        if(p>=GLOBALMEM_SIZE)                return 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;}

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);up(&dev->sem);printk(KERN_INFO "globalmem is set to zero\n");break;default:return -EINVAL;}return 0;}

完整的代碼大家下去自己整理一下,也可以對照原來的代碼進行改動。

這些就是對於linux的並發控制的一個大概的講解,其實原理不是很難,下面還需自己動手應用起來。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.