libc system函数的探究

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/theArcticOcean/article/details/81268162

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

猜你喜欢

转载自blog.csdn.net/theArcticOcean/article/details/81268162