APUE——system函數詳細分析

相關鏈接1
相關鏈接2

1、SYSTEM函數原理

#include <stdlib.h>
int system( const char *cmdstring );
  1. system()是先fork子進程,然後execl執行傳入的commod命令,然後主進程阻塞,wait等待子進程結束
  2. system函數之所以阻塞SIGCHLD,是爲了保證system函數能夠正確獲取子進程的退出狀態,並返回給system的調用者。
  3. 爲什麼忽略SIGINT和SIGQUIT?其實說白了,因爲system的調用者在指定的命令執行期間放棄了對程序的控制(waitpid阻塞在那裏),等待該執行程序的結束,所以system的調用者就不應該接收SIGINT和SIGQUIT信號,而只由子進程接收,這也是在子進程中一開始恢復SIGINT和SIGQUIT信號的原因。還是因爲希望獲取子進程的退出狀態不受到外界干擾。
  4. 關於system函數返回,看鏈接1

system()函數先fork一個子進程,在這個子進程中調用/bin/sh -c來執行command指定的命令。/bin/sh在系統中一般是個軟鏈接,指向dash或者bash等常用的shell,-c選項是告訴shell從字符串command中讀取要執行的命令(shell將擴展command中的任何特殊字符)。父進程則調用waitpid()函數來爲變成殭屍的子進程收屍,獲得其結束狀態,然後將這個結束狀態返回給system()函數的調用者。

1.1 普通system函數

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "apue.h"
 
/* version without signal handling */
int system_without_signal(const char *cmd_string)
{
    pid_t pid;
    int status = -1;
 
    if (cmd_string == NULL)
        return (1);     /* always a command processor with UNIX */
 
    if ((pid = fork()) < 0) {
        status = -1;    /* probably out of processes */
    } else if (pid == 0) {  /* child */
        execl("/bin/sh", "sh", "-c", cmd_string, (char *)0);
        _exit(127); /* execl error */
    } else {                /* parent */
//      sleep(1);
        pid_t wait_pid;
        while ((wait_pid = waitpid(pid, &status, 0)) < 0) {
            printf("[in system_without_signal]: errno = %d(%s)\n",
                                        errno, strerror(errno));
            if (errno != EINTR) {
                status = -1;    /* error other than EINTR form waitpid() */
                break;
            }
        }
        printf("[in system_without_signal]: pid = %ld, wait_pid = %ld\n",
                                        (long)pid, (long)wait_pid);
        pr_exit("[in system_without_signal]", status);
    }
 
    return (status);
}

1、2帶信號屏蔽函數分析

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
 
/* with appropriate signal handling */
int system_with_signal(const char *cmd_string)
{
	pid_t 		pid;
	int 		status;
	struct 		sigaction ignore, saveintr, savequit;
	sigset_t	chld_mask, save_mask;
	
	if (cmd_string == NULL)
		return (1);		/* always a command processor with UNIX */
 
	/* ignore signal SIGINT and SIGQUIT */
	ignore.sa_handler = SIG_IGN;
	ignore.sa_flags = 0;
	sigemptyset(&ignore.sa_mask);
	if (sigaction(SIGINT, &ignore, &saveintr) < 0) 
		return (-1);
	if (sigaction(SIGQUIT, &ignore, &savequit) < 0)
		return (-1);
 
	/* block SIGCHLD and save current signal mask */
	sigemptyset(&chld_mask);
	sigaddset(&chld_mask, SIGCHLD);
	if (sigprocmask(SIG_BLOCK, &chld_mask, &save_mask) < 0)
		return (-1);
 
	if ((pid = fork()) < 0) {
		status = -1;	/* probably out of processes */
	} else if (pid == 0) {		/* child */
		/* restore previous signal actions & reset signal mask */
		sigaction(SIGINT, &saveintr, NULL);
		sigaction(SIGQUIT, &savequit, NULL);
		sigprocmask(SIG_SETMASK, &save_mask, (sigset_t *)NULL);
 
		execl("/bin/sh", "sh", "-c", cmd_string, (char *)0);
		_exit(127);
	} else {					/* parent */
		int wait_pid;
	//	sleep(10);	/* */
		while ((wait_pid = waitpid(pid, &status, 0)) < 0) {
			printf("[in system_with_signal]: errno = %d(%s)\n", 
										errno, strerror(errno));
			if (errno != EINTR) {
				status = -1;	/* error other than EINTR from waitpid() */
				break;
			}
		}
		printf("[in system_with_signal]: pid = %ld, wait_pid = %ld\n", 
										(long)pid, (long)wait_pid);
		pr_exit("[in system_with_signal]", status);
	}
 
	/* in parent: restore previous signal action & reset signal mask */
	if (sigaction(SIGINT, &saveintr, NULL) < 0) 
		return (-1);
	if (sigaction(SIGQUIT, &savequit, NULL) < 0)
		return (-1);
	if (sigprocmask(SIG_SETMASK, &save_mask, (sigset_t *)NULL) < 0)	/* */
		return (-1);
 
	return (status);
}

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include "apue.h"
 
#define SETSIG(sa, sig, fun, flags)	\
do {								\
	sa.sa_handler = fun;			\
	sa.sa_flags = flags;			\
	sigemptyset(&sa.sa_mask); 		\
	sigaction(sig, &sa, NULL);		\
} while (0)
 
extern int system_without_signal(const char *cmd_string);
 
static void sig_chld(int signo)
{
	printf("\nenter SIGCHLD handler\n");
	
	pid_t pid;
	int exit_status = -1;
	int errno_saved = errno;
	pid = wait(&exit_status);
	if (pid != -1) {
		printf("[in sig_chld] reaped %ld child,", (long)pid);
		pr_exit("wait: ", exit_status);
		printf("\n");
	} else {
		printf("[in sig_chld] wait error: errno = %d(%s)\n\n", 
										errno, strerror(errno));
	}
 
	errno = errno_saved;
	printf("leave SIGCHLD handler\n");
}
 
int main(int argc, const char *argv[])
{
	pid_t pid;
	struct sigaction sigchld_act;
 
	SETSIG(sigchld_act, SIGCHLD, sig_chld, 0);
 
	int status;
	if ((status = system_without_signal("/bin/ls -l; exit 44")) < 0) {
		err_sys("system() error(status = %d): ", status);
	}
	pr_exit("system() return:", status);
 
	exit(EXIT_SUCCESS);
}

在這裏插入圖片描述
情形1:在子進程正在運行指定程序時,或者說在子進程結束之前,父進程中的waitpid阻塞在那裏。

這種情形下,一旦子進程結束,內核會嚮應用程序遞送SIGCHLD信號,運行信號處理函數,在信號處理函數中調用wait系列函數,那麼現在問題來了:究竟是信號處理函數中的wait系列函數還是system_without_signal中的waitpid爲子進程收屍呢? 答案是未知的。因爲信號本身是異步的,我們掌控不了(在我的系統中,waitpid還總能正確的獲取子進程退出狀態,而在信號處理函數中的wait卻返回-1,errno設置爲ECHLD,表明沒有可收屍的子進程,見下圖。但是,在你的系統中,結果也許就是相反的噢)。所以,在這種情形下,我們得出的結論是:儘管system函數完成了其任務(正確執行了我們指定的程序),但卻有可能返回-1。很顯然,這不是我們希望發生的。

情形2:在一個繁忙的系統中,很可能在調用waitpid之前子進程就已經結束了,此時內核會向父進程遞送SIGCHLD信號。

在這種情形下,問題就更明顯了。在調用waitpid之前就已經調用了SIGCHLD信號的信號處理函數,信號處理函數中的wait函數爲子進程收了屍,那麼接下來的waitpid不就獲取不了子進程的退出狀態了嗎? 事實也的確如此!我們可以在waitpid之前調用加個sleep來模擬系統負荷重的情形,會發現waitpid會出錯,返回-1,errno設置爲ECHLD,表明沒有可收屍的子進程,最終system函數返回-1。所以,在這種情形下,我們得出的結論是:儘管system函數完成了其任務(正確執行了我們指定的程序),但卻一直返回-1。很顯然,這也不是我們希望發生的。

如果將上面例子中的system_without_signal替換成system_with_signal,那麼system函數在調用fork之前就已經阻塞了SIGCHLD信號的話,那麼就不會出現上述兩種情況了。因爲阻塞了SIGCHLD信號,那麼不管system函數創建的子進程什麼時候結束,即不管SIGCHLD信號什麼時候來,在沒有解除阻塞之前,是不會處理該信號的,即SIGCHLD信號是未決的。所以,無論如何,waitpid都會正確獲取子進程的退出狀態。只有在最後調用sigprocmask時,系統纔會解除對SIGCHLD的阻塞。解除阻塞後,這才調用信號處理函數,不過這次信號處理函數中的wait會出錯,返回-1,errno設置爲ECHLD,表明沒有可收屍的子進程。那麼system函數就能正確的返回子進程的退出狀態了。

看到這裏,你可能會說,問題都是SIGCHLD信號處理函數中的wait惹的禍,如果去掉SIGCHLD信號處理函數中的wait函數,不就不會帶來上述的兩個問題了嗎? 我的答案是:的確可以避免上述兩個問題,即system函數可以正確的獲取子進程的退出狀態。但是這樣做還是會有問題的:我們先不管在SIGCHLD信號處理函數中不調用wait系列函數這種不正統的做法,我們在這裏考慮這樣一種情形:如果信號處理函數需要運行一分鐘的時間才返回(實際編程中,信號處理函數要儘量短噢,這裏只是一種極端的假設),那麼system函數豈不是也要阻塞一分鐘才能返回?因爲如果不阻塞SIGCHLD信號並且主進程註冊了SIGCHLD信號處理函數(未調用wait系列函數),那麼就需要等主進程的信號處理函數返回後waitpid才能接受到子進程的退出狀態,也就是信號處理函數需要運行多長時間,那麼system也就需要這麼多時間才能返回。一個函數的運行受到外界不確定因素的影響,這種情形還是應該避免的。所以在調用system函數的時候阻塞SIGCHLD,這樣在執行期間信號被阻塞就不會調用信號處理函數了,system中的waitpid就能"及時"地獲取到子進程的狀態。-- 但是仔細想想,其實system函數還是避免不了這種情形的,因爲在最後調用sigprocmask解除阻塞時(一般在sigprocmask返回之前,就至少遞送一個阻塞的信號),還是會調用信號處理函數,system依然會阻塞,唯一的不同是,這種情況下waitpid是在調用信號處理函數之前就獲取了子進程的退出狀態,避免了多線程的諸多影響。所以,在平時的編程實踐當中,信號處理函數要儘量的短,這樣纔不會對其他函數造成不必要的未知影響。

信號屏蔽集的繼承關係

fork後,子進程繼承了父進程的信號屏蔽集,但是由於是兩個存儲空間,所以更改子進程的信號屏蔽集,並不改變父進程的信號屏蔽集

#include "apue.h"

struct sigaction act;
sigset_t old;
void fun();
//void funchild();
pid_t pid;
void pr_mask(const char *str)
{
    sigset_t    sigset;
    int        errno_save;
    
    //errno_save = errno;    /* we can be called by signal handlers */
    if (sigprocmask(0, NULL, &sigset) < 0)
        printf("sigprocmask error");

    printf("%s", str);
    
    if (sigismember(&sigset, SIGINT))    printf("SIGINT ");
    if (sigismember(&sigset, SIGQUIT))    printf("SIGQUIT ");
    if (sigismember(&sigset, SIGUSR1))    printf("SIGUSR1 ");
    if (sigismember(&sigset, SIGALRM))    printf("SIGALRM ");

    /* remaining signals can go here */

    printf("\n");
    //errno = errno_save;
}
int main()
{
    act.sa_flags = 0;
    act.sa_handler = fun;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGQUIT);
    //sigaction(SIGUSR1,funchild);
    signal(SIGQUIT,fun);
    sigprocmask(SIG_SETMASK,&act.sa_mask,&old);
    
    printf("getpid = %d\n",getpid());
    pid_t pid =fork();
    if(pid<0)
    {
        pr_mask("error fork");
    }
    else if(pid==0)
    {
        pr_mask("child pid ok");
         sleep(5);                           //fork後,子進程繼承了父進程的信號屏蔽集
        sigprocmask(SIG_SETMASK,&old,NULL);  //解除子進程對SIGQUIT的阻塞

        //pause();
        pr_mask("child pid ok");
        sleep(10);
    }
    else if(pid>0){
        //sleep(2);
        pr_mask("this is parent1");        //父進程裏沒有接觸阻塞
        pause();
        pr_mask("this is parent2");     
    }
    return 0;
}
void fun(int signo)
{
    pr_mask("fun ok");
}
xianzhi@qianchen-ThinkStation-P310:~/apuetest$ gcc child_sigprocmask.c -o child_sigprocmask
xianzhi@qianchen-ThinkStation-P310:~/apuetest$ ./child_sigprocmask 
getpid = 1396
this is parent1SIGQUIT 
child pid okSIGQUIT 
child pid ok
^\fun ok
^C
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章