《Linux 内核设计与实现》09. 内核同步介绍

共享资源之所以要防止并发访问,是因为如果多个执行线程同时访问和操作数据,就有可能发生各线程之间相互覆盖共享数据的情况,从而造成被访问的数据不一致状态。

临界区和竞争条件

  • 临界区:访问和操作共享数据的代码段。
  • 原子操作:对资源的操作必须保证在结束之前不可被打断。
  • 竞争条件:两个执行线程对同一个临界区操作。
  • 同步:避免并发和防止竞争条件。

加锁

现在有一个队列,有两个函数,一个是在尾部添加元素,另一个是删除尾部元素。这两个函数在内核的各个部分都可以调用,因此现在假设程序 A 要增加一个元素,当增加完了后程序 A 被程序 B 抢占,此时程序 B 上 CPU 执行,程序 B 要删除尾部元素。

这样可以明显的看到,队列是共享资源,A 和 B 冲突了,B 删除了 A 需要的数据。

给程序加锁可以解决问题。

image-20230421202104944

锁有多种多样的形式,而且加锁的粒度范围也各不相同。

各种锁机制之间的区别在于,当锁已经被其它线程所持有,因而不可用时的行为表现:

  • 一些锁被争用时,会简单地执行忙等待(反复处于一个循环中,不断检测锁的状态,等待锁变为可用)
  • 一些锁会使当前任务睡眠直到锁可用为止。

锁是采用原子操作实现的,而原子操作不存在竞争。

造成并发执行的原因

用户空间之所以需要同步,是因为用户程序会被调度程序抢占和重新调度。由于用户进行任何时刻都可能会抢占,因此如果抢占后的那个进程也处于同一个临界区时,前后两个进程相互之间就会产生竞争。

另外,因为型号处理是异步发生的,所以,即使是单线程的多个进程共享文件,或者在一个程序内部处理信号,也有可能产生竞争条件。这种类型的并发操作,其实两者并不是真的同时发生,但它们相互交叉进行,所以也可称作伪并发执行。

在对称多处理器中,两个进程就可以真正地在临界区中同时执行,这种称为真并发。

内核中可能造成并发执行的原因:

  • 中断
  • 软中断和 tasklet
  • 内核抢占
  • 睡眠以及用户空间的同步
  • 对称多处理

在编写代码的开始阶段就要设计恰当的锁。

了解要保护什么

  • 如果有其它执行线程可以访问这些数据,那么就给这些数据加上某种所。
  • 如果任何其它睡眠东西都能看到它,那么就要锁住它。

要上锁的是数据,而不是代码。

在编写内核代码时,要清楚:

  • 这个数据是不是全局的?除了当前线程外,其它线程能不能访问它。
  • 这个数据会不会在进程上下文和中断上下文中共享?它是不是要在两个不同的中断处理程序中共享?
  • 进程在访问数据时是否可能被抢占?被调度的新程序会不会访问同一数据?
  • 当前进程是否会休眠(阻塞)在某些资源上,若是,它会让共享数据处于何种状态?
  • 怎么防止数据失控?
  • 如果这个函数又在另一个处理器上被调度将会发生什么?
  • 如何确保代码远离并发威胁?

几乎访问所有的内核全局变量和共享数据都需要某种形式的同步。

死锁

当有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用了。所有线程都在相互等待,但它们永远都不会释放已经占用的资源。于是任何线程都无法继续,这便是死锁

自死锁: 一个执行线程试图去获得一个自己已经持有的锁,它将不得不等待锁被释放,但因为它正在忙着等待这个锁,所以自己永远也不会有机会释放锁,最终形成死锁。

image-20230422092034533

ABBA死锁:n 个线程和 n 个锁,若每个线程都持有一把其它进程需要得到的锁,那么所有的线程都将阻塞地等待它们希望得到的锁重新可用。

image-20230422092057110

写代码中,避免死锁的一些规则:

  • 按顺序加锁。使用嵌套的锁时必须保证以相同的顺序获得锁。
  • 防止发生饥饿。程序是否一定会执行结束?若A进程不结束,那么B进程还需要等下去吗?
  • 不要重复请求同一个锁。
  • 设计锁要力求简单,越复杂的加锁方案越可能造成死锁。

如果有两个或多个锁曾在同一时间内被请求,那么以后其它函数请求它们也必须按照前次的加锁顺序进行。

现在有个函数(线程1)以 cat、dog 最后以 fox 的顺序获得锁,那么其它函数也都必须要以这个顺序获得锁。现假设另一个函数并没有以这个顺序得到锁,而是先得到 fox 锁,然后获得 dog 锁,因为 dog 总是先于 fox 被获得(dog 被线程1拿走了),因此造成死锁。

image-20230422094940676

可以看到,线程2等到 dog 锁,而线程1在等到 fox 锁,两边都不肯释放自己所占用的锁,因此造成死锁。

在代码中使用锁的地方,对锁的获取顺序加上注释是个良好习惯,例如:

/*
 * cat_lock —— 用于保护访问 cat 数据结构的锁,总是要在获得锁 dog 前先获得
 */

争用和扩展性

略…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值