热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Linux创建子进程执行任务的实现方法

下面小编就为大家分享一篇Linux创建子进程执行任务的实现方法,具有很好的参考价值,希望对大家有所帮助,一起跟随小编过来看看吧

Linux 操作系统紧紧依赖进程创建来满足用户的需求。例如,只要用户输入一条命令,shell 进程就创建一个新进程,新进程运行 shell 的另一个拷贝并执行用户输入的命令。Linux 系统中通过 fork/vfork 系统调用来创建新进程。本文将介绍如何使用 fork/vfork 系统调用来创建新进程并使用 exec 族函数在新进程中执行任务。

fork 系统调用

要创建一个进程,最基本的系统调用是 fork:

# include 
pid_t fork(void);
pid_t vfork(void);

调用 fork 时,系统将创建一个与当前进程相同的新进程。通常将原有的进程称为父进程,把新创建的进程称为子进程。子进程是父进程的一个拷贝,子进程获得同父进程相同的数据,但是同父进程使用不同的数据段和堆栈段。子进程从父进程继承大多数的属性,但是也修改一些属性,下表对比了父子进程间的属性差异:

继承属性 差异
uid,gid,euid,egid 进程 ID
进程组 ID 父进程 ID
SESSION ID 子进程运行时间记录
所打开文件及文件的偏移量 父进程对文件的锁定
控制终端
设置用户 ID 和 设置组 ID 标记位
根目录与当前目录
文件默认创建的权限掩码
可访问的内存区段
环境变量及其它资源分配

下面是一个常见的演示 fork 工作原理的 demo(笔者的环境为 Ubuntu 16.04 desktop):

#include 
#include 
#include 
#include 
int main(void)
{
 pid_t pid;
 char *message;
 int n;
 pid = fork();
 if(pid <0)
 {
  perror("fork failed");
  exit(1);
 }
 if(pid == 0)
 {
  printf("This is the child process. My PID is: %d. My PPID is: %d.\n", getpid(), getppid());
 }
 else
 {
  printf("This is the parent process. My PID is %d.\n", getpid());
 }
 return 0;
}

把上面的代码保存到文件 forkdemo.c 文件中,并执行下面的命令编译:

$ gcc forkdemo.c -o forkdemo

然后运行编译出来的 forkdemo 程序:

$ ./forkdemo

fork 函数的特点是 "调用一次,返回两次":在父进程中调用一次,在父进程和子进程中各返回一次。在父进程中返回时的返回值为子进程的 PID,而在子进程中返回时的返回值为 0,并且返回后都将执行 fork 函数调用之后的语句。如果 fork 函数调用失败,则返回值为 -1。
我们细想会发现,fork 函数的返回值设计还是很高明的。在子进程中 fork 函数返回 0,那么子进程仍然可以调用 getpid 函数得到自己的 PID,也可以调用 getppid 函数得到父进程 PID。在父进程中用 getpid 函数可以得到自己的 PID,如果想得到子进程的PID,唯一的办法就是把 fork 函数的返回值记录下来。
注意:执行 forkdemo 程序时的输出是会发生变化的,可能先打印父进程的信息,也可能先打印子进程的信息。

vfork 系统调用

vfork 系统调用和 fork 系统调用的功能基本相同。vfork 系统调用创建的进程共享其父进程的内存地址空间,但是并不完全复制父进程的数据段,而是和父进程共享其数据段。为了防止父进程重写子进程需要的数据,父进程会被 vfork 调用阻塞,直到子进程退出或执行一个新的程序。由于调用 vfork 函数时父进程被挂起,所以如果我们使用 vfork 函数替换 forkdemo 中的 fork 函数,那么执行程序时输出信息的顺序就不会变化了。

使用 vfork 创建的子进程一般会通过 exec 族函数执行新的程序。接下来让我们先了解下 exec 族函数。

exec 族函数

使用 fork/vfork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往需要调用一个 exec 族函数以执行另外一个程序。当进程调用 exec 族函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的起始处开始执行。调用 exec 族函数并不创建新进程,所以调用 exec 族函数前后该进程的 PID 并不改变。

exec 族函数一共有六个:

#include 
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

函数名字中带字母 "l" 的表示其参数个数不确定,带字母 "v" 的表示使用字符串数组指针 argv 指向参数列表。
函数名字中含有字母 "p" 的表示可以自动在环境变量 PATH 指定的路径中搜索要执行的程序。
函数名字中含有字母 "e" 的函数比其它函数多一个参数 envp。该参数是字符串数组指针,用于指定环境变量。调用这样的函数时,可以由用户自行设定子进程的环境变量,存放在参数 envp 所指向的字符串数组中。

事实上,只有 execve 是真正的系统调用,其它五个函数最终都调用 execve。这些函数之间的关系如下图所示(此图来自互联网):

exec 族函数的特征:调用 exec 族函数会把新的程序装载到当前进程中。在调用过 exec 族函数后,进程中执行的代码就与之前完全不同了,所以 exec 函数调用之后的代码是不会被执行的。

在子进程中执行任务

下面让我们通过 vfork 和 execve 函数实现在子进程中执行 ls 命令:

#include 
#include 
#include 
#include 
int main(void)
{
 pid_t pid;
 if((pid=vfork()) <0)
 {
  printf("vfork error!\n");
  exit(1);
 }
 else if(pid==0)
 {
  printf("Child process PID: %d.\n", getpid());
  char *argv[ ]={"ls", "-al", "/home", NULL}; 
  char *envp[ ]={"PATH=/bin", NULL};
  if(execve("/bin/ls", argv, envp) <0)
  {
   printf("subprocess error");
   exit(1);
  }
  // 子进程要么从 ls 命令中退出,要么从上面的 exit(1) 语句退出
  // 所以代码的执行路径永远也走不到这里,下面的 printf 语句不会被执行
  printf("You should never see this message.");
 }
 else
 {
  printf("Parent process PID: %d.\n", getpid());
  sleep(1);
 }
 return 0;
}

把上面的代码保存到文件 subprocessdemo.c 文件中,并执行下面的命令编译:

$ gcc subprocessdemo.c -o subprocessdemo

然后运行编译出来的 subprocessdemo程序:

$ ./subprocessdemo

总结

fork/vfork 函数和 exec 族函数都是 Linux 系统中非常重要的概念。本文试图通过简单的 demo 来演示这些函数的基本用法,为理解 Linux 系统中父进程与子进程的概念提供一些直观的感受。


推荐阅读
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 在Ubuntu 16.04 LTS上配置Qt Creator开发环境
    本文详细介绍了如何在Ubuntu 16.04 LTS系统中安装和配置Qt Creator,涵盖了从下载到安装的全过程,并提供了常见问题的解决方案。 ... [详细]
  • 本文详细介绍了如何在 Linux 平台上安装和配置 PostgreSQL 数据库。通过访问官方资源并遵循特定的操作步骤,用户可以在不同发行版(如 Ubuntu 和 Red Hat)上顺利完成 PostgreSQL 的安装。 ... [详细]
  • 本文详细介绍了如何在Ubuntu系统中下载适用于Intel处理器的64位版本,涵盖了不同Linux发行版对64位架构的不同命名方式,并提供了具体的下载链接和步骤。 ... [详细]
  • 本文介绍如何通过更改软件源来提前体验Ubuntu 8.10,包括详细的配置步骤和相关注意事项。 ... [详细]
  • 本文介绍如何在Linux Mint系统上搭建Rust开发环境,包括安装IntelliJ IDEA、Rust工具链及必要的插件。通过详细步骤,帮助开发者快速上手。 ... [详细]
  • PHP 时间与日期工具类:星座、干支、生肖的实现
    本文介绍了一个PHP时间与日期工具类,涵盖了时区设置、有效日期和时间检查、星座、干支、生肖计算等功能。该工具类特别适用于需要处理中国农历及西方星座的应用场景。 ... [详细]
  • 在Ubuntu 8.04中安装美观的Cairo-Dock桌面增强工具
    本文介绍如何在Ubuntu 8.04系统中安装和配置Cairo-Dock,这款桌面增强工具以其精美的界面和高效的功能备受用户青睐。文章将指导您通过简单的步骤完成安装,并提供一些实用的配置建议。 ... [详细]
  • 选择适合生产环境的Docker存储驱动
    本文旨在探讨如何在生产环境中选择合适的Docker存储驱动,并详细介绍不同Linux发行版下的配置方法。通过参考官方文档和兼容性矩阵,提供实用的操作指南。 ... [详细]
  • 在创建新的Android项目时,您可能会遇到aapt错误,提示无法打开libstdc++.so.6共享对象文件。本文将探讨该问题的原因及解决方案。 ... [详细]
  • 本文详细介绍了如何在预装Ubuntu系统的笔记本电脑上安装Windows 7。针对没有光驱的情况,提供了通过USB安装的具体方法,并解决了分区、驱动器无法识别等问题。 ... [详细]
  • 深入解析 HDFS Federation:多命名空间架构详解
    HDFS Federation 是一种扩展 HDFS 架构的方式,通过引入多个独立的 NameNode 来解决单点故障和性能瓶颈问题。本文将详细探讨 HDFS Federation 的工作原理、优势以及潜在挑战。 ... [详细]
  • 本文详细介绍了如何在BackTrack 5中配置和启动SSH服务,确保其正常运行,并通过Windows系统成功连接。涵盖了必要的密钥生成步骤及常见问题解决方法。 ... [详细]
  • 在哈佛大学商学院举行的Cyberposium大会上,专家们深入探讨了开源软件的崛起及其对企业市场的影响。会议指出,开源软件不仅为企业提供了新的增长机会,还促进了软件质量的提升和创新。 ... [详细]
  • JavaScript 中创建对象的多种方法
    本文详细介绍了 JavaScript 中创建对象的几种常见方式,包括对象字面量、构造函数和 Object.create 方法,并提供了示例代码和属性描述符的解释。 ... [详细]
author-avatar
风一样的男孩668
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有