【极速理解】内核中解决竞态的方法之 信号量、互斥体、原子操作

内核中解决竞态的方法之 信号量、互斥体、原子操作

1. 信号量

1.1 原理

​ 当一个进程获取到了信号量之后,信号量的值会减为 0 ,如果另外一个进程想获取信号量,会无法获取到值为 0 的信号量资源,然后进入休眠模式

1.2 注意项

  1. 信号量对多核 CPU 也有效;
  2. 获取不到信号量资源的进程,会进入休眠状态,所以这种状态不消耗 CPU 资源
  3. 信号量不会有死锁效应(本质也是因为获取不到的进程会休眠,而不是忙等);
  4. 信号量保护的临界区可以很大,所以信号量保护的临界资源区域中,可以有延时甚至休眠的操作(本质也是因为获取不到的进程会休眠,而不是忙等);
  5. 信号量不能用在中断中,但是在进程上下文中很好用;
  6. 信号量上锁的时候是不会关闭抢断的。

1.3 信号量的函数接口

/* 定义一个信号量 */
struct semaphore sem;

/* 初始化信号量 */
sema_init(struct semaphore *sem, int val);	// val 通常设置为1 这样才有互斥效果
										// 设置为 0 可以用作同步机制的设计 不常用

/* 上锁 */
down(struct semaphore *sem);			// 上锁 也就是把信号量的值减 1
down_trylock(struct semaphore *sem); 	 // 尝试获取信号量 成功返回 0 失败返回 1 这个函数非阻塞
    
/* 解锁 */
up(struct semaphore *sem);

1.4 实例

............
struct semaphore sem; 					// 定义一个信号量

/* 入口函数 */
static int __init ledDev_init(void)
{
    ............
    sema_init(&sem,1); 					// 初始化信号量 值设定为1 实现互斥逻辑
    return 0;
    .............
}

/* 打开设备文件的函数 */
int ledDev_open(struct inode* inode, struct file* file)
{
    // down(&sem);						// 获取信号量
    if((down_trylock(&sem)) == 1)		 // 其他进程尝试获取信号量 无法成功获取时直接返回设备忙的错误码
    {
        return -EBUSY;
    }
    .........
}
............
/* 关闭设备文件的函数 */
int ledDev_close(struct inode* inode, struct file* file)
{
    ........
    up(&sem);						   // 恢复信号量值
    return 0;
}

2. 互斥体

2.1 原理

​ 当前进程获取到互斥体之后,别的进程也尝试获取互斥体时会失败,短暂等待后,进入休眠状态。

2.2 注意项

  1. 多核 CPU 也有效;
  2. 获取不到互斥体的时候,短暂等待之后才开始休眠;
  3. 互斥体不会产生死锁
  4. 互斥体保护的临界区域很大,里面可以放延时甚至休眠操作;
  5. 互斥体无法工作在中断,但是很适合用在进程间的上下文切换;
  6. 互斥体上锁时不会关闭抢占
  7. 由于进程在无法获取到互斥体时,会短暂等待,才开始休眠,因此在未知临界区大小的时候,优先使用互斥体。原因是:如果临界区非常小,在执行完临界区的全部操作后,另一个进程还未来得及进入休眠状态(还在短暂等待中),可以直接接管互斥体,省去了进入休眠然后停止休眠的过程,执行效率高。

2.3 互斥体的函数接口

/* 定义一个互斥体 */
struct mutex mutex;

/* 初始化互斥体 */
mutex_init(&mutex);

/* 上锁 */
mutex_lock(struct mutex *lock);			// 上锁
mutex_trylock(struct mutex *lock); 	     // 尝试上锁 成功返回 1 失败返回 0
    
/* 解锁 */
mutex_unlock(struct mutex *lock);

2.4 实例

............
struct mutex mutex;						// 定义一个互斥体
............
/* 入口函数 */
static int __init ledDev_init(void)
{
    ............
    mutex_init(&mutex);					// 初始化互斥体
    return 0;
    .............
}

/* 打开设备文件的函数 */
int ledDev_open(struct inode* inode, struct file* file)
{
    if((mutex_trylock(&mutex)) == 0)	  // 其他进程尝试上锁 无法成功上锁时直接返回设备忙的错误码
    {
        return -EBUSY;
    }
    .........
}
............
/* 关闭设备文件的函数 */
int ledDev_close(struct inode* inode, struct file* file)
{
    ........
    mutex_unlock(&mutex);				 // 解锁
    return 0;
}

3. 原子操作

3.1 原理

​ 原子操作本质上是在操作原子变量。被设定为原子变量的数据,是无法通过常规的方式进行值的修改的,必须使用特定的函数接口,通过内联汇编实现值的修改。这个过程可以保证同时只有一个进程在执行,也就是不会产生数据竞争问题。可以借助一个原子变量作为标志位,实现对竞态的处理。

3.2 原子操作的函数接口

/* 方案一 */
atomic_t atm = ATOMIC_INIT(1);		// 定义并初始化原子变量 值初始化为 1
atomic_dec_and_test(atomic_t *v);	// 上锁操作 让原子变量的值减去 1 然后和 0 比较
								 // 如果结果为 0 表示修改成功(是本进程的操作) 返回真
								 // 如果结果不为 0 表示修改失败(是别的进程的操作) 返回假
atomic_inc(atomic_t *v);		   // 解锁

/* 方案二 */
atomic_t atm = ATOMIC_INIT(-1);		// 定义并初始化原子变量 值初始化为 -1
atomic_inc_and_test(atomic_t *v);	// 上锁操作 让原子变量的值加上 1 然后和 0 比较
								 // 如果结果为 0 表示修改成功(是本进程的操作) 返回真
								 // 如果结果不为 0 表示修改失败(是别的进程的操作) 返回假
atomic_dec(atomic_t *v);		   // 解锁

3.3 实例

/* 打开设备文件的函数 */
int ledDev_open(struct inode* inode, struct file* file)
{
   if(!atomic_dec_and_test(&atm))	// 如果走进判断语句 说明原子操作后的结果不为0 说明是别的进程在操作
    {
		atomic_inc(&atm);		   // 恢复原子变量的值
		return -EBUSY;
    }
    .........
}
............
/* 关闭设备文件的函数 */
int ledDev_close(struct inode* inode, struct file* file)
{
    ........
    atomic_inc(&atm);				 // 解锁 恢复原子变量的值
    return 0;
}

Tips: 原子操作进行对竞态的解决,本质上原理和自旋锁中的标志位 flag 的实现原理是一样的,只是原子操作的对象是原子变量,天生就可以保证防止竞态。而普通的 flag 变量作为标志位,就不能天生保证防止多个进程同时修改 flag 值,因此需要配合自旋锁进行工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值