Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。
本篇导航
- 0. 创建进程
- 1. 认识fork函数
- 2.使用Fork函数
- 3.关于fork的为什么
- 3.1 一个函数如何返回两次?fork究竟在干什么?
- 3.2 为什么要给子进程返回0,父进程返回子进程的PID呢?
- 3.3 一个变量为什么会有两个不同的内容
- 4. Bash与子进程

0. 创建进程
我们介绍了每一个在内存中运行的任务都称为一个进程.那么我们如何自己去创建一个进程呢?
在之前我们可以通过./xxx来运行一段程序,这样这段程序就被加载到内存当中成为了一个进程.这是在指令层面上的创建进程.
例如:
创建了一个名为proce的程序,将其运行.之后输入
ps -ajx | grep proce
就可以查看到当前进程的相关信息.可以看到其PID为58014,PPID为57120
我们在代码中可以通过
getpid() getppid()来获取当前进程的PID和PPID,被包含在 **<unistd.h> <sys/types.h>**中,返回值为:pid_t 类型
在man二号手册中可以看到对其的相关介绍,说明其为一个系统调用接口
常见的节号包括:
- 1:用户命令
- 2:系统调用
- 3:C库函数
- 4:设备和特殊文件
- 5:文件格式和约定
- 6:游戏和演示
- 7:杂项
- 8:系统管理命令
那么我们在代码中如何去创建一个进程呢?
1. 认识fork函数
在代码层面中,我们可以使用fork去创建我们的子进程.
我们先来简单看下这个函数的接口说明,在man二号手册中可以看到对其的相关介绍.说明fork是一个系统调用接口
常见的节号包括:
- 1:用户命令
- 2:系统调用
- 3:C库函数
- 4:设备和特殊文件
- 5:文件格式和约定
- 6:游戏和演示
- 7:杂项
- 8:系统管理命令
man fork
函数接口:
pid_t fork(void)
头文件:
#include <sys/types.h>
#include <unistd.h>
返回值:
若成功创建子进程则对父进程返回子进程ID,对子进程返回0
我们来试着使用一下fork函数
2.使用Fork函数
先直接写一个简单的代码
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("before:\n");
pid_t id=fork();
printf("after:\n");
sleep(1);
return 0;
}
编译运行,惊奇的发现:before被打印了一次,after被打印了两次.
我们可以得出一个结论:代码段在fork之后的部分被执行了两次
我们再来看看这份代码:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n",getpid());
pid_t id=fork();
if(id>0)
{
while(1)
{
printf("my pid : %d , my parent :%d,id:%d\n, ",getpid(),getppid(),id);
sleep(1);
}
}
else if(id==0)
{
while(1)
{
printf("my pid : %d , my parent :%d,id:%d\n",getpid(),getppid(),id);
sleep(1);
}
}
return 0;
}
- 居然在一份代码中既执行了if,又执行了else.这意味着id既 >0 又 <=0
这在我们过去的学习中是不可能出现的事情.
-
根据之前所学,fork()对父进程返回子进程PID,对子进程返回0
所以 249101是父进程,249102是子进程
现在肯定有以下疑问:
- 一个函数如何返回两次?
- 为什么要给子进程返回0,父进程返回子进程的PID呢?
- 一个变量为什么会有两个不同的内容
- fork究竟在干什么?
3.关于fork的为什么
在将之前,我们先来理解下这个概念:子进程 创建时会和父进程共享内存中的代码与数据
这里只是将他们分开来看了,实际中是使用同一个地址上的内容
3.1 一个函数如何返回两次?fork究竟在干什么?
我们这两个问题一起来看.
假设这是一份fork函数的伪代码
pid_t fork()
{
创建子进程task_struct
填充PCB对应内容
让父子进程指向相同的代码
可以被CPU调度运行了
子进程创建完成
return ret;
}
注意看最后的一步return ret,此时子进程已经被创建出来了.他共享到了return ret.所以父子进程都需要返回一个ret的值;
我们可以大胆假设:
-
**ret为一个被初始化为0的变量.在填充子进程对应PCB这一步中,ret被赋值为子进程对应的PID.**但此时ret还未被子进程共享.
子进程共享时,仅共享到了未被赋值的ret,所以返回时因为都有return 所以返回两次
3.2 为什么要给子进程返回0,父进程返回子进程的PID呢?
这是为了方便我们对进程进行管理.
如同上面的代码,我们通常情况下,希望父进程和子进程做不同的事情.所以需要对其进行分流
3.3 一个变量为什么会有两个不同的内容
这里可以先简单的理解为发生了 写时拷贝
虽然父子进程共享代码中的数据,但当任何一个进程需要修改变量中的内容时,会拷贝一份新的数据供修改对象使用.
这里后面谈进程地址空间时会详细解释.
4. Bash与子进程
我们运行起刚刚的进程后,用ps查看发现父进程的PPID(父进程的父进程)为80738.这是什么呢?
发现其为zsh(一个Bash)
的数据供修改对象使用.
这里后面谈进程地址空间时会详细解释.
平常我们在命令行中输入一个指令,bash负责解释完这个指令会新起一个子进程去运行本条指令.