文章目录
- 通过系统调用查看进程PID
- 父进程、子进程
- 通过系统调用创建进程-fork初识
- 为什么fork给父进程返回子进程的PID,给子进程返回0
- fork函数如何做到返回两个值
- 一个变量为什么同时会有两个返回值?
- bash
- 总结
通过系统调用查看进程PID
getpid()
函数可以获取进程的PID,返回的是进程的PID,返回类型为pid_t
以一个程序为例:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(1)
{
printf("The PID is: %d\n",getpid());
sleep(1);
}
return 0;
}
运行结果如下:
一段脚本查看程序执行起来后的进程:
通过getpid()
得到的PID和ps
指令获取的进程PID是一致的,都是5258
一个进程属性中,除了有自己的进程PID还有父进程PID,ps
指令中查询到的PPID是当前进程的父进程的PID,可以是用函数getppid()
获取父进程的PID。
父进程、子进程
这个父进程PID到底是什么?
使用指令查看一下:ps axj | head -1 ; ps axj | grep 4943
4943是bash
进程的PID,bash
是命令解释器,他会将用户输入的指令翻译给操作系统核心处理,指令的本质也是一个可执行程序。
结论:当我们在命令行输入指令去执行的时候,bash会帮助我们创建一个子进程去执行该指令。子进程出问题不会影响到父进程。
通过系统调用创建进程-fork初识
创建进程的方式———fork
之前我们是写一段代码来创建一个可执行程序,从而形成一个进程。
现在通过系统调用创建进程。
先来认识一下fork
函数:
fork
函数会以调用该函数的进程作为父进程创建一个子进程
创建成功时,会在父进程中返回子进程的PID,在子进程中返回0;如果失败,在父进程中返回-1,没有子进程创建。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret=fork();
printf("before : %d!, after: %d\n", getpid(), ret);
sleep(1);
return 0;
}
执行结果:
会发现:fork
后面的代码执行了两次,在fork
之前有一个执行流,fork
后面有两个执行流。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("I am process!!");
pid_t id=fork();
if(id>0)
{
while(1)
{
printf("I am parent process,pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
}
else if(id==0)
{
while(1)
{
printf("I am child process,pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
}
else
{
printf("error\n");
}
return 0;
}
运行效果:
通过上述代码以及运行效果,当id>0
时,执行父进程,当id==0
时,执行子进程。if
和else if
同时满足,并且这两个程序都是死循环同时在运行。
也就是说明,这里有两个程序在同时运行,即myprocess.exe
进程和myprocess.exe
进程创建的子进程,从而实现了fork
函数创建子进程后,会从原来的一个执行流变成两个执行流。
为什么fork给父进程返回子进程的PID,给子进程返回0
fork
之后的代码父子共享,返回不同的值是为了区分不同的执行流。创建父子进程,是为了让父子进程执行不同的任务。
一个父进程可以创建多个子进程,为了区分这些子进程,fork
函数在创建子进程后,会给父进程返回子进程的pid。子进程只需调用getppid()
函数即可找到父进程。fork
函数创建子进程后,只需要给子进程返回一个0用来标识创建成功即可。
fork函数如何做到返回两个值
进程=PCB+代码和数据。
进程的PCB对象会找到对应的代码和数据,然后CPU去调度这个进程,也就是说找到这个代码和数据去运行。调用fork
函数创建子进程,实际上是在操作系统中多了一个进程,一样的,它也需要先创建一个属于自己的PCB对象,子进程的PCB对象大部分都是以父进程的PCB对象为模板创建的,即直接从父进程的PCB对象那拷贝过来的,对某一小部分进行修改。
**为什么说子进程和父进程的代码和数据是共享的?**刚刚谈到,子进程创建了属于自己的PCB对象,但是没有代码和数据,因此它只能使用父进程的代码和数据,也就是说父子进程的代码和数据是共享的。
因此fork
函数之后程序执行了两次,本质上是父子进程各执行了一次。
创建子进程是为了执行和父进程不同的任务,但是父子进程共享一套代码,因此我们需要给父子进程加一区分,以便于让他们执行不同的任务。fork
函数会在父子进程中返回不同的值,用户只需要判断不同的的返回值即可。
fork
是一个系统调用接口,本质上是一个函数,在操作系统内执行。fork
函数在执行return语句之前子进程的PCB对象就会被创建出来,CPU可以同时去调度父子进程。因此fork函数中的return语句也是父子进程共享的。
fork有两个返回值本质上是因为父子进程共用一份代码导致的,父进程会执行return返回一个值,子进程也会执行return返回一个值。
一个变量为什么同时会有两个返回值?
在代码中,fork函数的返回值我们用变量id
来接收:pid_t id=fork()
,为什么id
可以同时有两个值?
在任何平台,进程在运行的时候都是具有独立性的。也就是说一个进程退出、失败、崩溃了,都不会影响其他进程。父进程和子进程也是两个进程,也具有独立性,父子进程不能访问同一份数据,数据在代码执行过程中可能会被修改。所以子进程要把父进程的数据单独拷贝一份,这个过程是由操作系统来完成的。
子进程可以把父进程的数据全被拷贝一份,但是大部分数据对于子进程来说可能都是没用的,这就造成了浪费,所以操作系统只是把父进程中数据层面的代码临时拷贝一份给子进程,即子进程创建后,会共享父进程的代码和数据,如果子进程需要修改父进程的一部分数据时,操作系统会制止。也就是说,子进程用多少数据,就会拷贝多少数据,效率大大提高。
变量id
接收的值是fork函数返回的值,父进程return一次,子进程return一次,子进程会执行写时拷贝,所以变量会有两个返回值,本质上是有两块空间。
共享代码不会影响独立性,因为代码加载到内存之后是不会发生改变的。
bash
bash作为命令行解释器,本身也是一个进程,我们在bash命令行输入指令的本质上也是一个可执行程序,加载到内存后也是一个进程。因此在bash的源代码实现中一定会调用fork函数接口,创建子进程,bash自己去执行命令行解释,子进程去执行我们输入的指令进程。
总结
- 运行 man fork 认识fork
- fork有两个返回值
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)