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

猜你喜欢

转载自blog.csdn.net/weixin_44537992/article/details/105843477