1. 程式人生 > >libc system函式的探究

libc system函式的探究

system導致父程序等待

在mac上的線上幫助有對system的如下說明:

The system() function hands the argument command to the command interpreter
sh(1). The calling process waits for the shell to finish executing the com-
mand, ignoring SIGINT and SIGQUIT, and blocking SIGCHLD.

system的工作大致是這樣的:
父程序fork子程序
父程序等待子程序
在子程序中執行字串所表述的命令
返回執行結果。

原始碼:

int system(const char * cmdstring)
{
    pid_t pid;
    int status;
    if( cmdstring == NULL )
    { 
        return 1;
    }
    if( (pid = fork()) < 0 )
    {
        status = -1;
    }
    else if( pid == 0 )
    {
        // execl create new process to replace old process
        execl("/bin/sh"
, "sh", "-c", cmdstring, (char *)0); // if above statement execute successfully, the following function would not work. _exit(127); } else { while( waitpid(pid, &status, 0) < 0 ) { if( errno != EINTR ) { status = -1
; break; } } } return status; }

在執行命令期間,他會忽略一些訊號:SIGINT,SIGQUIT,並且阻塞SIGCHLD
我很好奇, 說明中的wait是不是掛起之意(程序掛起:程序被移除記憶體之外,暫時儲存在外存)。於是實驗了一番。
test.c

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

void* run( void *arg )
{
    while(1)
    {
        printf("test main is running.\n");
    }
}

int main(int argc, char *argv[]) {
    int ret;
    pthread_t tid;

    ret = pthread_create( &tid, NULL, run, NULL );
    if( ret != 0 )
    {
        perror("pthread_create: ");
        return 1;
    }
    system( "./run.sh" );
    pthread_join( tid, NULL );
    return 0;
}

run.sh

#! /bin/bash

while true; do
    echo "shell script is running"
done

標準輸出的結果是這樣的:

test main is running.
test main is running.
test main is running.
shell script is running
test main is running.
test main is running.
test main is running.
shell script is running
test main is running.
test main is running.
test main is running.
...

這樣看來,父程序仍然正常工作。
但如果父程序所有的工作任務全部放在一個執行緒中,那麼就有可能出現使用system後進程掛起的假象。
比如這樣:

int main(int argc, char *argv[]) {
    int ret;
    scanf("%d", &ret);
    system( "./run.sh > ~/code/txt" );
    write( STDOUT_FILENO, "finish\n", 7 );
    return 0;
}

這樣的話,除非主動kill程序,否則finish不會輸出。
那我們將system的任務放到一個獨立的執行緒中,這樣就可以使得兩邊同時工作。
比如這樣:

void* run( void *arg )
{
    system( "./run.sh > ~/code/txt" );
}

int main(int argc, char *argv[]) {
    int ret;
    pthread_t tid;

    ret = pthread_create( &tid, NULL, run, NULL );
    if( ret != 0 )
    {
        perror("pthread_create: ");
        return 1;
    }
    scanf("%d", &ret);
    write( STDOUT_FILENO, "finish\n", 7 );
    pthread_join( tid, NULL );
    return 0;
}

當system子程序變成孤兒程序

在mac上,一旦父程序退出,子程序變成孤兒程序,隨即被/sbin/launchd收養。

實驗程式碼:
➜ code cat run.sh

#! /bin/bash

while true; do
    echo "shell script is running"
    done

➜ code cat test.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    system( "./run.sh" );
    return 0;
}
➜ MacOS git:(master) ✗ pstree -p 8445
-+= 00001 root /sbin/launchd
 \-+= 02083 weiyang /Applications/iTerm.app/Contents/MacOS/iTerm2
   \-+= 08323 weiyang /Applications/iTerm.app/Contents/MacOS/iTerm2 --server login -fp wei
     \-+= 08324 root login -fp weiyang
       \-+= 08325 weiyang -zsh
         \-+= 08442 weiyang ./a.out
           \--- 08445 weiyang /bin/bash ./run.sh
➜ MacOS git:(master) ✗ kill 8442
➜ MacOS git:(master) ✗ pstree -p 8445
-+= 00001 root /sbin/launchd
 \--- 08445 weiyang /bin/bash ./run.sh

使得父程序退出後,system子程序也結束

通過重寫system函式,我們可以得到子程序的程序號。在父程序退出的時候,也令子程序退出。

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>

int system2( const char * cmdstring, int * _pid )
{
    pid_t pid;
    *_pid = 0;
    int status;
    if( cmdstring == NULL )
    {
        return 1;
    }
    if( ( pid = fork() ) < 0 )
    {
        status = -1;
    }
    else if( pid == 0 )
    {
        // execl create new process to replace old process
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        // if above statement execute successfully, the following function would not work.
        _exit(127);
    }
    else
    {
        *_pid = pid;
        // For waitpid return value, -1 means error, 0 means no exited child.
        // Positive interger means child PID.
        while( waitpid(pid, &status, 0) < 0 )
        {
            // EINTR means interrupt signal, operate failed. Please try again.
            if( errno != EINTR ) 
            {
                status = -1;
                break;
            }
        }
    }
    *_pid = 0;
    return status;
}

int pid;

void sigCapture( int sig )
{
    printf( "sig is %d, child pid is %d\n", sig, pid );
    if( pid > 0 && -1 == kill( pid, SIGKILL ) )
    {
        perror( "kill child process: " );
    }
}

int main( int argc, char *argv[] ) {
    int ret;
    signal( SIGTERM, sigCapture );
    ret = system2( "./run.sh", &pid );
    // Deal with return value.
    if (-1 == ret)
    {
        printf("system error!");
    }
    else
    {
        printf("ret is not -1, exit ret value = 0x%x\n", ret); //0x7f00

        // call shell script and finish successfully.
        if (WIFEXITED(ret)) // WIFEXITED is a macro
        {
            if (0 == WEXITSTATUS(ret)) // return value of shell script run.
            {
                printf("run shell script successfully.");
            }
            // 0x7f = 127
            else
            {
                printf("run shell script fail, script exit code: %d, reason: %s\n", WEXITSTATUS(ret), strerror(errno));
            }
        }
        else
        {
            printf("exit ret = %d\n", WEXITSTATUS(ret));
        }
    }
    return 0;
}

command:

➜ code ps aux |grep run.sh
weiyang 13681 93.0 0.0 4279596 1232 s003 S+ 9:46AM 0:01.86 /bin/bash ./run.sh
weiyang 13684 0.0 0.0 4268776 668 s002 R+ 9:46AM 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn run.sh
➜ code ps aux |grep a.out
weiyang 13690 0.0 0.0 4267752 748 s002 R+ 9:46AM 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn a.out
weiyang 13680 0.0 0.0 4268752 812 s003 S+ 9:46AM 0:00.01 ./a.out
➜ code kill 13680
➜ code ps aux |grep run.sh
weiyang 13704 0.0 0.0 4267752 884 s002 S+ 9:46AM 0:00.01 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn run.sh

result:

shell script is running
shell script is running
shell script is running
shell script is running
sig is 15, child pid is 13681
shell script is running
shell script is running
ret is not -1, exit ret value = 0x9
exit ret = 0

注: 使用kill傳送訊號,格式為 ps -signal_number pid,如果不指定訊號編碼,預設的訊號是15 SIGTERM。各類訊號編碼可以使用kill -l檢視。