linux system函数分析
system函数是在应用编程里面想调用外部命令时最方便的方式了,除非想要获取命令行执行的输出信息, 那system就不行了,需要用popen。但是system内部具体怎么实现及怎么处理它的返回值经常被忽略,我认为还是得特别注意。
关于system的实现用manpage里面的话:
DESCRIPTION
       The system() library function uses fork(2) to create a child process that executes the shell command specified in command using execl(3) as follows:
           execl("/bin/sh", "sh", "-c", command, (char *) 0);
       system() returns after the command has been completed.
       During  execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored, in the process that calls system() (these signals
       will be handled according to their defaults inside the child process that executes command).
       If command is NULL, then system() returns a status indicating whether a shell is available on the system
RETURN VALUE
       The return value of system() is one of the following:
       *  If command is NULL, then a nonzero value if a shell is available, or 0 if no shell is available.
       *  If a child process could not be created, or its status could not be retrieved, the return value is -1.
       *  If a shell could not be executed in the child process, then the return value is as though the child shell terminated by calling _exit(2) with the
          status 127.
       *  If all system calls succeed, then the return value is the termination status of the child shell used to execute command.  (The termination status
          of a shell is the termination status of the last command it executes.)
       In the last two cases, the return value is a "wait status" that can be examined using the macros described in waitpid(2).  (i.e., WIFEXITED() WEXIT‐
       STATUS() and so on).
       system() does not affect the wait status of any other children.
如果你看过代码后,你就会觉得manpage的描述非常精准。
总体的意思就是说:
它是通过调用/bin/sh -c 来执行你指定的命令, 并且直到命令执行完才返回。在执行命令的过程中, SIGCHLD信号是被屏蔽的, SIGINT和SIGQUIT信号是被忽略的。
需要补充的是关于“SIGCHLD信号是被屏蔽的, SIGINT和SIGQUIT信号是被忽略的”它指的是执行system命令的那个进程, system内部会fort一个进程来执行/bin/sh -c xxx, 那个子进程对于SIGCHLD SIGINT SIGQUIT都是默认的信号处理。
下面贴出glibc-2.19里面system的实现(有点多,我会在关键的地方注释)
glibc-2.19/sysdeps/posix/system.c:
/* 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);
  save = errno;
  if (__sigprocmask (SIG_BLOCK, &sa.sa_mask, &omask) < 0)//阻塞sigchld
    {
#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 ();//产生子进程
#endif
  if (pid == (pid_t) 0)
    {
      /* Child side.  */
      const char *new_argv[4];//构建/bin/sh -c命令行
      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);//子进程恢复sigint信号处理
      (void) __sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);//子进程恢复sigquit信号处理
      (void) __sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL);//子进程恢复信号屏蔽的处理
      INIT_LOCK ();
      /* Exec the shell.  */
      (void) __execve (SHELL_PATH, (char *const *) new_argv, __environ);
      _exit (127);//一般不会执行到这里, 如果到这里,说明上面的/bin/sh本身就失败了,那么返回127
    }
  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.  *///父进程等待子进程, 从这里看出返回值是和wait的status意义一样
      if (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) != pid)
    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;
}
int
__libc_system (const char *line)//这是system的原型, 如果具体架构层没有定义system
                                //, 那么glibc就用__libc_system来当作system
{
  if (line == NULL) //这里可以看出, 如果line为NULL, 那么返回0或者1, 具体0还是1就
                    //如manpage里面那句话所说:If the
                    //value of command is NULL, system() returns nonzero if the
                    //shell is available, and zero if not.
    /* Check that we have a command processor available.  It might
       not be available after a chroot(), for example.  */
    return do_system ("exit 0") == 0;
  return do_system (line);//否则, do_system
}
weak_alias (__libc_system, system)
通过上面的分析再看看manpage, 就应该懂的差不多了。
下面总结下system的返回值
- line为NULL 返回0或者1
- line不为NULL, 返回status(status具体多少看命令行的执行效果)
- -1 当fork失败的时候或则waitpid失败的时候一般通过WIFEXITED WIFSIGNALED WIFSTOPPED WIFCONTINUED来分析status和提取退出码或者信号值。
WIFEXITED是根据status的低7位是否为0来判别是否正常推出,
#define __WTERMSIG(status)  ((status) & 0x7f)
#define __WIFEXITED(status) (__WTERMSIG(status) == 0)
如果低7位为0, 则是正常推出,否则异常退出。
正常退出的退出码由WEXITSTATUS提取,
#define __WEXITSTATUS(status)   (((status) & 0xff00) >> 8)
WIFSIGNALED也是根据status的低7位来决定是否有信号异常发生,
#define __WIFSIGNALED(status) \
  (((signed char) (((status) & 0x7f) + 1) >> 1) > 0)
信号值由WTERMSIG提取,
#define __WTERMSIG(status)  ((status) & 0x7f)
这样就有问题了, 因为根据返回值1>里面是有可能返回1的,这种情况下会和收到信号且信号值
刚好为1的SIGHUP冲突, 因此我们在使用system的时候最好先判断命令行是否为NULL,也就是
if (!cmd)
    return ERR_INVALID_ARGV;
status = system(cmd);
if (status < 0)
      return xxx;
else if (WIFEXITED(status)) {
    printf("exited, status=%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
    printf("killed by signal %d\n", WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
    printf("stopped by signal %d\n", WSTOPSIG(status));
} else if (WIFCONTINUED(status)) {
    printf("continued\n");
}
其他的两个信号就暂时不分析了
完!
2014年1月
linux system函数分析的更多相关文章
- linux system函数引发的错误
		转: https://my.oschina.net/renhc/blog/54582 先看一下问题 简单封装了一下system()函数: int pox_system(const char *cm ... 
- Linux system函数返回值
		例: status = system("./test.sh"); 1.先统一两个说法: (1)system返回值:指调用system函数后的返回值,比如上例中status为syst ... 
- Linux system函数详解
		system 功能:system()函数调用"/bin/sh -c command"执行特定的命令,阻塞当前进程直到command命令执行完毕 原型 int system(cons ... 
- Linux system 函数的一些注意事项
		在日常的代码编程中 , 我们可以利用system 函数去调用一些我们自己想调用的命令 , 并获取他的返回值. 函数的原型如下: int system(const char *command); 上一 ... 
- linux system()函数详解
		system(3) - Linux man page Name system - execute a shell command Synopsis #include <stdlib.h> ... 
- 对于linux下system()函数的深度理解(整理)
		原谅: http://blog.sina.com.cn/s/blog_8043547601017qk0.html 这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同 ... 
- 转:对于linux下system()函数的深度理解(整理)
		这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同的system()函数,直接在shell下输入system()函数中调用的命令也都一切正常.就没理这个bug,以为 ... 
- 【C/C++】Linux下使用system()函数一定要谨慎
		[C/C++]Linux下使用system()函数一定要谨慎 http://my.oschina.net/renhc/blog/53580 曾经的曾经,被system()函数折磨过,之所以这样,是因为 ... 
- 【C/C++】Linux下system()函数引发的错误
		http://my.oschina.net/renhc/blog/54582 [C/C++]Linux下system()函数引发的错误 恋恋美食 恋恋美食 发布时间: 2012/04/21 11:3 ... 
随机推荐
- JMeter接口响应数据出现乱码的三种解决方法
			第一种方法: Content encoding设置为utf-8,若仍为乱码,请用方法2 图1 第二种方法: 修改bin文件夹下的jmeter.properties文件 搜索ISO,把“#sampler ... 
- Unity和Lua交互
			用lua就表示项目用到了热更新,通常每次热更新都会从服务器获取最新的lua脚本放到Android/ios设备的本地目录下,但是lua应该放到哪个目录下呢,这里就先说说lua里面的路径问题 1.不可以放 ... 
- 06-Mysql数据库----表的操作
			06-表的操作 本节掌握 存储引擎介绍(了解) 表的增删改查 一.存储引擎(了解) 前几节我们知道mysql中建立的库===>文件夹,库中的表====>文件 现实生活中我们用来存储数据 ... 
- python 网络篇(网络编程)
			一.楔子 你现在已经学会了写python代码,假如你写了两个python文件a.py和b.py,分别去运行,你就会发现,这两个python的文件分别运行的很好.但是如果这两个程序之间想要传递一个数据, ... 
- Java串口编程学习1-环境配置(64位Win7)
			最近在做zigbee的课程设计,需要Java实现对串口数据的读写操作. 网上找了很多代码,好像都比较过时了,直接拿来用没法跑通……QAQ……然后自己写个教程留底,如有不当之处还请各位路过的大神赐教. ... 
- [问题解决]Python locale error: unsupported locale setting
			原文来源:https://stackoverflow.com/questions/14547631/python-locale-error-unsupported-locale-setting 安装f ... 
- [译]10个有关SCP的命令
			原文来源: https://www.tecmint.com/scp-commands-examples/ 基本语法 scp source_file_name username@destination_ ... 
- doget,doPost在底层走的是service
			doget,doPost在底层走的是service 因为在源码上 先执行service方法 然后再调用doget,doPost方法 
- BZOJ 3876:支线剧情(有下界最小费用最大流)
			3876: [Ahoi2014]支线剧情 Description [故事背景]宅男JYY非常喜欢玩RPG游戏,比如仙剑,轩辕剑等等.不过JYY喜欢的并不是战斗场景,而是类似电视剧一般的充满恩怨情仇的剧 ... 
- JavaScript正则表达式大全
			一.校验数字的表达式 1 数字:^[0-9]*$ 2 n位的数字:^\d{n}$ 3 至少n位的数字:^\d{n,}$ 4 m-n位的数字:^\d{m,n}$ 5 零和非零开头的数字:^(0|[1-9 ... 
