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