之前曾經讀過《UNIX環境進階編程》,但是對其中的檔案鎖的概念沒有深入的瞭解與學習,近日在操MySQL資料庫時,遇到死結現象。順便聯想到了Linux下的檔案鎖,風聞資料庫中的庫是需要OS下的檔案來支撐的,為此更要來對Linux檔案鎖來深入學習一下。
Linux系統上的檔案鎖主要分為建議鎖(advisory lock)和強制鎖(mandatory lock)。在Linux上使用的檔案鎖大部分為建議鎖,而且使用強制鎖的時候也要檢查系統是否支援強制鎖(http://www.ibm.com/developerworks/cn/linux/l-cn-filelock/index.html,這裡有份代碼傳說可以檢查是否支援強制鎖)。根據查看的資料顯示, OpenSuse11.1,
CentOS5.3等系統不支援強制檔案鎖。
下面分別對建議鎖和強制鎖做簡要的介紹:
1、
建議鎖又稱協同鎖。對於這種類型的鎖,核心只是提供加減鎖以及檢測是否加鎖的操作,但是不提供鎖的控制與協調工作。也就是說,如果應用程式對某個檔案進行操作時,沒有檢測是否加鎖或者無視加鎖而直接向檔案寫入資料,核心是不會加以阻攔控制的。因此,建議鎖,不能阻止進程對檔案的操作,而只能依賴於大家自覺的去檢測是否加鎖然後約束自己的行為;
2、
強制鎖,是OS核心的檔案鎖。每個對檔案操作時,例如執行open、read、write等操作時,OS內部檢測該檔案是否被加了強制鎖,如果加鎖導致這些檔案操作失敗。也就是核心強制應用程式來遵守遊戲規則;
對檔案鎖操作的相關函數簡介:
Flock和fcntl,flock主要完成的是建議鎖,當然也可以用fcntl來完成建議鎖。Fcntl除了可以完成建議鎖之外,還可以完成記錄鎖,即對檔案的一部分進行加鎖操作;
鎖的隱含繼承和釋放
關於記錄鎖的自動繼承和釋放規則:
1、
鎖與進程和檔案兩方面相關。兩層含義:當一個進程終止時,它所建立的鎖全部釋放;第二,任何時候關閉一個描述符時,則該進程通過這一描述符可以引用的檔案上的任何一把鎖都被釋放(這些鎖都是被該進程設定的)。這意味著:
Fd1=open(pathname,….);
Read_lock(fd1,…);
Fd2=dup(fd1);
Close(fd2);
則在close(fd2)後,fd1上設定的鎖都被釋放。如果將dup換為open,以開啟另一描述符上的同一檔案,其效果也是一樣的;
2、
由fork產生的子進程不繼承父進程所設定的鎖。意味著,若一個進程得到一把鎖,然後調用fork,那麼對於父進程獲得鎖而言,子進程被視為另一個進程,對於從父進程處繼承過來的任一描述符,子進程需要調用fcntl才能獲得它自己的鎖。這與鎖的作用是一致的。鎖的作用是阻止多個進程同時操作同一個檔案,如果子進程繼承父進程的鎖,則父子進程操作同一個檔案,這與鎖的初衷相違背;
3、
在執行exec後,新程式可以繼承原執行程式的鎖。但是注意,如果對一個檔案描述符設定了close-on-exec標誌,那麼當作為exec的一部分關閉該檔案描述符時,對相應檔案的所有鎖都被釋放啦。
附:檢測系統是否支援強制鎖的代碼(《Unix環境進階編程》);
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
volatile sig_atomic_t sigflag;
int pfd1[2], pfd2[2];
sigset_t
newmask, oldmask, zeromask;
void err_doit(int errnoflag, const char *fmt,
va_list ap)
{
int errno_save;
char buf[4096];
errno_save = errno;
vsprintf(buf, fmt, ap);
if (errnoflag)
sprintf(buf+strlen(buf), ": %s", strerror(errno_save));
strcat(buf, "/n");
fflush(stdout);
fputs(buf, stderr);
fflush(stderr);
return;
}
void err_sys(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, fmt, ap);
va_end(ap);
exit(1);
}
void sig_usr(int signo)
{
sigflag = 1;
return;
}
TELL_WAIT()
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR) /*SIGUSR1 為使用者自訂訊號*/
err_sys("signal(SIGINT) error");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("signal(SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);
/*阻塞 SIGUSR1 和 SIGUSR2 並且儲存當前訊號掩碼*/
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
}
void WAIT_PARENT(void)
{
while( sigflag == 0)
sigsuspend(&zeromask);
/*suspend()取消了所有訊號屏蔽,等待父進程發來訊號*/
sigflag = 0;
/*恢複訊號掩碼*/
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
int lock_reg(int fd, int cmd, int type,
off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
return( fcntl(fd, cmd, &lock) );
}
void set_fl(int fd, int flags)
{
int val;
if ( (val = fcntl(fd, F_GETFL, 0)) < 0)
err_sys("fcntl F_GETFL error");
val |= flags; /*置標誌*/
if (fcntl(fd, F_SETFL, val) < 0)
err_sys("fcntl F_SETFL error");
}
void err_ret(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, fmt, ap);
va_end(ap);
return;
}
int main(void)
{
int fd;
pid_t pid;
char buff[5];
struct stat statbuf;
if ( (fd = open("templock", O_RDWR | O_CREAT | O_TRUNC, 0644))
< 0)
err_sys("open error",buff);
if (write(fd, "abcdef", 6) != 6)
err_sys("write error");
/*開啟
set-group-ID(s-gid),並關閉組執行許可權*/
if (fstat(fd, &statbuf) < 0)
err_sys("fstat error");
if (fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
err_sys("fork error");
TELL_WAIT();
if ( (pid = fork()) < 0) {
err_sys("fork error");
}
else if (pid > 0) { /*父進程*/
/*整個檔案寫鎖*/
if (lock_reg(fd, F_SETLK, F_WRLCK, 0, SEEK_SET, 0) < 0)
err_sys("write_lock error");
kill(pid, SIGUSR1); /*給子進程發送訊號告知鎖完成*/
if (waitpid(pid, NULL, 0) < 0)
err_sys("waitpid error");
}
else {
WAIT_PARENT(); /*等待父進程設定鎖*/
set_fl(fd, O_NONBLOCK);
if (lock_reg(fd, F_SETLK, F_RDLCK, 0, SEEK_SET, 0) != -1)
err_sys("child: read_lock succeeded");
printf("read_lock of already-locked region return %d/n",
errno);
if (lseek(fd, 0, SEEK_SET) == -1)
err_sys("lseek, error");
if (read(fd, buff, 2) < 0)
err_ret("read
failed (mandatory locking wroks)");
else
printf("read OK (no mandatory locking), buff = %2.2s/n",
buff);
}
exit(0);
}