linux内核笔记-内核同步

时间:2023-12-21 23:47:02

linux内核就相当于不断对请求进行响应的服务器,这些请求可能来自CPU,可能来自发出中断的外部设备。我们将内核看作两种请求的侍者。

(1)老板提出请求,侍者如果空闲,为老板服务。(系统调用或异常)

(2)侍者正在无顾客服务时,老板提出请求,则转去为老板服务。(中断异常嵌套)

(3)侍者正为老板服务时,另一老板提出请求,此时转去为另一个老板服务,回头再给这个老板服务。(中断嵌套)

(4)老板可命令老板停止为当前客户服务,侍者完成老板请求之后,可能为新选中的顾客服务。(内核抢占)

内核抢占特点:一个内核态运行的进程,可以被执行内核函数期间被另一个进程取代。有关内核抢占参照本文:内核抢占

同步概念

      临界区是一段代码,进入临界区的内核控制路径必须全部执行这段代码,其他控制路径才能进入临界区。单核cpu可以通过简单的关闭中断、禁止内核抢占来进行保证临界区的互斥访问,多cpu就不行 了。

下列条件不需要同步:

(1)执行中断的内核路径不可能被可延迟或系统调用服务例程内核路径所中断,所以不需要同步;

(2)软中断和tasklet不能再同一个CPU上交替执行,所以仅这两者存在时,不需要同步;

(3)仅被一种tasklet访问的数据结构不需要同步。

同步原语

1.每CPU变量

最重要的同步技术就是把内核变量声明为每CPU变量,每CPU变量主要是数据结构的数组,系统每个CPU对应数组的一个元素。一个CPU不应该访问与其他CPU对应的数组元素。每个CPU可以随意修改自己的元素不用担心出现竞争条件。每CPU变量对来自异步函数(中断和可延迟函数)的访问不提供保护,在这种情况下需要提供另外的同步原语。内核抢占可导致每CPU变量产生竞争条件。

2.原子操作

这样的操作是芯片原子级的,执行这样的指令,中间不能中断,且避免其他的CPU访问同样的内存单元。这种原子操作,通过“锁定”内存总线实现,直到这条指令执行结束为止,其他处理器不能访问该内存单元。原子操作:

atom_read(v),atom_set,atom_add,atom_sub.....

3.优化和内存屏障

     当使用编译器的时候,编译器可能重新安排汇编语言指令以使寄存器以最优的方式执行。当处理同步时,必须避免指令重新排序。可通过设置内存屏障,确保原语之后的操作语句执行之前,原语操作之前的语句已经完成,这样保证汇编语句顺序执行。

4.自旋锁

     自旋锁是用来多处理环境中的一种特殊锁。如果内核路径发现锁是开着的,则可获取锁进入临界区。相反如果内核路径发现锁由另一个CPU上的控制路径所持有,则在周围“旋转”(其实,占着cpu死等,等到为止)。

自旋锁的循环指令表示“忙等”(这种忙等,是nop操作,经过linux优化了,说是节省能源)。注意,在忙等期间,内核抢占有效,就是等待的进程可能被其他进程替代。一般来说,由自旋锁所保护的每个临界区都是禁止内核抢占的。

spinlock_t含有的两个字段:

slock;自旋锁的状态,1表示未加锁

break_lock:表示有无等待该锁的进程

提供的操作:

spin_lock_init();//初始化一把锁

spin_lock();//循环试图获得自旋锁

spin_unlock();//释放锁

spin_unlock_wait();//等待锁

spin_is_locked();//判断锁释是否被持有

5.读写自旋锁

      读写锁是为了增加内核的并发能力。该锁的特点:读者进程可以同时多个进入临界区;写者只能一个进入临界区(有写者的时候,读者无法进入)。读者和写者具有相同的优先权。

读/写自旋锁是一个rwlock_t结构。有read_lock和write_lock。

6.顺序锁

     读/写自旋锁中,读者和写者具有相同的优先权。事实上,可以这样,即使读者在读的时候,也可以让写者继续进行,这种策略,写者永远不会等待。但是,读者需要对所读到的东西检查,检查是否有被某个写者修改,一旦发现被修改,要重新读取。

7.读-拷贝-创新(RCU)

写者要写的时候,拷贝一份出来写,然后拷回去。

8.信号量

信号量其实也是为了实现互斥和同步(无外乎就是一种P/V操作封装)。但是,信号量中的进程一旦发现资源不可用时,就将自己挂起进入睡眠状态,这不是一种忙等。

然后,使用该资源的进程使用结束后,唤醒正在等待的进程,ok,同步完毕。

linux提供两种信号量:(1)内核信号量,由内核控制路径控制;(2)System V IPC信号量,用户态进程使用。

9.信号量

     跟信号量是一样的,但是允许多个读者同时进入设计的,你懂的。