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查看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章