首頁 > 軟體

進程管理之system 詳解

2020-06-16 17:08:37

system定義

#include<stdlib.h>
int system(const char *command);

首先要知道,system函數是c庫中的函數,而不是系統呼叫。其實system函數使用起來並不複雜,難就難在對其返回值的理解。這個問題,下文會詳細分析。引數的話,很簡單,就是終端的命令即可。這是因為system函數的實現中呼叫了shell的緣故。

system優缺點

  • 優點:可以讓c程式猿很方便地呼叫其他語言編寫的程式,當然呼叫c程式自然也沒問題;
  • 缺點:第一:效率低,第二:返回值難理解;
    效率低是因為system函數的實現過程至少要建立兩個進程,一個是shell進程,還有一個或著多個shell命令執行的進程。所以在對效率高有要求的程式中,直接用fork和exec函數族比較合適;返回值的問題下文會講;

system函數返回值

先寫個簡化版的system函數的實現過程。簡化是沒有考慮處理信號的問題。程式碼如下:

#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>

int system(char * command)
{
    int status;
    pid_t child;
    
    swicth(child = fork())
    {
        case -1:
            return -1;
        case 0:
            execl("/bin/sh","sh","-c",command,NULL);
            _exit(127);
        default:
            while(waitpid(child,&status,0)<0)
            {
                if(errno != EINTR)
                {
                    status = -1;
                    break;
                }
            }
            return status;
    }
}

1)返回值為 “0” 或 “1”
這中情況一般不會出現,只有當寫成system(NULL)時,才會出現這種結果。目的是為了檢測系統的shell是否可用。返回1表示shell可用,返回0表示shell不可用;這種情況在上訴程式碼中看不出來,通過glibc庫中的原始碼可以看出:

glibc-2.17/sysdeps/posix/system.c
int __libc_system(const char *line)
{
    if(line == NULL)
    return do_system("exit 0") == 0;
    ......
}

line指標指向的就是command命令列引數,system函數呼叫do_system系統呼叫,當執行“exit 0”(表示結束當前shell),執行成功do_system返回0,說明shell可用。反之,shell不可用。

2)返回值為 -1
有兩個原因造成這樣的結果。第一,因為fork建立子進程失敗導致的。即“case -1”的情況,這中情況比較少見;第二,是不能正常處理子進程的“墓誌銘”導致的,說白了,就是子進程的進程表在子進程結束時,沒有經過父進程處理,而自己銷毀了。這樣的效果是因為父進程中安插的處理SIGCHLD信號的處理常式是SIG_IGN,或者使用者設定了SA_NOCLDWAIT標誌位導致,waitpid函數返回值為-1且全域性變數errno的值為ECHLD;
例如:

signal(SIGCHLD,SIG_IGN); //出錯的根源

if( (status = system(command)) < 0 )
{
    fprintf(stderr,"system return %d (%s)n",status,strerror(errno));
    return -2;
}

所以在使用system函數時,一定要判斷SIGCHLD是否被設定為SIG_IGN。

3)返回值為 _exit(127)的返回值
這種情況是因為程式執行到“case 0”中,execl函數執行失敗後,執行_exit函數導致的。說明子進程無法執行shell該shell命令。

4)返回值為shell執行的進程的返回值
shell的終止狀態是其執行最後一條命令的退出狀態。這種情況下和獲取子進程的退出狀態一樣。通過如下宏獲取其退出狀態:

  • WIFEXITED(status)
  • WEXITSATUS(status)
  • WIFSIGNALED(status)
  • WTERMSIG(status)
  • WCOREDUMP(satus)

綜上所述,可以給出一個system函數返回值判斷的例程:

if( (status == system(command))==-1 )
{
    fprintf(stderr,"system() function return -1 (%s)n",strerror(errno));
}
else if(WIFEXITED(status)&&WEXITSTATUS(status) == 127)
{
    fprintf(stderr,"cannot invoke shell to exec command (%s)n",strerror(enrrno));
}
else
    print_wait_exit(status);

print_wait_exit函數就是上文中提到的獲取shell退出狀態的宏,根據需要去實現即可。

system函數和信號

影響system函數執行的信號有三個:SIGCHLD、SIGINT和SIGQUIT信號。

SIGCHLD信號:上文已經提過,它會影響waitpid函數,產生競爭現象的出現。呼叫system函數的進程可能還有其他的子進程,當然同樣也會有wait或waitpid函數。當有SIGCHLD信號到來時,system函數中的waitpid函數和其他的wait或waitpid函數產生競爭狀態,從而使得system執行異常。所以system執行期間會暫時遮蔽SIGCHLD信號;

SIGINT信號和SIGQUIT信號:呼叫system函數的進程會遮蔽這兩個信號。system函數建立的進程會恢復這兩個信號的預設處理狀態。呼叫system函數的進程其實已經放棄了控制權,所以不能夠去終止進程。反之,system函數建立的進程當然就有了控制權,所以要恢復控制權。

本文永久更新連結地址http://www.linuxidc.com/Linux/2017-09/146735.htm


IT145.com E-mail:sddin#qq.com