父子進程間交互執行是指用一種同步原語,實現父進程和子進程在某一時刻只有一個進程執行,以後由另一個進程執行,用一段代碼舉例以下:git
SYNC_INIT(); int i=0, counter=0; pid_t pid = fork (); if (pid < 0) err_sys ("fork error"); else if (pid > 0) { // parent for (i=0; i<NLOOPS; i+=2) { counter = update ((long *)area); if (counter != i) err_quit ("parent: expected %d, got %d", i, counter); else printf ("parent increase to %d based %d\n", i+1, counter); SYNC_TELL(pid, 1); SYNC_WAIT(0); } printf ("parent exit\n"); } else { for (i=1; i<NLOOPS+1; i+=2) { SYNC_WAIT(1); counter = update ((long *)area); if (counter != i) err_quit ("child: expected %d, got %d", i, counter); else printf ("child increase to %d based %d\n", i+1, counter); SYNC_TELL(getppid (), 0); } printf ("child exit\n"); }
其中area是指向共享內存的一個地址,update用來增長area指向的內容(爲long),在fork以後,父子進程交替更新此值。github
它們使用了一些抽象的同步原語,例如SYNC_INIT用於初始化同步設施、SYNC_WAIT等待另外進程的信號、SYNC_TELL向另外進程發送信號。bash
下面是成功同步後的輸出(假設NLOOPS爲100):測試
create shared-memory 3801126 with size 4 ok attach shared-memory at 0xb7733000 parent increase to 1 based 0 child increase to 2 based 1 parent increase to 3 based 2 child increase to 4 based 3 parent increase to 5 based 4 child increase to 6 based 5 parent increase to 7 based 6 child increase to 8 based 7 parent increase to 9 based 8 child increase to 10 based 9 parent increase to 11 based 10 child increase to 12 based 11 parent increase to 13 based 12 child increase to 14 based 13 parent increase to 15 based 14 child increase to 16 based 15 parent increase to 17 based 16 child increase to 18 based 17 parent increase to 19 based 18 child increase to 20 based 19 parent increase to 21 based 20 child increase to 22 based 21 parent increase to 23 based 22 child increase to 24 based 23 parent increase to 25 based 24 child increase to 26 based 25 parent increase to 27 based 26 child increase to 28 based 27 parent increase to 29 based 28 child increase to 30 based 29 parent increase to 31 based 30 child increase to 32 based 31 parent increase to 33 based 32 child increase to 34 based 33 parent increase to 35 based 34 child increase to 36 based 35 parent increase to 37 based 36 child increase to 38 based 37 parent increase to 39 based 38 child increase to 40 based 39 parent increase to 41 based 40 child increase to 42 based 41 parent increase to 43 based 42 child increase to 44 based 43 parent increase to 45 based 44 child increase to 46 based 45 parent increase to 47 based 46 child increase to 48 based 47 parent increase to 49 based 48 child increase to 50 based 49 parent increase to 51 based 50 child increase to 52 based 51 parent increase to 53 based 52 child increase to 54 based 53 parent increase to 55 based 54 child increase to 56 based 55 parent increase to 57 based 56 child increase to 58 based 57 parent increase to 59 based 58 child increase to 60 based 59 parent increase to 61 based 60 child increase to 62 based 61 parent increase to 63 based 62 child increase to 64 based 63 parent increase to 65 based 64 child increase to 66 based 65 parent increase to 67 based 66 child increase to 68 based 67 parent increase to 69 based 68 child increase to 70 based 69 parent increase to 71 based 70 child increase to 72 based 71 parent increase to 73 based 72 child increase to 74 based 73 parent increase to 75 based 74 child increase to 76 based 75 parent increase to 77 based 76 child increase to 78 based 77 parent increase to 79 based 78 child increase to 80 based 79 parent increase to 81 based 80 child increase to 82 based 81 parent increase to 83 based 82 child increase to 84 based 83 parent increase to 85 based 84 child increase to 86 based 85 parent increase to 87 based 86 child increase to 88 based 87 parent increase to 89 based 88 child increase to 90 based 89 parent increase to 91 based 90 child increase to 92 based 91 parent increase to 93 based 92 child increase to 94 based 93 parent increase to 95 based 94 child increase to 96 based 95 parent increase to 97 based 96 child increase to 98 based 97 parent increase to 99 based 98 child increase to 100 based 99 child exit parent exit remove that shared-memory
這套同步原語能夠有多種實現方案,簡單如管道、xsi信號量,甚至直接使用信號。下面是一些例子:ui
1. 使用管道atom
#ifdef USE_PIPE_SYNC // pp is the pipe that parent notify(write) child wait(read) // pc is the pipe that child notify(write) parent wait(read) static int pp[2], pc[2]; void SYNC_INIT (void) { if (pipe (pp) < 0 || pipe(pc) < 0) err_sys ("pipe error"); } void SYNC_TELL (pid_t pid, int child) { // close unused read end to avoid poll receive events // note, we can NOT do it in SYNC_INIT, // as at that moment, we have not fork yet ! if (child) { close (pp[0]); close (pc[1]); pp[0] = pc[1] = -1; } else { close (pc[0]); close (pp[1]); pc[0] = pp[1] = -1; } if (write (child ? pp[1] : pc[1], child ? "p" : "c", 1) != 1) err_sys ("write error"); } void SYNC_WAIT (int child /* unused */) { int n = 0, m = 0; struct pollfd fds[2] = {{ 0 }}; // if fd==-1, just be a place taker ! //if (pp[0] != -1) { fds[n].fd = pp[0]; fds[n].events = POLLIN; n++; } //if (pc[0] != -1) { fds[n].fd = pc[0]; fds[n].events = POLLIN; n++; } int ret = poll (fds, n, -1); if (ret == -1) err_sys ("poll error"); else if (ret > 0) { char c = 0; //printf ("poll %d from %d\n", ret, n); for (m=0; m<n; ++m) { //printf ("poll fd %d event 0x%08x\n", fds[m].fd, fds[m].revents); if (fds[m].revents & POLLIN) { if (fds[m].fd == pp[0]) { if (read (pp[0], &c, 1) != 1) err_sys ("read parent pipe error"); if (c != 'p') err_quit ("wait parent pipe but got incorrect data %c", c); } else { if (read (pc[0], &c, 1) != 1) err_sys ("read child pipe error"); if (c != 'c') err_quit ("wait child pipe but got incorrect data %c", c); } } } } else printf ("poll return 0\n"); } #endif
管道的話,TELL時就是向管道寫一個字節,WAIT的時候就是阻塞在對應的端讀取一個字節。spa
注意這裏WAIT沒有直接使用child參數,而是使用poll同時檢測兩個讀端,看哪一個有數據就返回哪一個。其實直接讀對應的端更直接一些。code
2.使用xsi信號量blog
#ifdef USE_SEM_SYNC union semun { int val; //<= value for SETVAL struct semid_ds *buf; // <= buffer for IPC_STAT & IPC_SET unsigned short int *array;// <= array for GETALL & SETALL struct seminfo *__buf; // <= buffer for IPC_INFO }; static int semid = -1; void SYNC_INIT () { int mode = 0666; // 0; int flag = IPC_CREAT; #ifdef USE_EXCL flag |= IPC_EXCL; #endif semid = semget (IPC_PRIVATE, 2, flag | mode); if (semid < 0) err_sys ("semget for SYNC failed"); printf ("create semaphore %d for SYNC ok\n", semid); union semun sem; //sem.val = 1; //int ret = semctl (semid, 0, SETVAL, sem); //if (ret < 0) // err_sys ("semctl to set val failed"); short arr[2] = { 0 }; sem.array = arr; int ret = semctl (semid, 0, SETALL, sem); if (ret < 0) err_sys ("semctl to set all val failed"); printf ("reset all semaphores ok\n"); } void SYNC_TELL (pid_t pid, int child) { struct sembuf sb; sb.sem_op = 1; sb.sem_num = child ? 1 : 0; sb.sem_flg = 0; // IPC_NOWAIT, SEM_UNDO int ret = semop (semid, &sb, 1); if (ret < 0) printf ("semop to release resource failed, ret %d, errno %d\n", ret, errno); else printf ("release %d resource %d\n", sb.sem_op, ret); } void SYNC_WAIT (int child) { struct sembuf sb; sb.sem_op = -1; sb.sem_num = child ? 1 : 0; sb.sem_flg = 0; // IPC_NOWAIT, SEM_UNDO int ret = semop (semid, &sb, 1); if (ret < 0) printf ("semop to require resource failed, ret %d, errno %d\n", ret, errno); else printf ("require %d resource %d\n", sb.sem_op, ret); } #endif
xsi信號量的話,在TELL時是向對應的信號量執行V操做,釋放一個資源;在WAIT時是向對應的信號量執行P操做,申請一個資源,若是申請不到,就阻塞在那裏。進程
3.使用信號
#ifdef USE_SIGNAL_SYNC static volatile sig_atomic_t sigflag; static sigset_t newmask, oldmask, zeromask; static void sig_usr (int signo) { sigflag = 1; printf ("SIGUSR1/2 called\n"); } void SYNC_INIT () { if (apue_signal (SIGUSR1, sig_usr) == SIG_ERR) err_sys ("signal (SIGUSR1) error"); if (apue_signal (SIGUSR2, sig_usr) == SIG_ERR) err_sys ("signal (SIGUSR2) error"); sigemptyset (&zeromask); sigemptyset (&newmask); sigaddset (&newmask, SIGUSR1); sigaddset (&newmask, SIGUSR2); if (sigprocmask (SIG_BLOCK, &newmask, &oldmask) < 0) err_sys ("SIG_BLOCK error"); } void SYNC_TELL (pid_t pid, int child) { kill (pid, child ? SIGUSR1 : SIGUSR2); } void SYNC_WAIT (int child /* unused */) { while (sigflag == 0) sigsuspend (&zeromask); sigflag = 0; if (sigprocmask (SIG_SETMASK, &oldmask, NULL) < 0) err_sys ("SIG_SETMASK error"); } #endif
直接使用signal的話,這裏分別使用了SIGUSR1和SIGUSR2表示父子進程,TELL操做就是激發一個信號給對方;WAIT操做就是sigsuspend在某個特定信號上,直到有信號發生才返回。
注意TELL時須要指定發送信號的進程號,因此多了一個pid參數,這個參數在以前聽說的兩種方法中並無使用。這也是signal很差的一點。
而後,apue 15章最後一道習題中,要求使用文件記錄鎖來實現上述交互執行時,發現這是不可能完成的任務!
假設咱們以加鎖文件或文件中一個字節來實現WAIT,使用解鎖來實現TELL,那麼會發現文件記錄鎖有如下缺點,致使它不能勝任這個工做:
1. 文件記錄鎖是基於文件+進程的,當fork後產生子進程時,以前加的鎖自動釋放;
2. 文件記錄鎖對於重複施加鎖於一個文件或文件中某個特定字節時,它的表現就和以前沒有加鎖同樣,直接成功返回,不會產生阻塞效果;
對於 問題1,直接的影響就是父進程加好鎖以後fork,子進程啓動後卻沒有任何初始鎖,致使父子進程同步困難。
雖然這個能夠經過在子進程中從新初始化來部分的解決,可是這種問題由於有進程競爭存在,問題不嚴密從而不完美的;
對於 問題2,就直接致使其中一個進程在它的任務循環中,TELL另一個進程後,再WAIT本進程的同步原語時(內部經過加鎖實現),
另外一個進程即便沒有解鎖相應的文件或字節,WAIT也直接成功返回(由於本進程已經持有該鎖),從而形成其中一個進程執行屢次,另外一個進程沒有辦法插進去執行的狀況(雖然兩個進程也不能同時執行)。
因此結論是,對於交互執行的同步場景,管道、semaphore、signal都適用,而file lock不適用。