⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 12.htm

📁 UNIX环境下C编程的详细详细介绍
💻 HTM
📖 第 1 页 / 共 5 页
字号:
该区域可以在当前文件尾端处开始或超过其尾端处开始,但是不能在文件起始位 
</p>

<p>置之前开始或越过该起始位置。 </p>

<p>l 如若l_len为0,则表示锁的区域从其起点(由l_start和l_whence决定)开始直 
</p>

<p>至最大可能位置为止。也就是不管添写到该文件中多少数据,它都处于锁的范围。 
</p>

<p>l 为了锁整个文件,通常的方法是将l_start说明为0,l_whence说明为SEEK_SET, 
</p>

<p>l_len说明为0。 </p>

<p>上面提到了两个锁类型:共享读锁(l_type为F_RDLCK)和独占写琐(F_WRLCK)。 
</p>

<p>基本规则是:多个进程在一个给定的字节上可以有一把共享的读锁,但是在一个给 
</p>

<p>定字节上的写锁则只能由一个进程独用。更进一步而言,如果在一个给定加字节上 
</p>

<p>已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一 
</p>

<p>把独占性的写锁,则不能再对它加任何读锁。在图12.2中示出了这些规则。 
</p>

<p>图12.2 不同类型锁之间的兼容性 </p>

<p>为了加读锁,该描述符必须是读打开,为了加写锁,该描述符必须是写打开。 
</p>

<p>现在说明fcntl函数的三种命令。 </p>

<p>F_GETLK 决定由flockptr所说明的锁是否被另外一把锁所排斥(阻塞)。如 
</p>

<p>果存在一把锁,它阻止创建由flockptr所描述符的锁,则这把现存的锁的信息写到 
</p>

<p>flockptr指向的结构中;如果不存在这种情况,则除l_type设置为F_UNLCK之外, 
</p>

<p>flockptr所指向结构中的其它信息、保持不变。 </p>

<p>F_SETLK 设置由flockptr所描述的锁。如果试图建立一把按上述兼容性规则 
</p>

<p>并不允许的锁,则fcntl立即出错返回,此时errno设置为EACCES或EAGAIN。 
</p>

<p>SVR2和SVR4返回EACCES,但手册页警告将来返回EAGAIN。4.3+BS </p>

<p>D则返回EAGAIN。POSIX.1允许这两种情况。 </p>

<p>此命令也用来清除由flockptr说明的锁(l_type为F_UNLCK)。 </p>

<p>F_SETLKW 这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。如果 
</p>

<p>由于存在其它锁,那么按兼容性规则由flockptr所要求的锁不能被创建,则调用进 
</p>

<p>程睡眠。如果捕捉到信号则睡眠中断。 </p>

<p>应当了解,用F_GETLK测试能否建立一把锁,然后用F_SETLK和F_SETLKW企图建立一 
</p>

<p>把锁,这两者不是一个原子操作。在这两个操作之间可能会有另一个进程插入并建 
</p>

<p>立一把相关的锁,使原来测试到的情况发生变化,如果不希望在建立锁时可能产生 
</p>

<p>的长期阻塞,则应使用F_SETLK,并对返回结果进行测试,以判别是否成功地建立 
</p>

<p>了所要求的锁。 </p>

<p>在设置或释放在一个文件上的一把锁时。系统按需组合或裂开相邻区。例如若100 
</p>

<p>-199字节是加锁的区,然后解锁第150字节,则系统核将维持两把锁,一把是从10 
</p>

<p>0-149字节,另一把是从151-199字节。 </p>

<p>实例-要求和释放一把锁 </p>

<p>为了免于每次分配flock结构,然后又填入各项信息,可以用程序12.2中的函数lo 
</p>

<p>ck_reg来处理这些细节。 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;fcntl.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>int </p>

<p>lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len </p>

<p>) </p>

<p>{ </p>

<p>struct flock lock; </p>

<p>lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */ </p>

<p>lock.l_start = offset; /* byte offset, relative to l_whence */ </p>

<p>lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */ </p>

<p>lock.l_len = len; /* #bytes (0 means to EOF) */ </p>

<p>return( fcntl(fd, cmd, &amp;lock) ); </p>

<p>} </p>

<p>程序12.2 锁和解锁一个文件区域的函数 </p>

<p>因为大多数锁调用是锁或解锁一个文件区域(命令F_GETLK很少使用)。我们通常 
</p>

<p>使用下列五个宏,它们都定义在ourhdr.h中(附录B)。 </p>

<p>#define read_lock(fd,offset,whence,len) </p>

<p>lock_reg(fd,F_SETLK,F_RDLCK,offset,whence,len) </p>

<p>#define needw_lock(fd,offset,whence,len) </p>

<p>lock_reg(fd,F_SETLKW,F_RDLCK,offset,whence,len) </p>

<p>#define write_lock(fd,offset,whence,len) </p>

<p>lock_reg(fd,F_SETLK,F_WRLCK,offset,whence,len) </p>

<p>#define writew_lock(fd,offset,whence,len) </p>

<p>lock_reg(fd,F_SETLKW,F_WRLCK,offset,whence,len) </p>

<p>#define un_lock(fd,offset,whence,len) </p>

<p>lock_reg(fd,F_SETLK,F_UNLCK,offset,whence,len) </p>

<p>我们以lseek函数中的同样顺序定义了这些宏中的三个参数。 </p>

<p>实例-测试一把锁 </p>

<p>程序12.3定义了一个函数lock_test,可用其测试一把锁。 </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;fcntl.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>pid_t </p>

<p>lock_test(int fd, int type, off_t offset, int whence, off_t len) </p>

<p>{ </p>

<p>struct flock lock; </p>

<p>lock.l_type = type; /* F_RDLCK or F_WRLCK */ </p>

<p>lock.l_start = offset; /* byte offset, relative to l_whence */ </p>

<p>lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */ </p>

<p>lock.l_len = len; /* #bytes (0 means to EOF) */ </p>

<p>if (fcntl(fd, F_GETLK, &amp;lock) &lt; 0) </p>

<p>err_sys(&quot;fcntl error&quot;); </p>

<p>if (lock.l_type == F_UNLCK) </p>

<p>return(0); /* false, region is not locked by anothe </p>

<p>proc */ </p>

<p>return(lock.l_pid); /* true, return pid of lock owner */ </p>

<p>} </p>

<p>程序12.3 测试一个锁条件的函数 </p>

<p>如果存在一把锁,它阻塞由参数说明的锁,则此函数返回持有这把现存锁的进程的 
</p>

<p>ID,否则此函数返回0。通常用下面两个宏来调用此函数(它们也定义在ourhdr.h 
</p>

<p>)。 </p>

<p>#define is_read_lockable(fd,offset,whence,len) </p>

<p>lock_test(fd,F_RDLCK,offset,whence,len) </p>

<p>#define is_write_lockable(fd,offset,whence,len) </p>

<p>lock_test(fd,F_WRLCK,offset,whence,len) </p>

<p>实例-死锁 </p>

<p>如果两个进程相互等待对方持有并且不释放(锁定)的资源时,则这两个进程就处 
</p>

<p>于死锁状态。如果一个进程已经控制了一个文件中的一个加锁区域,然后它又试图 
</p>

<p>对另一个进程控制的区域加锁,则它就会睡眠,在这种情况下,有发生死锁的可能 
</p>

<p>性。 </p>

<p>程序12.4示出了一个死锁的例子。子进程锁字节0,父进程锁字节1。然后,它们中 
</p>

<p>的每一个又试图锁对方已经加了锁的字节。在该程序中使用了8.8节中介绍的父-子 
</p>

<p>进程同步例程(TELL_xxx,WAIT_xxx),使得对方都能建立第一把锁。运行程序12 
</p>

<p>..4得到: </p>

<p>#include &lt;sys/types.h&gt; </p>

<p>#include &lt;sys/stat.h&gt; </p>

<p>#include &lt;fcntl.h&gt; </p>

<p>#include &quot;ourhdr.h&quot; </p>

<p>static void lockabyte(const char *, int, off_t); </p>

<p>int </p>

<p>main(void) </p>

<p>{ </p>

<p>int fd; </p>

<p>pid_t pid; </p>

<p>/* Create a file and write two bytes to it */ </p>

<p>if ( (fd = creat(&quot;templock&quot;, FILE_MODE)) &lt; 0) </p>

<p>err_sys(&quot;creat error&quot;); </p>

<p>if (write(fd, &quot;ab&quot;, 2) != 2) </p>

<p>err_sys(&quot;write error&quot;); </p>

<p>TELL_WAIT(); </p>

<p>if ( (pid = fork()) &lt; 0) </p>

<p>err_sys(&quot;fork error&quot;); </p>

<p>else if (pid == 0) { /* child */ </p>

<p>lockabyte(&quot;child&quot;, fd, 0); </p>

<p>TELL_PARENT(getppid()); </p>

<p>WAIT_PARENT(); </p>

<p>lockabyte(&quot;child&quot;, fd, 1); </p>

<p>} else { /* parent */ </p>

<p>lockabyte(&quot;parent&quot;, fd, 1); </p>

<p>TELL_CHILD(pid); </p>

<p>WAIT_CHILD(); </p>

<p>lockabyte(&quot;parent&quot;, fd, 0); </p>

<p>} </p>

<p>exit(0); </p>

<p>} </p>

<p>static void </p>

<p>lockabyte(const char *name, int fd, off_t offset) </p>

<p>{ </p>

<p>if (writew_lock(fd, offset, SEEK_SET, 1) &lt; 0) </p>

<p>err_sys(&quot;%s: writew_lock error&quot;, name); </p>

<p>printf(&quot;%s: got the lock, byte %d\n&quot;, name, offset); </p>

<p>} </p>

<p>程序12.4 死锁检测实例 </p>

<p>$ a.out </p>

<p>child:got the lock,byte 0 </p>

<p>parent:got the lock,byte 1 </p>

<p>child:writew_lock error:Deadlock situation detected/avoided </p>

<p>parent:got the lock,byte 0 </p>

<p>检测到死锁时,系统核必须选择一个进程收到出错返回。在本实例中,选择了子进 
</p>

<p>程,这是一个实现细节。当此程序在另一个系统上运行时,一半次数是子进程接到 
</p>

<p>出错信息,另一半则是父进程。 </p>

<p>锁的隐含继承和释放 </p>

<p>关于记录锁的自动继承和释放有三条规则: </p>

<p>1. 
锁与一个进程、一个文件两方面有关。这有两重含意。第一重是很明显的,当 
</p>

<p>一个进程终止时,它所建立的锁全部释放。第二重意思就不很明显,任何时候关闭 
</p>

<p>一个描述符时,则该进程通过这一描述符可以存访的文件上的任何一把锁都被释放 
</p>

<p>(这些锁都是该进程设置的)。这就意味着如果执行下列四步: </p>

<p>fd1=open(pathname,…); </p>

<p>read_lock(fd1,…); </p>

<p>fd2=dup(fd1); </p>

<p>close(fd2); </p>

<p>则在close(fd2)后,在fd1上设置的锁被释放。如果将dup代换为open </p>

<p>,其效果也一样: </p>

<p>fd1=open(palhname,…); </p>

<p>read_lock(fd1,…); </p>

<p>fd2=open(palhname,…); </p>

<p>close(fd2); </p>

<p>2. 由fork产生的子程序不继承父进程所设置的锁。这意味着,若一个进程得到一 
</p>

<p>把锁,然后调用fork,那么对于父进程获得的锁而言,子进程被视为另一个进程, 
</p>

<p>对于从父进程处继承过来的任一描述符,子进程要调用fcntl以获得它自己的锁。 
</p>

<p>这与锁的作用是相一致的。锁的作用是阻止多个进程同时写同一个文件(或同一文 
</p>

<p>件区域)。如果子进程继承父进程的锁,则父、子进程就可以同时写同一个文件。 
</p>

<p>3. 在执行exec后,新程序可以继承原执行程序的锁。 </p>

<p>POSIX.1没有要求这一点。但是,SVR4和4.3+BSD都支持这一点 </p>

<p>4.3+BSD的实现 </p>

<p>先简要地观察4.3+BSD实现中使用的数据结构,从中可以看到锁是与一个进程、一 
</p>

<p>个文件相关联的。 </p>

<p>考虑一个进程,它执行下列语句(忽略出错返回): </p>

<p>fd1 = open(pathname, … ); </p>

<p>write_lock(fd1, 0, SEEK_SET, 1); 父进程在字节0写? </p>

<p>if (fork() &gt; 0) { </p>

<p>fd2 = dup(fdl); </p>

<p>fd3 = open(pathname, …); </p>

<p>pause; </p>

<p>} else { </p>

<p>read_lock(fd1, 1, SEEK_SET, 1); 子进程在字节1读? </p>

<p>pause; </p>

<p>} </p>

<p>图12.3 显示了父、子进程暂停(执行pause( ))后的数据结构情况。 </p>

<p>图12.3 关于记录锁的4.3+BSD数据结构 </p>

<p>在以前的图3.4和8.1中已显示了open、fork以及dup后的数据结构有了记录锁后, 
</p>

<p>在原来的这些图上新加了flock结构,它们由i_node结构开始相互连接起来。注意 
</p>

<p>,每个flock结构说明了一个给定进程的一个加锁区域。在图中显示了两个flock结 
</p>

<p>构,一个是由父进程调用write_lock形成的,另一个则由子进程调用read_lock形 
</p>

<p>成的。每一个结构都包含了相应进程ID。 </p>

<p>在父进程中,关闭fd1、fd2和fd3中的任何一个都释放由父进程设置的写锁。在关 
</p>

<p>闭这三个描述符中的任何一个时,系统核会从该描述符所关连的i_node开始,逐个 
</p>

<p>检查flock连接表中各项,释放由调用进程持有的各把锁。系统核并不清楚也不关 
</p>

<p>心父进程是用哪一个描述符来设置这把锁的。 </p>

<p>实例: </p>

<p>建议性锁可由精灵进程使用以保证该精灵进程只有一个副本在运行。在起动时,很 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -