fork函数
创建一个子进程。
头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t fork(void);
返回值:
RETURN VALUE
On success, the PID of the child process is returned in the parent, and
0 is returned in the child. On failure, -1 is returned in the parent,
no child process is created, and errno is set appropriately.
返回值: 成功后,子进 程的pid将返回到父级中,并且 在子节点中返回0。当失败时,将在父文件中返回-1, 没有创建任何子进程,并且正确地设置了errno。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("before fork--1--\n");
printf("before fork--2--\n");
printf("before fork--3--\n");
printf("before fork--4--\n");
printf("before fork--5--\n");
pid_t pid=fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid==0)
{
printf("---child is created\n");
}
else if(pid>0)
{
printf("---parent process:my child is %d\n",pid);
}
printf("=================end of file\n");
return 0;
}
执行结果如下:
当使用fork函数以后,先执行完父进程,再执行子进程。
使用getpid(),getppid()函数获取进程的本身的pid、和本进程对应的父进程pid
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("before fork--1--\n");
printf("before fork--2--\n");
printf("before fork--3--\n");
printf("before fork--4--\n");
printf("before fork--5--\n");
pid_t pid=fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid==0)
{
printf("---child is created,pid=%d,parent-pid=%d\n",getpid(),getppid());
}
else if(pid>0)
{
printf("---parent process:my child is %d,my pid:%d,my parent pid:%d\n",pid,getpid(),getppid());
}
printf("=================end of file\n");
return 0;
}
执行结果如下:
题目:
一次fork函数调用可以创建一个子进程。那么创建N个子进程应该怎么实现呢?
简单想,for(i=0;i<n;i++){frok()}即可。但这样创建的是N个子进程吗?
例如:for(i=0;i<3 ;i++)
fork();
答案:2的3次方=8,因为有一个是父进程,所以产生了七个子进程。
从上图我们可以很清晰的看到,当n为3时候,循环创建了(2^n-1)个子进程,而不是N个子进程。需要在循环的过程,保证子进程不再执行fork,因此当(fork()==0)时,子进程来个break才正确。
实现程序:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
pid_t pid;
for(int i=0;i<5;i++)
{
pid=fork();
if(pid==0)
{
break;
}
if(i==5)
{
sleep(1);
printf("I'm parent\n");
}
else
printf("I'm %dth child\n",i+1);
}
return 0;
}
执行结果如下:
进程共享
父子进程之间在fork后。有哪些相同,哪些相异之处呢?
刚fork之后:
父子进程相同之处:全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式......
父子进程不同之处:1、进程ID 2、fork返回值 3、父进程ID 4、进程运行时间 5、闹钟 6.未决信号集
父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
练习:编写程序测试,父子进程是否共享全局变量
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int var=100;
int main(int argc,char *argv[])
{
pid_t pid;
pid=fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}else if(pid > 0){
var = 288;
printf("parent,var=%d\n",var);
printf("I'm parent pid=%d,getppid = %d\n",getpid(),getppid());
}else if(pid==0){
var = 200;
printf("I'm child pid=%d,ppid=%d\n",getpid(),getppid());
printf("child,var = %d\n",var);
}
printf("-----------------finish-------------------\n");
return 0;
}
执行结果如下:
重点注意! 父子进程间共享全局变量的知识误区!
重点:父子进程共享:1、文件描述符(打开文件的结构体)2、mmap建立的映射区(进程间通信详解)
fork函数:
pid_t fork(void);
创建子进程。父子进程各自返回。父进程返回子进程pid。子进程返回0.
getpid();getppid();
循环创建N个子进程模型。每个子进程标识自己的身份。
父子进程gdb调试
使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟父进程。
set follow-fork-mode child命令设置gdb在fork之后跟踪子进程。
set follow-fork-mode parent设置跟踪父进程。
注意,一定要在fork函数调用之前设置才有效。
生成可调试文件的命令:
gcc fork.c -o gdbfork -g
list 1列出调试代码,l列出调试代码
设置断点:b(break) 设置断点的行数
r(run):运行到断点处
打印变量的值:p(print) i
$1 = 0
exec函数族(库函数)
fork创建子进程后执行的是和父进程相同的程序(但有可能
能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
有六种以exec开头的函数(我们只需要记住两种就可以了),统称exec函数:
int execl(const char *path,const char *arg,....);
int execlp(const char *file,const char *arg,....);
execlp函数
加载一个进程,借助PATH环境变量
int execlp(const char *file,const char *arg,.....);
返回值:
成功:无返回
失败:-1
参数1:(命令)"ls"要加载的程序名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。
参数2,3....:命令选项 "-l","-d","-h"
该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。
execlp("ls","-l","-d","-h",NULL);
后面一定要加NULL
例子:用execlp函数实现date命令
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
pid_t pid=fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid==0)
{
//execlp("ls","ls","-l","-h" ,NULL);
execlp("date","date",NULL);
perror("exec error");
exit(1);
}
else if(pid>0)
{
printf("I'm parent:%d\n",getpid());
}
return 0;
}
执行结果如下:
execl函数
函数原型
int execl(const char *path, const char *arg, ...);
execl("/bin/ls","ls","-l",NULL);
注意第一个参数,和第二个参数的区别
例子:使用execl函数在子进程中实现./a.out 的执行
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
pid_t pid=fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid==0)
{
execl("./fork","./fork",NULL);
}
else if(pid>0)
{
printf("I'm parent:%d\n",getpid());
}
return 0;
}
执行结果如下:
用execl函数执行ls命令:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
pid_t pid=fork();
if(pid == -1)
{
perror("fork error");
exit(1);
}
else if(pid==0)
{
execl("/bin/ls","ls","-l",NULL);
}
else if(pid>0)
{
printf("I'm parent:%d\n",getpid());
}
return 0;
}
执行结果如下:
exec函数族特性
execlp("ls","ls","-l",NULL); //使用程序名在PATH中搜索
execl("/bin/ls","ls","-l",NULL); //使用参数1给出的绝对路径搜索
练习:将当前系统中的进程信息,打印到文件中,
exec_ps.c
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd;
fd=open("ps.out",O_WRONLY|O_CREAT|O_TRUNC,0644);
if(fd<0)
{
perror("open ps.out failure");
exit(1);
}
dup2(fd,STDOUT_FILENO);
execlp("ps","ps","ax",NULL);
perror("execlp error");
return 0;
}
l(list) 命令行参数列表
p(path) 搜索file时使用path变量
v(vector) 使用命令行参数数组
e(environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
回收子进程
孤儿进程
孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
僵尸进程
僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程。
wait函数
一个进程在终止时会关闭所有的文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保留着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。
pid_t wait(int *wstatus);
成功:清理掉的子进程ID
失败:-1(没有子进程)
当进程终止是,操作系统的隐式回收机制会:1.关闭所有文件描述符 2.释放用户空间分配的内存
内核的PCB仍存在。其中保存该进程的退出状态。(正常终止>退出值 ;异常终止>终止信号)
例程1:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
pid_t pid,wpid;
pid=fork();
int status;
if(pid == -1)
{
perror("fork error");
exit(1);
}else if(pid > 0){
wpid=wait(&status);
if(wpid==-1)
{
perror("wait error");
exit(1);
}
printf("----------parent wait finish:%d\n",wpid);
}else if(pid==0){
printf("---child,my pid=%d,going to sleep 10s\n",getpid());
sleep(10);
printf("-----------child die---------------------------------\n");
}
return 0;
}
执行结果如下:
如果是wait(NULL)不关心子进程结束原因
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为以下三组:
这几个宏的使用方法如下:
(wait if exited)WIFEXITED(wstatus)(为真说明子进程正常终止)
(wait exit status)WEXITSTATUS(wstatus)(得到子进程的退出值)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
pid_t pid,wpid;
pid=fork();
int status;
if(pid == -1)
{
perror("fork error");
exit(1);
}else if(pid > 0){
wpid=wait(&status);
if(wpid==-1)
{
perror("wait error");
exit(1);
}
if(WIFEXITED(status))
{
printf("child exit with %d\n",WEXITSTATUS(status));
}
printf("----------parent wait finish:%d\n",wpid);
}else if(pid==0){
printf("---child,my pid=%d,going to sleep 1s\n",getpid());
sleep(1);
printf("-----------child die---------------------------------\n");
return 73;
}
return 0;
}
执行结果如下:
(wait if signaled) WIFSIGNALED(wstatus)(为真,说明子进程是被信号终止)
(wait term signal)WTERMSIG(wstatus)
例程②(获取导致子进程异常终止信号)
得到导致子进程异常终止的信号编号
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
pid_t pid,wpid;
pid=fork();
int status;
if(pid == -1)
{
perror("fork error");
exit(1);
}else if(pid > 0){
wpid=wait(&status);
if(wpid==-1)
{
perror("wait error");
exit(1);
}
if(WIFEXITED(status))
{
printf("child exit with %d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status))
{
printf("child kill with signal %d\n",WTERMSIG(status));
}
printf("----------parent wait finish:%d\n",wpid);
}else if(pid==0){
printf("---child,my pid=%d,going to sleep 1s\n",getpid());
sleep(10);
printf("-----------child die---------------------------------\n");
return 73;
}
return 0;
}
执行结果如下:
kill -9 2925
kill -10 2930
WCOREDUMP(wstatus)
WIFSTOPPED(wstatus)
WSTOPSIG(wstatus)
WIFCONTINUED(wstatus)
一次wait/waitpid函数调用,只能回收一个进程。
waitpid函数
回收指定的进程
作用:同wait,但可以指定pid进程清理,可以不阻塞
函数原型
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数:
pid:指定回收的子进程pid。
>0:待回收的子进程pid
-1:任意子进程
0 :同组的子进程
status:(传出)回收进程的状态。
options:WNOHANG指定回收方式为非阻塞。
返回值:
成功:回收的子进程PID;(>0)
0:函数调用时,参3指定了WNOHANG,并且,没有子进程结束。
失败:-1(无子进程)。errno
参数pid(无子进程)
>0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
<-1 回收指定进程组内的任意子进程
返回0:参数3为WNOHANG,且子进程正在运行
例程:回收第三个子进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
int i;
pid_t pid,wpid,tmpid;
for( i=0;i<5;i++)
{
pid=fork();
if(pid==0)
{
break;
}
if(i==2)
{
tmpid=pid;
printf("----------pid=%d\n",tmpid );
}
}
if(i==5)
{
sleep(5);
printf("-----------in parent,before waitpid,pid=%d\n",tmpid);
wpid=waitpid(tmpid,NULL,0);
if(wpid==-1)
{
perror("waitpid error");
exit(1);
}
printf("I'm parent,wait a child finish:%d\n ",wpid);
}
else
{
sleep(i);
printf("I'm %dth child,pid=%d\n",i+1,getpid());
}
return 0;
}
执行结果如下:
总结:waitpid一次调用,回收一个子进程。
如果想回收多个子进程。while循环
waitpid回收多个子进程
例程:waitpid回收多个子进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
int i;
pid_t pid,wpid;
for( i=0;i<5;i++)
{
pid=fork();
if(pid==0)
{
break;
}
}
if(i==5)
{
while( (wpid = waitpid(-1,NULL,0)) && (wpid>0))
{
printf("wait child %d\n",wpid);
}
}
else
{
sleep(i);
printf("I'm %dth child,pid=%d\n",i+1,getpid());
}
return 0;
}
执行结果如下: