【美高梅手机版4858】exec函数的利用,进度的创办及有关api

By admin in 美高梅手机版4858 on 2019年1月30日

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() 函数

美高梅手机版4858 1

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

美高梅手机版4858 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一样)。

美高梅手机版4858 3

 

            为啥呢, 因为fork()函数将以此程序分叉了呀,  见上面的图解:

美高梅手机版4858 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,
我就让认为它是子进度, 否则是主程序. 
那么自己就能够让那两条长河输出差别的音信了.

 

       

          输出音信如下图:

美高梅手机版4858 5

 

          可以看看 子程序和主程序分别出口了8条差距的新闻, 
不过它们并不是规则交替输出的, 因为它们两条长河是相互平行影响的,
什么人的手快就在屏幕上先输出,  每便运行的结果都有可能不一致哦.

 

        上边是图解:

美高梅手机版4858 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. 【美高梅手机版4858】exec函数的利用,进度的创办及有关api。  
  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. }  

       再看看输出:

 

美高梅手机版4858 7

 

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

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

 

流程图:

美高梅手机版4858 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. }  

 

输出:

 

美高梅手机版4858 9

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

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

     流程图:

  美高梅手机版4858 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. }  

 

 

 输出:

美高梅手机版4858 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);

 

}

运行结果:

}

美高梅手机版4858 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)

运作结果如下:

{

美高梅手机版4858 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的不同

美高梅手机版4858 14

_exit()函数的意义最为简单:直接进度甘休运作,清除其采纳的内存空间,并销毁其在根本中的种种数据结构;

 

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

 

探究 1._exit()

 

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

美高梅手机版4858 15

编译运行结果:

美高梅手机版4858 16

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

 

探究2.exit()

美高梅手机版4858 17

编译运行结果:
美高梅手机版4858 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);

美高梅手机版4858 19

美高梅手机版4858 20
美高梅手机版4858 21

只顾:不管file,首个参数必须是可执行文件的名字

 

可执行文件查找方法

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

 

参数表传递方式

二种办法:一个一个点数和将装有参数通过指针数组传递

一函数名的第5个字母按来不同,字母”l”(list)的代表一个一个点数情势;字母”v”(vector)的意味将富有参数构造成指针数组传递,其语法为char
*const argv[]

 

环境变量的接纳

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

 

利用的分别

可执行文件查找方法

参数表传递情势

环境变量的拔取

美高梅手机版4858 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;

}

美高梅手机版4858 , 

运作结果如下:

美高梅手机版4858 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;

}

 

运行结果:

美高梅手机版4858 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;

}

 

运转结果:

美高梅手机版4858 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;

}

 

运行结果:

美高梅手机版4858 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()

美高梅手机版4858 27

编译运行:

美高梅手机版4858 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);

}

 

运行结果:

美高梅手机版4858 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);

}

 

}

美高梅手机版4858 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);

}

 

}

 

没有输入字符前:

美高梅手机版4858 31

输入字符后:

美高梅手机版4858 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);

}

美高梅手机版4858 33

给进度15494发个信号

美高梅手机版4858 34

程序运行结果:

美高梅手机版4858 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 )

美高梅手机版4858 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);

}

 

 

程序运行结果:

美高梅手机版4858 37

使用ps -aux结果

美高梅手机版4858 38

从以上可以看到,子进程15783退出时,父进度并不曾回收它的资源,此时可以看看它地处僵尸态。

是因为父进程调用waitpid等待子进度15784脱离,此时以此历程还没退出,看可以看看父进度处于可暂停的睡眠情形。

 

俺们给子进程15784发个信号,在探视结果

美高梅手机版4858 39

程序运行结果:

美高梅手机版4858 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);

}

美高梅手机版4858 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);

}

 

运作结果:

美高梅手机版4858 42

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

那时waitpid(0,NULL,0);只好处理以父进度ID为组的历程组中的历程,能够观察首个头进度为止后waitpid函数回收了它未释放的资源,而第二身材进度则处于僵尸态

美高梅手机版4858 43

 

 

from:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 美高梅手机版4858 版权所有