unix/linux创建新进程,父子进程详解,附有案例

来源:互联网 发布:战锤全面战争 知乎 编辑:程序博客网 时间:2024/06/09 14:38

fork()

用来创建进程fork(void)

在linux中所有进程都是由init进程直接或间接创建

成功:在父进程中将返回子进程的PID;子进程返回0,以区别父进程

失败:父进程中返回-1

复制代码
 1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4  5 int main(int argc,char *argv[]) 6 { 7         pid_t pid; 8         if((pid=fork())==-1) 9                 printf("fork error");10         printf("bye\n");11         printf("当前进程的进程号pid:%d\n当前进程的父进程号ppid:%d\n",getpid(),getppid());12         return 0;13         }         
复制代码

结果:

[root@sun PCB]# ps aux

root      3905  0.0  0.1 108468  1904 pts/0    S    Dec17   0:00 bash
[root@sun PCB]# ./fork 
bye
bye
当前进程的进程号pid:4570
当前进程的父进程号ppid:3905
pid=fork()中pid的值:4571  //在父进程中将返回子进程的PID
当前进程的进程号pid:4571
当前进程的父进程号ppid:4570
pid=fork()中pid的值:0  //子进程返回0,以区别父进程

子进程中的代码在fork返回位置执行;子进程创建成功之后,和父进程同时执行,竞争系统资源,谁先执行由调度算法决定。

父子进程

子进程会复制父进程的几乎所有信息:子进程复制父进程用户空间所有数据;

                 子进程复制父进程内核空间PCB中绝大多数数据;

一、文件流缓冲区的资源位于用户空间,所以全部复制。即如果流缓冲区中有临时信息,都会复制到子进程的用户空间流缓冲区中。

复制代码
 1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4  5 int main(int argc,char *argv[]) 6 { 7         pid_t pid; 8         printf("在fork之前,有回车\n"); 9         printf("在fork之前,没有回车,getpid()——pid=%d\t",getpid());10         pid=fork();11         if(pid==0)12                 printf("\nfork后创建的子进程getpid()——pid=%d\n",getpid());13         else14                 printf("\nfork后创建的父进程getpid()——pid=%d\n",getpid());15         }
复制代码

 

[root@sun PCB]# ./streamfork 
在fork之前,有回车
在fork之前,没有回车,getpid()——pid=5536    
fork后创建的父进程getpid()——pid=5536
在fork之前,没有回车,getpid()——pid=5536    
fork后创建的子进程getpid()——pid=5537

按照上面所说,子进程要在fork方法执行并返回某值后才会复制代码到子进程,子进程从返回值位置向后执行,不会执行之前的代码,但这段代码却输出了之前的代码,这就是复制了缓冲区的缘故。

之所以出现两次输出,有两方面原因,首先是跟printf的缓冲机制有关,我们在前面说过printf("%d",i)=fprintf(stdout,"%d",i),就是说printf函数输出某些内容时,操作系统仅仅是把该内容放到了stdout的缓冲队列里了,并没有立刻写到屏幕上。但是,只要看到有/n 则会立即刷新stdout,才能够马上打印了。

其次就是因为复制了缓冲区。由于父进程在fork前输出的第二个printf函数时没有回车,而输出流是带缓冲的,从而该信息缓存到用户空间,在fork创建子进程后,系统为子进程复制父进程数据空间以及标准输出缓冲区,子进程刷新了输出缓冲区,将数据输出。

 二、子进程复制父进程的数据段,BSS段,代码段,堆空间,栈空间,文件描述符,但是对于文件描述符关联的内核文件表项(即struct file结构体)则是采用共享的方式

 

复制代码
 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <fcntl.h> 6 #include <sys/types.h> 7  8 int main(int argc,char *argv[]) 9 {10         pid_t pid;11         int fd;12         int i=1;13         int status;14         char *ch1="hello";15         char *ch2="world";16         char *ch3="IN";17         if((fd=open("test.txt",O_RDWR|O_CREAT,0644))==-1)18         {19                 perror("parent open");20                 exit(EXIT_FAILURE);21                 }22         if(write(fd,ch1,strlen(ch1))==-1)23         {24                 perror("parent write");25                 exit(EXIT_FAILURE);26                 }27         if((pid=fork())==-1)28         {29                 perror("fork");30                 exit(EXIT_FAILURE);31                 }32         else if(pid==0)33         {34                 i=2;35                 printf("in chile\n");36                 printf("i=%d\n",i);37                 if(write(fd,ch2,strlen(ch2)));38                 perror("chile write");39                 return 0;40                 }41         else42         {43                 sleep(1);//等待子进程先执行44                 printf("in parent\n");45                 printf("i=%d\n",i);46                 if(write(fd,ch3,strlen(ch3)));47                 perror("parent write");48                 wait(&status);//等待子进程结束49                 return 0;50                 }51         }
复制代码

 

[root@sun PCB]# ./forkfilrstruct 
in chile
i=2
chile write: Success  

//在这里明显等待1s才出现in parent,即sleep()让父进程等待1s好让子进程完成写ch2的操作,1s后再写ch3
in parent
i=1
parent write: Success


[root@sun PCB]# cat test.txt 
helloworldIN

从test.txt的内容可以看出,父子进程对同一个文件操作,写入数据也不覆盖,即说明父子进程共享文件偏移,因此共享文件表项

而从变量i可以看出子进程赋值后父进程的i值不变,说明父子进程各自拥有这一变量的副本,互相不影响。

这里对wait函数稍加介绍:

wait(等待子进程中断或结束)

wait()会暂时停止进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值,由参数status 返回,而子进程的进程识别码也会一起返回。如果不在意结束状态值,则参数status 可以设成NULL。

 vfork()

vfolk()创建新进程时不复制父进程的地址空间,而是在必要的时候才申请新的存储空间,共享父进程的代码以及数据段等

复制代码
 1 #include<unistd.h> 2 #include<error.h> 3 #include<sys/types.h> 4 #include<stdio.h> 5 #include<stdlib.h> 6 int glob=6;    //已初始化全局变量,存放在数据段 7 int main() 8 { 9     int var;10     pid_t pid;11     var=88;    //局部变量,存放在栈12     printf("in beginning:\tglob=%d\tvar=%d\n",glob,var);13     if((pid=vfork())<0)14     {15         perror("vfork");16         exit(EXIT_FAILURE);17     }18     else if(pid==0)19     {20         printf("in child,modify the var:glob++,var++\n");21         glob++;22         var++;23         printf("in child:\tglob=%d\tvar=%d\n",glob,var);24         _exit(0);25     }26     else27     {    28         printf("in parent:\tglob=%d\tvar=%d\n",glob,var);29         return 0;30     }31 }
复制代码

输出的glob,var的值相同,均是自加之后的结果,说明子进程修改后父进程跟着改变,即两者共享。

若vfork改成fork,则子进程是自加后的结果,父进程不变,说明子进程是父进程的一份复制。

但是由于父子空间共享内存空间,使得由子函数调用vfork创建的子进程(架设子进程为先执行函数的进程)调用其它函数或运行其他程序后会,父进程会出现段错误,如下:

复制代码
 1 #include<stdio.h> 2 #include<unistd.h> 3 #include<stdlib.h> 4 void test() 5 { 6     pid_t pid; 7     pid=vfork();//创建子进程 8     if(pid==-1) 9     {10        perror("vfork");11        exit(EXIT_FAILURE);12     }13     else if(pid==0)  //子进程先运行14     {   15        printf("1:child pid=%d,ppid=%d\n",getpid(),getppid());16        return;17     }18     else19        printf("2:parent pid=%d,ppid=%d\n",getpid(),getppid());20 }21 void fun()22 {  23    int i;24    int buf[100];25    for(i=0;i<100;i++)26        buf[i]=0;27    printf("3:child pid=%d,ppid=%d\n",getpid(),getppid());    28 }29 int main()30 {31    pid_t pid;32    test();33    fun(); 34 }
复制代码

1.创建主函数,申请栈空间(局部变量、返回值、参数等)

2.调用test函数,申请test函数的栈空间

3.test函数创建子进程,子进程先运行,在test中输出pid和ppid,清理栈空间

4.子进程调用fun函数,覆盖原来test函数的栈空间,执行完毕后退出

5.父进程从返回处开始执行,可是栈已经不存在了

 

所以如果希望在创建的子进程中运行新的程序,则用fork()函数创建子进程,再用exec系列函数替代子进程用户空间的资源(代码、堆、栈等),内核信息基本不修改。

execl系列

复制代码
 1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4  5 int main(int argc,char* argv[]) 6 { 7         pid_t pid; 8         if((pid=fork())<0) 9         {10                 printf("error");11         }12         else if(pid==0)13         {14                 execl("/bin/ls","ls","-l","/home",(char *)0);15                 }16         else17                 printf("father ok!\n");18 19         }
复制代码

[root@sun task]# ./execl 
father ok!
[root@sun task]# 总用量 16
drwxr-xr-x.  4 root   root   4096 11月 14 23:10 df
drwx------. 29 hadoop hadoop 4096 9月   4 23:15 hadoop
drwx------. 44 sun    sun    4096 12月 30 04:45 sun
drwxr-xr-x. 12 root   root   4096 12月 30 05:01 test
在执行execl系列函数时,默认情况下,新代码可以使用原来代码中打开的文件描述符,即执行execl时,并不关闭进程原来打开的文件

复制代码
 1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <fcntl.h> 5 #include <string.h> 6 #include <stdlib.h> 7  8 int main(int argc,char* argv[]) 9 {10         int fd,status;11         pid_t pid;12         fd=open("test.txt",O_RDWR|O_APPEND|O_CREAT,0644);//打开文件,产生一个文件描述符fd,从文件尾开始追加13         if(fd==-1)14         {15                 perror("open");16                 exit(EXIT_FAILURE);17                 }18         printf("before child process write\n");19         system("cat test.txt");//创建新进程,在新进程中运行命令,直到新进程运行结束在运行父进程20         if((pid=fork())==-1)21         {22                 perror("fork");23                 exit(EXIT_FAILURE);24                 }25         if(pid==0)26         {27                 char buf[128];28                 sprintf(buf,"%d",fd);//将文件描述符写入缓冲区29                 execl("./newcode","newcode",buf,(char *)0);//执行newcode,把文件描述符以参数的形式传递给代码newcode,在newcode中执行对文件的追加写入工作30                 }31         else32         {33                 wait(&status);34                 printf("after child_process write\n");35                 system("cat test.txt");36                 }37         }
复制代码
复制代码
 1 #include <stdio.h> 2 #include <string.h> 3 #include <unistd.h> 4  5 int main(int argc,char* argv[]) 6 { 7         int i; 8         int fd; 9         char *ptr="helloworld\n";10         fd=atoi(argv[1]);//argv[1]中的值是写入buf的fd=open("test.txt",O_RDWR|O_APPEND|O_CREAT,0644)11         i=write(fd,ptr,strlen(ptr));//写入fd关联的test.txt中,执行成功,说明原来的文件描述符可以使用12         if(i<=0)13                 perror("write");14         close(fd);15         }
复制代码

[root@sun task]# ./system_execl 
before child process write
我是测试文件
after child_process write
我是测试文件
helloworld

0 0
原创粉丝点击