须具备以下三个条件: |
---|
1. 必须有多个执行单元 |
2. 必须有共享资源 |
3. 必须同时访问 |
这是在单处理器系统上的常用的一种同步机制:在进入临界区之前禁止所有的硬件中断,离开时再重新启用中断。这种机制尽管简单,但远不是最佳的。因为如果临界区比较大,那么在一个相对较长的时间内持续禁止中断就可能使所有的硬件活动处于冻结状态。
确保一组内核语句被当作一个临界区处理的主要机制之一就是 中断禁止。即使当硬件设备产生了一个IRQ信号时,中断禁止也让内核控制路径继续执行,因此,这就提供了一种有效的方式,确保中断处理程序访问的数据结构也受到保护。然而,禁止本地中断并不保护运行在另一个CPU上的中断处理程序对数据结构的并发访问,因此,在多处理器系统上,禁止本地中断经常与自旋锁结合使用。
摘自——《深入理解linux内核》第三版
中断可以以嵌套的方式执行,所以内核不必知道当前控制路径被执行之前IF禁止位的值是什么。在这种情况下,控制路径必须保存先前赋给该标志位上的值,并在执行结束时恢复它。摘自——《深入理解linux内核》第三版
#ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT
#define local_irq_disable() \
do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)
#else
#define local_irq_disable() do { raw_local_irq_disable(); } while (0)
#endif
功能:宏 local_irq_disable()使用cli汇编语言指令关闭本地CPU上的中断。汇编语言指令cli设置eflags控制寄存器的IF标志。
IRQ配置位I:BIT[7] | BIT[7] = 1 (禁止IRQ中断功能) |
---|---|
FIQ配置位F:BIT[6] | BIT[6] = 1 (禁止FIQ中断功能) |
#ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT
#define local_irq_enable() \
do { trace_hardirqs_on(); raw_local_irq_enable(); } while (0)
#else
#define local_irq_enable() do { raw_local_irq_enable(); } while (0)
#endif
功能:宏 local_irq_enable()使用sti汇编语言指令打开本地CPU上被关闭的中断。汇编语言指令sti设置eflags控制寄存器的IF标志。(参考上图)
#ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT
#define local_irq_save(flags) \
do { \
raw_local_irq_save(flags); \
trace_hardirqs_off(); \
} while (0)
#else
#define local_irq_save(flags) \
do { \
raw_local_irq_save(flags); \
} while (0)
#endif
功能:宏 local_irq_save(flags)先把eflags寄存器的内容拷贝到一个局部变量中,随后使用cli汇编指令将IF禁止位置1,关闭中断。
#ifdef CONFIG_TRACE_IRQFLAGS_SUPPORT
#define local_irq_restore(flags) \
do { \
if (raw_irqs_disabled_flags(flags)) { \
raw_local_irq_restore(flags); \
trace_hardirqs_off(); \
} else { \
trace_hardirqs_on(); \
raw_local_irq_restore(flags); \
} \
} while (0)
#else
#define local_irq_restore(flags) \
do { raw_local_irq_restore(flags); } while (0)
#endif
功能:宏 local_irq_restore(flags)恢复eflags原来的的内容。
使用中断屏蔽解决LED灯被多个进程访问所引发的竞态问题,见下图
#include
#include
#include
#include
//共享资源
static int open_cnt = 1; //记录LED打开的状态开关
static int led_open(struct inode *inode, struct file *file)
{
unsigned long flags;
//屏蔽中断
local_irq_save(flags);
//临界区
if(--open_cnt != 0) {
printk("设备已被打开!\n");
open_cnt++;
//恢复中断
local_irq_restore(flags);
return -EBUSY;//返回设备忙错误码
}
//恢复中断
local_irq_restore(flags);
printk("设备打开成功!\n");
return 0; //open返回成功
}
static int led_close(struct inode *inode, struct file *file)
{
unsigned long flags;
//屏蔽中断
local_irq_save(flags);
//临界区
open_cnt++;
//恢复中断
local_irq_restore(flags);
return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};
static int led_init(void)
{
misc_register(&led_misc);
return 0;
}
static void led_exit(void)
{
misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
一种广泛应用的同步技术是加锁(locking)。当内核控制路径必须访问共享数据结构或进入临界区时,就需要为自己获取一把“锁”。锁机制保护的资源非常类似于限制在房间内的资源,当某人进入房间时,就把门锁上。如果内核控制路径希望访问资源,就试图获取钥匙“打开门”。当且仅当资源空闲时,它才能成功。然后,只要它还想使用这个资源,门就依然锁着。当内核控制路径释放了锁时,门就被打开,另一个内核控制路径就可以进入房间。如下图
上图显示了锁的使用。5个内核控制路径(P0,P1,P2,P3和P4)试图访问两个临界区(C1和C2)。内核控制路径P0正在C1中,而P2和P4正等待进入C1。同时,P1正在C2中,而P3正在等待进入C2。注意P0和P1可以并行运行。临界区C3的锁现在大开着,因为没有内核控制路径需要进入C3。
自旋锁(spin lock)是用来在多处理器环境中工作的一种特殊的锁。如果内核控制路径发现自旋锁“开着”,就获取锁并继续自己的执行。相反,如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径“锁着”,就在周围“旋转”,反复执行一条紧凑的循环指令,直到锁被释放。
自旋锁的循环指令表示“忙等"。即使等待的内核控制路径无事可做(除了浪费时间),它也在CPU上保持运行。不过,自旋锁通常非常方便,因为很多内核资源只锁1毫秒的时间片段;所以说,释放CPU和随后又获得CPU都不会消耗多少时间。
一般来说,由自旋锁所保护的每个临界区都是禁止内核抢占的。在单处理器系统上,这种锁本身并不起锁的作用,自旋锁原语仅仅是禁止或启用内核抢占。注意:在自旋锁忙等期间,内核抢占还是有效的,因此,等待自旋锁释放的进程有可能被更高优先级的进程替代。
摘自——《深入理解linux内核》第三版
spinlock_t结构体在linux内核3.1.18中的定义如下
定义在include\linux\spinlock_types.h
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
struct raw_spinlock定义如下
定义在include\linux\spinlock_types.h
typedef struct raw_spinlock {
arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
/*表示进程正在忙等自旋锁(只在内核支持SMP和内核抢占的情况下使用)*/
unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} raw_spinlock_t;
arch_spinlock_t定义如下
定义在arch\arc\include\asm\spinlock_types.h
typedef struct {
/*该字段表示自旋锁的状态:值为1表示“未加锁”状态,而任何负数和0都表示“加锁”状态*/
volatile unsigned int slock;
} arch_spinlock_t;
#define spin_lock_init(_lock) \
do { \
spinlock_check(_lock); \
raw_spin_lock_init(&(_lock)->rlock); \
} while (0)
功能:动态初始化自旋锁,并把自旋锁置为1(未锁)
static inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
功能:循环等待,直到自旋锁变成1(未锁),然后把自旋锁置为0(锁上),即获取指定的自旋锁
static inline void spin_unlock(spinlock_t *lock)
{
raw_spin_unlock(&lock->rlock);
}
功能:把自旋锁置为1(未锁),即释放指定的锁
#include
#include
#include
#include
//共享资源
static int open_cnt = 1; //记录LED打开的状态开关
//定义自旋锁对象
static spinlock_t lock;
static int led_open(struct inode *inode,
struct file *file)
{
//获取自旋锁
spin_lock(&lock);
//临界区
if(--open_cnt != 0) {
printk("设备已被打开!\n");
open_cnt++;
//释放自旋锁
spin_unlock(&lock);
return -EBUSY;//返回设备忙错误码
}
//释放自旋锁
spin_unlock(&lock);
printk("设备打开成功!\n");
return 0; //open返回成功
}
static int led_close(struct inode *inode,
struct file *file)
{
//获取自旋锁
spin_lock(&lock);
//临界区
open_cnt++;
//释放自旋锁
spin_unlock(&lock);
return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};
static int led_init(void)
{
misc_register(&led_misc);
//初始化自旋锁对象
spin_lock_init(&lock);
return 0;
}
static void led_exit(void)
{
misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
将以上中断屏蔽和自旋锁组合起来,可以解决所有竞态引起的同步问题。
#define spin_lock_irqsave(lock, flags) \
do { \
raw_spin_lock_irqsave(spinlock_check(lock), flags); \
} while (0)
功能:保存本地中断的当前状态,禁止本地中断,并获取指定的锁
static inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
{
raw_spin_unlock_irqrestore(&lock->rlock, flags);
}
功能:释放指定的锁,并让本地中断恢复到以前的状态。
#include
#include
#include
#include
//共享资源
static int open_cnt = 1; //记录LED打开的状态开关
//定义自旋锁对象
static spinlock_t lock;
static int led_open(struct inode *inode,
struct file *file)
{
unsigned long flags;
//屏蔽中断,获取衍生自旋锁
spin_lock_irqsave(&lock, flags);
//临界区
if(--open_cnt != 0) {
printk("设备已被打开!\n");
open_cnt++;
//释放自旋锁,恢复中断
spin_unlock_irqrestore(&lock, flags);
return -EBUSY;//返回设备忙错误码
}
//释放自旋锁,恢复中断
spin_unlock_irqrestore(&lock, flags);
printk("设备打开成功!\n");
return 0; //open返回成功
}
static int led_close(struct inode *inode,
struct file *file)
{
unsigned long flags;
//屏蔽中断,获取衍生自旋锁
spin_lock_irqsave(&lock, flags);
//临界区
open_cnt++;
//释放自旋锁,恢复中断
spin_unlock_irqrestore(&lock, flags);
return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};
static int led_init(void)
{
misc_register(&led_misc);
//初始化自旋锁对象
spin_lock_init(&lock);
return 0;
}
static void led_exit(void)
{
misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
广泛使用的一种机制是信号量(semaphore),它在单处理器系统和多处理器系统上都有效。信号量仅仅是与一个数据结构相关的计数器。所有内核线程在试图访问这个数据结构之前,都要检查这个信号量。可以把每个信号量看成一个对象,其组成如下:
down()方法对信号量的值减1,如果这个新值小于0,该方法就把正在运行的进程加入到这个信号量链表,然后阻塞该进程(即调用调度程序)。
up()方法对信号量的值加1,如果这个新值大于或等于0,则激活这个信号量链表中的一个或多个进程。
每个要保护的数据结构都有它自己的信号量,其初始值为1。当内核控制路径希望访问这个数据结构时,它在相应的信号量上执行down()方法。如果信号量的当前值不是负数,则允许访问这个数据结构。否则,把执行内核控制路径的进程加入到这个信号量的链表并阻塞该进程。当另一个进程在那个信号量上执行up()方法时,允许信号量链表上的一个进程继续执行。
从本质上说,它们实现了一个加锁原语,即让等待者睡眠,知道等待的资源变为空闲。实际上,Linux提供两种信号量:
/* Please don't access any members of this structure directly */
struct semaphore {
raw_spinlock_t lock;
unsigned int count; //在本案赋值为1
struct list_head wait_list;
};
static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}
功能:初始化信号量对象
/**
* down - acquire the semaphore
* @sem: the semaphore to be acquired
*
* Acquires the semaphore. If no more tasks are allowed to acquire the
* semaphore, calling this function will put the task to sleep until the semaphore is released.
*
* Use of this function is deprecated, please use down_interruptible() or
* down_killable() instead.
*/
void down(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
__down(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(down);
功能:获取信号量,如果获取信号量成功,进程从此函数中立马返回
然后可以踏踏实实的访问临界区,如果获取信号量失败,进程将进入此函数中进入不可中断的休眠状态(释放 CPU 资源,在休眠期间接收到信号不会立即处理信号)
“不可中断的休眠状态”:进程在休眠期间,如果接收到了一个 kill 信号
进程不会立即处理接收到的信号,而是获取信号量的任务释放信号量以后唤醒这个休眠的进程,进程一旦被唤醒以后会处理之前接收到的信号
/**
* down_interruptible - acquire the semaphore unless interrupted
* @sem: the semaphore to be acquired
*
* Attempts to acquire the semaphore. If no more tasks are allowed to
* acquire the semaphore, calling this function will put the task to sleep.
* If the sleep is interrupted by a signal, this function will return -EINTR.(#define EINTR 4 /* Interrupted system call */)
* If the semaphore is successfully acquired, this function returns 0.
*/
int down_interruptible(struct semaphore *sem)
{
unsigned long flags;
int result = 0;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
result = __down_interruptible(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
return result;
}
EXPORT_SYMBOL(down_interruptible);
功能:获取信号量,如果获取信号量成功,进程从此函数中立马
返回,然后去访问临界区,如果获取信号量失败,进程将进入可中断的休眠状态
“可中断的休眠状态”:进程在休眠期间,如果接收到了一个信号进程会被立即唤醒并且处理接收到的信号
/**
* down_killable - acquire the semaphore unless killed
* @sem: the semaphore to be acquired
*
* Attempts to acquire the semaphore. If no more tasks are allowed to
* acquire the semaphore, calling this function will put the task to sleep.
* If the sleep is interrupted by a fatal signal, this function will return
* -EINTR. If the semaphore is successfully acquired, this function returns
* 0.
*/
int down_killable(struct semaphore *sem)
{
unsigned long flags;
int result = 0;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
result = __down_killable(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
return result;
}
EXPORT_SYMBOL(down_killable);
/**
* up - release the semaphore
* @sem: the semaphore to release
*
* Release the semaphore. Unlike mutexes, up() may be called from any context and even by tasks which have never called down().
*/
void up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;
else
__up(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(up);
功能:释放信号量
//头文件
#include
#include
#include
#include
#include
#include
#include
//声明描述LED灯硬件信息的结构体
static struct led_resource{
int gpio;
char* name;
};
//定义信号量对象
static struct semaphore sem;
//定义初始化led2灯的硬件信息
struct led_resource led2_info ={PAD_GPIO_C+7, "LED2"};
//led_open
static int led_open(struct inode* inode, struct file* file){
//获取信号量
down(&sem);
//开灯
gpio_set_value(led2_info.gpio, 0);
return 0;
}
//led_close
static int led_close(struct inode* inode, struct file* file){
//释放信号量
up(&sem);
return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
.open = led_open,
.release = led_close,
.owner = THIS_MODULE
};
//定义初始化一个混杂设备对象
static struct miscdevice led2_misc = {
.minor = MISC_DYNAMIC_MINOR,
.fops = &led_fops,
.name = "v_led2"
};
//入口函数
static int led_drv_init(void){
gpio_request(led2_info.gpio, led2_info.name);
gpio_direction_output(led2_info.gpio, 1);
misc_register(&led2_misc);
//初始化信号量
sema_init(&sem, 1);
return 0;
}
//出口函数
static void led_drv_exit(void){
gpio_set_value(led2_info.gpio, 1);
gpio_free(led2_info.gpio);
misc_deregister(&led2_misc);
}
//各种修饰和GPL规则
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
#include
#include
#include
#include
int main(void){
int fd = open("/dev/v_led2", O_RDWR);
sleep(60);
close(fd);
return 0;
}
/tmp/vaccine # insmod led_drv.ko
/tmp/vaccine # ./led_test & //启动 A 进程,A 进程打开成功,A 进程调用 sleep 进行休眠 不关闭
/tmp/vaccine # ./led_test & // 启动 B 进程,B 进程获取信号量失败,进入不可中断的休眠状态,等待 A 进程来唤醒
ps //查看 A,B 的 PID
top //查看 A,B 的进程状态
S:进程进入可中断的休眠状态
D:进程进入不可中断的休眠状态
在上位机进行以上操作之后,结果出现吐核现象如下
/tmp/vaccine # [ 310.229000] INFO: task led_test:124 blocked for more than 10 seconds.
[ 310.230000] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 310.232000] led_test D c06cdb3c 0 124 85 0x00000000
[ 310.235000] [<c06cdb3c>] (__schedule+0x384/0x7d4) from [<c06cc190>] (schedule_timeout+0x1bc/0x2f8)
[ 310.236000] [<c06cc190>] (schedule_timeout+0x1bc/0x2f8) from [<c06cd1f8>] (__down+0x6c/0x9c)
[ 310.237000] [<c06cd1f8>] (__down+0x6c/0x9c) from [<c0066320>] (down+0x44/0x4c)
[ 310.238000] [<c0066320>] (down+0x44/0x4c) from [<bf000028>] (led_open+0x10/0x2c [led_drv])
[ 310.240000] [<bf000028>] (led_open+0x10/0x2c [led_drv]) from [<c02c49e8>] (misc_open+0x134/0x1a8)
[ 310.242000] [<c02c49e8>] (misc_open+0x134/0x1a8) from [<c0120c0c>] (chrdev_open+0x88/0x170)
[ 310.243000] [<c0120c0c>] (chrdev_open+0x88/0x170) from [<c011b0d4>] (__dentry_open+0x200/0x2b8)
[ 310.245000] [<c011b0d4>] (__dentry_open+0x200/0x2b8) from [<c011c1bc>] (nameidata_to_filp+0x60/0x68)
[ 310.246000] [<c011c1bc>] (nameidata_to_filp+0x60/0x68) from [<c012a04c>] (do_last+0x35c/0x7ec)
[ 310.247000] [<c012a04c>] (do_last+0x35c/0x7ec) from [<c012a6e4>] (path_openat+0xbc/0x3d8)
[ 310.248000] [<c012a6e4>] (path_openat+0xbc/0x3d8) from [<c012aad8>] (do_filp_open+0x2c/0x80)
[ 310.249000] [<c012aad8>] (do_filp_open+0x2c/0x80) from [<c011c2ac>] (do_sys_open+0xe8/0x174)
[ 310.250000] [<c011c2ac>] (do_sys_open+0xe8/0x174) from [<c000ec80>] (ret_fast_syscall+0x0/0x30)
解决办法:下位机执行:
原因是因为进程led_test休眠超过10秒
echo 0 > /proc/sys/kernel/hung_task_timeout_secs
若干汇编语言指令具有“读——修改——写”类型——也就是说,它们访问存储单元两次,第一次读原值,第二次写新值。
假定运行在两个CPU上的两个内核控制路径试图通过执行非原子操作来同时 “读——修改——写” 同一存储器单元。首先,两个CPU都试图读同一单元,但是存储器仲裁器【对访问RAM芯片的操作进行串行化的硬件电路】插手,只允许其中的一个访问而让另一个延迟。然而,当第一个读操作已经完成后,延迟的CPU从那个存储器单元正好读到同一个(旧)值。然后,两个CPU都试图向那个存储器单元写一个新值,总线存储器访问再一次被存储器仲裁器串行化,最终,两个写操作都成功。但是,全局的结果是不对的,因为两个CPU写入同一(新)值。因此,两个交错的 “读——修改——写” 操作成了一个单独的操作。
避免由于 “读——修改——写” 指令引起的竞争条件的最容易的办法,就是确保这样的操作在芯片级是原子的。任何一个这样的操作都必须以单个指令执行,中间不能中断,且避免其他的CPU访问同一存储器单元。
在编写C代码程序时,并不能保证编译器会为a=a+1 或甚至像a++ 这样的操作使用一个原子指令。因此,linux内核提供了一个专门的atomic_t类型(一个原子访问计数器)和一些专门的函数和宏。这些函数和宏作用于atomic_t类型的变量,并当做单独的,原子的汇编语言指令来使用。在多处理器系统中,每条这样的指令都有一个lock字节的前缀。
本文介绍的是arm芯片架构下的宏定义,其操作上具有原子性
都定义在arch\arm\include\asm\bitops.h
#define set_bit(nr,p) ATOMIC_BITOP(set_bit,nr,p)
#ifndef CONFIG_SMP
/*
* The __* form of bitops are non-atomic and may be reordered.
*/
#define ATOMIC_BITOP(name,nr,p) \
(__builtin_constant_p(nr) ? ____atomic_##name(nr, p) : _##name(nr,p))
#else
#define ATOMIC_BITOP(name,nr,p) _##name(nr,p)
#endif
功能:将p地址内数据的第 nr 位置 1(nr 从 0 开始),具有原子性,不允许发生CPU资源的切换。
#define clear_bit(nr,p) ATOMIC_BITOP(clear_bit,nr,p)
功能:将p地址内数据的第 nr 位清 0(nr 从 0 开始),具有原子性,不允许发生CPU资源的切换。
#define change_bit(nr,p) ATOMIC_BITOP(change_bit,nr,p)
功能:将p地址内数据的第 nr 位反转(nr 从 0 开始),具有原子性,不允许发生CPU资源的切换。
#define test_and_set_bit(nr,p) ATOMIC_BITOP(test_and_set_bit,nr,p)
功能:将p地址内数据的第 nr 位置 1(nr 从 0 开始),并返回原来的值
#define test_and_clear_bit(nr,p) \
ATOMIC_BITOP(test_and_clear_bit,nr,p)
功能:将p地址内数据的第 nr 位清 0(nr 从 0 开始),并返回原来的值
#define test_and_change_bit(nr,p) \
ATOMIC_BITOP(test_and_change_bit,nr,p)
功能:将p地址内数据的第 nr 位反转(nr 从 0 开始),并返回原来的值
注意:以上函数不要被表面给欺骗,它们的内在核心在于在 addr 地址内数据进行位操作过程不允许发生 CPU 资源切换以上函数的操作对象一定是共享资源,虽说也可以利用以上函数对非共享资源进行位操作,但是致命的后果就是代码执行效率降低
参考代码:
int open_cnt = 1; //共享资源
open_cnt &= ~(1 << 1); //临界区,处于裸奔状态,如果考虑竞态问题,此代码相当危险
优化:
采用中断屏蔽
local_irq_save
open_cnt &= ~(1 << 1);
local_irq_restore
采用衍生自旋锁
spin_lock_irqsave
open_cnt &= ~(1 << 1);
spin_unlock_irqrestore
采用信号量
down
open_cnt &= ~(1 << 1);
up
采用位原子操作
clear_bit(1, &open_cnt)
整型原子操作:即是对整型数的操作具有原子性,在对驱动代码的共享资源进行整型操作过程中,不允许发生CPU资源的切换。
数据类型
linux 内核对于整形原子操作专门提供了一个数据类型: atomic_t
定义在include\linux\types.h
typedef struct {
int counter;
} atomic_t;
#define ATOMIC_INIT(i) { (i) }
第一个宏用来定义,比如
static atomic_t my_counter = ATOMIC_INIT(1);
void atomic_set(atomic_t *v, int i); //设置原子变量v的值为i
atomic_t v = ATOMIC_INIT(0); //定义原子变量v, 并初始化为0 **************************
atomic_read(atomic_t *v); //获得原子变量的值,返回原子变量的值
void atomic_add(int i, atomic_t *v); //原子整型变量+i
void atomic_sub(int i, atomic_t *v); //原子整型变量-i
void atomic_inc(atomic_t *v); //原子整型变量自增 *******************************
void atomic_dec(atomic_t *v); //原子整型变量自减
int atomic_inc_and_test(atomic_t *v); //原子整型变量自增后,判断其是否为0,为0返回真,不为0,返回假
int atomic_dec_and_test(atomic_t *v);//原子整型变量自减后,判断其是否为0,为0返回真,不为0,返回假
int atomic_sub_and_test(int i, atomic_t *v);//原子整型变量减去i,判断其是否为0,为0返回真,不为0,返回假
int atomic_add_return(int i, atomic_t *v); //原子整型变量加上i,然后返回新的值
int atomic_sub_return(int i, atomic_t *v);//原子整型变量减去i,然后返回新的值
int atomic_inc_return(atomic_t *v);//原子整型变量自增后,然后返回新的值
int atomic_sub_return(atomic_t *v);//原子整型变量自减后,然后返回新的值
#include
#include
#include
#include
//定义原子变量,初始化为 1
static atomic_t open_cnt = ATOMIC_INIT(1); //记录LED打开的状态开关
static int led_open(struct inode *inode, struct file *file)
{
if(!atomic_dec_and_test(&open_cnt)) {
printk("设备已被打开!\n");
atomic_inc(&open_cnt);
return -EBUSY;//返回设备忙错误码
}
printk("设备打开成功!\n");
return 0; //open返回成功
}
static int led_close(struct inode *inode,
struct file *file)
{
atomic_inc(&open_cnt);
return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};
static int led_init(void)
{
misc_register(&led_misc);
return 0;
}
static void led_exit(void)
{
misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");