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

fork 系统调用

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

# include <unistd.h>
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 <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>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 <unistd.h>
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 <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
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 c 创建子进程执行任务 简介相关推荐

  1. linux c 创建子进程 执行任务

    目录 fork 系统调用 vfork 系统调用 exec 族函数 在子进程中执行任务 总结 Linux 操作系统紧紧依赖进程创建来满足用户的需求.例如,只要用户输入一条命令,shell 进程就创建一个 ...

  2. Linux 创建子进程执行任务

    Linux 操作系统紧紧依赖进程创建来满足用户的需求.例如,只要用户输入一条命令,shell 进程就创建一个新进程,新进程运行 shell 的另一个拷贝并执行用户输入的命令.Linux 系统中通过 f ...

  3. linux 下创建子进程

    1.创建单个子进程 #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { ...

  4. linux命令执行的通过程,Linux下shell命令执行过程简介

    浅析linux 下shell命令执行和守护进程 执行shell脚本有以下几种方式 1.相对路径方式,需先cd到脚本路径下 [root@banking tmp]# cd /tmp [root@banki ...

  5. Linux下创建可执行bin安装文件

    需求及应用场景 1.简化操作.一般的软件安装过程,如果想要精简步骤,我们一般会将需要在命令行中输入的命令写成一个脚本,同时将安装介质准备好.我们将脚本和安装介质上传到生产环境,然后通过执行脚本来完成安 ...

  6. linux中安shell怎么传入参数,【linux】linux 下 shell命令 执行结果赋值给变量【两种方式】...

    方法1:[通用方法] 使用Tab键上面的反引号 例子如下: find命令 模糊查询在/apps/swapping目录下 查找 文件名中包含swapping并且以.jar结尾的文件 使用反引号 引住命令 ...

  7. linux用vfork创建进程,[Linux进程]使用vfork创建子进程并且执行命令

    /*这是一个其分别利用子进程和父进程对一个count进行计数并且输出, 用于展示父进程和子进程是共享一个数据段*/ #include #include #include #include int ma ...

  8. Linux下创建进程简介

    在博文https://blog.csdn.net/fengbingchun/article/details/108940548中简单介绍了Windows下通过函数CreateProcess创建进程的过 ...

  9. linux创建进程读共享写复制,Linux下进程的创建、执行和终止

    1)进程的创建和执行 许多操作系统提供的都是产生进程的机制,也就是说,首先在新的地址空间里创建进程.读入可执行文件,后再开始执行.Linux中进程的创建很特别,它把上述步骤分解到两个单独的函数中去执行 ...

最新文章

  1. IDEA创建maven项目报错解决:Failed to create a Maven project: 'C:/Users/../IdeaProjects/../pom.xml' already e
  2. vlan和如何建立vlan和vconifg
  3. HDOJ1874最短路【spfa】
  4. Codeforces 1254C/1255F Point Ordering (交互题)
  5. python复制多个文件_python 之 复制多个模板文件
  6. OpenCV为轮廓创建边界框和圆
  7. 此windows副本不是正版解决方法
  8. 重新安装SCCM 2012 client,解决Windows10 1909在线更新问题
  9. springboot整合websocket实现简易版单人聊天
  10. Ruby 之父:编程要给程序员带来乐趣
  11. 基于视图的DNS解析
  12. python 基本数据类型之set
  13. windows安装vnm
  14. Vue2.0组织浏览器返回事件
  15. 利用Swoole编写一个TCP服务器,顺带测试下Swoole的4层生命周期
  16. POJ 3414 Pots【BFS水】
  17. 黑客+马拉松=? 黑客马拉松?
  18. 统计web服务器类型
  19. 强烈呼吁弃用Notepad++,推荐几款优秀替代品
  20. python实现学员管理系统(增删改查)

热门文章

  1. 5G NGC — NWDAF 网络智能分析功能
  2. Linux_Bash脚本基础
  3. java多线程-CyclicBarrier
  4. containerd发布了CRI修复程序和CVE-2019-5736更新的runc
  5. C#String地址、拼接性能学习
  6. Linux配置示例:配置java环境变量
  7. web项目部署到linux服务器出现的问题
  8. C#获取网页中的验证码图片(转载)
  9. shell高级视频答学生while循环问题
  10. 设置/修改centos上的swap交换分区的方法