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

 

      假使我们在编排1个c程序时想调用1个shell脚本或者实施1段 bash
shell命令, 应该如何完毕吗?

      其实在<stdlib.h>
这一个头文件中隐含了1个调用shell命令或者脚本的函数 system();间接把
shell命令作为参数传入 system函数就足以了, 的确很方便. 关于system
有一段那样的介绍:   system 执行时内部会自动启用fork() 新建1个进程, 
功效没有直接动用fork() 和 exec函数高.

 

       那么那篇小说其实就是介绍一下fork() 和 exec函数的用法,
以及哪些使用它们来代替system函数.

      

一、进度的创始fork()函数

fork()函数通过系统调用创立一个与原先进度(父进度)差不离完全相同的历程(子进度是父进度的副本,它将获得父进度数据空间、堆、栈等资源的副本。注意,子进程具有的是上述存储空间的“副本”,那象征父子进度间不共享这几个囤积空间。linux将复制父进度的地方空间内容给子进程,因而,子进度有了独立的地方空间。),也就是那五个经过做完全相同的事。

  1. 示例

  2. /*exec函数示例*/  

  3. #include <stdio.h>   
  4. #include <unistd.h>   
  5.   
  6. int main(void)  
  7. {  
  8.     int flag;  
  9.     pid_t pid;  
  10.     char *const argv[] = {“%U”, “–user-data-dir=/home/Administrator/.chromiun”, NULL};  
  11.     //exec把近来进度印象替换成新的先后文件,故调用进程被遮住
      
  12.   
  13.     // 尽管不点名全路线,则只检查PATH变量中蕴藏的一声令下
      
  14.     if((pid = fork())==0) {  
  15.         printf(“in child process 1……\n”);  
  16.         //flag = execvp(“./hello”, NULL);
      
  17.         //envp变量的用   
  18.         char *envp[]={“PATH=.”, NULL};  
  19.         flag = execve(“hello”, NULL, envp);  
  20.         if(flag == -1)  
  21.             printf(“exec error!\n”);  
  22.     }  
  23.   
  24.     if((pid = fork())==0) {  
  25.         printf(“in child process 2……\n”);  
  26.         //执行ls命令   
  27.         flag = execlp(“ls”, “-al”, NULL);  
  28.         if(flag == -1)  
  29.             printf(“exec error!\n”);  
  30.     }  
  31.       
  32.     if((pid = fork())==0) {  
  33.         printf(“in child process 3……\n”);  
  34.         //启动chrome浏览器   
  35.         flag = execv(“/usr/bin/chromium-browser”, argv);  
  36.         if(flag == -1)  
  37.             printf(“exec error!\n”);  
  38.     }  
  39.     printf(“in parent process ……\n”);  
  40.     return 0;  
  41. }  

  42. hello程序

  43. #include <stdio.h>   

  44.   
  45. int main(void)  
  46. {  
  47.     printf(“Hello world澳门金沙国际,!\n”);  
  48.     return 0;  
  49. }  

  50. 运作结果

  51. root@Ubuntu:…/Linux_C/Process# ./exec_t  

  52. in child process 1……  
  53. in parent process ……  
  54. in child process 3……  
  55. root@ubuntu:…/Linux_C/Process# in child process 2……  
  56. Hello world!  
  57. exec_t    fifo_read.c   fork_1.c  hello.c    msg_send.c   signal_1.c  
  58. exec_t.c  fifo_write.c  hello     msg_receive.c  semop_P_V.c  
  59. 已在存活的浏览器会话中开创新的窗口。  

1. fork() 函数

澳门金沙国际 1

在fork后的子进度中使用exec函数族,可以装入和周转其它程序(子进度替换原有进度,和父进度做分裂的事)。

澳门金沙国际 2

1.1 fork() 函数的职能

       一般来讲, 我们编辑1个一般的c程序, 运行那个顺序直到程序为止,
系统只会分配1个pid给这一个顺序, 也就就说,
系统里只会有一条关于这么些顺序的进程.

 

        可是进行了fork() 那个函数就差距了. 

        fork 这几个英文单词在英文里是”分叉”意思,  fork()
那个函数功用也很合乎那几个意思. 
它的效应是复制当前进程(包蕴经过在内存里的仓库数据)为1个新的镜像.
然后这一个新的镜像和旧的历程同时进行下去. 相当于自然1个过程, 际遇fork()
函数后就分割成多个经过同时履行了. 而且那三个进程是互不影响

 

        参考上面这几个小程序:

 

[cpp] view
plain copy

 

  1. int fork_3(){  
  2.     printf(“it’s the main process step 1!!\n\n”);  
  3.   
  4.     fork();  
  5.   
  6.     printf(“step2 after fork() !!\n\n”);  
  7.   
  8.     int i; scanf(“%d”,&i);   //prevent exiting  
  9.     return 0;  
  10. }  

 

          在那一个函数里, 共有两条printf语句,
但是举办实施时则打出了3行新闻. 如下图: 

 由fork创设的新进度被称为子进度(child
process)。该函数被调用一回,但再次来到四遍。几回回到的区分是子进度的重回值是0,而父进程的重返值则是
新子进程的进度ID。将子进度ID重返给父进程的说辞是:因为一个进度的子进度能够多于一个,所有没有一个函数使一个进度可以收获其所有子进度的长河ID。fork使子进度取得重返值0的理由是:一个进度只会有一个父进程,所以子进程总是可以调用getppid以获得其父进度的历程ID(进度ID  0总是由互换进度使用,所以一个子进程的进程ID不容许为0)。

fork成立一个新的进度就发出了一个新的PID,exec启动一个新程序,替换原有的历程,由此那个新的被
exec 执行的长河的PID不会变动(和调用exec的进程的PID一样)。

澳门金沙国际 3

 

            为啥吗, 因为fork()函数将这一个程序分叉了呀,  见下边的图解:

澳门金沙国际 4

 

         可以看到程序在fork()函数执行时都唯有1条主进程, 所以 step 1
会被打印输出1次.

         执行 fork()函数后,  程序分叉成为了多少个进程, 1个是原本的主进度, 
另1个是新的子进度, 它们都会实施fork() 函数前面的代码, 所以 step2 会被
两条长河分别打印输出各一次, 显示器上就一起3条printf 语句了!

 

         可以看出那个函数最终边我用了 scanf()函数来预防程序退出, 
这时查看系统的进程, 就会发觉四个一样名字的经过:

 

 

#include<unistd.h>

 

如上图, pid 8808 那多少个就是主进程了, 而 pid  8809百般就是子进度啊,
因为它的parent pid是 8808啊!

          

          亟待小心的是, 倘若没有做尤其处理, 子进程会一向留存,
纵然fork_3()函数被调用已毕,  子进度会和主程序一样,再次来到调用fork_3()
函数的上一级函数继续执行, 直到全部程序退出.

 

          可以看到, 如果fork_3() 被实施2次,  主程序就会分开两回,
最后变成4个经过, 是否有点危险. 所以下面所谓的出格处理很要紧呀!

 

   
子进度和父进程继续执行fork之后的通令。子进度是父进度的复制品。例如,子进度取得父进度数据空间、堆和栈的仿制品。注意,那是子进度具有的正片。父、子进程并共享那么些囤积部分。假设正文段是只读的,则父、子进度共享正文段。

extern char **environ;

1.2 分化分主程序和子程序.

        实际选拔中, 单纯让程序分叉意义不大, 我们新增一个子顺序,
很可能是为了让子进程单独实施一段代码. 完结与主进度不一样的功用.

         要兑现地点所说的机能,
实际上就是让子进程和主进度执行不一样的代码啊.

         所以fork() 实际上有重返值, 而且在两条经过中的重回值是差距的,
在主进程里 fork()函数会回到主进程的pid,   而在子进程里会再次来到0!  
所以大家能够根据fork() 的重返值来判定进度到底是哪位进程, 就可以动用if
语句来执行不一的代码了!

 

        如上面这些小程序fork_1():

 

[cpp] view
plain copy

 

  1. int fork_1(){  
  2.     int childpid;  
  3.     int i;  
  4.   
  5.     if (fork() == 0){  
  6.         //child process  
  7.         for (i=1; i<=8; i++){  
  8.             printf(“This is child process\n”);  
  9.         }  
  10.     }else{  
  11.         //parent process  
  12.         for(i=1; i<=8; i++){  
  13.             printf(“This is parent process\n”);  
  14.         }  
  15.     }  
  16.   
  17.     printf(“step2 after fork() !!\n\n”);  
  18. }  

        我对fork() 函数的再次来到值举办了判断, 即使 再次回到值是0,
我就让认为它是子进度, 否则是主程序. 
那么我就足以让这两条经过输出不相同的新闻了.

 

       

          输出新闻如下图:

澳门金沙国际 5

 

          可以观察 子程序和主程序分别出口了8条差其他信息, 
不过它们并不是规则交替输出的, 因为它们两条长河是互为平行影响的,
何人的手快就在显示器上先输出,  每一趟运行的结果都有可能不相同哦.

 

        下边是图解:

澳门金沙国际 6

 

          由图解知两条经过都对fork()再次回到值执行判断,  在if
判断语句中分头施行各自的代码.  可是if判断完结后, 
照旧会回各自执行接下去的代码. 所以 step2 仍旧输出了2次.

    

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

1.4 使用exit() 函数令子进度在if 判断内停止.

          参考上边的函数, 尽管应用if 对 fork() 的重回值进行判定, 
完结了子进度和 主进度在if判断的限制内实施了区其余代码, 
然而似乎上边的流程图, 一旦if执行到位, 他们如故会分别执行后边的代码. 

          平时那不是我们愿意的, 
我们越来越多时会希望子进程执行一段特其他代码后就让他截至, 
前面的代码让主程序执行就行了.

          那么些落成起来很粗略, 在子程序的if 条件内最终加上exit()
函数就ok了.

 

         将方面的fork_1()函数修改一下, 加上exit语句:

 

[cpp] view
plain copy

 

  1. int fork_1(){  
  2.     int childpid;  
  3.     int i;  
  4.   
  5.     if (fork() == 0){  
  6.         //child process  
  7.         for (i=1; i<=8; i++){  
  8.             printf(“This is child process\n”);  
  9.         }  
  10.         exit(0);  
  11.     }else{  
  12.         //parent process  
  13.         for(i=1; i<=8; i++){  
  14.             printf(“This is parent process\n”);  
  15.         }  
  16.     }  
  17.   
  18.     printf(“step2 after fork() !!\n\n”);  
  19. }  

       再看看输出:

 

澳门金沙国际 7

 

            可以看来, step2只输出1次了,   那是因为子程序在
if条件内完工了呀, 一旦 if 判断成, 就只剩余1个主进度执行上边的代码了,
那多亏大家想要的!

            注意: exit() 函数在 stdlib.h 头文件内

 

流程图:

澳门金沙国际 8

 

 

 

   
现在不乏先例的贯彻并不做一个父进度数据段和堆的一心拷贝,因为在fork之后平日跟随着exec。作为代表,使用了写时复制(copy-on-write,cow)的技能。这么些区域由父、子进度共享,而且根本将她们的存取许可权改变位只读的。即使有进程试图修改这么些区域,则内核包十分,典型的是虚存系统中的“页”,做一个拷贝。

int execlp(const char *file,const char *arg, …);

1.4 使用wait() 函数主程序等子程序执行已毕(退出)后再执行.   

 

        由地点例子得知,  主程序和子程序的进行顺序是即兴的, 
可是其实景况下, 平时大家期待子进度执行后,  才继续执行主进程. 

        例如对于地方的fork_1()函数, 我想先输出子进度的8个 “This is
child process”  然后再出口 8个 主进度”This is parent process”, 改怎么办?

        wait()函数就提供了那些功用,    在if 条件内的  主进度呢部分内
加上wait() 函数, 就可以让主进程执行fork()函数时先hold 住,
等子进程退出后再进行, 常常会协作子进程的exit()函数一同使用.

 

        我将fork_1()函数修改一下, 添加了wait()语句:

 

[cpp] view
plain copy

 

  1. int fork_1(){  
  2.     int childpid;  
  3.     int i;  
  4.   
  5.     if (fork() == 0){  
  6.         //child process  
  7.         for (i=1; i<=8; i++){  
  8.             printf(“This is child process\n”);  
  9.         }  
  10.         exit(0);  
  11.     }else{  
  12.         //parent process  
  13.         wait();  
  14.         for(i=1; i<=8; i++){  
  15.             printf(“This is parent process\n”);  
  16.         }  
  17.     }  
  18.   
  19.     printf(“step2 after fork() !!\n\n”);  
  20. }  

exec函数的选取,函数的简介和用法。 

输出:

 

澳门金沙国际 9

      见到那时的屏幕输出就很有规律了!

      其实wait() 函数还有1个作用, 就是可以收起1个
pid_t(在unistd.h内,其实就是Int啦) 指针类型参数,  
给那一个参数赋上子进度退出前的系统pid值

     流程图:

  澳门金沙国际 10

 

 

 

 

 

int execle(const char *path,const char *arg,…,char * const
envp[]);

2. exec 函数组

 

      须要注意的是exec并不是1个函数, 其实它只是一组函数的统称,
它概括上边6个函数:

     

[cpp] view
plain copy

 

  1. #include <unistd.h>  
  2.   
  3. int execl(const char *path, const char *arg, …);  
  4.   
  5. int execlp(const char *file, const char *arg, …);  
  6.   
  7. int execle(const char *path, const char *arg, …, char *const envp[]);  
  8.   
  9. int execv(const char *path, char *const argv[]);  
  10.   
  11. int execvp(const char *file, char *const argv[]);  
  12.   
  13. int execve(const char *path, char *const argv[], char *const envp[]);  

 

       可以看来那6个函数名字差别, 而且他们用于接受的参数也分裂.

       实际上他们的意义都是大抵的,
因为要用于接受不相同的参数所以要用分化的名字分别它们,
毕竟c语言没有函数重载的作用嘛..  

 

       可是实际它们的命名是有规律的:

       exec[l or v][p][e]

       exec函数里的参数可以分成3个部分,      执行文书部分,    
命令参数部分,   环境变量部分.

        例如我要履行1个指令   ls -l /home/gateman  

        执行文书部分就是  “/usr/bin/ls”

        命令参赛部分就是 “ls”,”-l”,”/home/gateman”,NULL             
见到是以ls先导 每1个空格都必须分别成2个部分, 而且以NULL结尾的啊.

        环境变量部分, 那是1个数组,最终的元素必须是NULL 例如  char *
env[] = {“PATH=/home/gateman”, “USER=lei”, “STATUS=testing”, NULL};

        

        好了说下命名规则:

        e后续,  参数必须带环境变量部分,  
环境变零部分参数会化为执行exec函数时期的环境变量, 比较少用

        l 后续,   命令参数部分必须以”,” 相隔, 最后1个指令参数必须是NULL

        v 后续,  
命令参数部分必须是1个以NULL结尾的字符串指针数组的头顶指针.        
例如char * pstr就是1个字符串的指针, char * pstr[] 就是数组了,
分别指向各样字符串.

        p后续,   执行文书部分可以不带路径, exec函数会在$PATH中找

 

          

         还有1个注意的是, exec函数会取代执行它的历程,  也就是说,
一旦exec函数执行成功, 它就不会再次回到了, 进度为止.  
不过如若exec函数执行破产, 它会再次回到败北的信息, 
而且经过继续执行后边的代码!

 

       平时exec会放在fork() 函数的子进程部分, 来替代子进程执行啦,
执行成功后子程序就会磨灭,  可是举行破产以来,
必须用exit()函数来让子进度退出!

       下边是逐一例子:

 

实例1:

int execv(const char *path,char *const argv[]);

2.1  execv 函数

 

[cpp] view
plain copy

 

  1. int childpid;  
  2. int i;  
  3.   
  4. if (fork() == 0){  
  5.     //child process  
  6.     char * execv_str[] = {“echo”, “executed by execv”,NULL};  
  7.     if (execv(“/usr/bin/echo”,execv_str) <0 ){  
  8.         perror(“error on exec”);  
  9.         exit(0);  
  10.     }  
  11. }else{  
  12.     //parent process  
  13.     wait(&childpid);  
  14.     printf(“execv done\n\n”);  
  15. }  

小心字符串指针数组的定义和赋值

 

 

 

int execvp(const char *file,char *const argv[]);

2.2  execvp 函数

 

 

[cpp] view
plain copy

 

  1. if (fork() == 0){  
  2.     //child process  
  3.     char * execvp_str[] = {“echo”, “executed by execvp”,”>>”, “~/abc.txt”,NULL};  
  4.     if (execvp(“echo”,execvp_str) <0 ){  
  5.         perror(“error on exec”);  
  6.         exit(0);  
  7.     }  
  8. }else{  
  9.     //parent process  
  10.     wait(&childpid);  
  11.     printf(“execvp done\n\n”);  
  12. }  

 

#include <stdio.h>

int execve(const char *file,char *const argv[],char *const
envp[]);

2.3 execve 函数

 

[cpp] view
plain copy

 

  1. if (fork() == 0){  
  2.     //child process  
  3.     char * execve_str[] = {“env”,NULL};  
  4.     char * env[] = {“PATH=/tmp”, “USER=lei”, “STATUS=testing”, NULL};  
  5.     if (execve(“/usr/bin/env”,execve_str,env) <0 ){  
  6.         perror(“error on exec”);  
  7.         exit(0);  
  8.     }  
  9. }else{  
  10.     //parent process  
  11.     wait(&childpid);  
  12.     printf(“execve done\n\n”);  
  13. }  

 

 

#include <stdlib.h>

exec函数族装入并运行程序path/file,并将参数arg0(arg1, arg2, argv[],
envp[])传递给子程序,出错重临-1.

2.4 execl 函数

 

[cpp] view
plain copy

 

  1. if (fork() == 0){  
  2.     //child process  
  3.     if (execl(“/usr/bin/echo”,”echo”,”executed by execl” ,NULL) <0 ){  
  4.         perror(“error on exec”);  
  5.         exit(0);  
  6.     }  
  7. }else{  
  8.     //parent process  
  9.     wait(&childpid);  
  10.     printf(“execv done\n\n”);  
  11. }  

 

#include <unistd.h>

在exec函数族中,后缀l、v、p、e指定函数将享有某种操作能力:

2.5 execlp 函数

 

[cpp] view
plain copy

 

  1. if (fork() == 0){  
  2.     //child process  
  3.     if (execlp(“echo”,”echo”,”executed by execlp” ,NULL) <0 ){  
  4.         perror(“error on exec”);  
  5.         exit(0);  
  6.     }  
  7. }else{  
  8.     //parent process  
  9.     wait(&childpid);  
  10.     printf(“execlp done\n\n”);  
  11. }  

 

 

后缀操作能力

2.6 execle 函数

 

[cpp] view
plain copy

 

  1. if (fork() == 0){  
  2.     //child process  
  3.     char * env[] = {“PATH=/home/gateman”, “USER=lei”, “STATUS=testing”, NULL};  
  4.     if (execle(“/usr/bin/env”,”env”,NULL,env) <0){  
  5.         perror(“error on exec”);  
  6.         exit(0);  
  7.     }  
  8. }else{  
  9.     //parent process  
  10.     wait(&childpid);  
  11.     printf(“execle done\n\n”);  
  12. }  

 

 

 输出:

澳门金沙国际 11

 

 

int glob = 6;

l希望接受以逗号分隔的参数列表,列表以NULL指针作为完成标志

3. fork() 和exec 函数与system()函数比较

     见到上面execvp函数的输出. 你会意识 exec函数只是系统调用,
它是不匡助管线处理的

     而system()函数是支撑的.   他的里边会自行fork()
1个子进度,不过成效没有fork() 和 exec同盟使用好.

 

     可是exec 帮衬实施脚本. 
所以不需求管线处理的吩咐或者脚本可以行使fork() 和 exec函数来执行.

 

 

char buf[] = “a write to
stdout\n”;

v希望接受到一个以NULL结尾的字符串数组的指针

4. 利用 fwrite() ,fork() 和exec 函数 替代system()函数.

 

     下边讲过了, 即便exec函数不接济管线, 而且命令参数复杂,
不过它支持实施脚本啊, 所以我们得以采纳fwrite将
有管线处理的通令写入1个脚本中, 然后接纳exec函数来执行那些脚本.

     上面会编写1个base_exec(char *) 函数, 接收1个字符串参数,  
然后执行它.

 

      那里只会大体写出那几个函数的逻辑步骤:

      1. 利用getuid函数得到当前的pid,  然后使用pid得到当前唯一的文书名,
防止因为同样程序同时履行发生争辩!

      2.  利用fwrite函数在 /tmp/上边  建立1个方面文件名的台本文件.    
因为/tmp/ 任何用户都得以读写啊

     3.  把命令参数写入脚本

     4. 使用fork() 和 exec() 执行那几个本子

     5. 有亟待的话当exec执行完, 记录日志.

 

     上边就是i代码:

头文件:

base_exec.h

 

[cpp] view
plain copy

 

  1. #ifndef __BASE_EXEC_H_  
  2. #define __BASE_EXEC_H_  
  3.   
  4.     int base_exec(char *) ;  
  5.   
  6. #endif /* BASE_EXEC_H_ */  

源文件:

 

base_exec.c

 

[cpp] view
plain copy

 

  1. #include “base_exec.h”  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. #include <string.h>  
  5. #include <unistd.h>  
  6. #include <time.h>  
  7.   
  8. #define LOGFILE “/home/gateman/logs/c_exec.log”  
  9.   
  10. int base_exec(char * pcmd){  
  11.     FILE * pf;  
  12.     pid_t pid = getpid();  
  13.     char pfilename[20];  
  14.     sprintf(pfilename, “/tmp/base_exec%d.sh”,pid);  
  15.   
  16.     pf=fopen(pfilename,”w”); //w is overwrite, a is add  
  17.     if (NULL == pf){  
  18.         printf(“fail to open the file base_exec.sh!!!\n”);  
  19.         return -1;  
  20.     }  
  21.   
  22.     fwrite(“#!/bin/bash\n”, 12, 1, pf);  
  23.     fwrite(pcmd, strlen(pcmd),1, pf);  
  24.     fwrite(“\n”, 1,1, pf);  
  25.   
  26.     fclose(pf);  
  27.   
  28.     if (fork() ==0 ){  
  29.         //child processj  
  30.         char * execv_str[] = {“bash”, pfilename, NULL};  
  31.         if (execv(“/bin/bash”,execv_str) < 0){  
  32.             perror(“fail to execv”);  
  33.             exit(-1);  
  34.         }  
  35.     }else{  
  36.         //current process  
  37.         wait();  
  38.         pf=fopen(LOGFILE,”a”);  
  39.   
  40.         if (NULL == pf){  
  41.             printf(“fail to open the logfile !!!\n”);  
  42.             return -1;  
  43.         }  
  44.         time_t t;  
  45.         struct tm * ptm;  
  46.         time(&t);  
  47.         ptm  = gmtime(&t);  
  48.         char cstr[24];  
  49.         sprintf (cstr, “time: %4d-%02d-%02d %02d:%02d:%02d\n”, 1900+ptm->tm_year,ptm->tm_mon,ptm->tm_mday,ptm->tm_hour,ptm->tm_min,ptm->tm_sec);  
  50.         fwrite(cstr, strlen(cstr),1, pf);  
  51.   
  52.         int uid = getuid();  
  53.         sprintf(cstr, “uid: %d\ncommand:\n”,uid);  
  54.         fwrite(cstr, strlen(cstr),1, pf);  
  55.   
  56.         fwrite(pcmd, strlen(pcmd),1, pf);  
  57.         fwrite(“\n\n\n”, 3,1, pf);  
  58.         fclose(pf);  
  59.         remove(pfilename);  
  60.         return 0;  
  61.     }  
  62.     return 0;  
  63. }  

 

p是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件

int main()

e函数传递指定参数envp,允许改变子进度的环境,无后缀e时,子进度使用当前先后的条件

{

int main(int argc, char *argv[])

int
var;

{

int
pid;

//以NULL结尾的字符串数组的指针,适合包括v的exec函数参数

 

char *arg[] = {“ls”, “-a”, NULL};

var =
88;

/**

 

* 创造子进度并调用函数execl

if(write(STDOUT_FILENO,buf,sizeof(buf) -1) != sizeof(buf)
-1)

* execl 中希望接受以逗号分隔的参数列表,并以NULL指针为了却标志

{

*/

perror(“fail
to write”);

if( fork() == 0 )

return
-1;

{

}

// in clild

 

printf( “1————execl————\n” );

printf(“before fork\n”);

if( execl( “/bin/ls”, “ls”,”-a”, NULL ) == -1 )

 

{

if((pid =
fork()) < 0)

perror( “execl error ” );

{

exit(1);

perror(“fail
to fork”);

}

return
-1;

}

}else 

/**

if(pid ==
0)

*创设子进度并调用函数execv

{

*execv中希望接受一个以NULL结尾的字符串数组的指针

glob
++;

*/

var
++;

if( fork() == 0 )

}else{

{

sleep(2);

// in child

}

printf(“2————execv————\n”);

 

if( execv( “/bin/ls”,arg) < 0)

printf(“pid
= %d,glob = %d,var = %d\n”,getpid(),glob,var);

{

exit(0);

perror(“execv error “);

}

exit(1);

 

}

运转结果:

}

澳门金沙国际 12

/**

从地方能够见到,因为子进度和父进度拥有独立的大体内存空间,所以当子进程对拷贝来的数码做修改的时候,并没有影响到父进度。

*创设子进度并调用 execlp

 

*execlp中

注意:

*l希望接受以逗号分隔的参数列表,列表以NULL指针作为完毕标志

       
1.相似的话,fork之后父进度先实施或者子进度先执行是不确定的。那取决内核所使用的调度算法。

*p是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件

        

*/

       
2.从上边可以看来三遍的周转结果分歧。我们清楚write函数是不带缓存的。因为在fork从前调用write,所以其数额写到标准输出五回。不过,标准
I/O库是带缓存的。倘若标准输出连到终端设备,则它是行缓存的,否则它是全缓存的。当以交互方式运行该程序时,只得到printf输出的行三次,其原因是正统输出缓存由新行符刷新。可是当将业内输出重新定向到一个文本时,却收获printf输出游一遍。其缘由是,在fork以前调用了printf四回,当调用fork时,该行数据仍在缓存中,然后在父进度数据空间复制到子进程中时,该缓存数据也被复制到子进程中。于是当场父、子进度各自有了带该行内容的缓存。在exit从前的首个printf将其数额拉长到现存的缓存中。当每个进度终止时,其缓存中的内容被写到相应文件中。

if( fork() == 0 )

 

{

 

// in clhild

实例 2:

printf(“3————execlp————\n”);

 

if( execlp( “ls”, “ls”, “-a”, NULL ) < 0 )

#include <stdio.h>

{

#include <stdlib.h>

perror( “execlp error ” );

#include <unistd.h>

exit(1);

 

}

int glob = 6;

}

 

/**

int main()

*创制子里程并调用execvp

{

*v 望接收到一个以NULL结尾的字符串数组的指针

int
var;

*p
是一个以NULL结尾的字符串数组指针,函数能够DOS的PATH变量查找子程序文件

int
pid;

*/

 

if( fork() == 0 )

var =
88;

{

 

printf(“4————execvp————\n”);

printf(“father:\n”);

if( execvp( “ls”, arg ) < 0 )

printf(“&glob = %p\n”,&glob);

{

printf(“&var
= %p\n”,&var);

perror( “execvp error ” );

printf(“__________________________________\n”);

exit( 1 );

 

}

if((pid =
fork()) < 0)

}

{

/**

perror(“fail
to fork”);

*创制子进度并调用execle

return
-1;

*l 希望接受以逗号分隔的参数列表,列表以NULL指针作为完毕标志

 

*e
函数传递指定参数envp,允许改变子进度的条件,无后缀e时,子进程使用当前先后的条件

}else 

*/

if(pid ==
0)

if( fork() == 0 )

{

{

printf(“child var value not change\n:”);

printf(“5————execle————\n”);

printf(“&glob = %p\n”,&glob);

if( execle(“/bin/ls”, “ls”, “-a”, NULL, NULL) == -1 )

printf(“&var
= %p\n”,&var);

{

 

perror(“execle error “);

glob
++;

exit(1);

var
++;

}

 

}

printf(“__________________________________\n”);

/**

printf(“child var value change:\n”);

*创造子进度并调用execve

printf(“&glob = %p\n”,&glob);

* v 希望接受到一个以NULL结尾的字符串数组的指针

printf(“&var
= %p\n”,&var);

* e
函数传递指定参数envp,允许改变子进度的条件,无后缀e时,子进度使用当前程序的环境

}

*/

 

if( fork() == 0 )

exit(0);

{

}

printf(“6————execve———–\n”);

 

if( execve( “/bin/ls”, arg, NULL ) == 0)

运转结果如下:

{

澳门金沙国际 13

perror(“execve error “);

   从上边可以看出,按照copy-on-write的思维,在子进度中,改变父进度的多少时,会先
复制父进度的多少修然后再改,从而达到子进度对数据的改动不影响父进度。不过我们发现,复制的上下,其值的地方都是同等的。为啥吗?子进程拷贝的时候也拷贝了父进度的虚拟内存”页”,那样他们的虚拟地址都一律,可是对应不一样的情理内存空间。

exit(1);

 

}

二、copy-on-write工作原理

}

 

return EXIT_SUCCESS;

   
假诺进程A创造子进度B,之后进度A和经过B共享A的地点空间,同时该地方空间中的页面全体被标识为写尊崇。此时B若写address的页面,由于写珍贵的原故会挑起写极度,在那多少个处理中,内核将address所在的丰盛写爱戴页面复制为新的页面,让B的address页表项指向该新的页面,新页面可写。而A的address页表项依旧指向非常写尊崇的页面。然后当B在走访address时就会直接访问新的页面了,不会在访问到哪些写爱护的页面。当A试图写address所在的页面时,由于写爱慕的原因而时也会挑起尤其,在越发处理中,内核借使发现该页面唯有一个有着进度,此种情形下也就是A,则直接对该页面打消写尊崇,此后当A再拜访address时不会在有写爱惜错误了。如若此时A又成立子进度C,则该address所在的页面又被安装为写珍贵,拥有进度A和C,同时其余页面例如PAGEX依旧保持写保养,只是有所进程A、B和C。假使此时A访问PAGEX,则丰盛处理会成立一个新页面并将PAGEX中的内容复制到该页面,同时A相应
的pte指向该新页面。假使此时C也拜会PAGEX,也会复制新页面并且让C对应的pte指向新页面。即使B再拜访PAGEX,则是因为此时PAGEX只有一个有所进度B,故不再复制新页面,而是一贯废除该页面的写珍视,由于B的pte本来就是平昔指向该页面,所以无必要在做任何工作。

}

 

三、exit和_exit

 

(1)正常终止:

    (a)在main函数内实施return语句。那等效于调用exit。

    (b)调用exit函数

    (c)调用_exit系统调用函数

 

(2)分外终止:

    (a)调用abort。它发生SIGABRT信号,所以是一种非常终止的一种特列。

   
(b)当进程接收到某个信号时。例如,进度越出其地址空间访问存储单元,或者除以0,内核就会为该进度爆发相应的信号。

 

小心:不管进度怎么着终止,最后都会举行内核中的同一段代码。这段代码为对应进度关闭所有打开描述符,释放它所利用的存储器等。

 

 

exit和_exit的不同

澳门金沙国际 14

_exit()函数的作用最为简练:直接进程停止运行,清除其行使的内存空间,并销毁其在基础中的各样数据结构;

 

exit()函数与_exit()函数最大的分别就在于exit()函数在调用exit系统调用以前要反省文件的打开状态,把公文缓冲区中的内容写回文件,就是”清理I/O”缓冲。

 

探究 1._exit()

 

//_exit(0)   exit(0)  return 0

澳门金沙国际 15

编译运行结果:

澳门金沙国际 16

从地点大家看出,test.txt的始末为空.为什么吧?因为标准I/O函数是带缓存的,举办fputs的时候是先向缓存中写的,只有当缓存满的时候才会刷新的缓冲区的。从上述我们发现,当进程退出时,执行_exit()函数并没有刷新缓冲区的数量,而是径直终止进度的。

 

探究2.exit()

澳门金沙国际 17

编译运行结果:
澳门金沙国际 18

从地点大家得以见见,当exit()函数停止进度的时候,对缓存实行了处理,把缓存的数量写到了磁盘文件中。

 

探究3.return

 

由读者自己形成,其实return语句用在main函数中,和exit是一致的。可是大家领略,return再次回到的值是给调用者的,它代表着一个函数的扫尾。

 

四、exec函数族

 

exec.c  调用exec其中的一个函数; gcc exec.c -o exec; ./exec

exec函数族提供了一种在经过中启动另一个程序执行的艺术。它可以依照指定的文书名或目录名找到可执行文件,并用它来代替原调用经过的数据段、代码段、和堆栈段。在推行完之后,原调用经过的始末除了进度号外,其他一切都被轮换了。

 

可执行文件既可以是二进制文件,也得以是任何Linux下可进行的本子文件。

 

几时使用?

 

当进程认为自己不能够再为系统和用户做其他进献了就足以调用exec函数族中的函数,让自己执行新的主次。

当前目录: 可实施程序A    B(1,2,3)     

要是某个进度想同时实行另一个先后,它就可以调用fork函数创造子进程,然后在子进度中调用任何一个exec函数。那样看起来就类似通过履行应用程序而爆发了一个新进度一样。

 

execl(“./B”,”B”,”1″,”2″,”3″,NULL);

char *const envp[] = {“B”,”1″,”2″,”3″,NULL}

 

execv(“./B”,envp);

澳门金沙国际 19

澳门金沙国际 20
澳门金沙国际 21

瞩目:不管file,第三个参数必须是可执行文件的名字

 

可执行文件查找方法

表中的前三个函数的搜寻方法都是指定完整的文件目录路劲,而最终五个函数(以p结尾的函数)可以只交给文件名,系统会自动从环境变量”$PATH”所涵盖的路径中开展查找。

 

参数表传递格局

两种格局:一个一个点数和将具备参数通过指针数组传递

一函数名的第5个假名按来分化,字母”l”(list)的象征一个一个列举形式;字母”v”(vector)的意味将具有参数构造成指针数组传递,其语法为char
*const argv[]

 

环境变量的选取

exec函数族可以默许使用系统的环境变量,也足以流传指定的环境变量。那里,以”e”(Envirment)结尾的五个函数execle、execve就足以在envp[]中传递当前进度所采纳的环境变量。

 

采纳的界别

可执行文件查找方法

参数表传递格局

环境变量的施用

澳门金沙国际 22

案例一execl

 

#include <stdio.h>

#include <unistd.h>

 

int main(int argc,char *argv[])

{

printf(“start to execl.\n”);

if(execl(“/bin/ls”,”ls”,NULL) < 0)

{

perror(“Fail
to execl”);

return
-1;

}

printf(“end
of execl.\n”);

 

return
0;

}

 

运行结果如下:

澳门金沙国际 23

案例二、execlp

#include <stdio.h>

#include <unistd.h>

 

int main(int argc,char *argv[])

{

printf(“start to execl.\n”);

if(execlp(“ls”,”ls”,”-l”,NULL) < 0)

{

perror(“Fail
to execl”);

return
-1;

}

printf(“end
of execl.\n”);

 

return
0;

}

 

运转结果:

澳门金沙国际 24

案例三、execle

 

#include <stdio.h>

#include <stdlib.h>

 

int main(int argc,char *argv[])

{

if(getenv(“B”) == NULL)

{

printf(“fail
to getenv B.\n”);

}else{

printf(“env
B = %s.\n”,getenv(“B”));

}

 

if(getenv(“C”) == NULL)

{

printf(“fail
to getenv C.\n”);

}else{

printf(“env
C = %s.\n”,getenv(“C”));

}

 

if(getenv(“PATH”) == NULL)

{

printf(“fail
to getenv PATH.\n”);

 

}else{

printf(“env
PATH = %s.\n”,getenv(“PATH”));

}

return
0;

}

 

运作结果:

澳门金沙国际 25

#include <unistd.h>

 

int main(int argc,char *argv[])

{

printf(“start to execle.\n”);

char *
const envp[] = {“B=hello”,NULL};

 

if(execle(“./A.out”,”A.out”,NULL,envp) < 0)

{

perror(“Fail
to execl”);

return
-1;

}

 

printf(“end
of execl.\n”);

 

return
0;

}

 

运行结果:

澳门金沙国际 26

案例四:execv

 

#include <stdio.h>

#include <unistd.h>

#include <errno.h>

#include <stdlib.h>

 

int main()

{

char *
const arg[] = {“ps”, “-ef”, NULL};

//if
(execl(“/bin/ps”, “ps”, “-ef”, NULL) < 0)

if
(execv(“/bin/ps” ,arg) < 0)

{

perror(“execl”);

exit(-1);

}

 

while
(1);

 

return
0;

}

 

 

五、进度的制造vfork()函数

       

     vfork与fork一样都成立一个子历程,然则它并不将父进度的地点空完全复制到子进度中,因为子进度会马上调用exec(或exit)于是也就不会存、访该地点空间。不过在子进度调用exec或exit此前,它在父进度的长空中运作。

    

   
vfork和fork之间的另一个分别是:vfork保障子进度先运行,在它调用exec或exit之后
父进度才可能被调度运行。(即便在调用那五个函数之前子进度信赖于父进度的进一步动作,则会促成死锁)

 

探究1.vfork()

澳门金沙国际 27

编译运行:

澳门金沙国际 28

因为我们精晓vfork有限辅助子进度先运行,子进程运行为止后,父进度才开始运行。所以,第一回打印的是子进度的打印的新闻,可以观察var值变成了89。子进度为止后,父进度运行,父进度首先打印fork调用再次回到给他pid的值(就是子进程pid)。以上大家得以看来,vfork创设的子进度和父进度运行的地方空间相同(子进程改变了var
值,父进度中的var值也展开了变更)。

 

在意:假诺实进度中施行的是exec函数,那就是典型的fork的copy-on-wirte。

 

 

五、wait和waitpid

 

wait函数:调用该函数使进度阻塞,直到任一个子进程停止或者是该进度接收到一个信号甘休。假如该进程没有子进程或者其子进度一度终结,wait函数会立即回到。

 

waitpid函数:作用和wait函数类似。能够指定等待某个子进度截止以及等待的办法(阻塞或非阻塞)。

wait函数

#include <sys/types.h>

#include <sys/waith.h>

 

pid_t  wait(int  *status);

 

函数参数:

 

status是一个整型指针,指向的目的用来保存子进度退出时的动静。

 

A.status若为空,表示忽略子进度退出时的图景

 

B.status若不为空,表示保存子进度退出时的境况

 

子进度的利落状态可由Linux中一些一定的宏来测定。

 

 

案例一、

 

#include <stdio.h>

#include <stdlib.h>

 

int main()

{

int
pid;

 

if((pid =
fork()) < 0)

{

perror(“Fail
 to fork”);

return
-1;

}else if(pid
== 0){

printf(“child exit now.\n”);

exit(0);

}else{

while(1);

}

 

exit(0);

}

 

运行结果:

澳门金沙国际 29

从上述方可看看,子进度正常退出时,处于僵尸态。那几个时候子进程的pid,以及内核栈资源并不曾自由,那样是不客观的,我们理应防止僵尸进度。即使父进度先退出呢,子进程又会如何?

 

#include <stdio.h>

#include <stdlib.h>

 

int main()

{

int
pid;

 

if((pid =
fork()) < 0)

{

perror(“Fail
 to fork”);

return
-1;

}else if(pid
== 0){

printf(“child running now – pid : %d.\n”,getpid());

while(1);

}else{

getchar();

printf(“Father exit now – pid : %d.\n”,getpid());

exit(0);

}

 

}

澳门金沙国际 30

从地点可以观看,如若父进程先退出,则子进度的父进度的ID号变为1,也就是说当一个子进度的父进度退出时,这几个子进度会被init进程自动收养。

 

案例二、利用wait等待回收处于僵尸态的子进程

 

#include <stdio.h>

#include <stdlib.h>

 

int main()

{

int
pid;

 

if((pid =
fork()) < 0)

{

perror(“Fail
 to fork”);

return
-1;

}else if(pid
== 0){

printf(“child runing now – pid : %d.\n”,getpid());

getchar();

printf(“child exiting now – pid : %d.\n”,getpid());

exit(0);

}else{

printf(“Father wait zombie now – pid : %d.\n”,getpid());

wait(NULL);

printf(“Father exiting now – pid : %d.\n”,getpid());

exit(0);

}

 

}

 

一贯不输入字符前:

澳门金沙国际 31

输入字符后:

澳门金沙国际 32

那会儿咱们没有发觉僵尸进度,当子进程退出时,父进程的wait回收了子进度未释放的资源。

 

案例三、获取进程退出时的景观

 

#include <stdio.h>

#include <stdlib.h>

 

int main()

{

int
pid;

int
status;

 

if((pid =
fork()) < 0)

{

perror(“Fail
to fork”);

exit(-1);

}else if(pid
== 0){

printf(“create child process : %d.\n”,getpid());

printf(“child process : %d calling exit(7).\n”,getpid());

exit(7);

}else{

 

if((pid =
fork()) < 0 ){

perror(“Fail
to fork”);

exit(-1);

}else if(pid
== 0){

printf(“create child process : %d.\n”,getpid());

while(1);

}else{

 

while((pid =
wait(&status)) != -1)

{

if(WIFEXITED(status))

{

printf(“child process %d is normal exit,the value is
%d.\n”,pid,WEXITSTATUS(status));

 

}else
if(WIFSIGNALED(status)){

printf(“child process %d is exit by signal,the signal num is
%d.\n”,pid,WTERMSIG(status));

}else{

printf(“Not
know.\n”);

}

}

}

}

 

printf(“All
child process is exit,father is exit.\n”);

exit(0);

}

澳门金沙国际 33

给进度15494发个信号

澳门金沙国际 34

程序运行结果:

澳门金沙国际 35

从上述探讨可以领悟,每当子进程停止后,wait函数就会回去哪个子进度停止的pid。即使没有子进度存在,wait函数就回去-1。

 

函数重返值:

成功:子进程的经过号

失败:-1

#include <sys/types.h>

#include <sys/wait.h>

 

pid_t      waitpid(pid_t  pid,int *status,int options);

 

参数:

 

1.在父进度中创立五个子进度(A   B)

2.A进度打印”child process %d exit”,调用exit(2),甘休

3.B进度一向运转

 

在意:父进度调用while(waitpid(-1,&status,WUNTRACED)   != -1 )

澳门金沙国际 36

status:同wait

 

options:

 

WNOHANG,若由pid指定的子进度并不马上可用,则waitpid不阻塞,此时回去值为0

WUNTRACED,若某完毕辅助作业控制,则由pid指定的任一子进度情形已暂停,且其情状自暂停以来还没告诉过,则赶回其状态。

 

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

 

返回值

正规:为止的子进度的历程号

行使拔取WNOHANG且从未子进度甘休时:0

调用出错:-1

 

案例一、

 

#include <stdio.h>

#include <stdlib.h>

 

int main()

{

int
pid;

int
status;

 

if((pid =
fork()) < 0)

{

perror(“Fail
to fork”);

exit(-1);

}else if(pid
== 0){

printf(“create child process : %d.\n”,getpid());

printf(“child process : %d calling exit(7).\n”,getpid());

exit(7);

}else{

 

if((pid =
fork()) < 0 ){

perror(“Fail
to fork”);

exit(-1);

}else if(pid
== 0){

printf(“create child process : %d.\n”,getpid());

while(1);

}else{

 

while((pid =
wait(&status)) != -1)

{

if(WIFEXITED(status))

{

printf(“child process %d is normal exit,the value is
%d.\n”,pid,WEXITSTATUS(status));

 

}else
if(WIFSIGNALED(status)){

printf(“child process %d is exit by signal,the signal num is
%d.\n”,pid,WTERMSIG(status));

}else{

printf(“Not
know.\n”);

}

}

}

}

 

printf(“All
child process is exit,father is exit.\n”);

exit(0);

}

 

 

程序运行结果:

澳门金沙国际 37

使用ps -aux结果

澳门金沙国际 38

从上述可以见见,子进度15783脱离时,父进度并不曾回收它的资源,此时得以见到它地处僵尸态。

鉴于父进度调用waitpid等待子进度15784脱离,此时那些进度还没退出,看可以见见父进度处于可间歇的睡觉状态。

 

俺们给子进度15784发个信号,在探访结果

澳门金沙国际 39

程序运行结果:

澳门金沙国际 40

可以见到当waitpid指定等待的历程退出时,waitpid马上回去,此时父进程退出。

 

案例二、

 

#include <stdio.h>

#include <stdlib.h>

 

int main()

{

int
pid;

int
status;

 

if((pid =
fork()) < 0)

{

perror(“Fail
to fork”);

exit(-1);

}else if(pid
== 0){

printf(“create child process : %d.\n”,getpid());

printf(“child process : %d calling exit(7).\n”,getpid());

exit(7);

}else{

 

if((pid =
fork()) < 0 ){

perror(“Fail
to fork”);

exit(-1);

}else if(pid
== 0){

printf(“create child process : %d.\n”,getpid());

while(1);

}else{

sleep(2);

printf(“Father wait child %d exit.\n”,pid);

while((pid =
waitpid(pid,NULL,WNOHANG)))

{

printf(“The
process %d is exit.\n”,pid);

}

 

printf(“The
process %d is exit.\n”,pid);

}

}

 

exit(0);

}

澳门金沙国际 41

从地点商量大家得以观看,假使有子进度处于僵尸态,waitpid(pid,NULL,WNOHANG)立即处理后回到,如若没有子进度处于僵尸态,此时waitpid(pid,NULL,WNOHANG)也会及时回去,而不打断,此时回来值为0。

 

案例商讨三、

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

int main(int argc,char *argv[])

{

int
pid;

int
status;

 

if((pid =
fork()) < 0)

{

perror(“Fail
to fork”);

exit(-1);

}else if(pid
== 0){

printf(“create child process : %d.\n”,getpid());

printf(“child process in proces group %d.\n”,getpgid(0));

printf(“child process : %d calling exit(7).\n”,getpid());

exit(7);

}else{

 

if((pid =
fork()) < 0 ){

perror(“Fail
to fork”);

exit(-1);

}else if(pid
== 0){

sleep(3);

printf(“create child process : %d.\n”,getpid());

setpgid(0,0); //让子进程属于以相好ID作为组的进程组

printf(“child process in proces group %d.\n”,getpgid(0));

printf(“child process : %d calling exit(6).\n”,getpid());

}else{

while(pid =
waitpid(0,NULL,0))

{

printf(“Father wait the process %d is exit.\n”,pid);

}

}

}

 

exit(0);

}

 

运行结果:

澳门金沙国际 42

当在父进中创造子进度时,父进程和子进程都在以父进度ID号为组的进度组。以上代码中,有一个子历程改变了和谐所在的经过组.

此时waitpid(0,NULL,0);只好处理以父进度ID为组的经过组中的经过,可以看到第一身长进度截止后waitpid函数回收了它未释放的资源,而第三个子进程则处于僵尸态

澳门金沙国际 43

 

 

from:

相关文章