阅读了《Unix/Linux系统编程》,并查了一些资料,对fork wait exec等系统调用进行了总结。
新人一枚,有错误的话请批评指正!
参考资料

1. 一些Unix/Linux进程相关词汇

  • 进程上下文

    进程的上下文就是外界给进程提供的运行环境,即程序正确运行所需的状态组合。当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。

  • 上下文切换

    不同任务(进程)之间的执行切换机制称为上下文切换,将一个任务的执行环境更改为另一个任务的执行环境

  • 并发

    逻辑上的同时进行。如果上下文切换的够快,就会给人一种同时执行多个任务的错觉。这种逻辑并行性称为并发。

  • 并行

    物理上的同时进行(真正的同时进行)。对于多CPU或处理器内核的多处理器系统中,可在不同CPU上实时、并行的执行多项任务。

  • 执行映像

    包含代码、数据和堆栈的存储区。程序和进程之间不存在一一对应的关系,一个程序可以对应多个的进程。程序是静态的,存储在计算机磁盘中,进程是动态的,存在生命周期,有“生老病死”。我们通常把进程看作为是程序在内存中的镜像image

  • 进程

    进程是对映像的执行(也可以理解为一个执行中程序的实例)。每个进程用一个独特的数据结构表示,即为进程控制块(PCB),PCB保存着该进程的所有信息。

2. Linux中进程相关系统调用

2.1 fork()系统调用

用法:

#include<unistd.h>
#include<sys/types.h>
int pid = fork();

百度百科定义:复刻(英语:fork,又译作派生分支)是UNIX或类UNIX中的分叉函数,fork函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。

fork()创建子进程并返回子进程的pid(进程识别号),如果失败返回-1。父进程调用fork()系统调用后陷入内核态,并创建拥有自己的内核态堆栈和用户态映像的子进程。创建的子进程拥有与父进程完全相同的用户态映像,因此可以认为子进程就是父进程的一个复刻(因此连执行到哪一行都一样)

并且,允许子进程继承父进程打开的所有文件。因此,父进程和子进程都可以从同一个stdin标准输入文件获得输入,并输出到同一个stdout标准输出文件、stderr标准错误文件(可以理解为从同一个终端获取输入与显示输出)

在创建完子进程即fork()函数执行完后,parent父进程获得子进程pid号作为返回值,而刚创建的子进程会返回到相同的语句,获得的返回值为0

示例:父进程创建子进程后,两个进程打印fork()函数返回值并显示在相同终端上

main.c

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main(){printf("test begin\n");int pid = fork();if(pid){//父进程执行此部分printf("this is parent process : return value = %d\n",pid);}else{//子进程执行此部分printf("this is child process : return value = %d\n",pid);}
}

运行结果:

xtark@xtark-vmpc:~/桌面/linux_study/section3$ gcc main.c
xtark@xtark-vmpc:~/桌面/linux_study/section3$ ./a.out
test begin
this is parent process : return value = 3360
this is child process : return value = 0

可见父进程成功创建子进程并获得返回值3360(子进程pid),而子进程获得的fork()函数返回值为0。并且"test begin"只被打印了一次,因此说明fork函数将运行着的程序分成2个(几乎)完全一样的进程,连执行到哪一行都一样。父子进程能够将字符串输出到同一终端,说明子进程继承了父进程打开的所有文件。

2.2 getpid()和getppid()系统调用

这两个系统调用很简单,getpid()系统调用返回调用进程的pidgetppid()系统调用返回父进程的pid(ppid即为parent pid)

示例:父进程创建子进程后,父进程打印自己与子进程pid;子进程打印自己与父进程pid

main.c

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main(){int pid = fork();if(pid){//父进程执行此部分printf("my pid is %d , my child pid is %d\n",getpid(),pid);}else{//子进程执行此部分printf("my pid is %d , my parent pid is %d\n",getpid(),getppid());}
}

运行结果:

xtark@xtark-vmpc:~/桌面/linux_study/section3$ gcc main.c
xtark@xtark-vmpc:~/桌面/linux_study/section3$ ./a.out
my pid is 3528 , my child pid is 3529
my pid is 3529 , my parent pid is 3528

2.3 wait()系统调用

用法

#include <sys/types.h>
#include <sys/wait.h>
int pid = wait(int * status);

僵尸进程

僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源

父进程一旦调用了wait()系统调用就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回该子进程pid,并且status保存着僵尸子进程的exitCode(用于记录进程退出状态);如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

示例:父进程创建子进程后进行wait()系统调用,销毁该子进程并打印输出。子进程被创建后sleep五秒,之后到达程序末尾成为僵尸子进程。

main.c

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/wait.h>
int main(){int pid = fork();if(pid){//父进程执行此部分printf("I'm parent process,I create child process %d.\n",pid);int status;//保存子进程退出状态pid = wait(&status);//等待僵尸子进程出现并销毁该子进程printf("child process %d died\n",pid);}else{//子进程执行此部分printf("I'm child process %d\n",getpid());sleep(5);//令当前进程暂停5秒printf("five seconds left\n");}
}

运行结果:

xtark@xtark-vmpc:~/桌面/linux_study/section3$ gcc main.c
xtark@xtark-vmpc:~/桌面/linux_study/section3$ ./a.out
I'm parent process,I create child process 3904.
I'm child process 3904
five seconds left
child process 3904 died

补充waitpid系统调用

作用同于wait,等待指定子进程终止。与wait系统调用的区别是waitpid可以通过选项设置不用阻塞

 pid_t waitpid(pid_t pid, int *status, int options);
  • 参数pid

    pid > 0:等待指定pid的子进程终止

    pid = -1:等待任何一个子进程终止,此时和wait系统调用一样

    pid = 0:等待同一个进程组中的任何子进程

    pid < -1:等待一个指定进程组中的任何子进程,其进程组ID等于pid的绝对值

  • 参数status:同wait系统调用,如果不是NULL,将子进程退出状态保存在该参数内

  • 参数options:可使用以下宏(通过或|相连)或者0

    WNOHANG:如果没有子进程立即返回0。若pid指定子进程没有结束则waitpid不阻塞直接返回0

    WUNTRACED:返回终止子进程信息和因信号停止的子进程信息

    0:同wait,阻塞并等待子进程退出

  • 返回值:成功返回子进程pid;第三个参数使用WNOHANG但是没有子进程退出返回0;如果失败返回-1。

2.4 exec系列系统调用

exec系列中的系统调用都完成相同的功能(只是参数不同而已),即为更改进程执行映像。它们把一个新程序装入调用进程的内存空间,来改变调用进程的执行映像。如果exec调用成功,调用进程将被覆盖,然后从新程序的入口开始执行,但是它的pid与调用进程相同(即为exec系统调用不改变继承的pid)。这就是说,exec没有建立一个与调用进程并发的新进程,而是用新进程取代了原来的进程。所以,在exec调用成功后,没有任何数据返回,执行失败返回-1。下面给出了exec系列系统调用在linux系统库unistd.h中的函数声明:

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

2.4.1 获取系统环境变量

环境变量通过env[]参数传递给C程序,该参数是一个以NULL结尾的字符串指针数组,每个指针指向一个环境变量字符串

示例

showEnv.c

#include <stdio.h>
int main(int argc,char* argv[],char* env[]){int index = 0;while(env[index]){printf("%s\n",env[index++]);}
}

运行结果(环境变量太多了只截了一小部分)

xtark@xtark-vmpc:~/桌面/linux_study/section3$ gcc showEnv.c
xtark@xtark-vmpc:~/桌面/linux_study/section3$ ./a.out
LC_PAPER=zh_CN.UTF-8
XDG_VTNR=7

2.4.2 通过execl()更改进程映像

execl()函数格式:

第一个参数path可执行文件路径。第二个以及用省略号表示的其他参数一起组成了该程序执行时的参数表,按照linux的贯例,参数表的第一项是不带路径的程序文件名。被调用的程序可以访问这个参数表,它们相当于shell下的命令行参数(会传递给main函数的char* argv[]参数)

int execl(const char *path,const char *arg,...);

exec常用法:exec系统调用经常与fork()联合使用,我们可以先用fork建立一个子进程,然后在子进程中使用exec,这样就实现了父进程运行一个与其不同的子进程,并且父进程不会被覆盖

示例一:父进程fork一个子进程,子进程以另一个可执行文件更改自己的执行映像。

main.c

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main(){int pid = fork();if(pid){//父进程执行此部分printf("I'm parent process,I create child process %d.\n",pid);}else{//子进程执行此部分printf("I'm child process %d\n",getpid());int r = execl("another","another","0","1",NULL);//更改进程映像printf("change Process image failed\n");}
}

another.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char* argv[]){printf("I changed myself to a new Process image , my pid is %d\n",getpid());printf("paremeters:\n");for(int i = 0 ; i < argc ; ++i){printf("%d paremter is : %s\n",i,argv[i]);}
}

运行结果:

xtark@xtark-vmpc:~/桌面/linux_study/section3$ gcc another.c -o another
xtark@xtark-vmpc:~/桌面/linux_study/section3$ gcc main.c
xtark@xtark-vmpc:~/桌面/linux_study/section3$ ./a.out
I'm parent process,I create child process 5731.
I'm child process 5731
xtark@xtark-vmpc:~/桌面/linux_study/section3$ I changed myself to a new Process image , my pid is 5731
paremeters:
0 paremter is : another
1 paremter is : 0
2 paremter is : 1

通过输出结果可以看出,更改进程映像后进程pid没有变化,但是运行的代码变成了another.c中的内容

自然也可以通过exec系统调用来运行linux命令如ls。因为ls本质上也是一个可执行文件,保存在/bin/目录下

示例二:通过execl函数更改进程映像从而执行linux中的ls命令,需要注意传递的第二个及之后的参数相当于shell下的命令行参数,并且要以NULL结尾。

callCommand.c

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main(){printf("this is a Process\n");int r = execl("/bin/ls","ls","-l",NULL);//更改进程映像,执行命令。命令为ls -lprintf("change to new Process image failed\n");
}

运行结果

xtark@xtark-vmpc:~/桌面/linux_study/section3$ gcc callCommand.c
xtark@xtark-vmpc:~/桌面/linux_study/section3$ ./a.out
this is a Process
总用量 44
-rwxrwxr-x 1 xtark xtark 8712 6月  13 22:27 another
-rw-rw-r-- 1 xtark xtark  300 6月  13 22:26 another.c
-rwxrwxr-x 1 xtark xtark 8664 6月  13 22:45 a.out
-rw-rw-r-- 1 xtark xtark  240 6月  13 22:45 callCommand.c
drwxrwxr-x 2 xtark xtark 4096 6月  13 21:35 fork test
-rw-rw-r-- 1 xtark xtark  477 6月  13 22:40 main.c
-rw-rw-r-- 1 xtark xtark  150 6月  13 21:29 showEnv.c

Linux 系统调用 fork wait exec相关推荐

  1. linux fork 用法,Linux系统调用fork()用法详解

    linux 系统调用fork()的用法详解 Linux系统调用fork()用法详解 1. 先看下面代码: #include #include //pid_t类型定义 #include //函数fork ...

  2. linux中fork和exec

    学过C语言的都知道,Unix下某个进程的内存分成三部分:代码段,堆栈段,数据段.代码段用来存放程序运行的代码,堆栈段用来存放子程序的局部变量,数据段用来存放全局变量.这在perl里也是一样的. per ...

  3. Linux系统调用之execve函数与标准C库exec函数族(有关于进程方面的函数族)

    前言 如果,想要深入的学习Linux系统调用里面的execve函数与标准C库中的exec函数族,还是需要去自己阅读Linux系统中的帮助文档. 具体输入命令: man 2 execve man 3 e ...

  4. Linux进程5:exec族函数(execl, execlp, execle, execv, execvp, execvpe)总结及exec配合fork使用

    exec族函数(execl, execlp, execle, execv, execvp, execvpe)及exec配合fork使用 exec族函数函数的作用: 我们用fork函数创建新进程后,经常 ...

  5. linux c语言 fork() 和 exec 函数的简介和用法

    假如我们在编写1个c程序时想调用1个shell脚本或者执行1段 bash shell命令, 应该如何实现呢? 其实在<stdlib.h> 这个头文件中包含了1个调用shell命令或者脚本的 ...

  6. Linux系统调用之fork,getpid,getppid函数(进程相关函数)

    前言 如果,想要深入的学习Linux系统调用中的dup,dup2函数,还是需要去自己阅读Linux系统中的帮助文档. 具体输入命令: man 2 fork/getpid/getppid 即可查阅到完整 ...

  7. linux fork 函数,Linux的fork()系统调用

    Linux的fork()系统调用,就是以父进程为模版创建子进程,是Linux系统的进程管理机制的核心API之一,另一个是调度器函数schedule(),它的用户态API就是之前说自旋锁时提到的sche ...

  8. linux内核-系统调用fork、vfork与clone

    前面已经简要地介绍过fork与clone二者的作用于区别.这里先来看一下二者在程序设计接口上的不同: pid_t fork(void); int clone(int (*fn)(void *), vo ...

  9. Linux中fork()系统调用创建两个子进程

    使用系统调用fork()创建两个子进程: #include <stdio.h> #include <unistd.h>int main(){int fpid = fork(); ...

  10. Linux系统调用相关概念

    目录: 1. Linux系统调用原理 2. 系统调用的实现 3. Linux系统调用分类及列表 4.系统调用.用户编程接口(API).系统命令和内核函数的关系 5. Linux系统调用实例 6. Li ...

最新文章

  1. 091101 T IModel
  2. C++智能指针(设计和使用)
  3. 链表--只知道当前节点指针删除当前节点
  4. Rails开发细节《七》ActiveRecord Associations关联
  5. 51NOD 1212 无向图最小生成树
  6. C语言代码规范(一)缩进与换行
  7. c 富文本html编辑器,富文本HTML编辑器UEditor
  8. 简直没法玩!iOS 13三指手势影响多款游戏操作,腾讯建议玩家谨慎更新
  9. 【前端】vue Unknown custom element: xxxx did you register the component correctly
  10. IOS关于UIViewController之间的切换
  11. 写bat脚本--2021年5月18日
  12. [ZPG TEST 110] 多边形个数【DP】
  13. euraka有哪些组件_SpringCloud及其五大常用组件之Eureka和Zuul
  14. 广州地铁十三号线二期全线土建已完成53%,预计明年开通
  15. python程序输入两个整数、实现加减乘除_加减乘除
  16. Python中RE模块总结
  17. 重复工作到底有没有意义
  18. CentOS 安装 Xware 迅雷远程下载程序
  19. 天天向上python题目答案_Python练习:天天向上的力量
  20. R语言 substitute

热门文章

  1. 人人商城互动直播(与通信服务器连接失败)
  2. OpenCV图像识别技术+Mediapipe与Unity引擎的结合
  3. 1024程序员节活动继续:购书优惠劵,折后再折,赶紧来抢啊
  4. 819A - 如何成为一名职业程序员
  5. 安装了两种oracle数据库怎么卸载,oracle数据库卸载步骤
  6. 【6GHz矩形贴片天线设计与分析】
  7. linux下pdb文件除水,blast+本地化中blastp操作(基于PDB库)—linux
  8. CS:GO开服架设服务器搭建游戏配置方法教程教学插件配置下载资源配置
  9. windows 搭建eureka注册中心
  10. AutoVue使用教程:如何在64位Linux上安装AutoVue