linux进程的创建

Linux进程

Linux进程管理是Linux操作系统中一个重要的概念,涉及到进程的创建、调度、同步、通信等多个方面。

Linux进程是操作系统进行资源分配和调度的基本单位。每个进程都有自己的独立内存空间、数据、代码和状态。

进程的基本概念

  • 进程定义 :进程是正在运行的程序的实例,包括代码、数据和状态信息。
  • 进程与程序的区别 :程序是存储在磁盘上的指令集合,而进程是程序在处理器上执行时的实体。
  • 进程的状态 :新建、就绪、运行、等待、终止。

进程的创建

  • 系统调用fork() :创建一个新的进程,通常用于进程克隆。
  • 系统调用vfork() :快速创建一个新进程,用于避免复制父进程的数据。
  • 系统调用exec() :在当前进程的上下文中加载一个新程序。
fork()

在Linux中,创建进程最常见的是使用 fork()系统调用,它会创建一个与当前进程几乎完全相同的副本,这个副本称为子进程。子进程开始时会执行 fork()调用之后的代码。

fork()是一个基本的系统调用,用于创建一个新的进程,称为子进程。当一个进程调用 fork()时,它会返回两次:一次在父进程中,返回子进程的PID;一次在子进程中,返回0。子进程是父进程的精确副本,包括内存映像、文件描述符、环境变量等。子进程从 fork()调用处开始执行代码。

fork()的典型用途包括:

  • 创建并行执行的任务。
  • 在父进程和子进程之间建立通信机制,如管道。
  • 利用子进程来执行不同的程序或任务。
vfork()

vfork()是一个在Unix和类Unix操作系统中用于创建进程的系统调用。与 fork()系统调用相比,vfork()在创建子进程时的行为略有不同,特别是在子进程执行前,它会暂停父进程的执行,并且子进程会继承父进程的数据和堆栈内存,这使得 vfork()在某些情况下比 fork()更高效。

需要注意的是,由于 vfork()会复制父进程的地址空间给子进程,包括堆栈和数据段,因此在使用 vfork()时需要特别小心。在子进程中不应该执行任何可能改变父进程地址空间的操作,如写入父进程的堆栈或数据段。一旦子进程执行完任务并退出,父进程的地址空间会恢复到 vfork()调用前的状态。

exec()

exec()系列函数用于在当前进程的上下文中加载并运行一个新的程序。当一个进程调用 exec()时,它会被新程序替换掉,原有的内存空间、代码和数据被新程序的内容覆盖。exec()不会创建新的进程,而是替换当前进程的映像。也就是说,当一个进程调用 exec函数时,它放弃自己的代码、数据和堆栈,取而代之的是新程序的代码和数据。这意味着,一旦 exec调用成功,原来的进程就不再存在,取而代之的是新加载的程序。

exec函数的主要作用是在当前进程的基础上加载并运行一个新的程序,这样可以避免创建新进程的开销,同时还能保持现有的进程ID(PID)和环境信息。这种方式在进程需要改变其执行的程序时非常有用,例如在脚本中启动不同的程序,或者在程序中动态加载新的功能模块。在 exec调用之后,新程序会从其主函数(main)开始执行,就像它是一个全新的进程一样,执行完后不会返回调用 exec语句之后的地方。如果 exec调用失败,当前进程保持不变,并返回错误指示,返回到调用 exec语句之后的地方。

exec()的典型用途包括:

  • 运行不同的程序或命令。
  • 改变当前进程的执行流。
  • 动态链接程序,加载共享库或模块。

exec()系列函数包括:

  • execl():加载程序,但不提供命令行参数。
  • execle():加载程序,清除环境变量。
  • execlp():搜索PATH环境变量指定的目录,加载程序。
  • execv():使用给定的文件描述符数组作为文件描述符。
  • execve():提供完整的文件描述符表和环境变量。
fork()exec()的结合使用

fork()exec()经常结合使用来创建新的进程并运行不同的程序。典型的步骤如下:

  1. 父进程调用 fork()创建子进程。
  2. 子进程调用 exec()加载并运行新的程序。
  3. 父进程可以继续执行其他任务,或者等待子进程完成。

这种结合使用允许父进程创建多个子进程,每个子进程可以执行不同的任务或程序,从而实现并行处理和复杂的程序逻辑。同时,父进程可以通过子进程的PID来管理和监控它们的执行状态。

进程创建的实验:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> // 用于waitpid等函数

int test_fork() {
    pid_t pid_tmp;
    pid_t pid_child;

    printf("%s() enter\n", __func__);
    pid_tmp = getpid(); // 获取当前进程的PID
    printf("[%s: %d]: pid_tmp = %d\n", __func__, __LINE__, pid_tmp);

    // 调用fork()
    pid_child = fork();

    if (pid_child < 0) {
        // fork失败
        printf("fork failed");
    } else if (pid_child == 0) {
        pid_tmp = getpid(); // 获取当前进程的PID
        printf("[%s: %d]: pid_tmp = %d\n", __func__, __LINE__, pid_tmp);
        // 子进程
        // 调用execlp()执行'ps'命令
        // execlp()会替换当前进程的映像,包括代码、数据和堆栈
        // 第一个参数ps是程序名称,后续参数是传递给程序的参数
        // 第二个参数ps是传递给ps命令的第一个参数,通常它是命令的名称
        // 第三个参数-a是传递给ps命令的第一个参数,如果ps有多个参数可以继续往后加如"-l"
        // (char *)NULL: 这是execlp调用的最后一个参数,它是一个指向NULL的指针,表示参数列表的结束。在C语言中,字符串数组或参数列表通常以NULL结尾
        // 如果exec成功,程序不会返回,如果失败,返回-a
        execlp("ps", "ps", "-a", "-l", (char *)NULL);

        // 如果exec失败,打印错误并退出
        printf("execlp failed\n");
    } else {
        pid_tmp = getpid(); // 获取当前进程的PID
        printf("[%s: %d]: pid_tmp = %d, pid_child=%d\n", __func__, __LINE__, pid_tmp, pid_child);
        // 父进程
        // 等待子进程结束
        int status;
        waitpid(pid_child, &status, 0); // waitpid()等待特定的子进程pid结束
        if (WIFEXITED(status)) { // 如果子进程正常退出,则返回非零值
            printf("Child process exited with success,  WEXITSTATUS(status)=%d, status=%d\n", WEXITSTATUS(status), status);
        } else {
            printf("Child process exited with fail,  WEXITSTATUS(status)=%d, status=%d\n", WEXITSTATUS(status), status);
        }
    }
    printf("%s() exit\n\n", __func__);

    return 0;
}

int test_vfork() {
    pid_t pid;

    printf("%s() enter\n", __func__);
    // 调用vfork()创建子进程
    pid = vfork();

    if (pid == -1) {
        // vfork()失败,输出错误信息并退出
        perror("vfork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        sleep(2);   // 为了更好的验证父进程会等待子进程结束
        // 子进程的代码
        printf("I am the child process with PID: %d\n", getpid());

        // 执行完子进程的任务后退出
        exit(EXIT_SUCCESS);
    } else {
        // 父进程的代码
        printf("I am the parent process with PID: %d\n", getpid());

        // 等待子进程结束
        int status;
        waitpid(pid, &status, 0);

        // 检查子进程是否成功退出
        if (WIFEXITED(status)) {
            int exit_status = WEXITSTATUS(status);
            printf("Child exited with status %d\n", exit_status);
        }

        // 继续父进程的任务
    }
    printf("%s() exit\n\n", __func__);
    return 0;
}


int test_exec() {
    pid_t pid_tmp;
    pid_t pid_child;

    printf("%s() enter\n", __func__);
    pid_tmp = getpid(); // 获取当前进程的PID
    printf("[%s: %d]: pid_tmp = %d\n", __func__, __LINE__, pid_tmp);

    // 调用fork()
    pid_child = fork();

    if (pid_child < 0) {
        // fork失败
        printf("fork failed");
    } else if (pid_child == 0) {
        pid_tmp = getpid(); // 获取当前进程的PID
        printf("[%s: %d]: pid_tmp = %d\n", __func__, __LINE__, pid_tmp);
        // 子进程
        // 调用execlp()执行'psxx'命令,使其调用失败,原有的内存空间不会被覆盖
        // 如果exec成功,程序不会返回,如果失败,返回-a
        execlp("psxx", "psxx", "-a", "-l", (char *)NULL);

        // 如果exec失败,打印错误并退出
        printf("execlp failed\n");
        pid_tmp = getpid(); // 获取当前进程的PID
        printf("[%s: %d]: pid_tmp = %d\n", __func__, __LINE__, pid_tmp);
    } else {
        pid_tmp = getpid(); // 获取当前进程的PID
        printf("[%s: %d]: pid_tmp = %d, pid_child=%d\n", __func__, __LINE__, pid_tmp, pid_child);
        // 父进程
        // 等待子进程结束
        int status;
        waitpid(pid_child, &status, 0); // waitpid()等待特定的子进程pid结束
        if (WIFEXITED(status)) { // 如果子进程正常退出,则返回非零值
            printf("Child process exited with success,  WEXITSTATUS(status)=%d, status=%d\n", WEXITSTATUS(status), status);
        } else {
            printf("Child process exited with fail,  WEXITSTATUS(status)=%d, status=%d\n", WEXITSTATUS(status), status);
        }
    }

    printf("[%s: %d]: end pid_tmp = %d\n", __func__, __LINE__, pid_tmp);
    printf("%s() exit\n\n", __func__);
    return 0;
}

int main()
{
    test_fork();
    test_vfork();
    test_exec();

    return 0;
}

实验结果:

eon@ubuntu:/mnt/d/ubuntu/code/test/test_process$ gcc -o test_fork test_fork.c 
eon@ubuntu:/mnt/d/ubuntu/code/test/test_process$ ./test_fork
test_fork() enter
[test_fork: 13]: pid_tmp = 3469
[test_fork: 38]: pid_tmp = 3469, pid_child=3470
[test_fork: 23]: pid_tmp = 3470
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1000    12    11  0  80   0 -  2733 -      tty1     00:00:00 sh
0 S  1000    13    12  0  80   0 -  2733 -      tty1     00:00:00 sh
0 S  1000    18    13  0  80   0 -  2733 -      tty1     00:00:00 sh
0 S  1000    22    18  0  80   0 - 239914 ?     tty1     00:00:02 node
0 S  1000    34    33  0  80   0 - 152427 ?     tty2     00:00:00 node
0 S  1000    42    41  0  80   0 - 151300 ?     tty3     00:00:00 node
0 R  1000    43    22  0  80   0 - 164057 ?     tty1     00:00:01 node
0 S  1000    58    22  5  80   0 - 265791 ?     tty1     00:01:38 node
0 S  1000   118    22  1  80   0 - 247420 ?     tty1     00:00:24 node
0 S  1000  3469   129  0  80   0 -  2704 -      pts/0    00:00:00 test_fork
0 R  1000  3470  3469  0  80   0 -  3881 -      pts/0    00:00:00 ps
Child process exited with success,  WEXITSTATUS(status)=0, status=0
test_fork() exit

test_vfork() enter
I am the child process with PID: 3471
I am the parent process with PID: 3469
Child exited with status 0
test_vfork() exit

test_exec() enter
[test_exec: 99]: pid_tmp = 3469
[test_exec: 121]: pid_tmp = 3469, pid_child=3486
[test_exec: 109]: pid_tmp = 3486
execlp failed
[test_exec: 118]: pid_tmp = 3486
[test_exec: 133]: end pid_tmp = 3486
test_exec() exit

Child process exited with success,  WEXITSTATUS(status)=0, status=0
[test_exec: 133]: end pid_tmp = 3469
test_exec() exit

eon@ubuntu:/mnt/d/ubuntu/code/test/test_process$ 
  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

eon2718281828

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值