进程和线程是两种CPU资源以及调度的描述。
这句话可能有点拗口,因此简单展开说明一下。数据结构
现代CPU太快了,全部的资源(RAM, device, bus..)都没法填满它。因此有多个任务时,CPU就轮流着来处理。获得CPU资源时,其余总线,显卡,RAM等等都要准备好,这样就构成了咱们程序执行的上下文。等执行完或CPU分配给它的时间用完了,它就要被切换出去,等待CPU的下一次临幸。固然切换出去以前会保存上下文。
因此从CPU的角度看它成天就在: 加载A的上下文,执行A,保存A的上下文,调入B的上下文,执行B,保存B的上下文。。。函数
进程就是包括上下文切换在内的程序执行时间总和 = cpu加载上下文+CPU执行+CPU保存上下文学习
进程颗粒度太大,每次都有上下文的调入调出开销。而一段程序必定有多个块,能够把它分红A, B, C多个块,而后这样执行:
CPU加载程序上下文,执行A, 执行B,执行C, 执行A, 执行B,执行C。。。, CPU保存程序上下文。
也就是线程共享了进程的上下文。优化
一个进程包括代码,数据和分配给进程的资源。fork函数调用会使内核建立一个与原来进程几乎彻底相同的进程。内核会给新进程分配新的资源,而后把原来进程的值拷贝到新进程中,只有少数如fork()返回值不一样。
通常咱们称原进程为父进程,fork()出来的新进程为子进程。
注意: 父进程和子进程的执行顺序是不定的
因此运行下面的例子,可能输出结果会有差别,关键是理解fork()的机制原理。this
在fork.c里主要的函数包括spa
verify_area.net
void verify_area(void * addr,int size) // addr 是虚拟地址 ,size是须要写入的字节大小
copy_mem线程
int copy_mem(int nr,struct task_struct * p) //拷贝内存页表,把进程p的数据段copy到nr*TASK的线性地址处
copy_process3d
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none, long ebx,long ecx,long edx, long orig_eax, long fs,long es,long ds, long eip,long cs,long eflags,long esp,long ss) //拷贝系统进程信息,数据段,并设置必要的registers,
find_empty_processcode
int find_empty_process(void) //为新进程获取不重复的pid
在Linux中,对fork进行了优化,调用时采用写时复制 (COW,copy on write)的方式,在系统调用fork生成子进程的时候,不立刻为子进程复制父进程的资源,而是在遇到“写入”(对资源进行修改)操做时才复制资源。
#include <sys/types.h> //define pid_t #include <unistd.h> pid_t fork(void)
0: 返回给子进程,子进程的ID返回给父进程
-1: 出错, 返回给父进程,错误缘由返回到errno
>0: 子进程的ID返回给父进程
EAGAIN: 进程ID号达到最大可以使用值
ENOMEM: 内存不足,没法配置核心所需的数据结构
// // Created by : Harris Zhu // Filename : test.c // Author : Harris Zhu // Created On : 2017-08-19 17:36 // Last Modified : // Update Count : 2017-08-19 17:36 // Tags : // Description : // Conclusion : // //======================================================================= #include <stdio.h> #include <unistd.h> int main (int argc, char**argv) { pid_t pid; int count=0; pid=fork(); if (pid < 0) perror("fork error!"); else if (pid == 0) { printf("the pid of son is %d\n",getpid()); count++; } else { printf("the pid of parent is %d\n",getpid()); count++; } printf("count = %d\n",count); return 0; }
输出:
the pid of parent is 20081 count = 1 the pid of son is 20082 count = 1
fork函数执行结束后,若是创新成功,则出现两个进程,父进程(原来进程),子进程。
在子进程中,fork()返回0, 在父进程中返回子进程的ID。
fork后的两个进程没有固定的前后顺序。
能够经过getpid()函数来得到本身的进程ID,能够经过getppid()函数来得到本身父进程ID
// // Created by : Harris Zhu // Filename : test.c // Author : Harris Zhu // Created On : 2017-08-19 17:55 // Last Modified : // Update Count : 2017-08-19 17:55 // Tags : // Description : // Conclusion : // //======================================================================= #include <sys/types.h> #include <stdio.h> #include <unistd.h> int main(int argc, char** argv) { int i=0; printf("i\tson/father\tppid\tpid\trpid\n"); for(i=0;i<2;i++){ pid_t rpid=fork(); if(rpid==0) printf("%d\tson\t\t%4d\t%4d\t%4d\n",i,getppid(),getpid(),rpid); else printf("%d\tfather\t\t%4d\t%4d\t%4d\n",i,getppid(),getpid(),rpid); } sleep(1); return 0; }
注意 程序中我sleep 1s是为了防止子进程还未结束,父进程先结束,这时ppid会显示1
为了阅读方便,我这里用表格
i | son/father | ppid | pid | rpid |
---|---|---|---|---|
0 | father | 20425 | 20426 | 20427 |
0 | son | 20426 | 20427 | 0 |
1 | father | 20425 | 20426 | 20428 |
1 | father | 20426 | 20427 | 20429 |
1 | son | 20426 | 20428 | 0 |
1 | son | 20427 | 20429 | 0 |
for循环次数为N,
执行print次数: $2*(1+2+4+...2^{N-1})$
生成的新进程为: $(1+2+4+...+2^{N-1})$
能够用printf("%d\n", getpid())
或printf("+\n")
来判断
// // Created by : Harris Zhu // Filename : test.c // Author : Harris Zhu // Created On : 2017-08-19 21:30 // Last Modified : // Update Count : 2017-08-19 21:30 // Tags : // Description : // Conclusion : // //======================================================================= #include <sys/types.h> #include <stdio.h> #include <unistd.h> int main(int argc, char ** argv) { pid_t pid; static int a=0; printf("a=%d current pid=%d\n\n", a, getpid()); pid = fork(); if(pid <0) perror("fork error!"); printf("a=%d pid=%d, ppid=%d\n", a, pid, getppid()); sleep(0.1); a++; pid = fork(); if(pid <0) perror("fork error!"); printf("a=%d pid=%d, ppid=%d\n", a, pid, getppid()); sleep(0.1); a++; pid = fork(); if(pid <0) perror("fork error!"); printf("a=%d pid=%d, ppid=%d\n", a, pid, getppid()); sleep(0.1); printf("getpid = %d getppid= %d \n\n", getpid(), getppid()); sleep(1); return 0; }
a=0 current pid=24369 a=0 pid=24370, ppid=24368 a=0 pid=0, ppid=24369 a=1 pid=24371, ppid=24368 a=1 pid=24372, ppid=24369 a=2 pid=24373, ppid=24368 getpid = 24369 getppid= 24368 a=2 pid=0, ppid=24369 getpid = 24373 getppid= 24369 a=2 pid=24374, ppid=24369 getpid = 24370 getppid= 24369 a=1 pid=0, ppid=24369 a=2 pid=0, ppid=24370 getpid = 24374 getppid= 24370 a=2 pid=24375, ppid=24369 a=1 pid=0, ppid=24370 getpid = 24371 getppid= 24369 a=2 pid=0, ppid=24371 getpid = 24375 getppid= 24371 a=2 pid=24376, ppid=24370 getpid = 24372 getppid= 24370 a=2 pid=0, ppid=24372 getpid = 24376 getppid= 24372
对照输出,能够画出下面的继承关系图,以及执行顺序
我不展开讲解这个运行结果如何来的,做为学习,若是能本身画个框图搞清楚执行的顺序和逻辑,那么就算是学懂fork()了
这是网上很是著名的一个示例。理解这个例子感受就像在解初中的数学道,特别有趣。
// // Created by : Harris Zhu // Filename : test.c // Author : Harris Zhu // Created On : 2017-08-19 20:08 // Last Modified : // Update Count : 2017-08-19 20:08 // Tags : // Description : // Conclusion : // //======================================================================= #include <sys/types.h> #include <stdio.h> #include <unistd.h> int main(int argc, char** argv) { fork(); fork() && fork() || fork(); fork(); printf("+\n"); sleep(1); //if no this lien, it prints out 19 + return 0 }
第一个和最后一个是确定执行的
下面分析一下中间3个fork()的执行
A&&B: 若是A为0, 则忽略B的执行
A||B: 若是A为1, 则忽略B的执行
上图中一个5个进程, 总共2*5*2=20个进程
完整分支图以下,空间缘由fork #0未展开,它和fork #1是对称的
内核对于进程的建立,调度和清理等细节就不展开,有兴趣的能够自行搜索。若是我的有兴趣讨论,能够发信给我邮箱
本文主要参考这里