怎样创建一个子进程
首先配置号c语言的环境 gcc编译
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
//pid_t是数据类型,实际上是一个整型,通过typedef重新定义了一个名字,用于存储进程id
pid_t pid,cid;
//getpid()函数返回当前进程的id号
printf("Before fork Process id :%d\n", getpid());
/*
fork()函数用于创建一个新的进程,该进程为当前进程的子进程,创建的方法是:将当前进程的内存内容完整拷贝一份到内存的另一个区域,两个进程为父子关系,他们会同时(并发)执行fork()语句后面的所有语句。
fork()的返回值:
如果成功创建子进程,对于父子进程fork会返回不同的值,对于父进程它的返回值是子进程的进程id值,对于子进程它的返回值是0.
如果创建失败,返回值为-1.
*/
cid = fork();
printf("After fork, Process id :%d\n", getpid());
pause();
return 0;
}
gcc helloProcess.c -o helloProcess
执行下
./helloProcess
可以看到有三行输出
前两行创建子进程之前和创建子进程之后都为91476,这个91476为父进程的pid,在创建了子进程后,子进程只打印了after fork日志
印证下父子进程的关系
可以看到倒数后两行,81479的ppid即父进程为91476
其中有个关键函数fork函数,这个函数的作用为创建子进程
子进程将父进程完全复制一份内存空间变成子进程
在执行了fork之后会并发执行fork之后的所有代码,注意并发执行为交替执行,同一时刻只会有一个程序运行,原因以及父子进程执行时机区别如下:
- fork 创建子进程时,系统会为子进程分配独立的地址空间,但初始时父子进程共享相同的代码段(即程序计数器指向同一位置)。当子进程开始执行时,其程序计数器会从fork()调用点开始继续执行后续代码,而父进程则从fork()返回后继续执行后续代码
- 地址空间隔离
– fork()调用后,子进程获得父进程的内存映像副本(包括代码和数据),但操作系统通常采用写时复制(copy-on-write)机制优化资源使用,即初始共享地址空间仅在写入时才分配给子进程独立空间。 - 程序计数器初始化
– 子进程的初始程序计数器值与父进程相同,均指向fork()调用点。随后子进程通过程序计数器自动跳转到fork()之后的代码执行。 - 执行机制差异
– 父进程在fork()返回后继续执行后续代码,而子进程则从fork()返回点开始执行后续代码。这种机制确保父子进程按预期逻辑分离执行
可以看到刚才的程序fork之后先执行了父进程的打印日志,再执行的子进程的打印日志,那怎样让子进程先运行,父进程后运行呢
首先来看看怎样区分父子进程
区分父子进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
//pid_t是数据类型,实际上是一个整型,通过typedef重新定义了一个名字,用于存储进程id
pid_t pid,cid;
//getpid()函数返回当前进程的id号
printf("Before fork Process id :%d\n", getpid());
/*
fork()函数用于创建一个新的进程,该进程为当前进程的子进程,创建的方法是:将当前进程的内存内容完整拷贝一份到内存的另一个区域,两个进程为父子关系,他们会同时(并发)执行fork()语句后面的所有语句。
fork()的返回值:
如果成功创建子进程,对于父子进程fork会返回不同的值,对于父进程它的返回值是子进程的进程id值,对于子进程它的返回值是0.
如果创建失败,返回值为-1.
*/
cid = fork();
printf("After fork, Process id :%d\n", cid);
pause();
return 0;
}
注意这里的after 打印日志 改成了cid
cid即fork()的返回值:
- 如果成功创建子进程,对于父子进程fork会返回不同的值,对于父进程它的返回值是子进程的进程id值,对于子进程它的返回值是0.
- 如果创建失败,返回值为-1.
判断父子进程打印日志
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
//pid_t是数据类型,实际上是一个整型,通过typedef重新定义了一个名字,用于存储进程id
pid_t pid,cid;
//getpid()函数返回当前进程的id号
printf("Before fork Process id :%d\n", getpid());
/*
fork()函数用于创建一个新的进程,该进程为当前进程的子进程,创建的方法是:将当前进程的内存内容完整拷贝一份到内存的另一个区域,两个进程为父子关系,他们会同时(并发)执行fork()语句后面的所有语句。
fork()的返回值:
如果成功创建子进程,对于父子进程fork会返回不同的值,对于父进程它的返回值是子进程的进程id值,对于子进程它的返回值是0.
如果创建失败,返回值为-1.
*/
cid = fork();
if (cid ==0){ //子进程
printf("Child Process id :%d\n",getpid());
}else{ //父进程
printf("parent Process id :%d\n",getpid());
}
pause();
return 0;
}
在父子进程加上for循环
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t cid;
printf("Before fork process id :%d\n", getpid());
cid = fork();
if(cid == 0){ //该分支是子进程执行的代码
printf("Child process id (my parent pid is %d):%d\n", getppid(),getpid());
for(int i=0; i<3 ; i++)
printf("hello\n");
}else{ //该分支是父进程执行的代码
printf("Parent process id :%d\n", getpid());
for(int i=0; i<3 ; i++)
printf("world\n");
}
return 0;
}
可以看到先执行副进程的3次for循环,再执行子进程的3次循环
父子进程执行顺序
从上述实验看出父子进程好像执行有顺序,但时机父子进程为并发执行的,但为啥看起来像是串行执行的呢
这个是由于执行的循环此时太少 CPU一次执行得太快看起来没有并发交替执行
把循环此时增加再看看
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t cid;
printf("Before fork process id :%d\n", getpid());
cid = fork();
if(cid == 0){ //该分支是子进程执行的代码
printf("Child process id (my parent pid is %d):%d\n", getppid(),getpid());
for(int i=0; i<300 ; i++)
printf("hello\n");
}else{ //该分支是父进程执行的代码
printf("Parent process id :%d\n", getpid());
for(int i=0; i<300 ; i++)
printf("world\n");
}
return 0;
}
进程之间的变量共享
假设在父子进程执行之前定义了变量value,在两个进程的变量会怎样变化呢
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t cid;
int value = 100;
cid = fork();
if(cid == 0){ //该分支是子进程执行的代码
for(int i=0; i<3 ; i++)
printf("In child: value=%d\n",value--);
}else{ //该分支是父进程执行的代码
for(int i=0; i<3 ; i++)
printf("In parent: value=%d\n", value++);
}
return 0;
}
可以看到父进程执行了3次加法,子进程重新从100开始做减法了,说明进程之间的内存不共享,fork会复制一份新的,包括其中的变量
孤儿进程
wait (NULL) 等待子进程结束再执行 当子进程执行return 父进程再会进程ready状态
不使用的时候子进程执行完成不会等待父进程执行完成会变成孤儿进程
sleep函数,在子进程加上 等待3s会进入ready 父进程有wait函数会等待子进程等待时间再结束 但父进程不加上wait 则子进程仍然会变成孤儿进程
参考
B站操作系统