linux系統編程:IO讀寫過程的原子性操作實驗,linuxio
所謂原子性操作指的是:核心保證某系統調用中的所有步驟(操作)作為獨立操作而一次性加以執行,其間不會被其他進程或線程所中斷。
舉個通俗點的例子:你和女朋友OOXX的時候,突然來了個電話,勢必會打斷你們高潮的興緻,最好的辦法就是,你們做這事的時候,把通訊裝置關機,就能確保,這次的事情很圓滿的完成,這就是一次原子性操作。
在多進程IO過程中,如果操作不具有原子性,就可能會導致資料混亂,相互覆蓋等情況。這種現象也叫競爭狀態。
所謂競爭狀態指的是:操作共用資源的兩個進程(或線程),其結果取決於一個無法預期的順序,因為進程擷取的cpu執行時間是不確定的。
1,假想的,以獨佔方式建立一個檔案
下面這段代碼,用open和O_CREAT標誌示範一個獨佔方式建立檔案, 什麼叫獨佔方式建立檔案? 就是該進程始終認為這個檔案是他開啟的,或者是他建立的
1 /*================================================================ 2 * Copyright (C) 2018 . All rights reserved. 3 * 4 * 檔案名稱:bad_exclusive_open.c 5 * 創 建 者:ghostwu(吳華) 6 * 建立日期:2018年01月11日 7 * 描 述: 8 * 9 ================================================================*/10 11 #include <stdio.h>12 #include <sys/types.h>13 #include <sys/stat.h>14 #include <fcntl.h>15 #include <stdlib.h>16 #include <string.h>17 #include <sys/types.h>18 #include <unistd.h>19 #include <errno.h>20 21 22 int main(int argc, char *argv[])23 {24 if( argc < 2 || strcmp( argv[1], "--help" ) == 0 ){25 printf( "usage:%s filename\n", argv[0] );26 exit( -1 );27 }28 29 printf( "pid=%d, %s檔案不存在\n", getpid(), argv[1] );30 31 int fd = -1;32 33 fd = open( argv[1], O_WRONLY );34 if( fd < 0 ){35 sleep( 5 );36 printf( "pid=%d, 結束睡眠\n", getpid() );37 //其他錯誤原因,導致檔案開啟失敗38 if( errno != ENOENT ) {39 perror( "open" );40 exit( -1 );41 }else {42 //檔案不存在 導致檔案開啟失敗43 fd = open( argv[1], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR ); 44 if( fd < 0 ) {45 printf( "檔案%s建立失敗\n", argv[1] );46 exit( -1 );47 }48 printf( "檔案%s,建立並開啟成功:fd=%d\n", argv[1], fd );49 printf( "進程id=%d\n", getpid() );50 close( fd );51 }52 }else {53 printf( "檔案%s,開啟成功:fd=%d\n", argv[1], fd );54 printf( "進程id=%d\n", getpid() );55 close( fd );56 }57 58 59 return 0;60 }View Code
假如,我們要建立一個不存在的test.txt檔案。
為了示範方便,在程式第一次判斷檔案不存在的情況下,讓進程掛起( sleep 5 )交出cpu的執行時間,這個時候,我們可以這樣測試,兩種方法:
1,在另一個終端,登入另一個賬戶(如root賬戶),建立test.txt檔案
2,在另一個終端,再開啟一個進程
方法一:用shell指令碼建立一個test.txt,並賦予其他組的許可權為rw
createfile.sh
1 #!/bin/bash2 #建立檔案,並改變許可權配合測試3 4 touch test.txt5 sudo chmod a+rw test.txt
實驗結果:左邊的進程依然認為這個檔案是他建立並開啟的!
方法二,在另一個終端,再開一個進程測試
兩個進程都認為,test.txt是他們自己建立並開啟的
2,如何保證獨佔方式建立一個檔案?
非常簡單,只需要把加一個標誌O_EXCL,結合O_CREAT
fd = open( argv[1], O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR );
再次按照上面的2種方式測試,得到的結果就是:
如果在sleep期間,別的進程建立了檔案,那麼該進程會報錯
3,seek與write結合,產生相互覆蓋 1 /*================================================================ 2 * Copyright (C) 2018 . All rights reserved. 3 * 4 * 檔案名稱:seek_file.c 5 * 創 建 者:ghostwu(吳華) 6 * 建立日期:2018年01月11日 7 * 描 述: 8 * 9 ================================================================*/10 11 #include <stdio.h>12 #include <stdlib.h>13 #include <string.h>14 #include <sys/types.h>15 #include <sys/stat.h>16 #include <fcntl.h>17 #include <sys/types.h>18 #include <unistd.h>19 20 #ifndef BUFSIZE21 #define BUFSIZE 5022 #endif23 24 25 int main(int argc, char *argv[])26 {27 if( argc < 3 || strcmp( argv[1], "--help" ) == 0 ) {28 printf( "usage:%s filename w<string>\n", argv[1] );29 exit( -1 );30 }31 32 if( argv[2][0] != 'w' ) {33 printf( "必須以w開頭\n" );34 exit( -1 );35 }36 37 int fd = -1;38 fd = open( argv[1], O_RDWR );39 40 if( fd < 0 ) {41 printf( "檔案%s開啟失敗\n", argv[1] );42 exit( -1 );43 }44 45 if ( -1 == lseek( fd, 0, SEEK_END ) ) {46 printf( "指標移動到尾部失敗\n" );47 exit( -1 );48 }49 50 sleep( 5 );51 52 char buf[BUFSIZE];53 ssize_t nwrite;54 55 strcpy( buf, &argv[2][1] );56 57 nwrite = write( fd, buf, strlen( buf ) );58 if( -1 == nwrite ) {59 printf( "檔案寫入失敗\n" );60 exit( -1 );61 }62 printf( "pid=%d,向檔案%s寫入了%ld個位元組\n", getpid(), argv[1], nwrite );63 64 return 0;65 }View Code
如果第一個進程執行到seek與write之間,交出 cpu, 被執行相同代碼的第二個進程中斷,那麼這兩個進程在寫入資料前都把指標移動到相同的位置,如果一個進程先完成,那麼後一個進程會覆蓋前面進程寫入的資料
實驗結果:
第二個進程後結束: 第一個進程寫入的123被第二個進程的4567覆蓋,產生結果 4567
第一個進程後結束:第一個進程寫入的4567被第二個進程的123覆蓋,產生結果 1237
如何避免資料覆蓋?開啟檔案時候,加入O_APPEND標誌
fd = open( argv[1], O_RDWR | O_APPEND );
總結:
1)理解原子性操作
2)理解標誌O_CREAT與O_EXCL結合的意義
3)理解O_APPEND標誌
4)理解競爭狀態