文章目录
- 1.进程的创建
- 2.进程的替换
- 3.进程的阻塞
- 4.进程终止
- 5.进程的挂起
1.进程的创建
①调用fork函数的进程为父进程,调用后生成一个子进程;
②创建子进程成功时,父进程中fork函数的返回值是子进程的进程号PID;
③创建子进程失败时,父进程中fork函数的返回值是一个负数;
④子进程复制当前进程中所有变量的值,并复制fork函数调用后父进程剩下的所有代码。
⑤子进程中的进程号PID为0。
⑥需要引入头文件<unistd.h>
另外,可以用getpid函数获取当前进程的进程号PID
Current_Pid=getpid()
问题举例:
编写程序,使用系统调用fork()创建如下的进程树,当此程序运行时,在系统中有一个父进程和多个子进程活动,父进程等子进程运行结束后退出。
每个进程循环5次显示不同的字符串。假设X的初值为2022,父进程的字符串内容包括自己的学号、姓名、变量X的值;子进程字符串包括进程PID、子进程序号(子进程1或2)、变量X的值。每次循环X的值加5。记录屏幕上的显示结果,并分析变量X的变化规律。
求解代码:
#include<stdio.h>
#include<unistd.h> //使用fork函数和getpid函数都需要导入这个头文件
int main(void)
{
int pid1,pid2; //分别用两个变量记录两个子进程的进程号
int x=2022;
int temppid; //记录某时刻的进程号
pid1=fork(); //父进程用fork函数创建一个新的子进程
if(pid1<0) //如果子进程的进程号小于1则表示进程创建失败
{
printf("第一个子进程创建失败!\n");
}
else if(pid1==0) //如果进程号为0表示当前进程就是第一个子进程,则执行下面的代码段
{
pid2=fork(); //创建一个新的子进程
if(pid2<0) //创建第二个子进程失败的情况
{
printf("第二个子进程创建失败!\n");
}
else if(pid2==0) //第二个子进程执行的代码段
{
int k=0;
for(k=0;k<5;++k)
{
temppid=getpid();
printf("PID=%d 串联创建的子进程2 X=%d\n",temppid,x);
x+=5;
}
}
else //第一个子进程执行的代码段
{
int j=0;
for(j=0;j<5;++j)
{
temppid=getpid();
printf("PID=%d 串联创建的子进程1 X=%d\n",temppid,x);
x+=5;
}
}
}
else //父进程执行的代码段
{
int i=0;
for(i=0;i<5;++i)
{
temppid=getpid();
printf("学号:123 姓名:小明 X=%d\n",x);
x+=5;
}
}
return 0;
}
运行效果:
2.进程的替换
①调用execlp函数可以进行进程替换;
②execlp函数的参数是需要执行的带有完整路径的程序名以及一个指针(注意是可执行文件而不是源代码C文件,指针一般取NULL);
③进程替换后该进程的数据区和代码区都变为替换到的程序的数据和代码。
问题举例:
修改上一题编写的程序,将子进程改为独立的程序,父进程创建子进程并进行程序替换,观察程序执行时屏幕出现的现象,并分析原因。
求解代码:
①父进程代码:
#include<stdio.h>
#include<unistd.h>
int main(void)
{
int pid1,pid2;
int x=2022;
int temppid;
pid1=fork();
if(pid1<0)
{
printf("子进程1创建失败!\n");
}
else if(pid1==0)
{
pid2=fork();
if(pid2<0)
{
printf("第二个子进程创建失败!\n");
}
else if(pid2==0)
{
execlp("./2.2.2",0);
}
else
{
execlp("./2.2.1",0);
}
}
else
{
wait(NULL);
int i=0;
for(i=0;i<5;++i)
{
temppid=getpid();
printf("学号:123 姓名:小明 X=%d\n",x);
x+=5;
}
}
return 0;
}
②子进程1代码(即父进程中调用的2.2.1):
#include<stdio.h>
#include<unistd.h>
int main(void)
{
int x=2022;
int i;
int pid;
for(i=0;i<5;++i)
{
pid=getpid();
printf("PID=%d 子进程1 X=%d\n",pid,x);
x+=5;
}
return 0;
}
③子进程2代码(即父进程中调用的2.2.2):
#include<stdio.h>
#include<unistd.h>
int main(void)
{
int x=2022;
int i;
int pid;
for(i=0;i<5;++i)
{
pid=getpid();
printf("PID=%d 子进程2 X=%d\n",pid,x);
x+=5;
}
return 0;
}
运行效果:
3.进程的阻塞
①调用wait函数可以实现进程的阻塞;
②调用wait函数的进程必须等到其所有子进程结束后才能继续执行;
③wait函数的参数为一个指针,一般传入NULL。
4.进程终止
①调用exit函数可以实现进程的终止;
②终止当前进程的同时不会终止该进程的所有子孙进程。孤儿进程会被init进程收养。
③使用exit函数需要引入函数库<stdlib.h>
孤儿进程示例代码:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(void)
{
int pid;
pid=fork();
if(pid<0)
{
printf("子进程创建失败!\n");
}
else if(pid==0)
{
int i;
for(i=0;i<10;++i)
{
sleep(2);
printf("父进程的PID=%d\n",getppid());
}
}
else
{
sleep(3);
printf("父进程输出自己的PID=%d\n",getpid());
exit(0);
}
return 0;
}
运行效果:
5.进程的挂起
①使用sleep函数挂起一个进程;
②sleep函数的参数是需要休眠的时间,以秒计算。