#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> int main(void) { pid_t fpid; //fpid表示fork函数返回的值 int count=0; fpid=fork(); if (fpid < 0) printf("error in fork! "); else if (fpid == 0) { printf("i am the child process, my process id is %d ",getpid()); printf(" my parent process id is %d ",getppid()); count++; } else { printf("i am the parent process, my process id is %d ",getpid()); count++; } printf("count 统计结果是: %d ",count); while(1);//无限阻塞 return 0; }
fork 函数,创建子进程。
函数原型:
关于其返回值:
fork函数一次调用,两次返回。子进程中返回0,父进程中,返回子进程的ID。如果fork失败,返回-1.并且不会创建子进程,同时错误代码errno会被设置。
fork的读时共享,写时复制机制。子进程拥有和父进程一样的0-3G用户空间,但是3-4G内核空间中PCB(进程控制块)的进程ID号并不相同。子进程和父进程有如此多相同的地方,如果仅仅是读取0-3G用户空间的数据,则没有必要复制一份父进程的数据到内存,但如果需要写入数据,就必须开辟新的空间了。
——————————————————》》》分割线
但是,子进程创建出来如果所做的任务和父进程完全一致,那么也就没有必要创建子进程的意义了。通常而言,子进程被创建,是要做与父进程不一样的事情。
#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> int main(void) { pid_t fpid; int count=0; fpid=fork(); if (fpid < 0) printf("error in fork! "); else if (fpid == 0) { char *const argv[]={"ls","-l",NULL}; printf("i am a child process "); execvp("ls",argv); printf("where is my process? "); } else { printf("i am a parent process "); } while(1); return 0; }
execvp:属于exec族中的一个函数。
当进程调用了exec族的某一函数之后,该进程执行的程序被替换成全新的程序,新程序从其main函数开始执行。由于调用exec函数不会创建新的进程,所以替换前后的进程ID并不会改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
在上面的程序中,使用了ls –l这个指令,可以发现该指令确实正确执行了,但是显示的列表中,a.out没有颜色信息。其次,在char *const argv[]={"ls","-l",NULL};中,第一个“ls”字符串只有一个占位符的作用,该字符串无论是什么都不会影响程序的运行结果,因为在execvp("ls",argv);中已经指定了ls程序,在argv指针数组中的argv[0]只用作占位符。哪怕将其改为”hehe”:
程序依然正常运行,exec函数会从argv[1]开始,直到NULL结束。
现在新建一个upper.c文件:
/* upper.c */ #include<stdio.h> #include <ctype.h> int main(void) { in ch; while((ch=getchar())!=EOF) putchar(toupper(ch));//小写转大写 return 0; }
把之前的test.c改成:
#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> int main(void) { pid_t fpid; int count=0; fpid=fork(); if (fpid < 0) printf("error in fork! "); else if (fpid == 0) { char *const argv[]={"upper",NULL}; printf("i am a child process "); execv("./app",argv); printf("where is my process? "); } else { printf("i am a parent process "); } while(1); return 0; }
先编译upper.c生成app,然后编译test.c生成a.out,运行a.out:
exec函数运行之后,只有出错才会返回,这也就意味着,exec函数后面的语句不会被执行。同样,这里的char *const argv[]={"upper",NULL};中的“upper”字符串也可以是其他任何字符串(即使为NULL也行,但通常使用能代表其含义的名称)。
wait和waitpid函数:
僵尸进程: 子进程退出,父进程没有回收子进程资源(PCB),则子进程变成僵尸进程。
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为1号进程init进程,称为init进程领养孤儿进程。
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,并彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看(echo $?),因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。如果一个进程已经终止,但是它的父进程尚未调用wait或waitpid对它进行清理,这时的进程状态称为僵尸(Zombie)进程。任何进程在刚终止时都是僵尸进程,正常情况下,僵尸进程都立刻被父进程清理了。
目前,只用知道,wait和waitpid的基本用法即可,至于到底怎么应用在程序中,可以暂时不理会。
如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。