进程创建大盘点

进程创建回顾

通过 fork() 创建子进程,然后通过 execve(...) 将子进程的进程空间替代为 path 所指定程序的进程空间,随后执行 path 所指定的程序

问题

进程创建是否只能依赖于 fork() 和 execve(...) ?

再轮进程创建

fork() 通过完整复制当前进程的方式创建新进程,execve() 根据参数覆盖进程数据 (一个不留)

我们在 fork() 之后,立即执行 execve() 的话,fork() 复制父进程进程空间的操作是多余的,因为 execve() 会覆盖复制出来的父进程进程空间

pid_t vfork(void);

vfork() 用于创建子进程,然而不会复制父进程中的数据

vfork() 创建出的子进程直接使用父进程空间 (没有完整独立的进程空间)

vfork() 创建的子进程对数据 (变量) 的修改会直接反馈到父进程中

vfork() 是为了 execve() 系统调用而设计

下面的程序运行后会发生什么?


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char* argv[])
{   
    pid_t pid = 0;
    int var = 88;
    
    printf("parent = %d\n", getpid());
    
    if( (pid = vfork()) < 0 )
    {
        printf("vfork error\n");
    }
    else if( pid == 0 )
    {
        printf("pid = %d, var = %d\n", getpid(), var);
        var++;
        printf("pid = %d, var = %d\n", getpid(), var);
		
        return 0; /* destroy parent stack frame */
    }
    
    printf("parent = %d, var = %d\n", getpid(), var);
    
    return 0;
}

 程序运行结果如下图所示:

程序出现了段错误,崩溃了

程序崩溃是因为 vfork() 后,子进程和父进程共享同一个进程空间,第 26 行,子进程 return 0 之后,会从创建点返回,破坏栈结构,使得父进程的栈空间被破坏掉,从而导致程序崩溃

vfork() 深度解析

vfork() 使得子进程共享父进程的代码和数据

vfork() 要点分析

vfork() 成功后,父进程将等待子进程结束

子进程可以使用父进程的数据 (堆,栈,全局)

子进程可以从创建点调用其他函数,但不要从创建点返回

  • 当 子进程执行流 回到创建点 / 需要结束 时,使用 _exit(0) 系统调用
  • 如果使用 return 0 那么将破坏栈结构,导致后续父进程执行出错

vfork() 的正确使用


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char* argv[])
{   
    pid_t pid = 0;
    int var = 88;
    
    printf("parent = %d\n", getpid());
    
    if( (pid = vfork()) < 0 )
    {
        printf("vfork error\n");
    }
    else if( pid == 0 )
    {
        printf("pid = %d, var = %d\n", getpid(), var);
        var++;
        printf("pid = %d, var = %d\n", getpid(), var);
		
        // return 0; /* destroy parent stack frame */
		_exit(0);
    }
    
    printf("parent = %d, var = %d\n", getpid(), var);
    
    return 0;
}

vfork() 后,子进程共享父进程的地址空间,父进程会等待子进程运行结束后,再向下运行

第 23 行,将 var 的值加一,此时 var = 89,这个变量的改动会影响到父进程,所以父进程中的 var = 89

第 27 行,_exit(0),使得子进程结束运行,并且不会从创建点返回

程序运行结果如下图所示:

vfork() 虽然可以节约拷贝父进程进程空间的时间,但需要谨慎使用,使用不当会造成程序崩溃

当今的 fork() 实现了读时共享,写时复制的功能,所以如果在 fork() 之后马上调用 execve() 也不会多一次拷贝父进程进程空间的时间,此时的效率是和 vfork() 一样的,这种情况下可以用 fork() 取代 vfork(),程序也会更加安全

fork() 的现代化技术

Copy-on-Write

  • 多个任务访问同一资源,在写入操作修改资源时,复制资源的原始副本

fork() 引入 Copy-on-Write 之后,父子进程共享相同的地址空间

  • 当父进程或子进程的其中之一修改内存数据,则实时复制进程空间
  • fork() + execve()  <=>  vfork() + execve()

exec 家族函数

 

create_process 的功能增强

hellocworld.c

#include <stdio.h>

int main()
{
    printf("Hello World!\n");
    return 0;
}

main.c


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

int create_process(char* path, char* const args[], char* const env[], int wait)
{
    int ret = fork();
    
    if( ret == 0 )
    {
        if( execve(path, args, env) == -1 )
        {
            exit(-1);
        }
    }
    
    if( wait && ret )
    {
        waitpid(ret, &ret, 0);
    }
    
    return ret;
}

int main(int argc, char* argv[])
{
    char* target = argv[1];
    char* const ps_argv[] = {target, NULL};
    char* const ps_envp[] = {"PATH=/bin:/usr/bin", "TEST=Delphi", NULL};
    
    int result = 0;
    
    if( argc < 2 ) exit(-1);
    
    printf("current: %d\n", getpid());
    
    result = create_process(target, ps_argv, ps_envp, 1);
    
    printf("result = %d\n", result);
    
    return 0;
}

create_process() 函数新增一个参数来决定父进程是否等待子进程运行结束

第 40 行,传入的 wait 参数为 1,父进程会等待子进程运行结束

程序运行结果如下图所示:

进程创建库函数

#include <stdlib.h>

int system(const char* command);

  • 参数 -- 程序名及进程参数 (如:pstree -A -p -s $$)
  • 返回值 -- 进程退出状态值

system() 函数首先会调用 fork() 函数,父进程调用 waitpid(...) 等待子进程结束,子进程通过 execve(...) 来执行 shell 程序,shell 会解析要执行的命令,来运行程序或者脚本 

create_process(...) 和 system(...) 的对比

helloworld.c

#include <stdio.h>

int main()
{
    printf("Hello World!\n");
    return 0;
}

test.sh

echo "Hello world from shell ..."
a=1
b=1
c=$(($a+$b))
echo "c = $c"

main.cpp


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

int create_process(char* path, char* const args[], char* const env[], int wait)
{
    int ret = fork();
    
    if( ret == 0 )
    {
        if( execve(path, args, env) == -1 )
        {
            exit(-1);
        }
    }
    
    if( wait && ret )
    {
        waitpid(ret, &ret, 0);
    }
    
    return ret;
}

int main(int argc, char* argv[])
{
    char* target = argv[1];
    char* const ps_argv[] = {target, NULL};
    char* const ps_envp[] = {"PATH=/bin:/usr/bin", "TEST=Delphi", NULL};
    
    int result = 0;
    
    if( argc < 2 ) exit(-1);
    
    printf("current: %d\n", getpid());
    
	result = system(target);
    // result = create_process(target, ps_argv, ps_envp, 1);
    
    printf("result = %d\n", result);
    
    return 0;
}

我们使用分别使用 system(...) 函数和 create_process(...) 来运行 helloworld 程序 和 test.sh 脚本

使用 system(...) 函数:

使用 create_process(...) 函数:

通过打印可以看出,我们自己封装的 create_process(...) 函数只能运行可执行程序,而运行不了脚本;而系统库函数 system(...) 既可以运行可执行程序又可以运行脚本,这是因为 system(...) 函数会运行一个 shell 来执行命令

我们如果只想运行可执行程序,可以使用 create_process(...) 函数,这样的效率会更高,因为 system(...) 首先会运行 shell 程序,然后再通过 shell 来执行命令

想要运行脚本,就只能使用 system(...) 函数 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值