2020年4月23日 星期四

Exit status of a child process in Linux

Source https://www.geeksforgeeks.org/exit-status-child-process-linux/

It is known that fork() system call is used to create a new process which becomes child of the caller process.
Upon exit, the child leaves an exit status that should be returned to the parent. So, when the child finishes it becomes a zombie.
Whenever the child exits or stops, the parent is sent a SIGCHLD signal.
The parent can use the system call wait() or waitpid() along with the macros WIFEXITED and WEXITSTATUS with it to learn about the status of its stopped child.
(*)wait() system call : It suspends execution of the calling process until one of its children terminates.
Syntax of wait() system call:
pid_t wait(int *status);
(*)The waitpid() system call : It suspends execution of the calling process until a child specified by pid argument has changed state.
Syntax of waitpid() system call :
pid_t waitpid(pid_t pid, int *status, int options)
Note: By default, waitpid() waits only for terminated children, but this behavior is modifiable via the options argument such as WIFEXITED, WEXITSTATUS etc.
The value of pid can be :
  1. Less than -1 : Meaning wait for any child process whose process group ID is equal to the absolute value of pid.
  2. Equal to -1 : Meaning wait for any child process.
  3. Equal to 0 : Meaning wait for any child process whose process group ID is equal to that of the calling process.
  4. Greater than 0 : Meaning wait for the child whose process ID is equal to the value of pid.
WIFEXITED and WEXITSTATUS are two of the options which can be used to know the exit status of the child.
WIFEXITED(status) : returns true if the child terminated normally.
WEXITSTATUS(status) : returns the exit status of the child. This macro should be employed only if WIFEXITED returned true.
Below is a C implementation in which child uses execl() function but the path specified to execl() is undefned.
Let us see what is the exit status value of the child that parent gets.
filter_none
brightness_4
// C code to find the exit status of child process
#include
#include
#include
#include
#include
  
// Driver code
int main(void)
{
    pid_t pid = fork();
      
    if ( pid == 0 )
    {
       /* The pathname of the file passed to execl()
          is not defined   */
       execl("/bin/sh", "bin/sh", "-c", "./nopath", "NULL");
    }
  
    int status;
      
    waitpid(pid, &status, 0);
  
    if ( WIFEXITED(status) )
    {
        int exit_status = WEXITSTATUS(status);        
        printf("Exit status of the child was %d\n", 
                                     exit_status);
    }
    return 0;
}
Output:

Note : Above code may not work with online compiler as fork() is disabled.
Here the exit status is 127 which indicates that there is some problem with path or there is a typo.
Few exit status codes are listed below for extra information :
  • 1 : Miscellaneous errors, such as “divide by zero” and other impermissible operations.
  • 2 : Missing keyword or command, or permission problem.
  • 126 : Permission problem or command is not an executable
  • 128 : invalid argument to exit.
Note : The C standard does not define the meaning of return codes. Rules for the use of return codes vary on different platforms.

Linux下使用system()函數一定要謹慎

Source

曾經的曾經,被system()函數折磨過,之所以這樣,是因為對system()函數了解不夠深入。只是簡單的知道用這個函數執行一個系統命令,這遠遠不夠,它的返回值、它所執行命令的返回值以及命令執行失敗原因如何定位,這才是重點。當初因為這個函數風險較多,故拋棄不用,改用其他的方法。這裡先不說我用了什麼方法,這裡必須要搞懂system()函數,因為還是有很多人用了system()函數,有時你不得不面對它。

先來看一下system()函數的簡單介紹:
#include 
int system(const char *command);
system()通過調用/ bin / sh -c命令執行命令中指定的命令,並在命令完成後返回。在執行命令期間,SIGCHLD將被阻止,並且SIGINT和SIGQUIT將被忽略。
system()函數調用/bin/sh來執行參數指定的命令,/bin/sh 一般是一個軟連接,指向某個具體的shell,比如bash,-c選項是告訴shell從字符串command中讀取命令;
在該command執行期間,SIGCHLD是被阻塞的,好比在說:hi,內核,這會不要給我送SIGCHLD信號,等我忙完再說;
在該command執行期間,SIGINT和SIGQUIT是被忽略的,意思是進程收到這兩個信號後沒有任何動作。

再來看一下system()函數返回值:
錯誤返回的值為-1(例如fork(2)失敗),否則返回命令的返回狀態。後一個返回狀態採用wait(2)中指定的格式。因此,該命令的退出代碼將為WEXITSTATUS(status)。如果/ bin / sh無法執行,則退出狀態將是退出(127)的命令的狀態。
如果command的值為NULL,則如果shell可用,則system()返回非零,否則返回零。
為了更好的理解system()函數返回值,需要了解其執行過程,實際上system()函數執行了三步操作:
1.fork一個子進程;
2.在子進程中調用exec函數去執行command;
3.在父進程中調用wait去等待子進程結束。
對於fork失敗,system()函數返回-1。
如果exec執行成功,也即command順利執行完畢,則返回command通過exit或return返回的值。
(注意,command順利執行不代表執行成功,比如command:"rm debuglog.txt",不管文件存不存在該command都順利執行了)
如果exec執行失敗,也即command沒有順利執行,比如被信號中斷,或者command命令根本不存在,system()函數返回127.
如果command為NULL,則system()函數返回非0值,一般為1.

看一下system()函數的源碼
看完這些,我想肯定有人對system()函數返回值還是不清楚,看源碼最清楚,下面給出一個system()函數的實現:
int system(const char * cmdstring)
{
    pid_t pid;
    int status;

if(cmdstring == NULL)
{
    return (1); //如果cmdstring为空,返回非零值,一般为1
}

if((pid = fork())<0)
{
    status = -1; //fork失败,返回-1
}
else if(pid == 0)
{
    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
    _exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~
}
else //父进程
{
    while(waitpid(pid, &status, 0) < 0)
    {
        if(errno != EINTR)
        {
            status = -1; //如果waitpid被信号中断,则返回-1
            break;
        }
    }
}

    return status; //如果waitpid成功,则返回子进程的返回状态
}
仔細看完這個system()函數的簡單實現,那麼該函數的返回值就清晰了吧,那麼什麼時候system()函數返回0呢?只在command命令返回0時。

看一下該怎麼監控system()函數執行狀態
這裡給我出的做法:
int status;
if(NULL == cmdstring) //如果cmdstring为空趁早闪退吧,尽管system()函数也能处理空指针
{
    return XXX;
}
status = system(cmdstring);
if(status < 0)
{
    printf("cmd: %s\t error: %s", cmdstring, strerror(errno)); // 这里务必要把errno信息输出或记入Log
    return XXX;
}

if(WIFEXITED(status))
{
    printf("normal termination, exit status = %d\n", WEXITSTATUS(status)); //取得cmdstring执行结果
}
else if(WIFSIGNALED(status))
{
    printf("abnormal termination,signal number =%d\n", WTERMSIG(status)); //如果cmdstring被信号中断,取得信号值
}
else if(WIFSTOPPED(status))
{
    printf("process stopped, signal number =%d\n", WSTOPSIG(status)); //如果cmdstring被信号暂停执行,取得信号值
}
到於取得子進程返回值的相關介紹可以參考另一篇文章:http://my.oschina.net/renhc/blog/35116

system()函數用起來很容易出錯,返回值太多,而且返回值很容易跟command的返回值混淆。這裡推薦使用popen()函數替代,關於popen()函數的簡單使用也可以通過上面的鏈接查看。
popen()函數較於system()函數的優勢在於使用簡單,popen()函數只返回兩個值:
成功返回子進程的status,使用WIFEXITED相關宏就可以取得command的返回結果;
失敗返回-1,我們可以使用perro()函數或strerror()函數得到有用的錯誤信息。
這篇文章只涉及了system()函數的簡單使用,還沒有談及SIGCHLD、SIGINT和SIGQUIT對system()函數的影響,事實上,之所以今天寫這篇文章,是因為項目中因有人使用了system()函數而造成了很嚴重的事故。現像是system()函數執行時會產生一個錯誤:“ No child processes ”。
關於這個錯誤的分析,感興趣的朋友可以看一下:http://my.oschina.net/renhc/blog/54582