当前位置: 首页 > Linux

fork()的用法初探

时间:2023-04-06 19:37:36 Linux

前言进程和线程是两种CPU资源和调度的说明。这句话可能有点啰嗦,简单解释一下。现代CPU速度如此之快,以至于所有资源(RAM、设备、总线……)都无法填满它。所以当有多个任务时,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()出来的新进程为子进程。注意:父进程和子进程的执行顺序是不确定的,所以如果你运行下面的例子,输出可能会有所不同。关键是理解fork()的机制。fork.c中的主要函数包括verify_areavoidverify_area(void*addr,intsize)//addr为虚拟地址,size为要写入的字节大小copy_memintcopy_mem(intnr,structtask_struct*p)//复制内存Pagetable,将进程p的数据段复制到nr*TASK的线性地址copy_processintcopy_process(intnr,longebp,longedi,longesi,longgs,longnone,longebx,longecx,longedx,longorig_eax,longfs,longes,longds,longeip,longcs,longeflags,longesp,longss)//复制系统进程信息,数据段,并设置必要的寄存器,find_empty_processintfind_empty_process(void)//为新进程获取一个不重复的pidLinux中对fork进行了优化,调用时采用了写时复制(COW,copyonwrite)的方式。当系统调用fork生成子进程时,并不会立即复制父进程给子进程,但仅在遇到“写入”(对资源的修改)操作时才复制资源。语法#include//definepid_t#includepid_tfork(void)返回值0:返回子进程,子进程的ID返回给父进程-1:错误,返回父进程,返回错误原因errno>0:返回子进程ID给父进程EAGAIN:进程ID号已达到最大可用值ENOMEM:内存不足,无法配置核心示例所需的数据结构主要示例////创建者:HarrisZhu//文件名:test.c//作者:HarrisZhu//创建于:2017-08-1917:36//最后修改://更新计数:2017-08-1917:36//标签://描述://结论:////==========================================================================#include#includeintmain(intargc,char**argv){pid_tpid;整数计数=0;pid=fork();if(pid<0)perror("分叉错误!");elseif(pid==0){printf("儿子的pid是%d\n",getpid());计数++;}else{printf("父进程的pid是%d\n",getpid());计数++;}printf("计数=%d\n",计数);返回0;}输出:parent的pid为20081count=1son的pid为20082count=1fork函数执行后,如果创新成功,会有两个进程,父进程(原进程),子进程在子进程,fork()返回0,返回子进程在父进程中的ID。fork后两个进程没有固定顺序。可以通过getpid()函数获取自己的进程ID,通过getppid()函数可以获取自己的父进程ID中级示例代码////创建者:HarrisZhu//文件名:test.c//作者:HarrisZhu//创建于:2017-08-1917:55//最后修改://更新次数:2017-08-1917:55//标签://描述://结论:////==========================================================================#include#include#includeintmain(intargc,char**argv){inti=0;printf("我\tson/父亲\tppid\tpid\trpid\n");for(i=0;i<2;i++){pid_trpid=fork();if(rpid==0)printf("%d\tson\t\t%4d\t%4d\t%4d\n",i,getppid(),getpid(),rpid);否则printf("%d\tfather\t\t%4d\t%4d\t%4d\n",i,getppid(),getpid(),rpid);}睡眠(1);return0;}注意我在程序中sleep1s是为了防止子进程还没有结束,父进程先结束,然后ppid会显示1输出。为了阅读方便,我这里使用ison/fatherppidpidrpid0father20426204270son204262042701father20425204的形式26204281father2042620427204291son204262042801son20427204290关系图规律总结for循环次数为N次,打印执行次数为:$2*(1+2+4+...2^{N-1})$生成的新过程为:$(1+2+4+...+2^{N-1})$统计过程可以使用printf("%d\n",getpid())或printf("+\n")判断中高级样本代码////创建者:HarrisZhu//文件名:test.c//作者:HarrisZhu//创建于:2017-08-1921:30//最后修改://更新计数:2017-08-1921:30//标签://描述://结论:////=============================================================================#include#include#includeintmain(intargc,char**argv){pid_tpid;静态整数a=0;printf("a=%d当前pid=%d\n\n",a,getpid());pid=fork();if(pid<0)perror("分叉错误!");printf("a=%dpid=%d,ppid=%d\n",a,pid,getppid());睡眠(0.1);一个++;pid=fork();if(pid<0)perror("分叉错误!");printf("a=%dpid=%d,ppid=%d\n",a,pid,getppid());sleep(0.1);a++;pid=fork();if(pid<0)perror("分叉错误!");printf("a=%dpid=%d,ppid=%d\n",a,pid,getppid());sleep(0.1);printf("getpid=%dgetppid=%d\n\n",getpid(),getppid());sleep(1);返回0;}输出a=0当前pid=24369a=0pid=24370,ppid=24368a=0pid=0,ppid=24369a=1pid=24371,ppid=24368a=1pid=24372,ppid=24369a=2pid=24373,ppid=24368getpid=24369getppid=24368a=2pid=0,ppid=24369getpid=24373getppid=24369a=2pid=24374,93=24370getppid=24369a=1pid=0,ppid=24369a=2pid=0,ppid=24370getpid=24374getppid=24370a=2pid=24375,ppid=24369a=1pid=0,ppid=24370getpid=24371getppid=24369a=2pid=0,ppid=24371getpid=24375getppid=24371a=2pid=24376,ppid=24370getpid=24372getppid=24370a=2pid=0,ppid=24372getpid=24376getppid=24372可以画分析图显示继承关系图下面和执行顺序,我就不解释这个操作的结果是怎么来的了。作为学习,如果你能画个框图看懂执行顺序和逻辑,那你就学会了fork()。高级示例代码这是在线的一个非常有名的例子。理解这个例子感觉就像解初中数学一样,非常有趣。////创建者:HarrisZhu//文件名:test.c//作者:HarrisZhu//创建于:2017-08-1920:08//最后修改://更新计数:2017-08-1920:08//标签://描述://结论:////============================================================================#include#include#includeintmain(intargc,char**argv){fork();叉子()&&叉子()||叉();叉();printf("+\n");睡觉(1);//ifnothislien,printout19+return0}分析第一个和最后一个肯定执行了我们分析一下中间3的执行fork()A&&B:IfIfAis0,theexecutionofBisignored.A||B:如果A为1,则忽略B的执行。上图中有5个进程,一共2*5*2=20个进程。完整的分支图如下,空间原因fork#0没有展开,与fork#1对称。后序内核没有展开进程创建、调度、清理等细节。有兴趣的可以自行搜索。如果你有兴趣讨论,你可以给我发邮件。本文主要参考这里