Day29、加载子进程、Vfork创建子进程、进程间通讯(管道)

ubuntu内核的版本:

2.4       、 2.5  、 2.6

 

一、            在子进程里执行新的程序

1、 在fork创建子进程的时候,子进程的PCB是从父进程复制过来的,代码段共享。子进程和父进程各自拥有自己的数据段、堆栈空间。(代码段是只读的,只能读,不能写)

这时,可以在子进程的进程空间里,加载另一个新程序。

(首先父进程用fork产生一个子进程,子进程和父进程只有pid不一样,然后再将进程加载到子进程的空间里)

a.out的执行来说明: bash调用了fork,创建了一个子进程,为了让子进程执行一个新的程序,这时候,将程序加载到了内存,然后和虚拟地址空间一一做映射。这时,程序执行起来就是一个新的进程。    在bash的所有命令都是一样(ls  cd 等等)

tarena@tarena-virtual-machine:~/day28$./a.out

tarena@tarena-virtual-machine:~/day28$pstree

 

     ├─gnome-terminal─┬─bash───a.out

     │                ├─bash───pstree

       加载程序:

       execve(2):将进程加载到子进程的空间里

   #include <unistd.h>

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

功能:执行程序

参数:

返回值:

execl

execlp

execle

execv

execvp

execvpe    

返回值:只有出错的时候才有返回值,返回值为-1,且errno被设置

#include<unistd.h>

extern char**environ;

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

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

int execlp(constchar *file, const char *arg, ...);

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

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

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

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

 

execl  l:代表列表,就是将参数看成列表,每个参数都写出来

execv  v:就是将参数看成一个整体。

execvp  p:在PATH指定的路径下找文件

execvpe  e:environ

       举例:

       pid:子进程   ppid:父进程

int main(intargc, char *argv[])

int main(intargc, char *argv[],char *env[])

char * const ps_argv[]={“ps”,”-o”,”pid,ppid,comm”,NULL};

char *const ps_envp[]={“PATH=/bin:/usr/bin”,”TERM=console”,NULL};

execl(“/bin/ps”,”ps”,”-o”,”pid,ppid,comm”,NULL);

execle(“/bin/ps”,”ps”,”-o”,”pid,ppid,comm”,NULL,ps_envp);

execv(“/bin/ls”,ps_argv);

execvp(“ps”,ps_argv);

execve(“/bin/ps”,ps_argv,ps_envp);

execlp(“ps”,”ps”, ”-o”,”pid,ppid,comm”,NULL);

execvpe

(ps命令:查看进程的详细信息 pid:当前进程id,ppid:父进程的id)

tarena@tarena-virtual-machine:~/day29$ ps -o pid,ppid,comm

  PID  PPID COMMAND

 2531  2520 bash

 2681  2531 ps

 

举例:simple.c

  1 #include<stdio.h>

  2 #include<unistd.h>

  3 int main(){

  4     int ret;

  5     printf("pid=%d\n",getpid());

  6     //当前进程的image被ps的image替换

  7    ret=execl("/bin/ps","ps","-o","pid,ppid,comm",NULL);

  8     //出错的时候执行以下代码

  9     if(ret==1){

 10         perror("exec");

 11         return 0;

 12     }

 13     //下边的代码永远不会执行,此时新的子进程已经覆盖了当前进程,不会再往下执>    行原进程的内容

 14     printf("hahaha");

 15     return 0;

 16 }

 

pid=2768

  PID  PPID COMMAND

 2531  2520 bash

 2768  2531 ps

 

相当于这样查看

tarena@tarena-virtual-machine:~/day29$ ps -o pid,ppid,comm

  PID  PPID COMMAND

 2531  2520 bash

 2771  2531 ps

 

以上验证了这个实例验证了进程的pid不变,但是内容改变了

 

在子进程里执行新的程序:execl.c

  1 #include<stdio.h>

  2#include<sys/types.h>

  3 #include<sys/wait.h>

  4 #include<unistd.h>

  5

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

  7     pid_t pid;

  8

  9     pid=fork();

 10     if(pid<0){

 11         perror("fork");

 12         return 1;

 13     }

 14     if(pid==0){//子进程

 15        execl("/bin/ps","ps","-o","pid,ppid,comm",NULL);

 16     }

 17     else{//父进程

 18         等待子进程的结束,并回收子进程

 19         wait(NULL);

 20     }

 21     return 0;

 22 }

tarena@tarena-virtual-machine:~$ pstree

     ├─gnome-terminal─┬─bash───a.out

tarena@tarena-virtual-machine:~/day29$ ./a.out

  PID  PPID COMMAND

 2531  2520 bash

 3254  2531 a.out

 3255  3254 ps

 

二、            vfork创建子进程

fork是父进程复制出子进程,同时在内核给子进程复制出一模一样的PCB(文件描述符是PCB的一部分,所以父子进程有相同的文件描述符)

写时复制

vfork和fork的区别:

fork的时候,在内核里,复制父进程的PCB表。父进程和子进程各有各自的PCB

vfork的时候,在内核里,父进程和子进程共享一个PCB,采用写时复制技术

#include <sys/types.h>

#include <unistd.h>

pid_t vfork(void);

更多参见fork;  

举例:vfork.c

  1 #include<stdio.h>

  2#include<sys/types.h>

  3 #include<sys/wait.h>

  4 #include<unistd.h>

  5 #include<stdlib.h>

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

  7     pid_t pid;

  8

  9     pid=vfork();

 10     if(pid<0){

 11         perror("fork");

 12         return 1;

 13     }

 14     if(pid==0){//子进程

 15        printf("pid=%d\n",getpid());

 16        _exit(1);//要使用_exit(1)只结束子进程,如果使用exit(1),则结束整个程序

 17     }

 18     else{//父进程

 19        printf("ppid=%d\n",getpid());

 20     }

 21     return 0;

 22 }

tarena@tarena-virtual-machine:~/day29$ ./a.out

pid=3452

ppid=3451

注意:使用vfork创建的子进程中,使用_exit(2)结束子进程,不要使用exit(3)结束

 

三、简单的进程间通讯(管道)

pipe(2)

#include<unistd.h>

#include <unistd.h>

int pipe(int pipefd[2]);

使用管道通讯的两个进程需要具有亲属关系

父子进程或者兄弟进程

返回值:0代表成功

-1代表失败,erron被设置

参数:

pipefd:两个文件描述符

步骤:

第一步:调用pipe,创建两个文件描述符,用于管道通信

第二步:调用fork,创建子进程

第三步:父进程写,子进程读。就完成了管道的单向通信。

举例:子进程写,父进程读  pipe.c

注意:fd[0]指向管道的读端,fd[1]指向管道的写端

  1 #include<stdio.h>

  2 #include<unistd.h>

  3#include<sys/types.h>

  4 #include<sys/wait.h>

  5 int main(){

  6     int fd[2];

  7     char buf[8];

  8     pid_t pid;

  9     //调用pipe产生两个文件描述符

 10     pipe(fd);

 11     //创建子进程

 12     pid=fork();

 13     if(pid<0){

 14         perror("fork");

 15         return ;

 16     }

 17     if(pid==0){//子进程

 18         close(fd[0]);//关闭读端

 19         //向管道里写字符串

 20         write(fd[1],"tang",5);

 21     }

 22     else{//父进程

 23         close(fd[1]);//关闭写端

 24         wait(NULL);//等待子进程的结束

 25         read(fd[0],buf,5); //从管道里读出数据

 26         write(1,buf,5);//1表示打印到屏幕上

 27         write(1,"\n",1);

 28     }

 29     return 0;

 30 }

tarena@tarena-virtual-machine:~/day29$ ./a.out

tang

补充:pend.c

  1 #include<stdio.h>

  2 #include<unistd.h>

  3 int main(){

  4     pid_t pid;

  5     pid=fork();

  6     if(pid<0){

  7         perror("fork");

  8         return 1;

  9     }

 10     if(pid==0){

 11         sleep(5);

 12         printf("children..\n");

 13     }

 14     else{

 15         printf("parent..\n");

 16     }

 17     return 0;

 18 }

tarena@tarena-virtual-machine:~/day29$ ./a.out

parent..

tarena@tarena-virtual-machine:~/day29$ children..

上例说明:bash启动了父进程,父进程结束后,bash调用wait回收了父进程的退出信息。bash的提示信息就输出了,而此时,父进程创建的子进程还没有结束。等这个子进程sleep以后,信息就输出到bash提示信息以后。(bash只负责父进程的回收,这个父进程是bash的子进程)

进程小结:

1、  fork/vforl

2、  exec系列的函数,作用:将程序加载到内存空间里,用新的映像替换旧的映像

3、  wait/waitpid:用来回收进程的退出信息,检测进程怎么结束的

4、  管道用于进程间的通讯

5、  进程的退出。 exit(3)调用了_exit(2), exit(3)是对_exit(2)的封装

_exit(2)只是清除进程自己部分的空间,不清除父进程

调用exit(3)将父进程和子进程全部结束

6、  孤儿进程和僵尸进程

7、  bash   fork创建子进程,exec将新的image加载到子进程的空间里

 

 

 

 

四、信号的基本概念

作业:1、myls    2、myshell  实现bash的功能  

Vi myshell.c

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值