死鎖是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象。死鎖要產生必須具備四個必要條件:1. 互斥條件 2. 請求和保持條件 3.不可剝奪條件 4. 環路等待條件。由於資源占用是互斥的,當某個進程提出申請資源後,使得有關進程在無外力協助下,永遠分配不到必需的資源而無法繼續運行,這就產生了一種特殊現象死鎖。
下面舉一個Linux環境下產生死鎖的程序(首先是驅動部分):
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/semaphore.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/sched.h>
MODULE_LICENSE("GPL");
#define init_MUTEX(LOCKNAME) sema_init(LOCKNAME,1)
#define DEVICE_NAME "CDEV_ZHU"
static struct class *cdev_class;
struct cdev dev_c;
dev_t dev;
static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);
static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);
struct file_operations globalvar_fops =
{
read: globalvar_read, write: globalvar_write,
};
static int global_var = 0;
static struct semaphore sem;
static wait_queue_head_t outq;
static int flag = 0;
static int __init globalvar_init(void)
{
int ret,err;
ret = alloc_chrdev_region(&dev,0,1,DEVICE_NAME) ;
if (ret)
{
printk("globalvar register failure");
}
else
{
cdev_init(&dev_c,&globalvar_fops);
err = cdev_add(&dev_c,dev,1);
if(err)
{
printk(KERN_NOTICE "error %d adding FC_dev\n",err);
unregister_chrdev_region(dev, 1);
return err;
}
else
{
printk("device register success! \n");
}
cdev_class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(cdev_class))
{
printk("ERR:cannot create a cdev_class\n");
unregister_chrdev_region(dev, 1);
return -1;
}
device_create(cdev_class, NULL, dev, 0, DEVICE_NAME);
init_MUTEX(&sem);
init_waitqueue_head(&outq);
}
return ret;
}
static void __exit globalvar_exit(void)
{
device_destroy(cdev_class,dev);
class_destroy(cdev_class);
unregister_chrdev_region(dev,1);
printk("globalvar exit \n");
}
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
/*
//正常情況下這裡不應該注釋,順序應該是先wait_event_interruptible,再down_interruptible才不會導致死鎖
if (wait_event_interruptible(outq, flag != 0))
{
return - ERESTARTSYS;
}
*/
if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}
/*
交換了 down_interruptible 和 wait_event_interruptible 會造成死鎖通過添加打印語句,可以發現會打印 “size semaphore”其它的像“wake up”,
global_write()函數中的”write_down”和”waking up” 都不會打印,說明在up(&sem)之後 global_read()會立刻獲取該信號量,然後進入睡眠。
*/
printk(“size semaphore \n”);
if (wait_event_interruptible(outq, flag != 0))
{
return - ERESTARTSYS;
}
printk("wake up !\n");
flag = 0;
if (copy_to_user(buf, &global_var, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
up(&sem);
return sizeof(int);
}
static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}
if (copy_from_user(&global_var, buf, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
up(&sem);
flag = 1;
printk("write done!\n");
wake_up_interruptible(&outq);
printk("waking up \n");
return sizeof(int);
}
module_init(globalvar_init);
module_exit(globalvar_exit);
說明:該驅動和前一篇文章“Linux字符驅動中動態分配設備號與動態生成設備節點”中的效果是相同的(見 http://www.linuxidc.com/Linux/2014-03/97438.htm),只不過這裡使用了信號量對globalvar_write 和 globalvar_read函數進行了阻塞控制,當上層應用程序寫入驅動改變globalvar之後,讀操作才可以進行。
上面代碼為什麼會出現死鎖呢,可以查閱內核中的up()函數的定義:
/* /kernel/semaphore.c */
void up(struct semaphore *sem)
{
unsigned long flags;
spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;
else
__up(sem);
spin_unlock_irqrestore(&sem->lock, flags);
}
static noinline void __sched __up(struct semaphore *sem)
{
struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
struct semaphore_waiter, list);
list_del(&waiter->list);
waiter->up = 1;
wake_up_process(waiter->task);
}