1. 记录锁概念和功能
当两个人同时编辑一个文件时,其后果将如何呢?在很多U N I X系统中,该文件的最后状态取决于写该文件的最后一个进程。但是对于有些应用程序,例如数据库,有时进程需要确保它正在单独写一个文件。为了向进程提供这种功能,较新的U N I X系统提供了记录锁机制
记录锁(record locking)的功能是:一个进程正在读或修改文件的某个部分时,可以阻止
其他进程修改同一文件区。对于U N I X,“记录”这个定语也是误用,因为U N I X内核根本没有使用文件记录这种概念。一个更适合的术语可能是“区域锁”,因为它锁定的只是文件的一个区域(也可能是整个文件)以上是进程间记录锁的功能。在线程间就不能加入上面的”区域锁”。
此处的记录锁在线程之间不能应用。在程序中,和种树一样,很多任务也必须以确定的先后秩序执行,对于两个线程必须以先后秩序执行的情况,我们可以用线程锁来处理。线程锁的大致思想是:如果线程A和线程B会执行实例的两个函数a和b,如果A必须在B之前运行,那么可以在B进入b函数时让B进入wait set,直到A执行完a函数再把B从wait set中激活。这样就保证了B必定在A之后运行,无论在之前它们的时间先后顺序是怎样的。线程锁用于必须以固定顺序执行的多个线程的调度。线程锁的思想是先锁定后序线程,然后让线序线程完成任务再接触对后序线程的锁定
2. Fcntl记录锁结构说明:
#include <sys/types.h>
#include <unistd.h>
#include <fcnt1.h>
int fcntl(int fd,int cmd,struct flock * lock);
对于记录锁, c m d是F G E T L K、F S E T L K或F S E T L K W。第三个参数lock是一个指向f l o c k结构的指针。
struct flcok
{
short int l_type;
short int l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
};
f l o c k结构说明:
? 所希望的锁类型:F R D L C K(共享读锁)、F W R L C K(独占性写锁)或F U N L C K(解锁一个区域)
? 要加锁或解锁的区域的起始地址,由l _s t a r t和l_ w h e n c e两者决定。l _s t ar t是相对位移量(字节),l _w h e n c e则决定了相对位移量的起点。这与l s e e k函数中最后两个参数类似。
? 区域的长度,由l_ l e n表示。
关于加锁和解锁区域的说明还要注意下列各点:
? 该区域可以在当前文件尾端处开始或越过其尾端处开始,但是不能在文件起始位置之前
开始或越过该起始位置。
? 如若l _l e n为0,则表示锁的区域从其起点(由l _s t a r t和l_ w h e n c e决定)开始直至最大可能
位置为止。也就是不管添写到该文件中多少数据,它都处于锁的范围。
? 为了锁整个文件,通常的方法是将l _s t a r t说明为0,l _w h e n c e说明为S E E K S E T,l_ l e n说明为0。
上面提到了两种类型的锁:共享读锁( l_ t y p e为L R D L C K)和独占写琐(L W R L C K)。
基本规则是:多个进程在一个给定的字节上可以有一把共享的读锁,但是在一个给定字节上的写锁则只能由一个进程独用。更进一步而言,如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能再对它加任何读锁。在表2-1中示出了这些规则。加读锁时,该描述符必须是读打开;加写锁时,该描述符必须是写打开。
区域当前有 | 加读锁 | 加写锁 |
无锁 | 可以 | 可以 |
一把或多把读 | 可以 | 拒绝 |
一把写锁 | 拒绝 | 拒绝 |
表2-1
以下说明f c n t l函数的三种命令:
? F_GETLK 决定由f l o c k p t r所描述的锁是否被另外一把锁所排斥(阻塞)。如果存在一把锁,它阻止创建由f l o c k p t r所描述的锁,则这把现存的锁的信息写到f l o c k p t r指向的结构中。如果不存在这种情况,则除了将l_ t y p e设置为F U N L C K之外,f l o c k p t r所指向结构中的其他信息保持不变。
? F_SETLK 设置由f l o c k p t r所描述的锁。如果试图建立一把按上述兼容性规则并不允许的锁,则f c n t l立即出错返回,此时e r r n o设置为E A C C E S或E A G A I N。
3. 记录锁的隐含继承和释放
关于记录锁的自动继承和释放有三条规则:
(1) 锁与进程、文件两方面有关。这有两重含意:第一重很明显,当一个进程终止时,它
所建立的锁全部释放;第二重意思就不很明显,任何时候关闭一个描述符时,则该进程通过这一描述符可以存访的文件上的任何一把锁都被释放(这些锁都是该进程设置的)
(2) 由f o r k产生的子程序不继承父进程所设置的锁。这意味着,若一个进程得到一把锁,然后调用f o r k,那么对于父进程获得的锁而言,子进程被视为另一个进程,对于从父进程处继承过来的任一描述符,子进程要调用f c n t l以获得它自己的锁。这与锁的作用是相一致的。锁的作用是阻止多个进程同时写同一个文件(或同一文件区域)。如果子进程继承父进程的锁,则父、子进程就可以同时写同一个文件。
(3) 在执行e x e c后,新程序可以继承原执行程序的锁。
4. 建议性锁和强制性锁
考虑数据库存取例程序。如果该库中所有函数都以一致的方法处理记录锁,则称使用这些
函数存取数据库的任何进程集为合作进程( cooperating pro c e s s)。如果这些函数是唯一的用来存取数据库的函数,那么它们使用建议性锁是可行的。但是建议性锁并不能阻止对数据库文件有写许可权的任何其他进程写数据库文件。不使用协同一致的方法(数据库存取例程库)来存取数据库的进程是一个非合作进程。强制性锁机制中,内核对每一个o p e n、r e a d和w r i t e都要检查调用进程对正在存取的文件是否违背了某一把锁的作用。对一个特定文件打开其设置-组- I D位,关闭其组-执行位则对该文件启动了强制性锁机制。因为当组-执行位关闭时,设置-组- I D位不再有意义,用两者的这种组合来指定对一个文件的锁是强制性的而非建议性的。
5. 临界区
不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。每个进程中访问临界资源的那段代码称为临界区(Critical Section)。
每个进程中访问临界资源的那段程序称为临界区(Critical Section)(临界资源是一次仅允许一个进程使用的共享资源)。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。
多个进程中涉及到同一个临界资源的临界区称为相关临界区。
进程进入临界区的调度原则是: ①如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。②任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有 试图进入临界区的进程必须等待。③进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。④如果进程不能进入自己的临界区,则应让出 CPU,避免进程出现“忙等”现象。
如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
6. 进程锁和线程锁
前面讲到锁的概念时曾提及。进程锁和线程锁在功能和实现目的上相同,但其操作对象和生命周期不同。区域锁只能在进程间适用在线程间不适用。其次,进程间的锁对象主要是
进程间共用的共享内存,消息队列,文件等,而线程间的锁对象主要是在进程内的全局变量相对全局变量及各种方法函数。