1. 程式人生 > >APUE習題10.6(父子程序同步)

APUE習題10.6(父子程序同步)

編寫一段程式測試圖10_24中父子程序的同步函式,要求程序建立一個檔案並向檔案寫一個整數0,然後程序呼叫fork,接著父子程序交替增加檔案中的計數器值,每次計數器值增加1,列印是哪一個程序進行來該增加1操作

沒有完全按照題目要求做,直接fork之後01234這樣列印的

先上一個標準輸出的實現

1、寫到標準輸出的實現

main.c:以下實現是有問題的

問題在於:子程序的資料空間、堆疊空間都會從父程序得到一個拷貝,而不是共享。在子程序對count進行++操作,並沒有影響到父程序著哦您能夠的count值,即使是全域性變數也不會改變,《APUE》8.3節說到,子程序是父程序的副本(子程序獲得父程序資料空間、堆和棧的副本)。父程序和子程序並不共享這些儲存空間部分,父程序和子程序共享正文段。

#include "apue.h"

static int count = 0;
int main(void)
{
        pid_t pid;
        TELL_WAIT();


        
        if((pid = fork()) < 0 ){
                err_sys("fork error");
        } else if (pid == 0) {
                while(1){
                        TELL_WAIT();
                        WAIT_PARENT();
                        printf("output from child:");
                        printf("count=%d \n",count++);
                        TELL_PARENT(getppid());
                }
                exit(0);
        }
        else{
            printf("Output from parent:");
            printf("count=%d\n",count++);
            TELL_CHILD(pid);
            while(1){
                 sleep(2);
                 TELL_WAIT();
                 WAIT_CHILD();
                 printf("output from parent:");
                 printf("count=%d\n",count++);
                 TELL_CHILD(pid);
            }
        }
        exit(0);
}


所以會出現以下結果:

正確的實現為:

#include "apue.h"

static int count = 0;
int main(void)
{
        pid_t pid;
        TELL_WAIT();


        
        if((pid = fork()) < 0 ){
                err_sys("fork error");
        } else if (pid == 0) {
                count++;
                while(1){
                        TELL_WAIT();
                        WAIT_PARENT();
                        printf("output from child:");
                        printf("count=%d \n",count);
                        count=count+2;
                        TELL_PARENT(getppid());
                }
                exit(0);
        }
        else{
            printf("Output from parent:");
            printf("count=%d\n",count);
            TELL_CHILD(pid);//父程序一定要現執行一次,然後通知子程序,否則父子程序會永遠互相等待
            while(1){
                 sleep(2);
                 TELL_WAIT();
                 WAIT_CHILD();
                 printf("output from parent:");
                 count=count+2;
                 printf("count=%d\n",count);
                 TELL_CHILD(pid);
            }
        }
        exit(0);
}
結果如下:

2、寫檔案實現

fork的一個特性是父程序所有的開啟檔案描述符file_struct都被複制到子程序中,父子程序的每個相同的開啟描述符共享一個檔案表項如圖

這種共享的方式使父、子程序對同一個檔案使用了同一個檔案偏移量。如果父、子程序寫到同一個檔案描述符,但有沒有任何形式的同步,那麼它們的輸出就會相互混合。在fork之後處理檔案描述符有兩種常見的情況:

1)父程序等待子程序完成。在這種情況下,父程序無須對其描述符做任何處理。當子程序終止之後,它曾進行過讀、寫的人一個共享描述符的檔案偏移量已經執行了相應的更新。

2)父、子程序各自執行不同的程式段。這種情況下,在fork之後,父、子程序各自關閉它們不需使用的檔案描述符,這樣就不會干擾對方使用的檔案描述符。這種方式是網路服務程序中常用的方式。


#include "apue.h"
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

const char *str_parent="output from parent";
const char *str_child="output from child";

static int count = 0;

void print(int fd,const char*str)
{
   char str_count[5];
   write(fd,str,strlen(str));
   sprintf(str_count,":%d\n",count);
   write(fd,str_count,strlen(str_count));

}

int main(void)
{
        pid_t pid;
        int fd;         

        fd=open("./test.txt",O_RDWR|O_CREAT|O_TRUNC|O_SYNC,S_IRUSR|S_IWUSR);
        TELL_WAIT();

        
        print(fd,str_parent);
        TELL_CHILD(pid);

        
        if((pid = fork()) < 0 ){
                err_sys("fork error");
        } else if (pid == 0) {
                count++;
                print(fd,str_child);
                while(1){
                        TELL_WAIT();
                        WAIT_PARENT();
                        count=count+2;
                        print(fd,str_child);
                        TELL_PARENT(getppid());
                }
                exit(0);
        }
        else{
            while(1){
                 sleep(2);
                 TELL_WAIT();
                 WAIT_CHILD();
                 count=count+2;
                 print(fd,str_parent);
                 TELL_CHILD(pid);
            }
        }
        close(fd);
        exit(0);
}



附上tellwait.c

#include "apue.h"

static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */
static sigset_t newmask, oldmask, zeromask;

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)
		err_sys("signal(SIGUSR1) error");
	if (signal(SIGUSR2, sig_usr) == SIG_ERR)
		err_sys("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)
		err_sys("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 */
	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
		err_sys("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 */
	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
		err_sys("SIG_SETMASK error");
}