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
系统调用两次:
- 第一次发生在调用
system
时,父进程fork
一次,子进程执行execl("/bin/sh","sh","-c","/bin/ed",(char *)0)
一次,子进程被替换为/bin/sh
。 - 第二次发生在
/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
;
但实际我只看到三个进程,缺少子进程sh
,sleep 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…“的,原因分析:
- 收到
SIGINT
信号的是sleep 5
进程,sleep 5
进程异常退出时,它的退出值为2; - 但
sleep 5
的父进程是/bin/sh
,而shell会将子进程的退出码(此处为2)+128作为退出值正常退出(低8位全0)。 - 所以父进程
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
得到低七位的信号值。