Linux内核中的竞态和并发

内联汇编

看韦东山完全开发手册中的内联汇编部分

———————————————————————————————————————

什么是竞态

https://blog.csdn.net/lj19990824/article/details/104563968

竞态:多个执行线程访问了同一个临界资源,就会导致竞态

导致竞态原因:
1.多进程同时访问操作临界资源(进程和抢占它的进程之间会导致竞态)
2.进程和中断
3.对称多处理器

解决竞态的具体方法
1.屏蔽中断
2.原子变量
3.使用锁

———————————————————————————————————————

什么是内核抢占?

https://blog.csdn.net/Rong_Toa/article/details/114496009

一个线程在一个cpu上运行, 不是这个线程主动让出cpu导致的其他线程跑到这个cpu上 的情况都是抢占。
就是正在占用着CPU 但是被其他线程

关抢占就是禁止这样的行为发生。

线程主动让出cpu的行为有,比如一个线程发送了一个I/O任务,然后调用
wait_for_completion睡眠等待completion。其他的各种行为我们都认为是内核抢占行为,
比如,周期性的tick中断中调度器把另外一个线程调度到cpu上跑;各种中断程序里,
触发了调度把另一个线程调度到cpu上跑。

内核抢占发生的时机,一般发生在:

1.当从中断处理程序正在执行,且返回内核空间之前。当一个中断处理例程退出,在返回到内核态时(kernel-space)。这是隐式的调用schedule()函数,当前任务没有主动放弃CPU使用权,而是被剥夺了CPU使用权。

2.当内核代码再一次具有可抢占性的时候,如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等, 此时当kernel code从不可抢占状态变为可抢占状态时(preemptible
again)。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数 。
3. 如果内核中的任务显式的调用schedule(), 任务主动放弃CPU使用权 。
4. 如果内核中的任务阻塞(这同样也会导致调用schedule()), 导致需要调用schedule()函数。任务主动放弃CPU使用权。

所以,内核里一段程序之前关了内核抢占之后又把内核抢占打开,如果这块代码中没有
主动让出cpu的行为,我们可以认为这段代码在一个cpu上会持续的执行完,中间不会被
打断。但是,这里说的是单个cpu上的情况, 当这段代码里有访问全局的数据结构的时候,
对于那个全局的数据结构,还是可能和其他cpu上的程序并发访问的。

支持内核抢占的内核叫做抢占式内核,不支持内核抢占的内核叫做不可抢占式内核。

Linux是抢占式内核,2.6内核以前只支持用户态抢占,后来增加了Kernel抢占。
早期的的Linux内核是“不可抢占”的,假设有A、B两个程序在运行,当前是程序A在运行,什么时候轮到程序B运行呢?
① 程序A主动放弃CPU:
比如它调用某个系统调用、调用某个驱动,进入内核态后执行了schedule()主动启动一次调度。
② 程序A调用系统函数进入内核态,从内核态返回用户态的前夕:
这时内核会判断是否应该切换程序。
③ 程序A正在用户态运行,发生了中断:
内核处理完中断,继续执行程序A的用户态指令的前夕,它会判断是否应该切换程序。

从这个过程可知,对于“不可抢占”的内核,当程序A运行内核态代码时进程是无法切换的(除非程序A主动放弃),比如执行某个系统调用、执行某个驱动时,进程无法切换。
这会导致2个问题:
① 优先级反转:
一个低优先级的程序,因为它正在内核态执行某些很耗时的操作,在这一段时间内更高优先级的程序也无法运行。
② 在内核态发生的中断不会导致进程切换

为了让系统的实时性更佳,Linux内核引入了“抢占”(preempt)的功能:进程运行于内核态时,进程调度也是可以发生的。
回到上面的例子,程序A调用某个驱动执行耗时的操作,在这一段时间内系统是可以切换去执行更高优先级的程序。
对于可抢占的内核,编写驱动程序时要时刻注意:你的驱动程序随时可能被打断、随时是可以被另一个进程来重新执行。对于可抢占的内核,在驱动程序中要考虑对临界资源加锁。

下图给出非抢占式内核调度情况:
在这里插入图片描述
下图给出抢占式内核调度情况:
在这里插入图片描述
对比两个图可以发现:采用抢占式内核调度的情况下,在中断中唤醒一个高优先级任务能够得到很好的响应。

也就是说,如果内核不支持抢占,那么低优先级进程在内核态运行时就算发生了更高优先级的进程,内核也需要将当前的低优先级进程执行完再去处理高优先级进程。

什么是进程上下文

关于进程的上下文:
用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存器值、变量等。所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。

进程上下文是进程执行活动全过程的静态描述。我们把已执行过的进程指令和数据在相关寄存器与堆栈中的内容称为进程上文,把正在执行的指令和数据在寄存器与堆栈中的内容称为进程正文,把待执行的指令和数据在寄存器与堆栈中的内容称为进程下文。

硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓的“中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。

关于进程上下文LINUX完全注释中的一段话:
当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。

**当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。在LINUX中,当前进程上下文均保存在进程的任务数据结构中。在发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中断服务结束时能恢复被中断进程的执行。

一个进程表示的,就是一个可执行程序的一次执行过程中的一个状态 。 操作系统对进程的管理,典型的情况,是通过进程表完成的。进程表中的每一个表项,记录的是当前操作系统中一个进程的情况。对于单CPU的情况而言,每一特定时刻只有一个进程占用CPU,但是系统中可能同时存在多个活动的(等待执行或继续执行的)进程。
一个称为" 程序计数器(program counter,pc)“的寄存器,指出当前占用CPU的进程要执行的下一条指令的位置。
当分给某个进程的CPU时间已经用完,操作系统将该进程相关的寄存器的值,保存到该进程在进程表中对应的表项里面;把将要接替这个进程占用CPU的那个进程的上下文,从进程表中读出,并更新相应的寄存器(这个过程称为"上下文交换(process context switch)”,程序寄存器pc指出程序当前已经执行到哪里,是进程上下文的重要内容,换出CPU的进程要保存这个寄存器的值,换入CPU的进程,也要根据进程表中保存的本进程执行上下文信息,更新这个寄存器)。

进程上下文和中断上下文

https://blog.csdn.net/qq_16933601/article/details/107305828
一般来说,CPU在任何时刻都处于以下三种情况之一:

(1) 运行于用户空间,执行用户进程; (2) 运行于内核空间,处于进程上下文; (3) 运行于内核空间,处于中断上下文。

1.应用程序通过系统调用陷入内核,此时处于进程上下文
我们之前学习的保存现场——保存寄存器保持栈的值等,说的就是保存进程上下文
2.现代几乎所有的CPU体系结构都支持中断。当外部设备产生中断,向CPU发送一个异步信号,CPU调用相应的中断处理程序来处理该中断,此时CPU处于中断上下文

1.在进程上下文中,可以通过current关联相应的任务。进程以进程上下文的形式运行在内核空间,可以发生睡眠,所以在进程上下文中,可以使用信号量(semaphore)。实际上,内核经常在进程上下文中使用信号量来完成任务之间的同步,当然也可以使用锁。

2.中断上下文不属于任何进程,它与current没有任何关系(尽管此时current指向被中断的进程)。由于没有进程背景,在中断上下文中不能发生睡眠,否则又如何对它进行调度?所以在中断上下文中只能使用锁(使用自旋锁)进行同步,正是因为这个原因,中断上下文也叫做原子上下文(atomic context)(关于同步可以参考同步)。在中断处理程序中,通常会禁止同一中断,甚至会禁止整个本地中断,所以中断处理程序应该尽可能迅速,所以又把中断处理分成上部和下部(关于中断)。

中断上下文为什么不能睡眠?
1:Linux的调度是针对线程级别的,中断上下文不与某个线程关联,没有上下文,不需要睡眠调度;
2:由于中断上下文没有与之关联的进程上下文,如果因为睡眠被调度走了,那就没有办法调度回来。(意思就是如果在睡眠中失去了CPU 就没有办法再占用CPU了)(schedule里pick_next_task的时候,只能选中下一个task,但是中断上下文不是一个task,也就是 “ 丢了就回不来了 ”)

抢占和中断的区别

首先被中断不是被抢占,中断和抢占是两个概念。
https://blog.csdn.net/weixin_44062361/article/details/108705781

  1. 抢占必须涉及进程上下文的切换;2.而中断是在中断上下文。
    (1)所谓可抢占抢的是进程上下文,既占用CPU,人人都争取上台。
    (2)可中断指的是是否可以中断当前CPU而进入我的中断处理函数

如果内核是不可抢占的(比如说2.4的内核),一旦切进内核态,只要代码不是主动释放CPU它就可以一直占着CPU。虽不可抢占,但若此时发生中断,代码还是要交出CPU,但是中断返回之后,代码又能霸占CPU了,此为可中断但不可抢占。

不过我们可以通过中断 在中断服务程序中引发抢占!

如果内核是可抢占的(比如2.6或之后的内核),上述情况就不会发生了。

———————————————————————————————————————

同步与互斥的失败例子

01 static int valid = 1;
02
03 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file)
04 {
   
05      if (!valid)
06      {
   
07              return -EBUSY;
08      }
09      else
10      {
   
11              valid 
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值