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