歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Unix知識 >> Unix基礎知識 >> UNIX環境高級編程:記錄上鎖(fcntl函數)以及死鎖檢測

UNIX環境高級編程:記錄上鎖(fcntl函數)以及死鎖檢測

日期:2017/3/3 15:19:52   编辑:Unix基礎知識

一、記錄鎖 record locking

功能:當一個進程正在讀或修改文件的某個部分時,它可以阻止其它進程修改同一文件區。

字節范圍鎖 byte-range locking

二、歷史

flock函數,可以鎖整個文件,不能鎖文件中的一部分。

fcntl函數,增加了記錄鎖的功能。

lockf函數,在fcntl基礎上構造了lockf函數,提供一個簡化的接口。可以鎖文件中任意字節數的區域

三、fcntl 記錄鎖

函數原型:

int fcntl(int fd, int cmd, struct flock *flockptr);  
      
      
/* 
cmd = F_GETLK,測試能否建立一把鎖 
cmd = F_SETLK,設置鎖 
cmd = F_SETLKW, 阻塞設置一把鎖 
     
     
*/
//POSIX只定義fock結構中必須有以下的數據成員,具體實現可以增加  
struct flock {  
      short l_type;    /* 鎖的類型: F_RDLCK, F_WRLCK, F_UNLCK */
      short l_whence;  /* 加鎖的起始位置:SEEK_SET, SEEK_CUR, SEEK_END */
      off_t l_start;   /* 加鎖的起始偏移,相對於l_whence */
      off_t l_len;     /* 上鎖的字節數*/
      pid_t l_pid;     /* 已經占用鎖的PID(只對F_GETLK 命令有效) */
      /*...*/
};

(1)所希望的鎖類型:F_RDLCK(共享讀鎖)、F_WRLCK(獨占性寫鎖)、F_UNLCK(解鎖一個區域)。

(2)要加鎖或解鎖區域的起始字節偏移量由l_start和l_whence兩者決定。

(3)注意:該區域可以在當前文件尾端開始或越過其尾端處開始,但是不能在文件起始位置之前開始。

(4)如若l_len為0,則表示鎖的區域從其起點(由l_start和l_whence決定)開始直至最大可能偏移量為止。

(5)為了鎖住整個文件,我們設置l_start和l_whence,使鎖的起點在文件起始處,並說明長度(l_len)為0。

上面提到了兩種類型的鎖:共享讀鎖(F_RDLCK)和獨占寫鎖(F_WRLCK),基本規則是:

多個進程在一個給定的字節上可以有一把共享的讀鎖,但是在一個給定字節上只能有一個進程獨用的一把寫鎖。進一步而言,如果在一個給定字節上已經有一把或多把讀鎖,則不能再該字節上再加寫鎖;如果在一個字節上已經有一把獨占性的寫鎖,則不能再對它加任何讀鎖。

不同進程鎖請求的讀寫鎖規則:

------------------- 加讀鎖 加寫鎖

無鎖 允許 允許

一個或多個讀鎖 允許 拒絕

一個寫鎖 拒絕 拒絕

注意:上面這個規則適用於不同進程提出的鎖請求,並不適用於單個進程提出的多個鎖請求。單個進程提出多個鎖請求的時候,以最後一次鎖作為標准,即新鎖替換舊鎖對於同一文件,如果一直有不同的進程連續的對其添加讀鎖,則其它欲對其添加阻塞寫鎖的進程有可能延長等待時間(這樣的情況,對於添加寫鎖的進程會出現餓死情況)。

在讀鎖時,該描述符必須是讀打開;加寫鎖時,該描述符必須是寫打開。

F_GETLK: 判斷由flockptr所描述的鎖是否會被另外一把鎖所排斥(阻塞)。如果存在一把鎖,它阻止創建由flockptr所描述的鎖,則把該現存鎖的信息寫到flockptr指向的結構中。如果不存在這種情況,則除了將l_type設置為F_UNLCK之外,flockptr所指向結構中的其他信息保持不變。

F_SETLK : 獲取(l_type為F_RDLCK或F_WRLCK)或釋放由flockptr指向flock結構所描述的鎖,如果無法獲取鎖時,該函數會立即返回一個EACCESS或EAGAIN錯誤,而不會阻塞。

F_SETLKW: F_SETLKW和F_SETLK的區別是,無法設置鎖的時候,調用線程會阻塞到該鎖能夠授權位置。

這裡需要注意的是,用F_GETLK測試能否建立一把鎖,然後接著用F_SETLK或F_SETLKW企圖建立一把鎖,由於這兩者不是一個原子操作,所以不能保證兩次fcntl之間不會有另外一個進程插入並建立一把相關的鎖,從而使一開始的測試情況無效。所以一般不希望上鎖時阻塞,會直接通過調用F_SETLK,並對返回結果進行測試,以判斷是否成功建立所要求的鎖。

下面進行測試:

第一個程序:在同一進程中測試能否在加寫鎖後,繼續加讀寫鎖。

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <sys/stat.h>  
      
#define FILE_PATH "/home/huangcheng/data.txt"  
#define FILE_MODE 0777  
      
void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)  
{  
    if (lock == NULL)  
        return;  
      
    lock->l_type = type;  
    lock->l_whence = whence;  
    lock->l_start = start;  
    lock->l_len = len;  
}  
      
int writew_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
pid_t lock_test(int fd, short type, short whence, off_t start, off_t len)  
{  
    struct flock lock;  
    lock_init(&lock, type, whence, start, len);  
      
    if (fcntl(fd, F_GETLK, &lock) == -1)  
    {  
        return -1;  
    }  
      
    if(lock.l_type == F_UNLCK)  
        return 0;  
    return lock.l_pid;  
}  
      
int unlock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int main()  
{    
    int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);  
    writew_lock(fd);  
      
    printf(" F_WRLCK return %d\n",lock_test(fd, F_WRLCK, SEEK_SET, 0, 0));  
    printf(" F_RDLCK return %d\n", lock_test(fd, F_RDLCK, SEEK_SET, 0, 0));  
      
    unlock(fd);  
      
    return 0;  
}

運行結果:

huangcheng@ubuntu:~$ ./a.out
 F_WRLCK return 0  
 F_RDLCK return 0

結果表明:表明同一進程可以對已加鎖的同一文件區間,仍然能獲得加鎖權限;

第二個程序是在在父進程中加寫鎖後,然後再子進程中測試能否繼續加讀寫鎖。

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <sys/stat.h>  
#include <sys/types.h>  
      
#define FILE_PATH "/home/huangcheng/data.txt"  
#define FILE_MODE 0777  
      
void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)  
{  
    if (lock == NULL)  
        return;  
      
    lock->l_type = type;  
    lock->l_whence = whence;  
    lock->l_start = start;  
    lock->l_len = len;  
}  
      
int writew_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
pid_t lock_test(int fd, short type, short whence, off_t start, off_t len)  
{  
    struct flock lock;  
    lock_init(&lock, type, whence, start, len);  
      
    if (fcntl(fd, F_GETLK, &lock) == -1)  
    {  
        return -1;  
    }  
      
    if(lock.l_type == F_UNLCK)  
        return 0;  
    return lock.l_pid;  
}  
      
int unlock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int main()  
{  
    int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);  
    writew_lock(fd);  
      
    if (fork() == 0)  
    {  
        printf("child F_WRLCK return %d\n",lock_test(fd, F_WRLCK, SEEK_SET, 0, 0));  
        printf("child F_RDLCK return %d\n",lock_test(fd, F_RDLCK, SEEK_SET, 0, 0));  
        printf("child pid = %d and ppid = %d\n",getpid(),getppid());  
      
        exit(0);  
    }  
      
    sleep(3);  
    unlock(fd);  
      
    return 0;  
}

運行結果:

huangcheng@ubuntu:~$ ./a.out
child F_WRLCK return 7483  
child F_RDLCK return 7483  
child pid = 7484 and ppid = 7483

結果表明:不同進程不能對已加寫鎖的同一文件區間,獲得加鎖權限;

還有就是:加鎖時,該進程必須對該文件有相應的文件訪問權限,即加讀鎖,該文件必須是讀打開,加寫鎖時,該文件必須是寫打開。

四、記錄鎖的粒度

這裡要提到兩個概念:記錄上鎖和文件上鎖。

記錄上鎖:對於UNIX系統而言,“記錄”這一詞是一種誤用,因為UNIX系統內核根本沒有使用文件記錄這種概念,更適合的術語應該是字節范圍鎖,因為它鎖住的只是文件的一個區域。用粒度來表示被鎖住文件的字節數目。對於記錄上鎖,粒度最大是整個文件。

文件上鎖:是記錄上鎖的一種特殊情況,即記錄上鎖的粒度是整個文件的大小。

之所以有文件上鎖的概念是因為有些UNIX系統支持對整個文件上鎖,但沒有給文件內的字節范圍上鎖的能力。

五、記錄鎖的隱含繼承與釋放

關於記錄鎖的繼承和釋放有三條規則,如下:

(1)鎖與進程和文件兩方面有關,體現在:

當一個進程終止時,它所建立的記錄鎖將全部釋放;

當關閉一個文件描述符時,則進程通過該文件描述符引用的該文件上的任何一把鎖都將被釋放。

對於第一個方面,可以建立如下測試代碼:

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <sys/stat.h>  
      
#define FILE_PATH "/home/huangcheng/data.txt"  
#define FILE_MODE 0777  
      
void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)  
{  
    if (lock == NULL)  
        return;  
      
    lock->l_type = type;  
    lock->l_whence = whence;  
    lock->l_start = start;  
    lock->l_len = len;  
}  
      
int writew_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
//process 1  
int main()  
{  
    int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);  
       
    writew_lock(fd);  
    printf("process 1 get write lock...\n");  
          
    sleep(10);  
      
    printf("process 1 exit...\n");  
    return 0;  
}
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <sys/stat.h>  
      
#define FILE_PATH "/home/huangcheng/data.txt"  
#define FILE_MODE 0777  
      
void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)  
{  
    if (lock == NULL)  
        return;  
      
    lock->l_type = type;  
    lock->l_whence = whence;  
    lock->l_start = start;  
    lock->l_len = len;  
}  
      
int writew_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int unlock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
//process 2  
int main()  
{  
    int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);  
      
    writew_lock(fd);  
    printf("process 2 get write lock...\n");  
    unlock(fd);  
      
    return 0;  
}

先啟動進程1,然後立即啟動進程2,執行結果如下:

process 1 get write lock...    
process 1 exit...    
process 2 get write lock...

結果表明:當一個進程終止時,它所建立的記錄鎖將全部釋放。

對於第二個方面,可以進行如下測試:

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <sys/stat.h>  
      
#define FILE_PATH "/home/huangcheng/data.txt"  
#define FILE_MODE 0777  
      
void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)  
{  
    if (lock == NULL)  
        return;  
      
    lock->l_type = type;  
    lock->l_whence = whence;  
    lock->l_start = start;  
    lock->l_len = len;  
}  
      
int readw_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
          
    return 0;  
}  
      
int writew_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int unlock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int main()  
{  
    int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);  
      
    if (fork() == 0)  
    {  
        int fd_1 = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);  
      
        readw_lock(fd_1);  
        printf("child get read lock...\n");  
      
        sleep(3);  
      
        close(fd_1);  
        printf("close the file descriptor...\n");  
      
        pause();  
    }  
      
    sleep(1);  
      
    writew_lock(fd);  
    printf("parent get write lock...\n");  
    unlock(fd);  
      
    return 0;  
}

程序的執行結果如下:

huangcheng@ubuntu:~$ ./a.out
child get read lock...  
close the file descriptor...  
parent get write lock...

運行結果說明:當關閉文件描述符時,與該文件描述符有關的鎖都被釋放,同樣通過dup拷貝得到的文件描述符也會導致這種情況。

(2)由fork產生的子進程不繼承父進程所設置的鎖。即對於父進程建立的鎖而言,子進程被視為另一個進程。記錄鎖本身就是用來同步不同進程對同一文件區進行操作,如果子進程繼承了父進程的鎖,那麼父子進程就可以同時對同一文件區進行操作,這有違記錄鎖的規則,所以存在這麼一條規則。

下面是測試代碼(上面已經用過該代碼進行測試):

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <sys/stat.h>  
#include <sys/types.h>  
      
#define FILE_PATH "/home/huangcheng/data.txt"  
#define FILE_MODE 0777  
      
void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)  
{  
    if (lock == NULL)  
        return;  
      
    lock->l_type = type;  
    lock->l_whence = whence;  
    lock->l_start = start;  
    lock->l_len = len;  
}  
      
int writew_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
pid_t lock_test(int fd, short type, short whence, off_t start, off_t len)  
{  
    struct flock lock;  
    lock_init(&lock, type, whence, start, len);  
      
    if (fcntl(fd, F_GETLK, &lock) == -1)  
    {  
        return -1;  
    }  
      
    if(lock.l_type == F_UNLCK)  
        return 0;  
    return lock.l_pid;  
}  
      
int unlock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int main()  
{  
    int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);  
    writew_lock(fd);  
      
    if (fork() == 0)  
    {  
        printf("child F_WRLCK return %d\n",lock_test(fd, F_WRLCK, SEEK_SET, 0, 0));  
        printf("child F_RDLCK return %d\n",lock_test(fd, F_RDLCK, SEEK_SET, 0, 0));  
        printf("child pid = %d and ppid = %d\n",getpid(),getppid());  
      
        exit(0);  
    }  
      
    sleep(3);  
    unlock(fd);  
      
    return 0;  
}

查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/

我們知道在前面已經說過,同一個進程可以重復對同一個文件區間加鎖,後加的鎖將覆蓋前面加的鎖。那麼再假設如果子進程繼承了父進程的鎖,那麼子進程可以對該鎖進行覆蓋,那麼在子進程內對該鎖是否能獲得權限的測試應該是可以,但測試結果為:

huangcheng@ubuntu:~$ ./a.out
child F_WRLCK return 7483  
child F_RDLCK return 7483  
child pid = 7484 and ppid = 7483

表明已經進程7483已經占用該鎖,所以假設不成立,子進程不會繼承父進程的鎖;

(3)執行exec後,新程序可以繼承原執行程序的鎖。但是,如果一個文件描述符設置了close-on-exec標志,在執行exec時,會關閉該文件描述符,所以對應的鎖也就被釋放了,也就無所謂繼承了。

六、記錄鎖的讀和寫的優先級

具體進行以下2個方面測試:

進程擁有讀鎖,然後優先處理後面的讀鎖,再處理寫鎖,導致寫鎖出現餓死;

進程擁有寫入鎖,那麼等待的寫入鎖和等待的讀出鎖的優先級(FIFO);

測試1:父進程獲得對文件的讀鎖,然後子進程1請求加寫鎖,隨即進入睡眠,然後子進程2請求讀鎖,看進程2是否能夠獲得讀鎖。

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <sys/stat.h>  
      
#define FILE_PATH "/home/huangcheng/data.txt"  
#define FILE_MODE 0777  
      
void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)  
{  
    if (lock == NULL)  
        return;  
      
    lock->l_type = type;  
    lock->l_whence = whence;  
    lock->l_start = start;  
    lock->l_len = len;  
}  
      
int readw_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
          
    return 0;  
}  
      
int writew_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int unlock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int main()  
{  
    int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);  
    readw_lock(fd);  
      
    //child  1  
    if (fork() == 0)  
    {  
        printf("child 1 try to get write lock...\n");  
        writew_lock(fd);  
        printf("child 1 get write lock...\n");  
      
        unlock(fd);  
        printf("child 1 release write lock...\n");  
      
        exit(0);  
    }  
      
    //child 2  
    if (fork() == 0)  
    {  
        sleep(3);  
      
        printf("child 2 try to get read lock...\n");  
        readw_lock(fd);  
        printf("child 2 get read lock...\n");  
      
        unlock(fd);  
        printf("child 2 release read lock...\n");  
        exit(0);  
    }  
      
    sleep(10);  
    unlock(fd);  
      
    return 0;  
}

運行結果:

huangcheng@ubuntu:~$ ./a.out
child 1 try to get write lock...  
child 2 try to get read lock...  
child 2 get read lock...  
child 2 release read lock...  
child 1 get write lock...  
child 1 release write lock...

運行結果說明:可知在有寫入進程等待的情況下,對於讀出進程的請求,系統會一直給予的。那麼這也就可能導致寫入進程餓死的局面。

測試2:父進程獲得寫入鎖,然後子進程1和子進程2分別請求獲得寫入鎖和讀寫鎖,看兩者的響應順序。

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <sys/stat.h>  
      
#define FILE_PATH "/home/huangcheng/data.txt"  
#define FILE_MODE 0777  
      
void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)  
{  
    if (lock == NULL)  
        return;  
      
    lock->l_type = type;  
    lock->l_whence = whence;  
    lock->l_start = start;  
    lock->l_len = len;  
}  
      
int readw_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
          
    return 0;  
}  
      
int writew_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int unlock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int main()  
{   
    int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);  
    writew_lock(fd);  
      
    //child  1  
    if (fork() == 0)  
    {  
        sleep(3);  
      
        printf("child 1 try to get write lock...\n");  
        writew_lock(fd);  
        printf("child 1 get write lock...\n");  
      
        unlock(fd);  
        printf("child 1 release write lock...\n");  
      
        exit(0);  
    }  
      
    //child 2  
    if (fork() == 0)  
    {  
        printf("child 2 try to get read lock...\n");  
        readw_lock(fd);  
        printf("child 2 get read lock...\n");  
      
        unlock(fd);  
        printf("child 2 release read lock...\n");  
      
        exit(0);  
    }  
      
    sleep(10);  
    unlock(fd);  
      
    return 0;  
}

運行結果:

huangcheng@ubuntu:~$ ./a.out
child 2 try to get read lock...  
child 1 try to get write lock...  
child 2 get read lock...  
child 2 release read lock...  
child 1 get write lock...  
child 1 release write lock...

將上面代碼該成child2 sleep 3s,child1不sleep:

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <sys/stat.h>  
      
#define FILE_PATH "/home/huangcheng/data.txt"  
#define FILE_MODE 0777  
      
void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)  
{  
    if (lock == NULL)  
        return;  
      
    lock->l_type = type;  
    lock->l_whence = whence;  
    lock->l_start = start;  
    lock->l_len = len;  
}  
      
int readw_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
          
    return 0;  
}  
      
int writew_lock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int unlock(int fd)  
{  
    if (fd < 0)  
    {  
        return -1;  
    }  
      
    struct flock lock;  
    lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0);  
      
    if (fcntl(fd, F_SETLKW, &lock) != 0)  
    {  
        return -1;  
    }  
      
    return 0;  
}  
      
int main()  
{   
    int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);  
    writew_lock(fd);  
      
    //child  1  
    if (fork() == 0)  
    {  
        printf("child 1 try to get write lock...\n");  
        writew_lock(fd);  
        printf("child 1 get write lock...\n");  
      
        unlock(fd);  
        printf("child 1 release write lock...\n");  
      
        exit(0);  
    }  
      
    //child 2  
    if (fork() == 0)  
    {  
        sleep(3);  
        printf("child 2 try to get read lock...\n");  
        readw_lock(fd);  
        printf("child 2 get read lock...\n");  
      
        unlock(fd);  
        printf("child 2 release read lock...\n");  
      
        exit(0);  
    }  
      
    sleep(10);  
    unlock(fd);  
      
    return 0;  
}

運行結果:

huangcheng@ubuntu:~$ ./a.out
child 1 try to get write lock...  
child 2 try to get read lock...  
child 1 get write lock...  
child 1 release write lock...  
child 2 get read lock...  
child 2 release read lock...

由上可知在ubuntu 10.04下,等待的寫入鎖進程和讀出鎖進程的優先級由FIFO的請求順序進程響應。

七、死鎖檢測程序

//《APUE》程序14-2:加鎖和解鎖一個文件區域  
//《APUE》程序14-4:死鎖檢測實例  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <errno.h>  
#include <signal.h>  
#include <sys/stat.h>   
      
#define read_lock(fd, offset, whence, len) \  
    lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))  
#define readw_lock(fd, offset, whence, len)  \ 
    lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))  
#define write_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))  
#define writew_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))  
#define un_lock(fd, offset, whence, len) \
    lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))  
      
#define FILE_MODE   (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)  
      
sig_atomic_t sigflag; /* set nonzero by sig handler */
sigset_t newmask, oldmask, zeromask;  
      
//輸出錯誤信息並退出      
void error_quit(const char *str)      
{      
    fprintf(stderr, "%s\n", str);      
    exit(1);      
}  
      
static void sig_usr(int signo)  /* one signal handler for SIGUSR1 and SIGUSR2 */
{  
    sigflag = 1;  
}  
      
void TELL_WAIT(void)  
{  
    if (signal(SIGUSR1, sig_usr) == SIG_ERR)  
        error_quit("signal(SIGUSR1) error");  
    if (signal(SIGUSR2, sig_usr) == SIG_ERR)  
        error_quit("signal(SIGUSR2) error");  
    sigemptyset(&zeromask);  
    sigemptyset(&newmask);  
    sigaddset(&newmask, SIGUSR1);  
    sigaddset(&newmask, SIGUSR2);  
      
    /* 
    * Block SIGUSR1 and SIGUSR2, and save current signal mask. 
    */
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)  
        error_quit("SIG_BLOCK error");  
}  
      
void TELL_PARENT(pid_t pid)  
{  
    kill(pid, SIGUSR2);     /* tell parent we're done */
}  
      
void WAIT_PARENT(void)  
{  
    while (sigflag == 0)  
        sigsuspend(&zeromask);  /* and wait for parent */
    sigflag = 0;  
      
    /* 
    * Reset signal mask to original value. 
    */
    int temp = sigprocmask(SIG_SETMASK, &oldmask, NULL);  
    if (temp < 0)  
        error_quit("SIG_SETMASK error");  
}  
      
void TELL_CHILD(pid_t pid)  
{  
    kill(pid, SIGUSR1);         /* tell child we're done */
}  
      
void WAIT_CHILD(void)  
{  
    while (sigflag == 0)  
        sigsuspend(&zeromask);  /* and wait for child */
    sigflag = 0;  
      
    /* 
    * Reset signal mask to original value. 
    */
    int temp = sigprocmask(SIG_SETMASK, &oldmask, NULL);  
    if (temp < 0)  
        error_quit("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 lockabyte(const char *name, int fd, off_t offset)  
{  
    //在我的系統上(Ubuntu10.04),發生死鎖時writew_lock並不會返回-1  
    if( writew_lock(fd, offset, SEEK_SET, 1) < 0 )  
        error_quit("writew_lock error");  
    printf("%s: got the lock, byte %ld\n", name, offset);  
}  
      
int main(void)  
{  
    int fd;  
    pid_t pid;  
      
    fd = creat("templock", FILE_MODE);  
    if( fd < 0 )  
        error_quit("create error");  
    if( write(fd, "ab", 2) != 2 )  
        error_quit("write error");  
      
    TELL_WAIT();  
    pid = fork();  
    if( pid < 0 )  
        error_quit("fork error");  
    else if( pid == 0 )  
    {  
        lockabyte("child", fd, 0);  
        TELL_PARENT( getpid() );  
        WAIT_PARENT();  
        lockabyte("child", fd, 1);  
    }  
    else
    {  
        lockabyte("parent", fd, 1);  
        TELL_CHILD(pid);  
        WAIT_CHILD();  
        lockabyte("parent", fd, 0);  
    }  
    return 0;  
}

運行結果:

huangcheng@ubuntu:~$ ./a.out
parent: got the lock, byte 1  
child: got the lock, byte 0  
^C  
huangcheng@ubuntu:~$

注解:

1:在該程序中,子進程鎖住字節0,父進程鎖住字節1,然後,它們又都試圖鎖住對方已經加鎖的字節,這樣就造成了死鎖。

2:《APUE》上說:檢測到死鎖時,內核必須選擇一個進程出錯返回。但在我的系統(ubuntu 10.04)中,父子進程都被卡住,只有當你強制中斷時(Ctrl+C)時,程序才會結束。

Copyright © Linux教程網 All Rights Reserved