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程序間通訊的學習是基於以下文章和書籍