linux并发和竞态2

        SMP(对称多处理器), 中断, 强占式内核(多进程)并发导致的竞态.竞态通常是对共享资源的访问产生的, 因此驱动程序中要尽可能避免产生共享数据.最明显的,避免在驱动中使用全局变量全局变量不是共享数据的唯一途径, 只要我们的代码把一个指针传递给了内核的其他部分, 一个共享数据可能就会产生.解决共享的一个方式是"加锁".或者"互斥".

1. 原子变量

        普通变量 --> -->, 三步导致访问一个基础变量也会并发问题. 内核提供原子变量使用专用访问函数实现,,不可分割, 定义一个原子变量所有CPU有效.

特点: 使用简单, 高效,SMP系统所有CPU生效.

atomic_t 原子变量结构体, 定义在<asm/atomic.h>

原子变量是int类型, 但是不能记录24位以上的数据, 提供了一些宏定义完成对原子变量的增加和减少.

1.2 原子变量操作函数

#define ATOMIC_INIT(i)	{ (i) } /*定义 atomic_t ato = ATIMIC_INIT(0)*/

#define atomic_read(v)	READ_ONCE((v)->counter)
#define atomic_set(v,i)	WRITE_ONCE(((v)->counter), (i))

#define atomic_dec_return(v)		atomic_sub_return(1, (v))
#define atomic_inc_return(v)		atomic_add_return(1, (v))
#define atomic64_dec_return(v)		atomic64_sub_return(1, (v))
#define atomic64_inc_return(v)		atomic64_add_return(1, (v))

#define atomic_sub_and_test(i,v)	(atomic_sub_return((i), (v)) == 0)
#define atomic_dec_and_test(v)		(atomic_sub_return(1, (v)) == 0)
#define atomic_inc_and_test(v)		(atomic_add_return(1, (v)) == 0)
#define atomic64_sub_and_test(i,v)	(atomic64_sub_return((i), (v)) == 0)
#define atomic64_dec_and_test(v)	(atomic64_sub_return(1, (v)) == 0)
#define atomic64_inc_and_test(v)	(atomic64_add_return(1, (v)) == 0)

#define atomic_add(i,v)			(void)atomic_add_return((i), (v))  /*增加i*/
#define atomic_sub(i,v)			(void)atomic_sub_return((i), (v)) /*减少i*/
#define atomic_inc(v)			atomic_add(1, (v))    /*增加1*/
#define atomic_dec(v)			atomic_sub(1, (v))    /*减少1*/

#define atomic64_add(i,v)		(void)atomic64_add_return((i), (v))
#define atomic64_sub(i,v)		(void)atomic64_sub_return((i), (v))
#define atomic64_inc(v)			atomic64_add(1, (v))
#define atomic64_dec(v)			atomic64_sub(1, (v))

1.2 原子变量位操作函数

static inline void set_bit(int nr, volatile void *addr)  /*比特位nr 置1 nr是0到24的数字*/
static inline void clear_bit(int nr, volatile void *addr) /*比特位nr 清零*/
static inline void change_bit(int nr, volatile void *addr) /*变换bit位 nr*/
#define test_bit(nr, addr) __test_bit(nr, addr)  /*返回指定位的值, 这是唯一一个可以不使用原子操作的函数*/

/**
 * test_and_clear_bit - clear a bit and return its old value
 * @nr:  bit number to clear
 * @addr:  pointer to memory
 */
static inline int test_and_clear_bit(int nr, volatile void *addr)
static inline int test_and_set_bit(int nr, volatile void *addr)
static inline int test_and_change_bit(int nr, volatile void *addr)

2. 自旋锁

        信号量和互斥体都可能会导致获取者休眠, 在不能休眠的情况,比如中断处理函数中则不能使用信号量和自旋锁.

        自旋锁相比信号量优势,①可以在不允许休眠的地方使用,包括中断服务函数. ②自旋锁的执行效率更高, 合理使用可以提高系统效率.

        获取自旋锁的进程不会休眠, 它会不停的循环直到获取到自旋锁. 因此在单处理器的非强占试的系统中自旋锁是个空操作.

问题?

在只有优先级强占的系统中,如果低优先级的任务A已经获得到锁还没有释放, 此时高优先级B强占A优先级并获取锁. 这种情况系统是不是会卡死. 不会, 在A执行临界资源时是禁止被强占的. 但是B在忙等的过程中更高优先级的进程C是可以强占忙等的进程B的. 这有点类似于FreeRTOS的优先级继承机制.

2.1 自旋锁

自旋锁对应的API

/*结构体*/
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;




DEFINE_SPINLOCK(name) //帮助宏,定义并初始化一个全局的spinlock
spin_lock_init()   //手动初始化一个spinlokc实例 初始值是1

spin_lock(&srq->lock);
spin_unlock(&srq->lock);


spin_is_locked();  //查看自旋锁状态, 未锁, 返回0, 锁了 返回1
spin_try_lock();   //尝试获得锁,成功返回0 尝试失败返回1

        使用自旋锁的核心问题是在获得自旋锁后线程不能让出CPU. 自旋锁保存的代码必须是原子性的.对于强占式的系统必须禁止强占,即使在单CPU的系统上也要这样做.

        另外一种情况是中断, 如果在中断中获取一个已经被占用的自旋锁则系统将会卡死, 所以中断中有获取自旋锁的情况必须要关闭中断.不能使用spin_lock 和spin_unlock函数.

spin_lock_init(&srq->lock);
unsigned long flags;
/*申请和释放自旋锁*/
spin_lock_irqsave(&dd->sc_lock, flags);
spin_unlock_irqrestore(&dd->sc_lock, flags);


/*常规用法*/
static DEFINE_SPINLOCK(clocks_lock);
int clk_enable(struct clk *clk)
{
	unsigned long flags;

	spin_lock_irqsave(&clocks_lock, flags);
	if (clk->enabled++ == 0)
		(clk->enable)(clk, 1);
	spin_unlock_irqrestore(&clocks_lock, flags);

	return 0;
}

        如果确定没有其他地方禁用了中断则不用保存中断状态, 通常情况下我们无法保证别的地方没有禁用中断.不推荐使用下面的函数

spin_lock_irq(&qp->r_lock);
spin_unlock_irq(&qp->r_lock)

        如果仅仅要禁用软中断则可以使用如下函数

spin_lock_bh(&agent->lock);
spin_unlock_bh(&agent->lock);

        也可以使用尝试获取自旋锁.

spin_trylock(&mvotg->wq_lock) //释放自旋锁还是使用spin_unlokc()
spin_trylock_bh()  //释放使用spin_tryunlokc_bh()
/*中断没有try相关的函数.*

2.2 读写自旋锁

        允许读锁被申请多次, 多一个线程可以同时读临界资源, 当获取写锁时就要等待所有的读锁和写锁释放. 当获取读锁时就要保证写锁已经被释放.

/*结构体*/
typedef struct {
	arch_rwlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
	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
} rwlock_t;


rwlock_t	   tx_lock; //定义读写自旋锁实例
rwlock_init(&tx_lock); //初始化

/*锁函数 对应 unlock*/
read_lock()
read_lock_irqsave()
read_lock_irq()
read_lock_bh()

/*锁函数 对应 unlock*/
write_lock()
write_lock_irqsave()
write_lock_irq()
write_lock_bh()


/*应用实例*/
static DEFINE_RWLOCK(tape_device_lock);
static void tape_remove_minor(struct tape_device *device)
{
	write_lock(&tape_device_lock);
	list_del_init(&device->node);
	device->first_minor = -1;
	write_unlock(&tape_device_lock);
}

2.3 顺序锁

        顺序锁是对读写锁的优化,保证写优先. 在读写锁中如果有进程获取了读锁则写锁要等待读锁释放才能获得写锁. 顺序锁不用这样,在没有其他进程获取写锁的情况下进程可以直接获取写锁,而不受读锁的影响. 当然,读进程可以感知到自己读的过程中是否有写锁被获取, 如果有被获取则读的数据就是无效的,需要等待写锁释放后重新读.读进程如何感知自己读过程中是否有进程获取写锁? 顺序锁结构里有struct seqcount seqcount  变量, 获取写锁后该值会加一, 读进获取和释放读锁时会记录该值,如果两次不一致则认为数据是无效的.

        问题? , 因为顺序锁存在读过程被写过程打断的情况, 是否存写进程把指针设置成NULL 而读进程访问空指针而崩溃? 有可能,这要写进程不要把指针写成null 或无效. 或者读进程要判断无效指针.对于野指针的情况, 写进程如果抢占读进程,写进程没问题的情况下也不会出现野指针.换句话说,如果写进程有问题,无论是否有读写锁,都会有问题.

下面是API

/*类型定义*/
typedef struct {
	struct seqcount seqcount;
	spinlock_t lock;
} seqlock_t;

seqlock_init(&sde->head_lock);


#define seqlock_init(x)					\
	do {						\
		seqcount_init(&(x)->seqcount);		\
		spin_lock_init(&(x)->lock);		\
	} while (0)

/*不关中断,获取和释放顺序锁,要保证中断中不会调用该锁*/
write_seqlock(&dev->iowait_lock);
write_sequnlock(&dev->iowait_lock);

/*关闭中断 不保存中断状态, 不推荐使用. 以为无法保证别的地方修改中断*/
write_seqlock_irq(&dev->iowait_lock);
write_sequnlock_irq(&dev->iowait_lock);

/*关闭开启中断并保存中断状态, */
unsigned long flags;
write_seqlock_irqsave(&mdev->clock_lock, flags);
write_sequnlock_irqrestore(&mdev->clock_lock, flags);

/*读示例*/
uint  seq;
seq = read_seqbegin(&dev->iowait_lock);  //读并且保存计数值
read_seqretry(&dev->iowait_lock, seq);   //检查读前后是写计数是否一致.

         

3. 信号量

        特点: 低效, 对所有cpu适用, 不能在中断和以及不能休眠的地方使用.不常用,更多的使用互斥量来保护临界资源.

/* Please don't access any members of this structure directly */
struct semaphore {
	raw_spinlock_t		lock;  /*原始自旋锁,*/
	unsigned int		count;    /*信号量数量,大于0 则获取信号量会成功,并该值减小*/
	struct list_head	wait_list; /*灯盖该信号量的进程的链表*/
};


static DEFINE_SEMAPHORE(adb_probe_mutex);  //定义初始初始信号量为1的全局信号量
sema_init(&dev->cmd.poll_sem, 1);//代码里初始化
up(&adb_probe_mutex);
down(&adb_probe_mutex);
down_trylock(&port->sm_sem);  //成功返回0  


/**
down_interruptible - 尝试获取信号量,除非被中断
@sem: 要获取的信号量
尝试获取指定的信号量。如果没有更多的任务被允许获取该信号量,调用此函数将使当前任务进入睡眠状态。
如果睡眠被信号中断,此函数将返回-EINTR。
如果成功获取了信号量,此函数返回0。
*/
down_intrrruptible()
 

4. 互斥体

        驱动中互斥体应用更多.

/*
 * Simple, straightforward mutexes with strict semantics:
 *
 * - only one task can hold the mutex at a time
 * - only the owner can unlock the mutex
 * - multiple unlocks are not permitted
 * - recursive locking is not permitted
 * - a mutex object must be initialized via the API
 * - a mutex object must not be initialized via memset or copying
 * - task may not exit with mutex held
 * - memory areas where held locks reside must not be freed
 * - held mutexes must not be reinitialized
 * - mutexes may not be used in hardware or software interrupt
 *   contexts such as tasklets and timers
 *
 * These semantics are fully enforced when DEBUG_MUTEXES is
 * enabled. Furthermore, besides enforcing the above rules, the mutex
 * debugging code also implements a number of additional features
 * that make lock debugging easier and faster:
 *
 * - uses symbolic names of mutexes, whenever they are printed in debug output
 * - point-of-acquire tracking, symbolic lookup of function names
 * - list of all locks held in the system, printout of them
 * - owner tracking
 * - detects self-recursing locks and prints out all relevant info
 * - detects multi-task circular deadlocks and prints out all affected
 *   locks and tasks (and only those tasks)
 */
 /*  
 * 简单的、直接的互斥锁,具有严格的语义:  
 *  
 * - 一次只能有一个任务持有互斥锁  
 * - 只有锁的持有者才能解锁互斥锁  
 * - 不允许多次解锁  
 * - 不允许递归锁定  
 * - 必须通过API初始化互斥锁对象  
 * - 不允许通过memset或复制来初始化互斥锁对象  
 * - 持有互斥锁的任务不得退出  
 * - 持有锁的内存区域不得被释放  
 * - 已持有的互斥锁不得重新初始化  
 * - 互斥锁不得在硬件或软件中断上下文中使用,如tasklets和定时器  
 *  
 * 当DEBUG_MUTEXES启用时,这些语义会被完全强制实施。此外,除了强制执行上述规则外,  
 * 互斥锁调试代码还实现了一些额外的功能,使锁调试更容易和更快:  
 *  
 * - 在调试输出中打印互斥锁时使用其符号名  
 * - 捕获点跟踪,函数名的符号查找  
 * - 列出系统中持有的所有锁,并打印它们  
 * - 持有者跟踪  
 * - 检测自递归锁并打印所有相关信息  
 * - 检测多任务循环死锁,并打印所有受影响的锁和任务(仅这些任务)  
 */
struct mutex {
	atomic_long_t		owner;
	spinlock_t		wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
	struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
	struct list_head	wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
	void			*magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map	dep_map;
#endif
};


static DEFINE_MUTEX(block_class_lock);
mutex_init();
mutex_lock();
mutex_unlock()
mutex_trylock();
mutex_is_lockec();// 是 返回1, 否,返回0
mutex_lock_interruptible()

/*使用示例*/
static DEFINE_MUTEX(block_class_lock);
void blkdev_show(struct seq_file *seqf, off_t offset)
{
	struct blk_major_name *dp;

	mutex_lock(&block_class_lock);
	for (dp = major_names[major_to_index(offset)]; dp; dp = dp->next)
		if (dp->major == offset)
			seq_printf(seqf, "%3d %s\n", dp->major, dp->name);
	mutex_unlock(&block_class_lock);
}

待完善, 深入理解linux内核.

  • 16
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值