开始尝试着自己写博客,记录一下自己的学习历程,所遇到的困惑,也让以后的自己看看。
最近在看《UNIX环境高级编程》,看到system函数的解释,同时刚好下游有个执行system返回失败的问题,因此正好仔细研究一下这个函数。
#include <stdio.h>
int system(const char *cmdstring);
然后是返回值:
1、fork失败或者waitpid返回除EINTR(中断,如Ctrl+C)外的错误,则system返回-1,并且设置errno来标识错误类型(返回错误后尽量把错误码打印,以便进一步定位)。
2、如果exec失败,一般是不能执行shell,或者所要执行的命令不存在,那么返回127。
3、这种情况就取决于你要执行的命令或脚本的返回值了。
所以说system的返回值是很多的,这也是很多人不建议使用system函数的原因,我们项目组是已经见此函数列为禁止使用了。奈何下游要用,所以还是要看。
简单的说system函数的执行过程就是fork->exec->waitpid。
《UNIX》里写了一个简单的system函数的实现,我就不再赘述。我们直接看下system在glibc里的实现。
#define SHELL_PATH "/bin/sh" /* Path of the shell. */
#define SHELL_NAME "sh" /* Name to give it. */
#ifdef _LIBC_REENTRANT
static struct sigaction intr, quit;
static int sa_refcntr;
__libc_lock_define_initialized (static, lock);
# define DO_LOCK() __libc_lock_lock (lock)
# define DO_UNLOCK() __libc_lock_unlock (lock)
# define INIT_LOCK() ({ __libc_lock_init (lock); sa_refcntr = 0; })
# define ADD_REF() sa_refcntr++
# define SUB_REF() --sa_refcntr
#else
# define DO_LOCK()
# define DO_UNLOCK()
# define INIT_LOCK()
# define ADD_REF() 0
# define SUB_REF() 0
#endif
/* Execute LINE as a shell command, returning its status. */
static int
do_system (const char *line)
{
int status, save;
pid_t pid;
struct sigaction sa;
#ifndef _LIBC_REENTRANT
struct sigaction intr, quit;
#endif
sigset_t omask;
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
__sigemptyset (&sa.sa_mask);
DO_LOCK ();
if (ADD_REF () == 0)
{
if (__sigaction (SIGINT, &sa, &intr) < 0)//忽略SIGINT信号,同时保存原来的信号处理函数
{
(void) SUB_REF ();
goto out;
}
if (__sigaction (SIGQUIT, &sa, &quit) < 0)//忽略SIGQUIT信号,同时保存原来的信号处理函数
{
save = errno;
(void) SUB_REF ();
goto out_restore_sigint;
}
}
DO_UNLOCK ();
/* We reuse the bitmap in the 'sa' structure. */
__sigaddset (&sa.sa_mask, SIGCHLD);//阻塞SIGCHLD信号
save = errno;
if (__sigprocmask (SIG_BLOCK, &sa.sa_mask, &omask) < 0)
{
#ifndef _LIBC
if (errno == ENOSYS)
__set_errno (save);
else
#endif
{
DO_LOCK ();
if (SUB_REF () == 0)
{//如果设置阻塞失败则恢复刚才两个信号的信号处理函数
save = errno;
(void) __sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);
out_restore_sigint:
(void) __sigaction (SIGINT, &intr, (struct sigaction *) NULL);
__set_errno (save);
}
out:
DO_UNLOCK ();
return -1;
}
}
#ifdef CLEANUP_HANDLER
CLEANUP_HANDLER;
#endif
#ifdef FORK
pid = FORK ();
#else
pid = __fork ();//开始fork子进程执行命令
#endif
if (pid == (pid_t) 0)
{
/* Child side. */
const char *new_argv[4];
new_argv[0] = SHELL_NAME;
new_argv[1] = "-c";
new_argv[2] = line;
new_argv[3] = NULL;
/* Restore the signals. */
//父进程忽略这两个信号的处理,但是子进程要响应这两个信号
(void) __sigaction (SIGINT, &intr, (struct sigaction *) NULL);
(void) __sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);
(void) __sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL);//不再阻塞任何信号
INIT_LOCK ();
/* Exec the shell. 调用shell执行命令*/
(void) __execve (SHELL_PATH, (char *const *) new_argv, __environ);
_exit (127);//这里就是shell无法执行或者不存在执行的命令的返回处
}
else if (pid < (pid_t) 0)
/* The fork failed. */
status = -1;
else
/* Parent side. */
{
/* Note the system() is a cancellation point. But since we call
waitpid() which itself is a cancellation point we do not
have to do anything here. */
if (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) != pid)//调用waitpid或者子进程状态
status = -1;
}
#ifdef CLEANUP_HANDLER
CLEANUP_RESET;
#endif
save = errno;
DO_LOCK ();
//父进程退出前同样把信号处理函数恢复成原先的
if ((SUB_REF () == 0
&& (__sigaction (SIGINT, &intr, (struct sigaction *) NULL)
| __sigaction (SIGQUIT, &quit, (struct sigaction *) NULL)) != 0)
|| __sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL) != 0)
{
#ifndef _LIBC
/* glibc cannot be used on systems without waitpid. */
if (errno == ENOSYS)
__set_errno (save);
else
#endif
status = -1;
}
DO_UNLOCK ();
return status;
}
初一看,各种宏定义还是有点乱的,但是抽象的看,对比system的简单实现版,glibc的实现就是增加了对信号的处理。大概流程是:一进来父进程先把SIGINT和SIGQUIT的信号处理函数设置为SIG_IGN,也就是接收但不处理,这个概念很重要。然后设置阻塞SIGCHLD信号,这是不接收。另外默认的信号处理函数如下,表示接收信号,但是信号处理函数里什么都没做就返回。
#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
如果设置阻塞信号失败则恢复原来的信号处理函数再退出。之后便是调用fork子进程,子进程里一开始恢复对SIGINT和SIGQUIT的响应,然后调用waitpid去获取子进程状态,TEMP_FAILURE_RETRY 宏定义如下:
#define TEMP_FAILURE_RETRY(expression) \
(__extension__\
({ long int __result;\
do __result = (long int)(expression);\
while(__result == -1L&& errno == EINTR);\
__result;})\
#endif
说白了就是父进程再等待子进程过程中遇到中断信号,不处理,继续等待。获取到子进程状态后,父进程里恢复SIGINT和SIGQUIT信号原先的信号处理函数,之后退出。
因此如果在执行system前将SIGCHLD的信号处理函数设置为SINIGN的话,system函数里父进程就会因为获取不到子进程的状态而返回失败,即ECHLD错误。这也就是我遇到的问题。
参考资料
[1] http://ftp.gnu.org/gnu/glibc/
[2] http://www.cnblogs.com/lidabo/p/5344777.html