编程语言
342
1. 函数
Linux中进程ID为
pid_t
类型,其本质是一个正整数 通过上边的ps aux命令已经得到了验证。PID为1的进程是Linux系统中创建的第一个进程。
- 获取当前进程的进程ID(PID)
#include <sys/types.h> #include <unistd.h> pid_t getpid(void);
- 获取当前进程的父进程 ID(PPID)
#include <sys/types.h> #include <unistd.h> pid_t getppid(void);
- 创建一个新的进程
#include <unistd.h> pid_t fork(void);
Linux中看似创建一个新的进程非常简单,函数连参数都没有 实际上如果想要真正理解这个函数还是要下功夫。
2. fork() 剖析
pid_t fork(void);
启动磁盘上的应用程序, 得到一个进程, 如果在这个启动的进程中调用fork()
函数,就会得到一个新的进程,我们习惯将其称之为子进程
。
前面说过每个进程都对应一个属于自己的虚拟地址空间,子进程的地址空间是基于父进程的地址空间拷贝出来的
,虽然是拷贝但是两个地址空间中存储的信息不可能是完全相同的,下图是拷贝之后父子进程各自的虚拟地址空间:
- 相同点:
拷贝完成之后(注意这个时间点),两个地址空间中的用户区数据是相同的
。 用户区数据主要数据包括:- 代码区:默认情况下父子进程地址空间中的源代码始终相同。
- 全局数据区:父进程中的全局变量和变量值全部被拷贝一份放到了子进程地址空间中
- 堆区:父进程中的堆区变量和变量值全部被拷贝一份放到了子进程地址空间中
- 动态库加载区(内存映射区):父进程中数据信息被拷贝一份放到了子进程地址空间中
- 栈区:父进程中的栈区变量和变量值全部被拷贝一份放到了子进程地址空间中
- 环境变量:默认情况下,父子进程地址空间中的环境变量始终相同。
- 文件描述符表:
父进程中被分配的文件描述符都会拷贝到子进程中,在子进程中可以使用它们打开对应的文件
- 区别:
- 父子进程各自的虚拟地址空间是相互独立的,不会互相干扰和影响。
- 父子进程地址空间中代码区代码虽然相同,但是父子进程执行的代码逻辑可能是不同的。
- 由于父子进程可能执行不同的代码逻辑,因此地址空间拷贝完成之后,
全局数据区, 栈区, 堆区, 动态库加载区(内存映射区)
数据会各自发生变化,由于地址空间是相互独立的,因此不会互相覆盖数据。 - 由于每个进都有自己的进程ID,因此内核区存储的父子进程ID是不同的。
- 进程启动之后进入就绪态,运行需要争抢CPU时间片而且可能执行不同的业务逻辑,所以父子进程的状态可能是不同的。
- fork() 调用成功之后,会返回两个值,父子进程的返回值是不同的。
-
该函数调用成功之后,从一个虚拟地址空间变成了两个虚拟地址空间,每个地址空间中都会将 fork() 的返回值记录下来
这就是为什么会得到两个返回值的原因。 - 父进程的虚拟地址空间中将该返回值标记为一个大于0的数(其实记录的是子进程的进程ID)
- 子进程的虚拟地址空间中将该返回值标记 0
- 在程序中需要通过 fork() 的返回值来判断当前进程是子进程还是父进程。
-
int main() { // 在父进程中创建子进程 pid_t pid = fork(); printf("当前进程fork()的返回值: %d\n", pid); if(pid > 0) { // 父进程执行的逻辑 printf("我是父进程, pid = %d\n", getpid()); } else if(pid == 0) { // 子进程执行的逻辑 printf("我是子进程, pid = %d, 我爹是: %d\n", getpid(), getppid()); } else // pid == -1 { // 创建子进程失败了 } // 不加判断, 父子进程都会执行这个循环 for(int i=0; i<5; ++i) { printf("%d\n", i); } return 0; }