system系统调用探秘

6月3日更新

新的实验又发现使用/bin/sh和书中行为一致,但使用/bin/bash的行为和本文中的实验一致,看来是不同shell的底层的实现方式有差异。而之所以之前用/bin/sh做的实验和书中行为不一致,是因为在我做实验的机器上,/bin/sh其实是一个指向/bin/bash的软链接。。。

不过至少得到一个重要结论,那就是:对于不同的底层shell,system系统调用的表现会不同。这一点在编码时需要特别注意。

system实现原理

system这个系统调用的源码在网上已经有很多了,这里就不展示了。简单来说,就是父进程fork后,在子进程中通过执行execl("/bin/sh", "sh", "-c", cmdstring, (char *)0),使得/bin/sh成为新的子进程,然后在/bin/sh中执行cmdstring命令;父进程循环执行waitpid,等待子进程退出的信号。

到底有几个子进程?

实验一

在学习《UNIX环境高级编程(第3版)》信号一章时,根据图10-27所示,执行 system("/bin/ed") 命令后,会分别调用fork/exec系统调用两次:

  1. 第一次发生在调用system时,父进程fork一次,子进程执行execl("/bin/sh","sh","-c","/bin/ed",(char *)0)一次,子进程被替换为/bin/sh
  2. 第二次发生在/bin/sh这个子进程中,/bin/sh会先fork一个子进程,这个子进程执行exec("/bin/ed"),用/bin/ed替换/bin/sh

但是我在自己做实验时,用strace命令跟踪系统调用的过程,发现system系统调用执行过程中,fork了一次,exec了两次,主要的差异在于/bin/sh并没有fork子进程,而是直接执行了exec("/bin/ed")

实验二

我在shell下执行sh -c "sleep 5"&命令,根据书中的示例,执行ps -f后应该可以看到4个进程:

  • ps -f
  • 当前shell进程
  • 当前shell的子进程sh
  • sh的子进程sleep 5

但实际我只看到三个进程,缺少子进程shsleep 5直接成为了当前shell的子进程

1Storage:~ # sh -c "sleep 5" &
2[1] 101978
3Storage:~ # ps -o pid,ppid,cmd
4PID PPID CMD
548673 48658 -bash
6101978 48673 sleep 5
7103012 48673 ps -o pid,ppid,cmd

system的返回值到底是多少?

使用如下程序对system的返回值进行实验:

 1#include <stdio.h>
 2#include <stdlib.h>
 3void main ()
 4{
 5    int iStatus;
 6    iStatus = system("sleep 5");
 7    if (WIFEXITED(iStatus))
 8    {
 9        printf("normal exit code %d ,status %x\n",WEXITSTATUS(iStatus),iStatus);
10    }
11
12    if (WIFSIGNALED(iStatus))
13    {
14        printf("signal code %d ,status %x\n",WTERMSIG(iStatus),iStatus);
15    }
16}

在这个实验程序中,通过system系统调用执行sleep 5,sleep期间通过ctrl+C向main进程发送SIGINT信号,观察会打印出什么。

按照书中的实验结果,是会打印"normal exit…“的,原因分析:

  1. 收到SIGINT信号的是sleep 5进程,sleep 5进程异常退出时,它的退出值为2;
  2. sleep 5的父进程是/bin/sh,而shell会将子进程的退出码(此处为2)+128作为退出值正常退出(低8位全0)
  3. 所以父进程main通过waitpid得到/bin/sh的退出码为130,认为是正常执行退出(低8位全0)。

然而我的实验结果却是打印"signal code 2”。原因如下:

实际上sleep 2是main函数的子进程,所以,它收到信号退出,main函数通过waitpid得到的子进程退出的状态码就是2了。

waitpid得到的状态码低7位代表信号值,第8位代表是否core,高8位代表exit退出码。由此可见信号最多127种,exit最大值为255

通过WIFEXITED宏判断低七位的信号值是否为0,0为正常退出;通过WEXITSTATUS得到高8位的exit值;否则通过WIFSIGNALED得到低七位的信号值。