在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源) ,可能会引发“竞态” ,因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用) 。
自旋锁与信号量“类似而不类” ,类似说的是它们功能上的相似性, “不类”指代它们在本质和实现机理上完全不一样,不属于一类。
自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁, “自旋”就是“在原地打转” 。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的“不类” 。
但是,无论是信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。这就是它们的“类似” 。
鉴于自旋锁与信号量的上述特点,一般而言,自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用;信号量适合于保持时间较长的情况,会只能在进程上下文使用。如果被保护的共享资源只在进程上下文访问,则可以以信号量来保护该共享资源,如果对共享资源的访问时间非常短,自旋锁也是好的选择。但是,如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断) ,就必须使用自旋锁。
先贴代码
#include
#include
#include
#include
#include
#include
#include
/*
* 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 okn');
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:
[
[
[
[
[
[
[
[
[
[
[
[
[
[
[
BUG: unable to handle kernel NULL pointer dereference at (null)
IP: [
*pdpt = 000000002fed0001 *pde = 0000000000000000
Oops: 0000 [#1] SMP
是空指针错误,由其堆栈及调用信息可大概猜出是semaphore或者spin的错误。后来排查发现semaphore没有init,如果信号量用户互斥(mutex:mutual exclusion),将信号量的值初始化的1,这样只允许一个进程或者线程执行。这种情况下,信号量也成为互斥锁。从实践现象看来;没有初始化信号量直接down和up会造成访问错误,具体原因还没查到。炯!
应用程序:
#include
#include
#include
#include
#include
/*
* 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=%dn',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=%dn',num);
close(fd);
}else{
printf('device open failed!n');
}
return 0;
}