深入浅出Linux设备驱动之并发控制
刺猬@http://blog.csdn.net/littlehedgehog
注: 该系列文章转载自arm+linux chinaunix博客圈圈主之博客——http://blog.chinaunix.net/u/22630 /article_54997.html 为了适合我的编译环境,源代码有改动,但是相信我更改后的代码更加适合现在大多数读者的pc环境。
在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发"竞态",因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为
互斥锁使用)。
自旋锁与信号量"类似而不类",类似说的是它们功能上的相似性,"不类"指代它们在本质和实现机理上完全不一样,不属于一类。
自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直
循环查看是否该自旋锁的保持者已经释放了锁,"自旋"就是"在原地打转"。而
信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的"不类"。[最好的理解是读源码 read the fucking source code!]
但是,无论是信号量,还是自旋锁,在任何时刻,
最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。这就是它们的"类似"。
鉴于自旋锁与信号量的上述特点,一般而言,自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用;信号量适合于保持时间较长的情况,只能在进程上下文使用。如果被保护的共享资源只在进程上下文访问,则可以以信号量来保护该共享资源,如果对共享资源的访问时间非常短,自旋锁也是好的选择。但是,如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。
与信号量相关的API主要有:
定义信号量
struct semaphore sem;
初始化信号量
void sema_init (struct semaphore *sem, int val);
该函数初始化信号量,并设置信号量sem的值为val
void init_MUTEX (struct semaphore *sem);
该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1,等同于sema_init (struct semaphore *sem, 1);
void init_MUTEX_LOCKED (struct semaphore *sem);
该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,等同于sema_init (struct semaphore *sem, 0);
获得信号量
void down(struct semaphore * sem);
该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文使用;
int down_interruptible(struct semaphore * sem);
该函数功能与down类似,不同之处为,down不能被信号打断,但down_interruptible能被信号打断;
int down_trylock(struct semaphore * sem);
该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。它不会导致调用者睡眠,可以在中断上下文使用。
释放信号量
void up(struct semaphore * sem);
该函数释放信号量sem,唤醒等待者。
与自旋锁相关的API主要有:
定义自旋锁
spinlock_t spin;
初始化自旋锁
spin_lock_init(lock)
该宏用于动态初始化自旋锁lock
获得自旋锁
spin_lock(lock)
该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放;
spin_trylock(lock)
该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,实际上不再"在原地打转";
释放自旋锁
spin_unlock(lock)
该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用;
- #ifndef __KERNEL__
- #define __KERNEL__
- #endif
- #ifndef MODULE
- #define MODULE
- #endif
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/fs.h>
- #include <asm/uaccess.h>
- #include <asm/semaphore.h>
- MODULE_LICENSE("GPL");
- #define MAJOR_NUM 250 //主设备号
- 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 *,struct file *);
- static int globalvar_release(struct inode *,struct file *);
- struct file_operations globalvar_fops =
- {
- read:globalvar_read,
- write:globalvar_write,
- open:globalvar_open,
- release:globalvar_release,
- };
- static int global_var = 0;
- static int global_count=0;
- static struct semaphore sem;
- static spinlock_t spin=SPIN_LOCK_UNLOCKED;
- static int __init globalvar_init(void)
- {
- int ret;
-
- ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
- if (ret)
- printk("globalvar register failure!/n");
- else
- {
- printk("globalvar register success!/n");
- init_MUTEX(&sem);
- }
- return ret;
- }
- static void __exit globalvar_exit(void)
- {
- printk("globalvar unregister!/n");
- unregister_chrdev(MAJOR_NUM, "globalvar");
- }
- static int globalvar_open(struct inode *inode,struct file *filep)
- {
- spin_lock(&spin);
- if(global_count)
- {
- spin_unlock(&spin);
- return -EBUSY;
- }
- global_count++;
- spin_unlock(&spin);
- return 0;
- }
- static int globalvar_release(struct inode *inode,struct file *filep)
- {
- global_count--;
- return 0;
- }
- static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
- {
- if(down_interruptible(&sem))
- return -ERESTARTSYS;
- 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);
- return sizeof(int);
- }
- module_init(globalvar_init);
- module_exit(globalvar_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("neo");
为了上述驱动程序的效果,我们启动两个进程分别打开/dev/globalvar。当一个进程打开/dev/globalvar后,另外一个进程将打开失败。[注意 测试程序我全部改写了一下,原作者的测试程序有点让人摸不着头脑]
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <stdio.h>
- #include <fcntl.h>
- #include <unistd.h>
- int main()
- {
- int fd,num,test=22;
- int pid;
- #ifndef DEBUG
- if((pid=fork())==-1)
- return -1;
- #endif
- sleep(3);
- if(!pid)
- printf("I am a child /n");
- else
- printf("I am a parent/n");
- if((fd=open("/dev/test",O_RDWR, S_IRUSR | S_IWUSR))!=-1)
- {
- printf("welldone!/n");
- read(fd,&num,sizeof(int));
- printf("the init value is %d/nnow we need to change it/n",num);
- write(fd,&test,sizeof(int));
- read(fd,&num,sizeof(int));
- printf("%d/n",num);
- }
- else
- {
- printf("damn,we failed!/n");
- perror("reason ");
- }
- return 0;
- }