【Linux】fork父子进程简单理解

本文详细介绍了Linux系统中的fork函数,包括其返回值机制以及如何创建子进程。接着讨论了进程的不同状态,如运行、阻塞、就绪、僵尸和孤儿状态,强调了D状态的特殊性和僵尸进程的危害。此外,还探讨了进程的优先级和调度过程,以及如何通过位图结构判断运行队列是否为空。最后,提到了环境变量这一话题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1️⃣fork 函数

man手册:



返回值:

  我们可以发现fork函数有三个返回值,创建成功了,返回子进程的pid给父对象,再返回0给子进程,创建失败就返回-1给父进程。为什么将子进程id返回给父进程,而返回0给子进程?
  因为一个父进程的子进程可以多于一个,没有一个函数可以使一个进程获得其所有子进程的进程id。而给子进程返回0,我们此时需要一个将被视为成功且不能为实际PID的值,0不是真实的PID(否则,孩子可能会认为它是父母)。同时, 0返回值也是系统调用指示成功的标准返回值。子进程也可以随时通过getpid来获取自己的pid,因此返回0就行

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>    
    
int main()    
{    
    printf("begin:我是一个进程,pid:%d,ppid:%d\n",getpid(),getppid());    
    pid_t id=fork();    
    if(id==0)    
    {    
        //子进程    
        while(1)    
        {    
            printf("我是子进程,pid:%d,ppid:ppid%d\n",getpid(),getppid());    
            sleep(1);    
        }    
    }    
    else if(id>0)    
    {    
        //父进程    
        while(1)    
        {    
            printf("我是父进程,pid:%d,ppid:ppid%d\n",getpid(),getppid());    
            sleep(1);                                                                                                           
        }    
    }    
    else    
    {}  //error 
    return 0;    
}  

看上面代码运行结果:



为什么要有两个不同的返回值?

  从上面代码运行来看,若没有fork函数,则这里是单执行流,且加了while死循环,只能执行 if 或者 else if 代码块其中之一,不能实现if和else if两个代码块同时运行。而fork函数创建子进程后,通过返回不同的返回值,进入不同的执行流,同时执行不同的代码块,这也是创建子进程的意义

fork 函数怎么做到返回两次的?
  我们先对进程下一个简单的概念,进程=内核数据结构+代码和数据。父进程创建后,有自己的内核数据结构和代码,可以运行。而fork函数后创建子进程,子进程把父进程的内核数据结构拷贝一份,再把pid等部分属性进行修改,作为区分。然而此时子进程没有自己的代码和数据,它只能和父进程一起访问父进程的代码,即共享父进程代码,然而数据却不能够共享,数据采用读时共享,写时拷贝的方法
为什么代码可以共享,数据不能被共享?
  代码可以共享,是因为我们写好代码,将其加载到内存后,代码便不可以再被修改,因此共享一份代码既能满足需求,又极大节省内存空间。而数据不能共享,是因为首先要保持进程的独立性,一个进程运行或者结束,不能影响另外一个进程。因此要保证数据独立,而父对象的数据并不是都要访问修改,全盘拷贝内存负担太大。因此采用写时拷贝,需要修改再拷贝,节约空间




  这时候我们才回过头来看,调用fork函数返回两次的问题:fork函数再执行return语句前,已经完成了子进程的创建工作,而return语句也是代码,父子共享,因此父进程在调度时返回一次,子进程调度时返回一次


2️⃣进程相关状态

  操作系统进程状态,主要分为运行、阻塞和就绪状态。大纲是这样,但是不同操作系统具体设计又有所差异,下面我们学习LInux中的相关状态





对于R状态,看以下代码运行不同结果



  为什么两个死循环运行状态不同呢?这里我们不能用我们的感觉去揣测CPU的速度,代码一带有打印语句,需要访问我们的显示器设备。进程有相当大的概率在等待IO设备即显示器设备就绪, 而CPU处理速度和外设就绪速度不是一个量级的,因此进程只有一瞬间在运行,大部分时间在等待,很难捕获到R(运行状态),即我们看到的S(状态)正在等待某种资源就绪与阻塞状态相对应。这里简单说一下,S+表示前台运行,S表示后台运行,后台运行的进程我们可以用kill指令杀掉

D状态
  当我们从内存向磁盘写入数据时,需要得到磁盘写入返回的结果,因为磁盘可能空间不够就会写入失败。如果操作系统压力过大,就会有一些行为:杀掉自己认为不重要的进程。如果该进程正在从内存向磁盘写入数据,而磁盘空间不够,写入失败,而进程又被杀掉了,那么写入失败的信息无法返回给进程,未写入的数据将会造成丢失。防止这种行为的出现,我们给写入磁盘的进程设为D状态(也是阻塞状态的一种),深度休眠该进程,不响应任何操作系统的请求,操作系统无法杀死该进程。如果出现进程有D状态,说明磁盘压力太大了,那么这个操作系统离生命周期结束不远了

T状态
  一个进程在运行时,或者休眠时,我们可以暂停它,发送指令 “kill -19 进程ID”,也可以恢复这个状态 “kill -18 进程ID”,不过再我们暂定这个进程再恢复起来后,这个进程就变成了后台进程,S+变成了S,后台进程再用 "kill -9 进程ID"杀掉。我们平常进行调试时,就是 t(暂定)状态



Z状态
  Z状态即僵尸状态,比如一个人突然死了,警察第一时间过来并不是清理现场,而是让法医确认死因,给关心它的人一个交代。线程也是一样,最关心这个进程的对象是它的父进程, 子进程退出,它得把自己的状态信息和数据资源暂时维持一段时间,这段时间就是僵尸状态。上报给父进程后,父进程再对子进程的资源进行清理和回收,就变成真正的X(死亡)状态。我们通过下面这个案例来了解僵尸进程的危害

僵尸进程:





  在这里父进程并没有对子进程做任何处理,子进程用exit(0)退出后,父进程没有主动回收子进程状态信息。此时子进程的退出状态必须被维持下去,因为他要告诉关心它的父进程,交给它的任务,办的怎么样了。如果一直不读取,那子进程就一直处于Z状态。维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护。而子进程不是R(运行)状态不能再被CPU调度,占用的内存资源又一直不被清理回收,就导致了内存泄漏

孤儿进程:

  如果父进程先退出,子进程后退出。那么当子进程结束后,进入Z状态,进程状态信息该上报给谁呢?此时子进程就会被init进程(操作系统)领养,称为“孤儿进程”,操作系统会对该类进程进行资源清理回收。注意孤儿进程被领养后,会变成后台进程,需要kill用指令杀死,



3️⃣进程优先级及其调度过程

  cpu资源分配的先后顺序,就是指进程的优先权(priority)。优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能







操作系统是如何对优先级进行确认调度的呢?

  首先设置一个运行队列和镜像等待队列,新来的队列放入等待队列中,等运行队列调度结束后,再调度等待队列。我们用一个run指针指向运行队列,一个wait指针指向等待队列。运行队列调度完后,交换run和wait指针,这样run指针指向等待队列,继续运行我们的进程,而wait指向空队列,等待新的队列进入。循环交替run和wait指针,保证run指针一直调度不为空的运行队列

怎么确认队列调度完了,即队列为空?

  这里我们采用位图的结构,40个运行队列模拟成40个bit位,即5个char类型大小。如下,通过i和pos便能找到对应优先级的队列是否正在运行,1表示运行,0表示空队列,当全部40个bit位都为空,即整个运行队列为空。这时候交换run和wait指针了,就不需要遍历且在O(1)的时间复杂度内就能完成整个运行队列是否为空的判定



4️⃣环境变量5️⃣6️⃣7️⃣8️⃣

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰瑞的猫^_^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值