大连p2p网站建设,wordpress 金币插件,湖北做网站公司,海南人才在线官网了解linux中的“原子整形数据”操作、“原子位数据”操作、自旋锁、读写锁、顺序锁、信号量和互斥体#xff0c;以及相关函数。
并发就是多个“用户”同时访问同一个共享资源。如#xff1a;多个线程同时要求读写同一个EEPROM芯片#xff0c;这个EEPROM就是共享资源#x…了解linux中的“原子整形数据”操作、“原子位数据”操作、自旋锁、读写锁、顺序锁、信号量和互斥体以及相关函数。
并发就是多个“用户”同时访问同一个共享资源。如多个线程同时要求读写同一个EEPROM芯片这个EEPROM就是共享资源为了保证读写的正确性其它线程必须等待“持有者”释放使用权限才可以使用该EEPROM。并发带来的问题就是竞争。Linux采用“原子整形数据”操作、“原子位数据”操作、自旋锁、读写锁、顺序锁、信号量和互斥体的函数来解决并发与竞争。 1、Linux系统并发产生的原因:
1)、多线程并发访问。
2)、抢占式并发访问从2.6版本内核开始Linux内核支持抢占也就是说调度程序可以在任意时刻抢占正在运行的线程从而运行其他的线程。
3)、中断程序并发访问。
4)、SMP(多核)核间并发访问。 注意SOC称为系统级芯片也有称片上系统。 如果线程A和线程B修改同一个存储单元如果没有竞争就会按照下面的流程执行都会得到正确的结果。
线程A:
ldr r0, 0X30000000 /* r00X30000000 */
ldr r1, 10 /* r110 */
str r1, [r0] /*将地址0X30000000的存储单元设置为10*/ 线程B:
ldr r0, 0X30000000 /* r00X30000000 */
ldr r1, 20 /* r120 */
str r1, [r0] /*将地址0X30000000的存储单元设置为20*/
如果线程A和线程B发生竞争地址0X30000000的存储单元设置的值可能是错误的。 2、原子操作
原子操作是指不能再进行分割的操作。主要指整型变量操作或者位变量操作。
1)、“原子整形数据”操作
①、在“include/linux/types.h”文件中atomic_t的结构体如下
如果是32位的系统级芯片Linux内核定义的32位原子结构体如下
typedef struct { int counter;
}atomic_t; 如果是64位的系统级芯片Linux内核也定义了64位原子结构体如下
typedef struct {
s64 counter;
} atomic64_t; ②、“原子整形数据”操作函数:
ATOMIC INIT(int i)
定义原子变量的时候对其初始化 int atomic read(atomic_t *v)
读取v-counter的值并且返回; void atomic_set(atomic_t *v, int i)
向v-counter写入i值; void atomic_add(int i. atomic_t *v)
给v-counter加上i值; void atomic_sub(int i, atomic_t *v)
从v-counter减去i值; void atomic_inc(atomic_t *v)
给v-counter加1也就是自增; void atomic_dec(atomic_t *v)
从v-counter减1也就是自减; int atomic_dec_return(atomic_t *v)
从v-counter减1并且返回v的值; int atomic_inc_return(atomic_t *v)
给v-counter加1并且返回v的值; int atomic_sub_and_test(int i.atomic_t *v)
从v-counter减i如果结果为0就返回真否则返回假; int atomic_dec_and_test(atomic_t *v)
从v-counter减1如果结果为0就返回真否则返回假; int atomic_inc_and_test(atomic_t *v)
给v-counter加1如果结果为0就返回真否则返回假; int atomic_add_negative(int i, atomic_t *v)
给v-counter加i如果结果为负就返回真否则返回假; 举例
atomic_t v ATOMIC_INIT(0); /* 定义原子变量v并初始化原子变零v0 */
atomic_set(v, 10); /* 设置v-counter10 */
atomic_read(v); /* 读取v-counter的值肯定是10 */
atomic_inc(v); /* v-counter的值加1v-counter11 */ atomic_t Mylock; /* 原子变量 */
Mylock (atomic_t)ATOMIC_INIT(0); //初始化原子变量
atomic_set(Mylock, 1); //原子变量初始值为Mylock-counter1
if (!atomic_dec_and_test(Mylock)) {
//当Mylock-counter1时atomic_dec_and_test()返回1
//从Mylock-counter减1如果结果为0就返回1否则返回0; atomic_inc(Mylock); return -EBUSY; /* Mylock被使用返回忙 */
} 2)、“原子位数据”操作
需要包含“#include bitops.h”
void set_bit(int nr, void *p)
将p地址的第nr位置1; void clear_bit(int nr,void *p)
将p地址的第nr位清零; void change_bit(int nr, void *p)
将p地址的第nr位进行翻转; int test_bit(int nr, void *p)
获取p地址的第nr位的值; int test_and_set_bit(int nr, void *p)
将p地址的第nr位置 1并且返回第nr位原来的值; int test_and_clear_bit(int nr, void *p)
将p地址的第nr位清零并且返回第nr位原来的值; int test_and_change_bit(int nr, void *p)
将p地址的第nr位翻转并且返回第nr位原来的值;
举例
#include bitops.h
unsigned long mig_status;
#define NFS_MIG_FAILED (2) int ret;
if ( test_bit(NFS_MIG_FAILED, mig_status) )
{//mig_status的bit21则执行 ret -EIO; return ret;
} 3)、原子操作只对整型变量和位变量数据进行保护。 3、自旋锁
1)、自旋锁结构体spinlock_t
需要包含头文件“#include spinlock_types.h ”;
Linux内核使用结构体spinlock_t表示自旋锁定义如下
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; 2)、采用“自旋锁”实现线程之间的并发访问函数
需要包含头文件“#include spinlock.h”;
#define DEFINE_SPINLOCK(x) spinlock_t x __SPIN_LOCK_UNLOCKED(x)
声明x为自旋锁结构并初始化; int spin_lock_init(spinlock_t *lock)
初始化自旋锁; void spin_lock(spinlock_t *lock)
获取指定的自旋锁也叫做加锁; void spin_unlock(spinlock_t *lock)
释放指定的自旋锁; int spin_trylock(spinlock_t *lock)
尝试获取指定的自旋锁如果没有获取到就返回0; int spin_is_locked(spinlock_t *lock)
检查指定的自旋锁是否被获取如果没有被获取就返回非0否则返回0; 注意
线程之间的并发访问函数适用于线程之间的并发访问但不适合中断程序并发访问。 3)、采用“自旋锁”实现线程与中断之间的并发访问函数
void spin_lock_irq(spinlock_t *lock)
禁止本地中断并获取自旋锁; void spin_unlock_irq(spinlock_t *lock)
激活本地中断并释放自旋锁; void spin_lock_irqsave(spinlock_t *lockunsigned long flags)
保存中断状态禁止本地中断并获取自旋锁; void spin_unlock_irqrestore(spinlock_t*lock, unsigned long flags)
将中断状态恢复到以前的状态并且激活本地中断释放自旋锁。 注意在线程中使用spin_lock_irqsave()和spin_unlock_irqrestore();在中断中使用spin_lock()和spin_unlock()来获取自旋锁和释放自旋锁。不推荐使用spin_lock_irq()和spin_unlock_irq函数。 4)、举例
DEFINE_SPINLOCK(MyLock) /*声明MyLock为自旋锁结构并初始化*/
/* 线程A */
void functionA (){
unsigned long flags; /* 中断状态 */
spin_lock_irqsave(MyLock, flags); /* 获取锁 */ //保存中断状态禁止本地中断并获取自旋锁; /* 临界区 */ spin_unlock_irqrestore(MyLock, flags); /* 释放锁 */ //将中断状态恢复到以前的状态并且激活本地中断释放自旋锁;
} /* 中断服务函数 */
void irq() {
spin_lock(MyLock); /* 获取锁 */
//获取MyLock自旋锁也叫做加锁; /* 临界区 */ spin_unlock(MyLock); /* 释放锁 */
//释放MyLock自旋锁;
}
5)、“下半部”并发访问函数
需要包含文件“#include spinlock.h”
下半部(BH)也会竟争共享资源有些资料也会将下半部叫做底半部。如果要在“下半部”里面使用自旋锁则需要用到下面的函数
void spin_lock_bh(spinlock_t*lock)
关闭下半部并获取自旋锁; void spin_unlock_bh(spinlock_t*lock)
打开下半部并释放自旋锁; 6)、自旋锁使用注意事项
①、“其它线程”在等待自旋锁的时处于“自旋”状态,因此“持有锁的线程”不能长时间持用这个自旋锁一定要短,否则的话会降低系统性能。如果临界区比较大运行时间比较长的话要选择其他的并发处理方式比如信号量和互斥体。
②、自旋锁保护的临界区内不能调用任何可能导致线程休眠的API函数否则的话可能导致死锁。
③、不能递归申请白旋锁因为一旦通过递归的方式申请一个你正在持有的锁那么你就必须“自旋”等待锁被释放然而你正处于“自旋”状态根本没法释放锁。结果就是自己把自己锁死了!
④、在编写驱动程序的时候我们必须考虑到驱动的可移植性因此不管你用的是单核的还是多核的SOC都将其当做多核SOC来编写驱动程序。 4、读写锁函数
需要包含文件“#include rwlock_types.h”
typedef struct { arch_rwlock_t raw_lock; #ifdef CONFIG_DEBUG_SPINLOCK unsigned int magic, owner_cpu; void *owner;
#endif #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map;
#endif
} rwlock_t; #define DEFINE_RWLOCK(x) rwlock_t x __RW_LOCK_UNLOCKED(x)
声明“读写锁结构变量x”并初始化读写锁; 读写锁函数需要包含文件“#include rwlock.h”
void rwlock_init(rwlock_t *lock)
初始化读写锁; void read_lock(rwlock_t *lock)
获取读锁; void read_unlock(rwlock_t *lock)
释放读锁; void read_lock_irq(rwlock_t *lock)
禁止本地中断并且获取读锁; void read_unlock_irg(rwlock_t *lock)
打开本地中断并且释放读锁; void read_lock_irgsave(rwlock_t *lock,unsigned long flags)
保存中断状态禁止本地中断并获取读锁; void read_unlock_irqrestore(rwlock_t *lock,unsigned long flags)
将中断状态恢复到以前的状态并且激活本地中断释放读锁; void read_lock_bh(rwlock_t *lock)
关闭下半部并获取读锁; void read_unlock_bh(rwlock_t *lock)
打开下半部并释放读锁; void write_lock(rwlock_t *lock)
获取写锁; void write_unlock(rwlock_t *lock)
释放写锁; void write_lock_irg(rwlock_t *lock)
禁止本地中断并且获取写锁; void write_unlock_irg(rwlock_t *lock)
打开本地中断并且释放写锁; void write_lock_irqsave(rwlock_t *lock,unsigned long flags)
保存中断状态禁止本地中断并获取写锁。 void write_unlock_irqrestore(rwlock_t *lock,unsigned long flags)
将中断状态恢复到以前的状态并且激活本地中断释放读锁。 void write_lock_bh(rwlock_t *lock)
关闭下半部并获取读锁; void write_unlock_bh(rwlock_t *lock)
打开下半部并释放读锁; 5、顺序锁
顺序锁结构需要包含文件“#include seqlock.h”
顺序锁结构体seqlock_t
typedef struct {
struct seqcount seqcount;
spinlock_t lock;
}seqlock_t; 使用顺序锁实现同时读写但是不允许同时进行并发的写操作。虽然顺序锁的读和写操作可以同时进行,但是如果在读的过程中发生了写操作,最好重新进行读取保证数据完整性。
注意
顺序锁“保护的资源”不能是指针因为如果在写操作的时候可能会导致指针无效而这个时候恰巧有读操作访问指针的话就可能导致意外发生比如读取野指针导致系统崩溃。 #define DEFINE_SEQLOCK(x) \ seqlock_t x __SEQLOCK_UNLOCKED(x)
声明“顺序锁结构变量x”,并初始化 void write_seqlock(seqlock_t *sl)
获取“写顺序锁”; void write_sequnlock(seqlock_t *sl)
释放“写顺序锁”; void write_seqlock_irq(seqlock_t *sl)
禁止本地中断并且获取“写顺序锁”; void write_sequnlock_irq(seqlock_t *sl)
打开本地中断并且释放“写顺序锁”; void write_seqlock_irqsave(seqlock_t *sl,unsigned long flags)
保存中断状态禁止本地中断并获取“写顺序锁”; void write_sequnlock_irqrestore(seqlock_t *sl,unsigned long flags)
将中断状态恢复到以前的状态并且激活本地中断释放“写顺序锁”; void write_seqlock_bh(seqlock_t *sl)
关闭下半部并获取“写顺序锁”; void write_sequnlock_bh(seqlock_t *sl)
打开下半部并释放“写顺序锁”; unsigned read_seqbegin(const seqlock_t *sl)
读单元访问共享资源的时候调用此函数此函数会返回顺序锁的顺序号; unsigned read_seqretry(const seqlock_t *sl,unsigned start)
读结束以后调用此函数检查在读的过程中有没有对资源进行写操作如果有的话就要重读; 6、信号量
信号量有一个“信号量值”用来控制访问共享资源的访问数量相当于通过“信号量值”控制访问资源的线程数。
如果将“信号量值”设置大于 1那么这个信号量就是“计数型信号量”它允许多个线程同时访问共享资源。
如果将“信号量值”设置为0那么这个信号量就是“二值信号量”它具有互斥访问共享资源的作用。 需要包含文件“#include semaphore.h”
1)、信号量结构体semaphore
struct semaphore { raw_spinlock_t lock; unsigned int count; struct list_head wait_list;
}; #define __SEMAPHORE_INITIALIZER(name, n) \
{ \ .lock __RAW_SPIN_LOCK_UNLOCKED((name).lock), \ .count n, \ .wait_list LIST_HEAD_INIT((name).wait_list), \
}
并将“name.count”设置为n; 2)、信号量函数
#define DEFINE_SEMAPHORE(name) \ struct semaphore name __SEMAPHORE_INITIALIZER(name, 1)
声明信号量结构变量为name并将信号量的值“name. count”设置为1;这是一个“计数型信号量”。 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);
}
初始化信号量结构变量sem并将“sem-count”设置为val; void down(struct semaphore *sem)
获取信号量但会导致休眠进入休眠状态的线程不能被信号打断因此不能在中断中使用。 int down_trylock(struct semaphore *sem)
尝试获取信号量如果能获取到信号量就获取并且返回0。如果不能就返回非0并不会进入休眠。 int down_interruptible(struct semaphore *sem)
获取信号量,进入休眠状态的线程可以被信号打断。 void up(struct semaphore *sem)
释放信号量 int down_killable(struct semaphore *sem)
进入休眠状态的线程可以被唤醒,中断获取信号量的操作; 3)、举例
struct semaphore sem; /* 定义信号量 */
sema_init(sem, 1) /* 初始化信号量 */
down(sem); /* 申请信号量 */ /* 临界区 */ up(sem); /* 释放信号量 */ 4)、信号量的特点:
信号量可以使等待资源线程进入休眠状态因此适用于那些占用资源比较久的场
合因此信号量不能用于中断中因为信号量会引起休眠中断是不能进入休眠的。
如果共享资源的持有时间比较短那就不适合使用信号量。因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。 7、互斥体
需要包含文件“#include mutex.h”
struct mutex { atomic_long_t owner; spinlock_t wait_lock;
};
#define DEFINE_MUTEX(mutexname) \ struct mutex mutexname __MUTEX_INITIALIZER(mutexname)
声明互斥结构变量mutexname并初始化; void mutex_init(struct mutex *lock);
并初始化互斥结构变量lock void mutex_lock(struct mutex *lock)
获取 mutex也就是给 mutex 上锁。如果获取不到就进休眠; void mutex_unlock(struct mutex *lock)
释放 mutex也就给 mutex 解锁; int mutex_trylock(struct mutex *lock)
尝试获取mutex如果成功就返回 1如果失败就返回 0; int mutex_is_locked(struct mutex *lock)
判断mutex_是否被获取如果是的话就返回1否则返回 0; int mutex_lock_interruptible(struct mutex *lock)
使用此函数获取信号量失败进入休眠以后可以被信号打断; 举例
struct mutex lock; /* 定义一个互斥体 */
mutex_init(lock); /* 初始化互斥体 */ mutex_lock(lock); /* 上锁 */ /* 临界区 */ mutex_unlock(lock); /* 解锁 */ 使用“互斥体”时需要注意如下几点:
①、互斥体可以导致休眠因此不能在中断中使用互斥体在中断中只能使用自旋锁。
②、和信号量一样“互斥体”保护的临界区可以调用引起阻塞的API函数。
因为一次只有一个线程可以持有“互斥体”因此必须由“互斥体”的持有者释放“互斥体”。
③、“互斥体”不能递归上锁和解锁。