关于glibc的system函数调用实现

开始尝试着自己写博客,记录一下自己的学习历程,所遇到的困惑,也让以后的自己看看。

最近在看《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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值