handler原子锁_深入Linux内核架构——锁与进程间通信

本文详细探讨了Linux内核中的锁机制,包括原子操作、自旋锁、信号量、读者/写者锁、大内核锁、互斥量以及内存和优化屏障。这些机制用于在多处理器系统上保护共享资源,确保数据一致性。同时,文章还介绍了System V进程间通信的三种机制:信号量、消息队列和共享内存,以及信号和管道在进程通信中的应用。
摘要由CSDN通过智能技术生成

Linux作为多任务系统,当一个进程生成的数据传输到另一个进程时,或数据由多个进程共享时,或进程必须彼此等待时,或需要协调资源的使用时,应用程序必须彼此通信。

一、控制机制

1、竞态条件

几个进程在访问资源时彼此干扰的情况通常称之为竞态条件(race condition)。在对分布式应用编程时,这种情况是一个主要的问题,因为竞态条件无法通过系统的试错法检测。只有彻底研究源代码(深入了解各种可能发生的代码路径)并通过敏锐的直觉,才能找到并消除竞态条件。

2、临界区

对于竞态条件,其问题的本质是进程的执行在不应该的地方被中断,从而导致进程工作得不正确。对于此问题的解决方案不一定要求临界区不能中断,只要没有其他进程进入临界区,那么在临界区中执行的程序是可以中断的。确保几个进程不能同时改变共享值的禁止条件称为互斥。

大多数系统采用的方案是信号量(semaphore)的使用。信号量是由E. W. Dijkstra在1965年设计的。实质上,最初的信号量是受保护的特别变量,能够表示为正负整数,初始值为1。它有两个标准操作(up和down),这两个操作分别用于控制关键代码范围的进入和退出,且假定相互竞争的进程访问信号量机会均等。

在一个进程想要进入关键代码时,它调用down函数。这会将信号量的值减1,即将其设置为0,然后执行危险代码段(此时若有其他进程想进入该代码段调用down操作则会等待进入关键代码的进程完成操作)。在执行完操作之后,调用up函数将信号量的值加1,即重置为初始值。

信号量在用户层可以正常工作,原则上也可以用于解决内核内部的各种锁问题。但事实并非如此:性能是内核最首先的一个目标,虽然信号量初看起来容易实现,但其开销对内核来说过大,这也是内核中提供了许多不同的锁和同步机制的原因。

二、内核锁机制

在多处理器系统上,如果几个处理器同时处于核心态,理论上它们可以同时访问一个数据结构,刚好引发了竞态条件。因此,在第一个SMP功能的内核版本中,对该问题的处理是每次只允许一个处理器处于核心态,但这样效率不高。现在,内核使用了由锁组成的细粒度网络,用以明确保护各数据结构(如果处理器A在操作数据结构X,则处理器B可以执行任何其他的内核操作,但不能操作X)。

内核提供了各种锁选项,分别优化不同的内核数据使用模式:

原子操作:这些是最简单的锁操作。它们保证简单的操作,诸如计数器加1之类,可以不中断地原子执行,即使操作由几个汇编语句组成,也可以保证;

自旋锁:这些是最常用的锁选项,它们用于短期保护某段代码,以防止其他处理器的访问,在内核等待自旋锁释放时,会重复检查是否能获取锁,而不会进入睡眠状态(忙等待),如果等待时间较长,则效率显然不高;

信号量:这些是用经典方法实现的,在等待信号量释放时,内核进入睡眠状态,直至被唤醒,唤醒后,内核才重新尝试获取信号量,互斥量是信号量的特例,互斥量保护的临界区,每次只能有一个用户进入;

读者/写者锁:这些锁会区分对数据结构的两种不同类型的访问,任意数目的处理器都可以对数据结构进行并发读访问,但只有一个处理器能进行写访问(在进行写访问时,读访问是无法进行的)。

1、对整数的原子操作

内核定义了atomic_t数据类型,用作对整数计数器的原子操作的基础。从内核的角度看,这些操作相当于一条汇编语句。

为使得内核中平台独立的部分能够使用原子操作,用于操纵atomic_t类型变量的操作必须由特定于体系结构的代码提供(因为内核将标准类型进行了封装,原子变量只能借助于ATOMIC_INIT宏初始化,不能用普通运算符处理)。

内核为SMP系统提供了local_t数据类型。该类型允许在单个CPU上的原子操作。为修改此类型变量,内核提供了基本上与atomic_t数据类型相同的一组函数,只是将atomic替换为local。

2、自旋锁

自旋锁用于保护短的代码段,其中只包含少量C语句,会很快执行完毕。大多数内核数据结构都有自身的自旋锁,在处理结构中的关键成员时,必须获得相应的自旋锁。

自旋锁通过spinlock_t数据结构实现,基本上可使用spin_lock和spin_unlock操纵。(自旋锁的实现与体系结构相关,几乎全是汇编语言)

自旋锁工作情况:

如果内核中其他地方尚未获得lock,则由当前处理器获取。其他处理器不能再进入lock保护的代码范围;

如果lock已经由另一个处理器获得,spin_lock进入一个无限循环,重复地检查lock是否已经由spin_unlock释放(自旋锁因此得名)。如果已经释放,则获得lock,并进入临界区。

自旋锁使用注意:

如果获得锁之后不释放,系统将变得不可用,所有的处理器(包括获得锁的在内),迟早需要进入锁对应的临界区,它们会进入无限循环等待锁释放,但等不到,便产生了死锁;

自旋锁决不应该长期持有,因为所有等待锁释放的处理器都处于不可用状态,无法用于其他工作;

内核进入到由自旋锁保护的临界区时,就停用内核抢占,在启用了内核抢占的单处理器内核中,spin_lock(基本上)等价于preempt_disable,而spin_unlock则等价于preempt_enable。

3、信号量

内核使用的信号量定义如下(用户空间信号量的实现有所不同):

struct semaphore {

atomic_t count; //count指定了可以同时处于信号量保护的临界区中进程的数目int sleepers; //sleepers指定了等待允许进入临界区的进程的数目 wait_queue_head_t wait; //wait用于实现一个队列,保存所有在该信号量上睡眠的进程的task_struct};

与自旋锁相比,信号量适合于保护更长的临界区,以防止并行访问。它们不应该用于保护较短的代码范围,因为竞争信号量时需要使进程睡眠和再次唤醒,代价很高。

大多数情况下,不需要使用信号量的所有功能,只是将其用作互斥量,这是一种二值信号量。

信号量工作情况:

在进入临界区时,用down对使用计数器减1,在计数器为0时,其他进程不能进入临界区;

在试图用down获取已经分配的信号量时,当前进程进入睡眠,并放置在与该信号量关联的等待队列上,同时,该进程被置于TASK_UNINTERRUPTIBLE状态,在等待进入临界区的过程中无法接收信号,如果信号量没有分配,则该进程可以立即获得信号量并进入到临界区,而不会进入睡眠;

在退出临界区时,必须调用up,该例程负责唤醒在信号量睡眠的某个进程,该进程然后允许进入临界区,而所有其他等待的进程继续睡眠。

除了只能用于内核的互斥量之外,Linux也提供了futex(快速用户空间互斥量,fastuserspacemutex),由核心态和用户状态组合而成,为用户空间进程提供了互斥量功能。

4、RCU机制

RCU(read-copy-update)是一个同步机制,该机制记录了指向共享数据结构的指针的所有使用者。在该结构将要改变时,则首先创建一个副本(或一个新的实例),在副本中修改

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值