【Linux进程、线程、任务调度】二 fork/vfork与写时拷贝 线程的本质 托孤 进程睡眠和等待队列

  • 学习交流加(可免费帮忙下载CSDN资源):
  • 个人微信: liu1126137994
  • 学习交流资源分享qq群1(已满): 962535112
  • 学习交流资源分享qq群2(已满): 780902027
  • 学习交流资源分享qq群3:893215882

文章目录

    • 1、fork
    • 2、写时拷贝(Copy on write)
    • 3、vfork
    • 4、Linux线程的实现本质
    • 5、PID与TGID
    • 6、SUBREAPER与托孤
    • 7、进程0和进程1
    • 8、进程的睡眠和等待队列
    • 9、总结

上一篇文章 点击链接【Linux进程、线程、任务调度】一
讲了

  • Linux进程生命周期(就绪、运行、睡眠、停止、僵尸)
  • 僵尸的含义
  • 停止状态与作业控制, cpulimit
  • 内存泄漏的真实含义
  • task_struct以及task_struct之间的关系
  • 初见fork和僵尸

本篇接着上一篇文章主要记录以下学习内容:

  • fork vfork clone 的含义
  • 写时拷贝技术
  • Linux线程的实现本质
  • 进程0 和 进程1
  • 进程的睡眠和等待队列
  • 孤儿进程的托孤 ,SUBREAPER

1、fork

fork(),创建子进程,实际上就是将父进程的task_struct结构进行一份拷贝(注意拷贝的都是指针),假设有p1进程,fork后产生p2子进程:

上面的mm ,fs,files,signal等都是task_struct结构体里的指针,分别指向进程的内存资源,文件系统资源,文件资源,信号资源等,当父进程p1 fork后,内核把p1的task_struct拷贝一份,作为子进程p2的描述符。此时p1和p2所指向的资源实际上是一样的,这并不与进程是占有独立的空间矛盾,因为后面对资源进型任何的修改将导致资源分裂,比如当p1(或p2)对fs,files,signal等资源执行操作,将导致fs,files,signal各分裂一份作为p1(或p2)的资源。

其中对于mm(内存)的情况,就比较复杂,有一种技术:写时拷贝(copy on write)

2、写时拷贝(Copy on write)

看下图:

最开始的时候进程p1,假设某一块的虚拟内存为virt1,virt1所映射的物理内存为phy1,原则上virt1与phy1是可读可写的。当p1调用fork()后,产生了新的虚存和物理内存表示子进程p2的某一块地址,实际上此时p1和p2的是指向同样的物理内存地址,并且这块内存变得只读了 。假设p2(p1)要对这块内存进行写操作,就会产生page fault,此时就会重新开辟一块物理内存空间,让p2(p1)的virt1映射到新的物理内存phy2,然后重新对phy2的内存进行写操作。

我们注意到,这个过程中,需要有MMU进行地址翻译,所以写时拷贝技术必须要有MMU才能实现。

无MMU,不能写时拷贝,不能fork

  • 实验
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int data = 10;int child_process()
{printf("Child process %d, data %d\n",getpid(),data);data = 20;printf("Child process %d, data %d\n",getpid(),data);sleep(15);printf("Child process %d exit\n",getpid());_exit(0);
}int main(int argc, char* argv[])
{int pid;pid = fork();if(pid==0) {child_process();}else{sleep(1);printf("Parent process %d, data %d\n",getpid(), data);sleep(20);printf("Parent process %d exit\n",getpid());exit(0);}
}

编译运行结果:
在这里插入图片描述

  • 结果分析

以上程序父进程fork后,子进程对全局变量进行写,物理内存部分进行分裂,使得子进程与父进程data变量对应的物理内存部分分离(写时拷贝)。从此以后父子进程各自读写data将不会影响彼此,且父子进程的运行是独立的,可以同时运行。

3、vfork

那么如果没有MMU,该如何呢?vfork在无MMU的场合下使用。

无MMU时只能使用vfork

vfork在以下情况下使用:

父进程阻塞直到子进程:

  • exit 或 _exit
  • exec

vfork实际上内部是对mm(内存资源)进行一个clone,而不是copy,其他资源还是copy(因为其他资源不受MMU影响),见下图:

  • 实验
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int data = 10;int child_process()
{printf("Child process %d, data %d\n",getpid(),data);data = 20;printf("Child process %d, data %d\n",getpid(),data);sleep(15);printf("Child process %d exit\n",getpid());_exit(0);
}int main(int argc, char* argv[])
{int pid;pid = vfork();if(pid==0) {child_process();}else{sleep(1);printf("Parent process %d, data %d\n",getpid(), data);sleep(20);printf("Parent process %d exit\n",getpid());exit(0);}
}
  • 运行结果:
    在这里插入图片描述
  • 结果分析
    由结果可以看出vfork与fork的区别:vfork的mm部分,是clone的而非copy的。父进程在子进程exit或者exec之前一直都处于阻塞状态(可以自己运行下看看sleep效果)。

4、Linux线程的实现本质

线程,共享进程的所有资源!那么内部是如何实现的呢?

实际上pthread_create内部就是调用clone,当进程(线程)p1调用pthread_create,内部就是调用clone,新生成的线程p2与原来的线程p1共享所有资源。

其实,此时可以看成是p1和p2的task_struct结构体内的指向资源的指针是一样的。多线程是共享资源的。

我们可以看到,线程p1和p2都是有task_struct的,而且里面的资源是一样的,内核的调度器只看task_struct,所以进程,线程,都是可以被调度的对象。线程也被叫做轻量级进程。

5、PID与TGID

POSIX规定,进程中的多个线程getpid()后应该获得同一个id(主线程id(TGID)),但是实际上每一个线程都有一个task_struct结构体,这个结构体中存有各个线程的id(PID)。

为了解决有两个id的情况,内核搞出了一个TGID,每一个线程的TGID都是相等的,等于主线程的id。

假设现在有进程进程p1,它创建了三个子进程:

其中:
1、top 查看的是进程的视角,查看的id实际上是各个进程(线程)的TGID
2、top -H是线程视角,查看的是各个线程的自己独有的id即PID

看以下程序:

#include <stdio.h>
#include <pthread.h>
#include <stdio.h>
#include <linux/unistd.h>
#include <sys/syscall.h>static pid_t gettid( void )
{return syscall(__NR_gettid);
}static void *thread_fun(void *param)
{printf("thread pid:%d, tid:%d pthread_self:%lu\n", getpid(), gettid(),pthread_self());while(1);return NULL;
}int main(void)
{pthread_t tid1, tid2;int ret;printf("thread pid:%d, tid:%d pthread_self:%lu\n", getpid(), gettid(),pthread_self());ret = pthread_create(&tid1, NULL, thread_fun, NULL);if (ret == -1) {perror("cannot create new thread");return -1;}ret = pthread_create(&tid2, NULL, thread_fun, NULL);if (ret == -1) {perror("cannot create new thread");return -1;}if (pthread_join(tid1, NULL) != 0) {perror("call pthread_join function fail");return -1;}if (pthread_join(tid2, NULL) != 0) {perror("call pthread_join function fail");return -1;}return 0;
}
  • 运行结果:
    在这里插入图片描述
    可以看出此时程序处于死循环,getpid获得的id是一样的,gettid获得的是线程结构体中的id。所以是不一样的,而pthread_self 并不是任何id,这里我们不关心pthread_slef获得id,我们只关心PID与TGID。

另开一个终端执行命令:

$ top

在这里插入图片描述
可得知,只能看到一个thread,实际上就是我们的进程(主线程),它的id也是进程的id。top命令只能看到进程的视角,看到的都是进程与进程的id,看不到线程与线程id

$ top -H

在这里插入图片描述

可看到,两个被创建出来的线程thread,且它们的id都是各自的task_struct里面的id(PID),而不是进程的id(TGID)。top -H 看到的是线程视角,显示的id是线程的独有的id(PID)。这里id名词较多,容易弄混,知道原理即可。

6、SUBREAPER与托孤

  • 孤儿进程
    当父进程死掉,子进程就被称为孤儿进程

对孤儿进程,有一个问题,就是父进程挂掉了,孤儿进程最后怎门办,因为没有人来回收它了。

在Linux中,当父进程挂掉,子进程会在进程树上向上找subreaper进程,当找到subreaper进程,孤儿进程就会挂到subreaper进程下面成为subreaper进程的子进程,后面就由subreaper进程对该孤儿进程进行回收。如果没有subreaper进程,那么最终该孤儿进程会挂到init 1号进程下,由init进程回收。如下图:

此过程,称为托孤!

Linux内核中,有一种方法可以将某一进程变为subreaper进程:

在这里插入图片描述

prctl函数可以使当前调用它的进程变为subreaper进程。
PR_SET_CHILD_SUBREAPER是Linux 3.4引入的新特性,将它设置为非零值,就可以使当前进程变为像1号进程那样的subreaper进程,可以对孤儿进程进行收养了。

  • 实验
    life_period.c
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>int main(void)
{pid_t pid,wait_pid;int status;pid = fork();if (pid==-1)	{perror("Cannot create new process");exit(1);} else 	if (pid==0) {printf("child process id: %ld\n", (long) getpid());pause();_exit(0);} else {printf("parent process id: %ld\n", (long) getpid());wait_pid=waitpid(pid, &status, WUNTRACED | WCONTINUED);if (wait_pid == -1) {perror("cannot using waitpid function");exit(1);}if(WIFSIGNALED(status))printf("child process is killed by signal %d\n", WTERMSIG(status));exit(0);}
}

编译程序运行结果如下:
在这里插入图片描述
此时,子进程处于停止状态,父进程也处于阻塞状态(waitpid等待子进程结束)。
输入以下命令查看当前进程树,可以看到我们的life_period进程与其子进程在进程树中的位置:

$ pstree

然后,杀死父进程

   $  kill -9 3532

再看进程树:

  • 结论
    可以看到,当我们杀死父进程后,子进程被init进程托孤,以后如果该进程退出了,由init进程回收它的task_struct结构。

7、进程0和进程1

这是一个鸡生蛋蛋生鸡的故事,我们知道Linux系统中所有的进程都是init进程fork而来,init进程是内核中跑的所有进程的祖先,那么问题来了,init进程哪里来的?答案是,init进程是由编译器编译而来的?那么编译器又是哪里来的?答案是编译器是由编译器编译而来。那么编译编译器的编译器又是哪里来的?

可见,这是一个死循环。实际上,最开始,是有一些大牛用0 1写的编译器,写完直接可以在cpu上跑的,然后用最开始的编译器编译后面的编译器。这个不是重点。今天我们的重点是0号进程。0号进程是1号进程父进程。
0号进程也叫IDLE进程。0号进程在什么时候跑呢?

当所有其他进程,包括init进程都停止运行了,0号进程就会运行。此时0号进程会把CPU置为低功耗,非常省电。
此时内核被置为wait_for_interrupt状态,除非有一个中断过来,才会唤醒其他进程。

8、进程的睡眠和等待队列

上一篇文章点击链接 简单讲了深度睡眠和浅睡眠。那么什么情况下需要将进程置为深度睡眠呢?
假设有进程p,它正在运行,但是整个程序代码并没有完全进入内存,假如p调用一个函数fun(),这个函数代码也没有进入到内存,那么现在就会出现缺页(page fault),从而内核需要去执行缺页处理函数,这个阶段进程p就会进入到睡眠状态,假设进入浅睡眠,那么有可能会来一个信号signal1,假设signal1的信号处理函数也没有进入到内存,这个时候又会出现缺页错误(page fault) 。。。。这样的话,就有可能导致程序崩溃。

下面看一段代码来理解进程的睡眠与调度:

在这里插入图片描述


上面程序注解非常的清晰明了,我们只需要注意两点即可:

进程在阻塞读(或者其他类似于读的状态如sleep)时,那个读的函数内部一定会调用内核调度函数schedule(),让CPU去执行其他的进程。

当有信号来(或者有资源来)的时候,进程被唤醒,这里的唤醒,实际上是给等待队列一个信号,然后让队列自己去唤醒队列中的进程,并不是去直接唤醒进程的,此时等待队列可以看做一个中间机构代替我们去做复杂的唤醒工作。具体是如何实现的,在以后的学习中,还会继续分析。

9、总结

掌握以下内容

  • fork vfork clone的关系
  • 写时拷贝技术与fork,MMU的关系
  • Linux线程的实现本质,内部是调用clone
  • 0号进程与1号进程
  • 进程的托孤与subreaper进程
  • 进程的睡眠与等待队列

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422659.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用Log4Net来记录系统的日志信息

[http://www.cnblogs.com/xhwy/archive/2011/11/25/2263495.html] 几乎所有的大型应用都会有自己的用于跟踪调试的API。因为一旦程序被部署以后&#xff0c;就不太可能再利用专门的调试工具了。然而一个管理员可能需要有一套强大的日志系统来诊断和修复配置上的问题。 经验表明…

扩展插件_Adobe扩展工具插件系列

Adobe系列软件相信大家都已了解到其功能的强大&#xff0c;除了软件之外Adobe系列的插件的功能也是不可忽略的。今天给大家分享的几款Adobe系列超强PS扩展工具&#xff0c;都是一些摄影师、设计师经常用的到的&#xff0c;可以提升不少工作效率&#xff0c;大家千万别错过哦&am…

【Linux进程、线程、任务调度】三 CPU/IO消耗型进程 吞吐率/响应 SCHED_FIFO算法与SCHED_RR算法 SCHED_NORMAL算法和CFS算法 nice与renic chrt

学习交流加&#xff08;可免费帮忙下载CSDN资源&#xff09;&#xff1a;个人微信&#xff1a; liu1126137994学习交流资源分享qq群1&#xff08;已满&#xff09;&#xff1a; 962535112学习交流资源分享qq群2&#xff08;已满&#xff09;&#xff1a; 780902027学习交流资源…

dexpress 流程图_DevExpress控件使用经验总结

DevExpress是一个比较有名的界面控件套件&#xff0c;提供了一系列的界面控件套件的DotNet界面控件。本文主要介绍我在使用DevExpress控件过程中&#xff0c;遇到或者发现的一些问题解决方案&#xff0c;或者也可以所示一些小的经验总结。总体来讲&#xff0c;使用DevExpress控…

UCenter 表结构

Ucenter 数据库表结构说明uc_admins 管理员表数据表说明&#xff1a;管理员相关信息属性说明&#xff1a;uid mediumint(8) -- 用户IDusername char(15) -- 用户名allowadminsetting tinyint(1) --allowadminapp tinyint(1) --allowadminuser tinyint(1) --allowadminbadword t…

Java学习之路整理-技术书从入门到进阶最全50+本(珍藏版 )

学习交流加 个人微信&#xff1a;LyyCoder学习交流资源分享qq群1&#xff08;已满&#xff09;&#xff1a; 962535112学习交流资源分享qq群2&#xff1a; 780902027一.速读一遍&#xff08;最好在1~2天内完成&#xff09; 人的大脑记忆力有限&#xff0c;在一天内快速看完一本…

问题 seata_架构设计 | 基于Seata中间件,微服务模式下事务管理

一、Seata简介1、Seata组件Seata是一款开源的分布式事务解决方案&#xff0c;致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA、XA事务模式&#xff0c;为用户打造一站式的分布式解决方案。2、支持模式AT 模式基于支持本地 ACID 事务的关系型数…

Override and Overload (重写和重载)

1&#xff09;方法的重写overriding和重载overloading是java多态性的不同表现. &#xff08;2&#xff09;重写overriding是父类与子类之间的多态性的一种表现&#xff0c;重载是一个类中多态性的表现。如果子类中定义方法与其父类有相同的名称和参数&#xff0c;我们说该方法被…

程序员史诗级必读书单吐血整理四个维度系列80+本书(珍藏版)

互联网行业的特点是变化。若要提高互联网开发的技能&#xff0c;就必须跟上技术发展的步伐。埋首醉心于项目开发与实战&#xff0c;固然能够锤炼自己的开发技巧&#xff0c;却难免受限于经验与学识。 世界上并不存在速成的终南捷径&#xff0c;但阅读好的技术书籍&#xff0c;尤…

示波器1m和50欧姆示阻抗匹配_示波器输入阻抗选1MΩ还是50Ω的详细解析

熟悉示波器的朋友可能都会有过这样的困惑&#xff1a;输入阻抗有1MΩ和50Ω两种&#xff0c;我们到底该如何选择呢&#xff1f;一、传输线想要讲清楚50Ω的由来&#xff0c;我们需要先讲一下传输线。电信号实际上是以电磁波的形式在传输线中传播的。当传输线的尺寸不再远小于电…

前端学习(355):小练习

.已知两个矩形,宽高分别是200200,400400,甲矩形的坐标是x1,y1.乙矩形的坐标是x2,y2. 写出判断条件两个矩形是否相碰撞 <script>x1200;//自己随意定义坐标x2200;y1400;y2400;var boolfalse;//先定义一个布尔值为falseif(x1>x2 && x1<x2400 && y1&g…

Quartus 中快速分配器件管脚

在quartus中分配器件管脚最笨的方法是对于器件手册一个一个的敲进去&#xff0c;这样做如果用到的管脚很好还没有发觉什么不好&#xff0c;但是当用到的器件管脚很多的时候就会发现很麻烦&#xff0c;而且容易出错。接下来我来介绍一种很方便的方法。 首先在txt文档中建立管脚和…

Web前端书单从HTML到JS到AJAX到HTTP从框架到全栈

前言&#xff1a;技术书阅读方法论 一.速读一遍&#xff08;最好在1~2天内完成&#xff09; 人的大脑记忆力有限&#xff0c;在一天内快速看完一本书会在大脑里留下深刻印象&#xff0c;对于之后复习以及总结都会有特别好的作用。 对于每一章的知识&#xff0c;先阅读标题&…

自定义背景_新版快绘精选:自定义背景墙 | 吊顶 | 云渲染滤镜升级

New自定义背景墙没有合适的背景墙样板&#xff1f;你需要一个全世界独一无二&#xff0c;独属于你monent的背景墙&#xff1f;没问题&#xff01;无论是要这样↓还是这样↓快绘都能帮到你&#xff01;-- 请观看视频 --New自定义吊顶除了可以自定义背景墙&#xff0c;新版快绘也…

【C++学习详细教程目录】

学习交流加&#xff08;可免费帮忙下载CSDN资源&#xff09;&#xff1a;个人微信(进微信群加)&#xff1a; LyyCoder学习交流资源分享qq群1&#xff08;已满&#xff09;&#xff1a; 962535112学习交流资源分享qq群2&#xff08;已满&#xff09;&#xff1a; 780902027学习交…

删除开机选择系统

在我的电脑点右键--属性--高级--系统启动,系统失败和调试信息--设置--显示操作系统列表时间,和在需要时显示恢复选项的时间,在前面的小方格里打上构或去掉构就行了. 修改引导分区里面的BOOT.INI文件s C:\boot.ini 将只读属性去掉 [boot loader] timeout30 defaultmulti(0)dis…

底层知识学习记录目录表

学习交流加&#xff08;可免费帮忙下载CSDN资源&#xff09;&#xff1a;个人微信(进微信群加)&#xff1a; LyyCoder学习交流资源分享qq群1&#xff08;已满&#xff09;&#xff1a; 962535112学习交流资源分享qq群2&#xff08;已满&#xff09;&#xff1a; 780902027学习交…

cdn加载vue很慢_Vue.js 项目打包优化实践

首先上结果&#xff1a;把常用的 Vue&#xff0c;router&#xff0c;vuex&#xff0c;axios 的 runtime 包拆分了出来&#xff0c;改为 cdn&#xff1b;另外就是对于自己编写的业务代码进行分包&#xff0c;根据路由进行懒加载&#xff0c;可以较好的提高首屏加载速度。添加了全…

SharePoint 2010 网站模板要求在网站集中激活功能

从别人那里将其一个站点另存为模板&#xff0c;拷贝回来&#xff0c;上传到自己环境中的解决方案库里并激活。 然后新建站点&#xff0c;选择该模板&#xff0c;报如下错误&#xff1a; 解决方案&#xff1a; 打开在首要网站&#xff0c;网站操作---网站设置--网站集管理---网站…