大数跨境
0
0

Linux进程管理惊天漏洞!fork用错竟让服务器内存爆满?速修指南

Linux进程管理惊天漏洞!fork用错竟让服务器内存爆满?速修指南 小何出海
2025-06-11
0
导读:立即关注

Linux | 红帽认证 | IT技术 | 运维工程师

👇1000人技术交流QQ群 备注【公众号】更快通过

图片

01

通过系统调用创建进程

--fork初识


fork的介绍

man fork


  1. fork是系统调用,它可以复制调用进程创建一个新的进程,这个新的进程称为当前进程的子进程,当前进程则被称为子进程的父进程


  2. 使用fork时不要忘记包含其头文件,#include<unistd.h>


  3. fork的返回值类型是pid_t,这个pid_t类似于int类型,这个pid_t定义的变量是进程号类型


  4. fork会给子进程返回0,给父进程返回子进程的PID


  5. 如果创建失败,它会返回一个小于0的数给父进程


观察代码运行结果1

#include #include int main(){  printf("begin of line\n");  fork();  printf("end of line\n");    return 0;}

运行结果如下


  1. fork之后的语句被执行了两次,说明子进程被创建出来起作用了


  2. 如果不加条件限制,那么fork之后的语句默认会被子进程和父进程共同执行两次



观察代码运行结果2


通过系统调用getpid和getppid获取进程标识符

//使用man指令查帮助手册man getpidman getppid


  1. getpid和getppid是系统调用接口


  2. 可以使用getpid获取当前进程的PID标识符(每一个进程都有唯一的标识符)


  3. 可以使用getppid获取当前进程的PPID标识符(即当前进程的父进程的标识符)


  4. 使用getpid或getppid时不要忘记包含其头文件,#include<sys/types.h> #include<unistd.h>



  1. 使用小编编写的如下代码进行讲解,这是一个分别打印子进程和子进程的父进程(当前进程)的PID和PPID死循环程序,打印后程序休眠1s后再继续打印


  2. 并且这个看似和常规c语言逻辑不符合的判断语句却可以分别打印子进程和子进程的父进程(当前进程)的PID和PPID


  3. 在正式打印子进程和子进程的父进程的PID和PPID前,我们先打印一下当前进程的PID和PPID


  4. 我们使用pid_t类型的变量id来接收fork的返回值


  5. 由于这是一个死循环程序,那么当想要退出终止的时候,无脑按ctrl+c即可退出

#include #include #include int main(){  printf("我是一个进程啦,我的PID是:%d,我的PPID是:%d\n",getpid(),getppid());  sleep(3);  pid_t id=fork();  if(id>0)//父进程  {    while(1)    {      printf("我是一个父进程,我的PID是:%d,我的PPID是:%d\n",getpid(),getppid());      sleep(2);    }  }  else if(id==0)//子进程  {    while(1)    {      printf("我是一个子进程,我的PID是:%d,我的PPID是:%d\n",getpid(),getppid());      sleep(2);    }  }  else//子进程创建失败,返回的是小于0的值  {    printf("error\n");  }  return 0;}


运行结果如下


  1. 那么我们观察到正式打印后当前进程即为父进程,子进程的父进程对应的PPID即为当前进程的PID


  2. 判断语句中前两个条件判断语句中的语句都执行了,那么肯定代表前两个判断语句的判断条件都成立,即一个变量id不仅大于0,又等于0



那么我们根据上述运行结果思考如下几个问题


  1. 为什么fork要给子进程返回0,给父进程返回子进程的pid?


  2. 一个函数如何做到返回两次的?


  3. 一个变量怎么会有不同的内容?


  4. fork函数干了什么



为什么fork要给子进程返回0,给父进程返回子进程的pid?


  1. 我们知道fork是系统调用接口函数,那么一个父进程中可以多次使用fork。


    例如我使用了100次,那么一个父进程对应就有100个子进程,那么父进程如果不对子进程进行区分,那么是不是没有办法找到特定的子进程进行控制了。


    所以为了便于对子进程进行控制和管理,每一个子进程都有其对应的唯一的PID,所以就给父进程返回子进程的PID


  2. 子进程对应只要一个父进程,在子进程创建之初,它的进程控制块PCB即task_struct中就已经被对应初始化放置了其对应的父进程的PPID,并且它不需要对谁进行管理,所以给子进程返回0


  3. 我们想一下,父进程创建子进程的目的是就为了让子进程去执行和父进程不同的代码块。


    如果和小编演示的代码运行结果1,父进程和子进程执行相同的代码块,那么是不是也太没有意义了


  4. 所以fork分别给父进程和子进程返回不同的返回值,是为了区分不同的执行流,让父进程和子进程执行不同的代码块。


    一般而言,fork之后的代码子进程和父进程共享,那么程序执行fork之后就有两个进程了,由于fork之后的代码子进程和父进程共享,那么fork之后的判断语句会被执行两次即父进程执行一次,子进程执行一次。


    那么父进程进入id>0的语句中执行,子进程进入id==0的语句中执行,那么这样就显示出了if语句中的前两个判断语句都会执行的效果



fork函数做了什么?



  1. fork函数会创建子进程,此时创建子进程之后,系统之后就多了一个进程


  2. 子进程最初什么都没有,没有PCB,所以它就复制父进程的PCB进行适量修改PPID以及PID以及其它属性时候就成了子进程自己的PCB,即子进程的进程控制块task_struct


  3. 我们知道,程序文件是有对应的代码和数据的,程序文件运行之后对应的进程就是当前的父进程。


    所以父进程是有自己的代码和数据的,子进程什么都没有连进程控制块PCB中的大部分内容都是复制的父进程的。


    一个进程是由内核数据结构PCB加代码和数据构成,此时子进程已经有PCB了,那么它既然想要成为一个进程却又没有代码和数据,那么它只能从父进程的代码和数据想办法。


    其中对于代码,代码是不能修改的,那么父进程自然可以允许子进程和自己共用代码,即父进程和子进程指向同一块代码


  4. 但是对于数据,同样是子进程和父进程共用一块数据,但是这个中如果父进程或子进程使用数据过程中要对数据发生修改,那么谁对这个数据发生修改,那么谁就会发生数据的写使拷贝


  5. 一旦涉及到数据就可能会对数据进行修改,如果子进程和父进程共用数据,指向同一块数据空间,父进程使用着数据好好的,突然子进程运行过程中对数据进行修改,那子进程是运行过程中就对父进程产生了影响。


    同样的道理父进程如果对数据进行修改也会影响子进程,我们又知道进程是独立的,互不影响的,所以不能让子进程和父进程指向同一块数据空间。


    那么在linux中是这样做的,使用写时拷贝,即初始让子进程和父进程指向同一块数据空间,如果父进程或子进程不对数据进行修改,那么子进程始终和父进程共用同一块数据空间,因为互相之间数据不进行修改,实现了互不影响,进程独立。


    但是如果父进程或子进程需要对数据进行修改,那么不能在它指向的数据空间上进行修改,而是应该对于需要进行修改的部分数据拷贝到另一块空间上进行修改。


    这时候父进程或字进程的数据修改修改的是属于自己的内存的数据空间上的数据,此时父进程和子进程各自数据的修改不会影响彼此,实现了进程的独立和互不影响


一个变量怎么会有不同的内容?


父进程使用fork就是为了让子进程和父进程执行不同的代码块,那么子进程和父进程指向的同一块代码,那么如果做才能让子进程和父进程执行不同的代码块呢?


那么就是让fork返回给子进程和父进程的返回值不同即可,由于返回值需要对变量写入数据,子进程发生写时拷贝,在另一块空间上写入数据,子进程有了自己独立的内存数据空间。


子进程和父进程分别接收到返回值后,原代码中使用if和else语句进行判断返回值,根据子进程和父进程接收的返回值不同,那么也就实现了让子进程和父进程执行不同的代码块。


子进程被创建好后,子进程和父进程谁先运行?


这个不能确定,具体谁先运行是由调度器来决定


一个函数如何做到返回两次的?



  1. fork是系统调用接口函数,即fork是一个函数,在fork中的主要作用是进行创建子进程,子进程创建完成之后,此时CPU就开始对子进程和父进程开始调度运行了


  2. 在fork函数中的return ret语句是在子进程创建之后才进行返回的,因为fork的主要作用就是创建子进程,子进程创建后才会进行返回,而return ret语句又是属于代码,同时由于子进程和父进程指向同一个代码区域,在fork函数中的return ret语句是在子进程创建之后才进行返回的。


    那么CUP就会从return ret这个语句位置开始进行调度子进程和父进程,所以子进程和父进程就会各自都执行return ret语句一次,进而一个函数就会返回两次了



02

bash的子进程


  1. ps axj可以查看所有进程信息,ps axj | head -1 通过管道和head将进程信息的第一行,即对应信息含义显示,同时使用&&表示后续的运行结果在前一次是显示下继续进行显示。


    那么我们好奇这个当前进程(即子进程的父进程)对应的父进程是什么,当前进程(即子进程的父进程)的父进程的PPID是14605,那么我们使用ps axj查看所有进程信息,使用grep文本过滤器,过滤标识符14605,过滤时由于grep也是指令,即也是一个运行的程序,即也是一个进程。


    grep查找需要将查找的内容也附加在自己上执行命令中,grep查找过程中会连同自己的命令包含在内一同查找,所以对于查找的内容的进程的标识符会包含grep。


    这里为了更简洁,我们使用grep的-v选项将grep排除在外


  2. 那么我们观察PID14605对应的是bash,bash是命令行解释器,所以我们可以推断bash中也一定是使用了fork的形式进行了创建子进程,也就是当bash命令行中输入了指令或命令。


    对应的bash并不会自身实际去执行指令或命令,而是使用fork创建子进程去执行解释新命令,bash则继续去打印命令行,用户在命令行后输入指令, bash去接收用户的命令。



Linux运维资料领取/课程咨询

↓请扫描下方二维码↓

图片

【声明】内容源于网络
0
0
小何出海
跨境分享阁 | 长期积累行业知识
内容 41133
粉丝 1
小何出海 跨境分享阁 | 长期积累行业知识
总阅读261.0k
粉丝1
内容41.1k