zoukankan      html  css  js  c++  java
  • 死锁

        死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象。死锁要产生必须具备四个必要条件: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字符驱动中动态分配设备号与动态生成设备节点”中的效果是相同的,只不过这里使用了信号量对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);
    }

    由 __up() 函数中最后执行了wake_up_process() 唤醒另外一个进程,所以globalvar_write()函数中在up()后面的语句都没有执行,于是flag的值还是为0,当然globalvar_write()后面的打印语句也不会执行。当唤醒正在操作globalvar_read()函数的进程之后,globalvar_read()开始执行,globalvar_read() 立刻持有信号量sem,由于此时flag的值为0,wait_event_interruptible()将导致进程进入睡眠,此时就产生了死锁——该进程持有信号量睡眠后将不能释放该信号量。就会导致globalvar_write()函数得不到执行,自然flag的值也不会变为1,globalvar_read()也不会醒,因此产生了死锁。(后面会有文章介绍wait_event_interruptible的原理)

    注意到list_first_entry()这个函数可以获得链表的第一个元素,在内核中这是非常经典的一个东东,分析内核必会。后面还会专门介绍内核中的list.

    下面给出应用程序:

    首先是globalvar_read.c

    View Code
    #include <sys/types.h> 
    #include <sys/stat.h> 
    #include <stdio.h> 
    #include <fcntl.h> 
    int main() 
    { 
        int fd, num; 
     
        fd = open("/dev/CDEV_ZHU", O_RDWR, S_IRUSR | S_IWUSR); 
        if (fd !=    - 1) 
        { 
            while (1) 
            { 
                read(fd, &num, sizeof(int)); //程序将阻塞在此语句,除非有针对 globalvar 的输入 
                printf("The globalvar is %d\n", num); 
     
                //如果输入是 0,则退出 
                if (num == 0) 
                { 
                    close(fd); 
                    break;             } 
            } 
        } 
        else 
        { 
            printf("device open failure\n"); 
        } 
        return 0;
    } 

    然后是globalvar_write.c

    View Code
    #include <sys/types.h> 
    #include <sys/stat.h> 
    #include <stdio.h> 
    #include <fcntl.h> 
    int main() 
    { 
        int fd, num; 
     
        fd = open("/dev/CDEV_ZHU", O_RDWR, S_IRUSR | S_IWUSR); 
        if (fd != -1) 
        { 
            while (1) 
            { 
                printf("Please input the globalvar:\n"); 
                scanf("%d", &num); 
                write(fd, &num, sizeof(int)); 
     
                //如果输入 0,退出 
                if (num == 0) 
                { 
                    close(fd); 
                    break; 
                } 
            } 
        } 
        else 
        { 
            printf("device open failure\n"); 
        } 
        return 0;
    } 

    使用这两个程序可以互斥的对驱动程序中的globalvar进行读写,但是注意驱动中globalvar_read()函数中,代码需要改为先wait_event_interruptible 再 down_interruptible()。

  • 相关阅读:
    Scala泛型
    Tensorflow激活函数
    20181030-4 每周例行报告
    20181023-3 每周例行报告
    20181016-10 每周例行报告
    20181009-9 每周例行报告
    第三周作业(4)——单元测试
    第三周作业(5)——代码规范
    第三周作业(2)——功能测试
    第三周作业(3)——词频统计--效能分析
  • 原文地址:https://www.cnblogs.com/zhuyp1015/p/2524730.html
Copyright © 2011-2022 走看看