1. 程式人生 > >Linux學習筆記——程序間的通訊-檔案和檔案鎖

Linux學習筆記——程序間的通訊-檔案和檔案鎖

IPC(Inter-Process Communication,程序間通訊)

系統對檔案本身存在快取機制,使用檔案進行IPC的效率在某些多讀少寫的情況下並不低下

1.競爭條件(racing)

併發100個程序,約定好一個內容初值為0的檔案,每一個程序開啟檔案讀出數字,並且加一寫回:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>//提供write(),read(),lseek()函式及相關引數定義
#include <errno.h>
#include <fcntl.h>//提供open()函式及相關引數定義
#include <string.h> #include <sys/file.h> #include <wait.h> #define COUNT 100 #define NUM 64 #define FILEPATH "./count"//檔案的路徑 int do_child(const char *path)//子程序執行函式 { int fd,ret,count; char buf[NUM]; fd = open(path,O_RDWR); //成功返回0值,不成功返回-1,可讀可寫的方式開啟path路徑的檔案 if (fd<0)//若開啟失敗,列印返回的錯誤
{ perror("open()"); exit(1);//異常退出 } ret=read(fd,buf,NUM);//成功返回讀取位元組數,錯誤返回-1 if(ret<0) { perror("read()"); exit(1); } buf[ret] = "\0"; count=atoi(buf);//字串轉換成整型數,如果第一個非空格字元不存在或者不是數字也不是正負號則返回零,否則開始做型別轉換,之後檢測到非數字(包括結束符 \0) 字元時停止轉換,返回整型數 ++count; sprintf
(buf,"%d",count);//將整型變數count列印成字串輸出到buf中 lseek(fd,0,SEEK_SET);//將讀寫位置移到檔案開頭 ret= write(fd,buf,strlen(buf));//將buf寫進檔案中 close(fd); exit(0);//正常退出 } int main() { pid_t pid;//pid_t實際上就是int,在/sys/types.h中定義 int count; for(count=0;count<COUNT;count++) { pid=fork();//建立一個子程序程序,在父程序中,fork返回新建立子程序的程序ID;在子程序中,fork返回0;如果出現錯誤,fork返回一個負值; if(pid<0) { perror("fork()"); exit(1); } if(pid==0) { do_child(FILEPATH);//創建出來的那個新程序執行任務 } } for(count=0;count<COUNT;count++) wait(NULL);//等待所有程序退出 }

執行情況:

zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ echo 0 > count
zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ cat count
0
zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ ./racing
zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ cat count
35zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ echo 0 > count
zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ ./racing
zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ cat count
46zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ echo 0 > count
zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ ./racing
zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ cat count
57zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ 

理想狀態下,檔案最後的數字應該是100,因為有100個程序進行了讀數,加一,寫回操作,實際上每次執行的情況都不一樣,都沒有達到預期理想結果,造成這一現象的原因是————競爭條件

最開始檔案內容是0,假設此時同時打開了多個程序,多個程序同時打開了內容為0的檔案,每個程序讀到的數都是0,都給0加1並且寫1回到檔案。每次100個程序執行順序可能不一樣,每次結果也可能不一樣,但一定少於產生的實際程序數。

把多個執行過程(程序或執行緒)中訪問同一個共享資源,而這些共享資源又無法被多個執行過程存取的程式片段,叫做臨界區程式碼。

通過對臨界區程式碼加鎖,解決競爭條件問題:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>//提供write(),read(),lseek()函式及相關引數定義
#include <errno.h>
#include <fcntl.h>//提供open(),close()函式及相關引數定義
#include <string.h>
#include <sys/file.h>//提供flock()函式及相關引數定義
#include <wait.h>

#define COUNT 100
#define NUM 64
#define FILEPATH "./count"//檔案的路徑

int do_child(const char *path)//子程序執行函式
{
    int fd,ret,count;
    char buf[NUM];
    fd = open(path,O_RDWR); //成功返回0值,不成功返回-1,可讀可寫的方式開啟path路徑的檔案 
    if (fd<0)//若開啟失敗,列印返回的錯誤
    {
        perror("open()");
        exit(1);//異常退出
    }
    ret=flock(fd,LOCK_EX);//LOCK_EX 建立互斥鎖定;返回0表示成功,若有錯誤則返回-1,錯誤程式碼存於errno
    if(ret==-1)
    {   
        perror("flock()");
        exit(1);
    }
    ret=read(fd,buf,NUM);//成功返回讀取位元組數,錯誤返回-1
    if(ret<0)
    {
        perror("read()");
        exit(1);
    }
    buf[ret] = "\0";
    count=atoi(buf);//字串轉換成整型數,如果第一個非空格字元不存在或者不是數字也不是正負號則返回零,否則開始做型別轉換,之後檢測到非數字(包括結束符 \0) 字元時停止轉換,返回整型數
    ++count;
    sprintf(buf,"%d",count);//將整型變數count列印成字串輸出到buf中
    lseek(fd,0,SEEK_SET);//將讀寫位置移到檔案開頭
    ret= write(fd,buf,strlen(buf));//將buf寫進檔案中
    ret=flock(fd,LOCK_UN);//解除鎖定
    if(ret==-1)
    {
        perror("flock()");
        exit(1);
    }
    close(fd);
    exit(0);//正常退出
}

int main()
{
    pid_t pid;//pid_t實際上就是int,在/sys/types.h中定義
    int count;:
    for(count=0;count<COUNT;count++)
    {
        pid=fork();//建立一個子程序程序,在父程序中,fork返回新建立子程序的程序ID;在子程序中,fork返回0;如果出現錯誤,fork返回一個負值;

        if(pid<0)
        {
            perror("fork()");
            exit(1);
        }
        if(pid==0)
        {   
            do_child(FILEPATH);//創建出來的那個新程序執行任務
        }
    }   
    for(count=0;count<COUNT;count++)
        wait(NULL);//等待所有程序退出   
}

執行情況:

zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ echo 0 > count
zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ cat count
0
zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ ./racingn
zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ cat count
100zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ 

2.flock和lockf

Linux的檔案鎖主要有兩種:flock和lockf

flock只能對整個檔案加鎖;
lockf是fcntl系統呼叫的一個封裝,實現了更細粒度的檔案鎖————記錄鎖,可以對檔案的部分位元組上鎖;

flock的語義是針對檔案的鎖;
lockf是針對檔案描述符(fd)的鎖

:

檔案鎖程式:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <wait.h>

#define  PATH "./lock"

int main()
{
        int fd;
        pid_t pid;

        fd=open(PATH,O_RDWR|O_CREAT|O_TRUNC,0644);//O_TRUNC:若檔案存在並且以可寫的方
式開啟時,此旗標會令檔案的長度清0,存於檔案的資料消失
        if (fd<0)
        {
                perror("open()");
                exit(1);
        }

        if(flock(fd,LOCK_EX)<0)//使用flock對其加互斥,或者if(lockf(fd,F_LOCK,0)<0)使用lockf對其加互斥鎖

    {       
        perror("flock()");
            exit(1);
        }
        printf("%d: locked!\n",getpid());//打印表示加鎖成功

        pid=fork();
        if(pid<0)
        {
                perror("fork()");
                exit(1);
        }

        if(pid == 0)
        {
                fd=open(PATH,O_RDWR|O_CREAT|O_TRUNC,0644);
                if(fd<0)
                {
                        perror("open()");
                        exit(1);
                }
                if(flock(fd,LOCK_EX)<0)//在子程序中使用flock對同一個檔案加互斥鎖
                {
                        perror("flock()");
                        exit(1);
        }
                printf("%d: locked!\n",getpid());//列印加鎖成功
                exit(0);
        }
        wait(NULL);
        unlink(PATH);//刪除指定檔案
        exit(0);
}

執行情況:

zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ ./flock
3544: locked!

子程序flock/lockf的時候阻塞

兩種鎖之間互不影響,比如以下例子

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>
#include <fcntl.h>

#define PATH "./lock"
int main()
{
        int fd;
        pid_t  pid;
        if(open("PATH",O_RDWR|O_CREAT|O_TRUNC,0644)<0)
        {
                perror("open()");
                exit(1);
        }
        if(flock(fd, LOCK_EX)<0)
        {
                perror("flock()");
                exit(1);
        }
        printf("%d: Locked with flock\n",getpid());

        if(lockf(fd, F_LOCK, 0)<0)
        {
                perror("lockf()");
                exit(1);
        }
        printf("%d: Locked with lockf\n", getpid());
        exit(0);
}

執行情況如下:

zach@zach-i16:~/文件/note/Linux/程序通訊/2.檔案和檔案鎖$ ./lockf_and_flock
4162: Locked with flock
4162: Locked with lockf

3.標準IO庫檔案鎖

#include <stdio.h>

void flockfile(FILE *filehandle);
int ftrylockfile(FILE *filehandle);
void funlockfile(FILE *filehandle);

stdio庫中實現的檔案鎖與flock或lockf有本質區別,標準IO的鎖在多程序環境中使用是有問題的.

件鎖只能處理一個程序中的多個執行緒之間共享的FILE 的進行檔案操作.

多個執行緒必須同時操作一個用fopen開啟的FILE 變數,如果內部自己使用fopen重新開啟檔案,那麼返回的FILE *地址不同,起不到執行緒的互斥作用

4.小結

本次Linux程序間通訊的學習是基於以下文章和書籍