• linux驱动学习(3)同步、信号量和自旋锁


    在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源) ,可能会引发“竞态” ,因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用) 。

    自旋锁与信号量“类似而不类” ,类似说的是它们功能上的相似性, “不类”指代它们在本质和实现机理上完全不一样,不属于一类。

    自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁, “自旋”就是“在原地打转” 。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的“不类” 。

    但是,无论是信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。这就是它们的“类似” 。

    鉴于自旋锁与信号量的上述特点,一般而言,自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用;信号量适合于保持时间较长的情况,会只能在进程上下文使用。如果被保护的共享资源只在进程上下文访问,则可以以信号量来保护该共享资源,如果对共享资源的访问时间非常短,自旋锁也是好的选择。但是,如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断) ,就必须使用自旋锁。

    先贴代码

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <asm/uaccess.h>
    #include <asm/spinlock.h>
    #include <linux/spinlock.h>
    #include <linux/semaphore.h>
    
    /*
     * xiaoyang yi @HIT 2011-9-22
     * char device driver with sync(semaphore and spinlock)
     */
    
    MODULE_LICENSE("GPL");
    
    static int MAJOR_NUM=0;
    static struct semaphore sem;
    static int global_var = 0;
    static int global_var_count = 0;
    static spinlock_t spin;
    
    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*);
    static int globalvar_open(struct inode*node, struct file* fp);
    static int globalvar_release(struct inode*node, struct file* fp);
    
    /*init the file_operation structure*/
    static struct file_operations globalvar_fpos={
    	.read = globalvar_read,
    	.write = globalvar_write,
    	.open = globalvar_open,
    	.release = globalvar_release,
    };
    
    static int __init globalvar_init(void)
    {
    	int ret;
    
    	/*register device drivre*/
    	ret = register_chrdev(MAJOR_NUM,"globalvar",&globalvar_fpos);
    	if(ret < 0){
    		printk("globalvar reg failed!\n");
    	}else{
    		printk("globalvar reg ok\n");
    		spin_lock_init(&spin);
    	}
    	
    	if(MAJOR_NUM == 0){
    		MAJOR_NUM = ret;
    	}
    
    	sema_init(&sem,1);
    	return ret;
    }
    
    static void __exit globalvar_exit()
    {
    	unregister_chrdev(MAJOR_NUM,"globalvar");
    }
    
    static ssize_t globalvar_read(struct file* fp, char* buf, size_t len, loff_t* off)
    {
    	
    	printk("[debug]:globalvar read get in!\n");
    	/*get semaphore*/
    	if(down_interruptible(&sem)){
    		return -ERESTARTSYS;
    	}
    	
    	/*copy from kernel to user space*/
    	if(copy_to_user(buf,&global_var,sizeof(int)) != 0){
    		/*release semaphore*/
    		up(&sem);
    		return -EFAULT;
    	}
    	
    	/*release semaphore*/
    	up(&sem);
    	printk("[debug]:globalvar read ok!\n");
    	return sizeof(int);
    }
    
    static ssize_t globalvar_write(struct file* fs,const char* buf, size_t len, loff_t* off)
    {
    	/*get semaphore*/
    	if(down_interruptible(&sem)){
    		return -ERESTARTSYS;
    	}
    	
    	printk("down_interruptible ok!\n");
    	if(copy_from_user(&global_var,buf,sizeof(int) != 0)){
    		/*release semaphore*/
    		up(&sem);
    		return -EFAULT;
    	}
    
    	/*release semaphore*/
    	up(&sem);
    	return sizeof(int);
    }
    
    /*
     * open device with checking busy.
     * if busy,count++;else return 0
     */
    static int globalvar_open(struct inode*node, struct file* fp)
    {
    	/*get spinlock*/
    	spin_lock(&spin);
    	
    	/*reach criticle section*/
    	if(global_var_count){
    		spin_unlock(&spin);
    		printk("[debug]:globalvar open fialed!\n");
    		return -EBUSY;
    	}
    	
    	/*release spinlock*/
    	global_var_count++;
    	
    	spin_unlock(&spin);
    	printk("[debug]:globalvar open ok!\n");
    	return 0;
    }
    
    static int globalvar_release(struct inode*node, struct file* fp)
    {
    	spin_lock(&spin);
    	global_var_count--;
    	spin_unlock(&spin);
    	return 0;
    }
    
    /*module setting*/
    module_init(globalvar_init);
    module_exit(globalvar_exit);
    
    /*this is end of file*/

    makefile如下:

    CC=gcc

    PWD:=$(shell pwd)

    KERNELDIR=/usr/src/kernels/2.6.40.4-5.fc15.i686.PAE

    INSTALLDIR=./

    INC=$(KERNELDIR)/include

    MOD_NAME=globalvar

    obj-m:=globalvar.o

        modules:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules -I $(INC)
        @echo "compiled ok!"
        run:
        sudo insmod *.ko 
        install:
        @echo "install ok!"
        unstall:
        sudo rmmod $(MOD_NAME) 
        sudo rmmod /dev/$(MOD_NAME)
        @echo "unregister module ok!"
        debug:
        cat /var/log/messages | tail
        result:
        cat /proc/devices 
        help:
        @echo "install with 'mknod /dev/devname c dev_num 0' cmd"
        clean:
        rm -rf *.o *.ko *.mod.c *.markers *.order *.symvers


    程序很简单,就不多解释了。

    在测试时出现过Oops NULL Pointer内核错误。

    Oops信息如下:

    UG: sleeping function called from invalid context at arch/x86/mm/fault.c:1103
    in_atomic(): 0, irqs_disabled(): 1, pid: 11416, name: a.out
    Pid: 11416, comm: a.out Not tainted 2.6.40.4-5.fc15.i686.PAE #1
    Call Trace:
     [<c07f5274>] ? printk+0x2d/0x2f
     [<c0436c89>] __might_sleep+0xdd/0xe4
     [<c07ffd38>] do_page_fault+0x173/0x32d
     [<c04b82e4>] ? unlock_page+0x21/0x24
     [<c07ffbc5>] ? notify_page_fault+0x40/0x40
     [<c07fda17>] error_code+0x67/0x6c
     [<c05ef695>] ? __list_add+0x3e/0x81
     [<c07fc353>] __down_common+0x2a/0xb8
     [<c07fc427>] __down_interruptible+0x17/0x19
     [<c045f81a>] down_interruptible+0x2a/0x3c
     [<fa8a20b7>] globalvar_read+0x15/0x56 [globalvar]
     [<c04f4a51>] vfs_read+0x8d/0xd5
     [<fa8a20a2>] ? globalvar_write+0x54/0x54 [globalvar]
     [<c04f4adb>] sys_read+0x42/0x63
     [<c08028df>] sysenter_do_call+0x12/0x28
    BUG: unable to handle kernel NULL pointer dereference at   (null)
    IP: [<c05ef695>] __list_add+0x3e/0x81
    *pdpt = 000000002fed0001 *pde = 0000000000000000 
    Oops: 0000 [#1] SMP 

    是空指针错误,由其堆栈及调用信息可大概猜出是semaphore或者spin的错误。后来排查发现semaphore没有init,如果信号量用户互斥(mutex:mutual exclusion),将信号量的值初始化的1,这样只允许一个进程或者线程执行。这种情况下,信号量也成为互斥锁。从实践现象看来;没有初始化信号量直接down和up会造成访问错误,具体原因还没查到。炯!

    应用程序:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    
    /*
     * xiaoyang yi @HIT 2011.9.24
     * this is a test for char device "globalvar"
     */
     
    int main()
    {
    	int fd,num;
    	
    	/*opemn device*/
    	fd = open("/dev/globalvar",O_RDWR,S_IRUSR|S_IWUSR);
    	if(fd != -1){
    		/*read globalvar the first time*/
    		read(fd,&num,sizeof(int));
    		printf("read first time,globalvar=%d\n",num);
    
    		/*writing test*/
    		printf("print number to write: ");
    		scanf("%d",&num);
    		write(fd,&num,sizeof(int));
    
    		/*read globalvar*/
    		read(fd,&num,sizeof(int));
    		printf("read again globalvar=%d\n",num);
    
    		close(fd);
    	}else{
    		printf("device open failed!\n");
    	}
    	return 0;
    }
    

      

    测试结果:

    查看printk结果:







  • 相关阅读:
    从buffer、cache区别看linux文件系统的内存管理
    Aspose.Cell C# 导出数据
    vagrant,VirtualBox 安装使用
    docker系列 安装rabbitmq
    supervisor安装以及监控管理rabbitmq消费者进程
    docker系列 安装PHP扩展
    the usage of const
    Linux内核printk打印ip地址
    【Groovy】groovy基本数据类型的简单用法
    jsdelivr 不能用了的替代方案
  • 原文地址:https://www.cnblogs.com/yixiaoyang/p/2190023.html
Copyright © 2020-2023  润新知