上篇文章我们简单的介绍了linux内核同步中的原子操作,这篇文章我们继续看看linux内核提供的其他同步操作。
简单回顾一下原子操作
- 原子操作只是保护对变量或位的操作。
- 临界区同一时间,只有一个任务的代码区域,可以跨越多个函数。
- 举个例子,我们经常会碰到这种情况:
- 先得从一个数据结构中移出数据
- 对其进行格式转换和解析
- 最后再把它加入到另一个数据结构中
- 整个执行过程必须是原子的,在数据被更新完毕前,不能有其他代码读取这些数据。
- 显然,简单的原子操作对此无能为力,这就需要使用更为复杂的同步方法——锁来提供保护。
自旋锁
- 任何时候只能有一个线程持有的锁
- 注意:自旋锁被一个线程持有时,其他线程不能获得这个锁,只能忙等这个锁,
- 长时间占有自旋锁并不是一个明智的方法
- 接口定义在文件<linux/spinlock.h>中
自旋锁的使用:
DEFINE_SPINLOCK(x);
spin_lock (&mr_lock);
//临界区
spin_unlock (&mr_lock);
- 自旋锁不可递归
- 如果已经获得自旋锁,然后再次试图获得将会导致死锁
- 用于中断处理程序时要注意:
- 首先需要禁止本地中断
- 否则可能会试图两次获得自旋锁
- 有一个很方便的接口禁止本地中断的同时获得自旋锁:
spin_lock_irqsave() 保存当前的中断状态,禁止本地中断,获得锁。
-
spin_unlock_irqrestore()对指定的锁解锁,然后让中断恢复到加锁前的状态
典型的用法如下:
DEFINE_SPINLOCK(mr_lock)
unsigned long flags;
spin_lock_irqsave(&mr_lock, flags);
/* 临界区*/
spin_unlock_irqrestore(&mr_lock, flags);
- 如果你能确定中断在加锁前是激活的,那就不需要在解锁后恢复中断以前的状态了。你可以无条件地在解锁时激活中断。
spin_lock_irq()直接禁止中断,而不保存之前的状态
spin_unlock_irq() 直接激活中断
用法如下:
DEFINE_SPINLOCK(x);
spin_lock_irq(&mr_lock);
/*临界区…*/
spin_unlock_irq (&mr_lock);
自旋锁相关的方法如下表所示:
例子:
led_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <asm/atomic.h>
#include <linux/spinlock.h>
static unsigned long gpm4con;
#define GPM4CON (*(volatile unsigned long *)(gpm4con + 0x0))
#define GPM4DAT (*(volatile unsigned long *)(gpm4con + 0x04))
//open
//int led_flag = 0;
spinlock_t lock;
ssize_t led_open(struct inode *inop,struct file *filp)
{
GPM4CON = 0X1111;
GPM4DAT &= ~0XF;
printk("led open!\n");
return 0;
}
//release
ssize_t led_release(struct inode *inop,struct file *filp)
{
GPM4DAT |= 0xf;
printk("led release!\n");
return 0;
}
static unsigned char led_state = 0;
//read
ssize_t led_read(struct file *filp,char __user *buf,size_t size,loff_t f_pos)
{
spin_lock(&lock);
copy_to_user(buf,&led_state,size);
printk("led read data %d\n",led_state);
spin_unlock(&lock);
return size;
}
//write
ssize_t led_write(struct file *filp,const char __user *buf,size_t size,loff_t f_pos)
{
spin_lock(&lock);
copy_from_user(&led_state,buf,size);
printk("led write data %d\n",led_state);
GPM4DAT=led_state;
spin_unlock(&lock);
return size;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
static unsigned int led_major = 0;
static struct class *led_class;
#define LED_DRV_NAME "led_drv"
static int __init led_init(void)
{
spin_lock_init(&lock);
gpm4con=(unsigned long)ioremap(0x110002e0,12);
led_major=register_chrdev(0,LED_DRV_NAME,&fops);
led_class=class_create(THIS_MODULE,LED_DRV_NAME);
device_create(led_class,NULL,MKDEV(led_major,0),NULL,LED_DRV_NAME);
return 0;
}
static void __exit led_exit(void)
{
device_destroy(led_class,MKDEV(led_major,0));
class_destroy(led_class);
iounmap((unsigned long *)gpm4con);
unregister_chrdev(led_major,LED_DRV_NAME);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
led1.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define FILE_NAME "/dev/led_drv"
int main(void)
{
int fd = 0;
int i = 10;
fd = open(FILE_NAME,O_RDWR);
sleep(1);
printf("led1 open ok!\r\n");
while(i--)
{
printf("led1 write:\r\n");
usleep(2);
write(fd,&i,sizeof(i));
sleep(1);
}
close(fd);
return 0;
}
led2.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define FILE_NAME "/dev/led_drv"
int main(void)
{
int fd = 0;
int i = 0;
fd = open(FILE_NAME,O_RDWR);
sleep(1);
printf("led2 open ok!\r\n");
while(i<10)
{
printf("led2 write:\r\n");
usleep(3);
write(fd,&i,sizeof(i));
sleep(1);
i++;
}
close(fd);
return 0;
}
makefile
obj-m += led_drv.o
all:
make -C /home/sice/linux-3.5 M=`pwd` modules
install:
make -C /home/sice/linux-3.5 M=`pwd` INSTALL_MOD_PATH=/opt/rootfs modules_install
clean:
make -C /home/sice/linux-3.5 M=`pwd` clean
rm -rf led1 led2
led:
arm-linux-gcc led1.c -o led1
arm-linux-gcc led2.c -o led2
copy:
cp led1 led2 led.sh /opt/rootfs
led.sh
#!/bin/bash
./led1 &
./led2 &
ps
读写自旋锁
- 什么时候需要读写自旋锁?
- 有时,锁的使用可以明确划分为读者和写者
- 使用读写自旋锁的原则?
- 多个读者能够同时持有读锁
- 没有读者时只有一个写者能够持有写锁
-
方法:
初始化:
读锁:
写锁:
获得读锁后再次试图获得写锁会导致死锁:
读写自旋锁的方法如下表:
- 在使用Linux读写自旋锁时,最后要考虑的一点是这种锁机制照顾读比照顾写要多一点。
- 当读锁被持有时,写操作为了互斥访问只能等待,但是,读者却可以继续成功地作占用锁。而自旋等待的写者在所有读者释放锁之前是无法获得锁的。
- 所以,大量读者必定会使挂起的写者处于饥饿状态,在你自己设计锁时一定要记住这一点。
- 实例代码如下:
led_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <asm/atomic.h>
#include <linux/spinlock.h>
#include <linux/rwlock.h>
static unsigned long gpm4con;
#define GPM4CON (*(volatile unsigned long *)(gpm4con + 0x0))
#define GPM4DAT (*(volatile unsigned long *)(gpm4con + 0x04))
//open
int led_flag = 0;
rwlock_t lock;
ssize_t led_open(struct inode *inop,struct file *filp)
{
GPM4CON = 0X1111;
GPM4DAT &= ~0XF;
printk("led open!\n");
return 0;
}
//release
ssize_t led_release(struct inode *inop,struct file *filp)
{
GPM4DAT |= 0xf;
printk("led release!\n");
return 0;
}
static unsigned char led_state = 0;
//read
ssize_t led_read(struct file *filp,char __user *buf,size_t size,loff_t f_pos)
{
read_lock(&lock);
copy_to_user(buf,&led_state,size);
printk("led read data %d\n",led_state);
read_unlock(&lock);
return size;
}
//write
ssize_t led_write(struct file *filp,const char __user *buf,size_t size,loff_t f_pos)
{
write_lock(&lock);
copy_from_user(&led_state,buf,size);
printk("led write data %d\n",led_state);
GPM4DAT=led_state;
write_unlock(&lock);
return size;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
static unsigned int led_major = 0;
static struct class *led_class;
#define LED_DRV_NAME "led_drv"
static int __init led_init(void)
{
rwlock_init(&lock);
gpm4con=(unsigned long)ioremap(0x110002e0,12);
led_major=register_chrdev(0,LED_DRV_NAME,&fops);
led_class=class_create(THIS_MODULE,LED_DRV_NAME);
device_create(led_class,NULL,MKDEV(led_major,0),NULL,LED_DRV_NAME);
return 0;
}
static void __exit led_exit(void)
{
device_destroy(led_class,MKDEV(led_major,0));
class_destroy(led_class);
iounmap((unsigned long *)gpm4con);
unregister_chrdev(led_major,LED_DRV_NAME);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
led1.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define FILE_NAME "/dev/led_drv"
int main(void)
{
int fd = 0;
int i = 10;
unsigned char temp = 0;
fd = open(FILE_NAME,O_RDWR);
sleep(1);
printf("led1 open ok!\r\n");
while(i--)
{
printf("led1 write:\r\n");
usleep(3);
write(fd,&i,sizeof(i));
sleep(1);
read(fd,&temp,1);
usleep(3);
printf("led1 read value:%d\r\n",temp);
}
close(fd);
return 0;
}
led2.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define FILE_NAME "/dev/led_drv"
int main(void)
{
int fd = 0;
int i = 0;
unsigned char temp = 0;
fd = open(FILE_NAME,O_RDWR);
sleep(1);
printf("led2 open ok!\r\n");
while(i<10)
{
printf("led2 write:\r\n");
usleep(5);
write(fd,&i,sizeof(i));
sleep(1);
i++;
read(fd,&temp,1);
usleep(5);
printf("led2 read value:%d\r\n",temp);
}
close(fd);
return 0;
}
makefile和上面的代码一样,执行脚本也一样
信号量
自旋锁提供了一种快速简单的锁实现方法。如果加锁时间不长并且代码不会睡眠(比如中断处理程序),利用自旋锁是最佳选择。如果加锁时间可能很长或者代码在持有锁时有可能睡眠,那么最好使用信号量来完成加锁功能。
- Linux中信号量是睡眠锁
- 如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进到一个等待队列中,然后让其睡眠。这时处理器可以去执行其他代码。
- 当持有信号量的进程将信号量释放后,处于等待队列中的那个任务将被唤醒,并获得该信号量。
- 信号量适用于较长时间持有
- 持有信号量时可以睡眠
- 信号量操作头文件<asm/semaphore.h>;
- 静态地声明信号量
DEFINE_SEMAPHORE(name)
name是信号量的名字,初始值是1;
- 也可以用下面方式来声明、初始化信号量
struct semaphore sem;
sema_init(&sem,count);
count是信号量的初值;
信号量相关的方法:
sema_init(struct semaphore *, int)
//初始化动态创建的信号量为给定的值
down_interruptible(struct semaphore *)
//试图获得信号量,如果不能得到则进入睡眠
down(struct semaphore *)
//试图获得信号量,如果不能得到则进入不可中断睡眠
down_trylock(struct semaphore *)
//试图获得信号量,如果不能得到则立即返回0
up(struct semaphore *)
//释放给定的信号量,如果有等待的进程则唤醒
- 同一时间,只能有一个进程打开设备文件的代码,使用信号量如下:
led_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <asm/atomic.h>
//#include <asm/semaphore.h>
#include <linux/semaphore.h>
static unsigned long gpm4con;
#define GPM4CON (*(volatile unsigned long *)(gpm4con + 0x0))
#define GPM4DAT (*(volatile unsigned long *)(gpm4con + 0x04))
//open
struct semaphore sem;
ssize_t led_open(struct inode *inop,struct file *filp)
{
down(&sem);
GPM4CON = 0X1111;
GPM4DAT &= ~0XF;
printk("led open!\n");
return 0;
}
//release
ssize_t led_release(struct inode *inop,struct file *filp)
{
up(&sem);
GPM4DAT |= 0xf;
printk("led release!\n");
return 0;
}
static unsigned char led_state = 0;
//read
ssize_t led_read(struct file *filp,char __user *buf,size_t size,loff_t f_pos)
{
copy_to_user(buf,&led_state,size);
printk("led read data %d\n",led_state);
return size;
}
//write
ssize_t led_write(struct file *filp,const char __user *buf,size_t size,loff_t f_pos)
{
copy_from_user(&led_state,buf,size);
printk("led write data %d\n",led_state);
GPM4DAT=led_state;
return size;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
static unsigned int led_major = 0;
static struct class *led_class;
#define LED_DRV_NAME "led_drv"
static int __init led_init(void)
{
//DEFINE_SEMAPHORE(sem);
sema_init(&sem,1);
gpm4con=(unsigned long)ioremap(0x110002e0,12);
led_major=register_chrdev(0,LED_DRV_NAME,&fops);
led_class=class_create(THIS_MODULE,LED_DRV_NAME);
device_create(led_class,NULL,MKDEV(led_major,0),NULL,LED_DRV_NAME);
return 0;
}
static void __exit led_exit(void)
{
device_destroy(led_class,MKDEV(led_major,0));
class_destroy(led_class);
iounmap((unsigned long *)gpm4con);
unregister_chrdev(led_major,LED_DRV_NAME);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
读写信号量
- 类似于自旋锁, 信号量也有读写信号量
- 如果没有写者,任意多个读者可以同时持有读锁
- 如果在代码中能够明确区分读写,则使用读写信号量
- 创建和初始化读写信号量:
- 静态声明读写信号量:
DECLARE_RWSEM(name) ;
- 初始化动态创建的读写信号量:
init_rwsem(struct rw_semaphore *sem);
- 静态声明读写信号量:
- 操作函数
down_read(struct rw_semaphore *sem);
up_read、down_write、up_write
互斥体
- 可以认为是“初值为1的信号量”
- 其操作如下:
初始化:
DEFINE_MUTEX(mutexname);
mutex_init(mutex);
上锁
mutex_lock(lock);
释放
mutex_unlock(lock);
实例代码如下:
led_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <asm/atomic.h>
//#include <asm/semaphore.h>
#include <linux/mutex.h>
static unsigned long gpm4con;
#define GPM4CON (*(volatile unsigned long *)(gpm4con + 0x0))
#define GPM4DAT (*(volatile unsigned long *)(gpm4con + 0x04))
//open
struct mutex my_mutex;
ssize_t led_open(struct inode *inop,struct file *filp)
{
// mutex_lock(&my_mutex);
GPM4CON = 0X1111;
GPM4DAT &= ~0XF;
printk("led open!\n");
return 0;
}
//release
ssize_t led_release(struct inode *inop,struct file *filp)
{
// mutex_unlock(&my_mutex);
GPM4DAT |= 0xf;
printk("led release!\n");
return 0;
}
static unsigned char led_state = 0;
//read
ssize_t led_read(struct file *filp,char __user *buf,size_t size,loff_t f_pos)
{
mutex_lock(&my_mutex);
copy_to_user(buf,&led_state,size);
printk("led read data %d\n",led_state);
mutex_unlock(&my_mutex);
return size;
}
//write
ssize_t led_write(struct file *filp,const char __user *buf,size_t size,loff_t f_pos)
{
mutex_lock(&my_mutex);
copy_from_user(&led_state,buf,size);
printk("led write data %d\n",led_state);
GPM4DAT=led_state;
mutex_unlock(&my_mutex);
return size;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
static unsigned int led_major = 0;
static struct class *led_class;
#define LED_DRV_NAME "led_drv"
static int __init led_init(void)
{
//DEFINE_SEMAPHORE(sem);
mutex_init(&my_mutex);
gpm4con=(unsigned long)ioremap(0x110002e0,12);
led_major=register_chrdev(0,LED_DRV_NAME,&fops);
led_class=class_create(THIS_MODULE,LED_DRV_NAME);
device_create(led_class,NULL,MKDEV(led_major,0),NULL,LED_DRV_NAME);
return 0;
}
static void __exit led_exit(void)
{
device_destroy(led_class,MKDEV(led_major,0));
class_destroy(led_class);
iounmap((unsigned long *)gpm4con);
unregister_chrdev(led_major,LED_DRV_NAME);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
完成变量
- 类似于信号量
- 一个任务在等待完成变量,另一个进程在进行某种工作
- 另一个进程完成工作后,使用完成变量唤醒等待的进程
定义:
DECLARE_COMPLETION(work);
init_completion(struct completion *)
等待完成条件
wait_for_completion(struct completion *)
通知等待的进程唤醒
complete(struct completion *)
complete_all(struct completion *);
大内核锁
- 大内核锁(BKL)是全局自旋锁
- 可以持有BKL睡眠
- 可递归
- 可在进程上下文中使用BKL
- BKL多用于保护代码而不是数据
- 在内核中不鼓励使用BKL
使用方法:
lock_kernel();
/* 临界区 */
unlock_kernel();