问题:在 exit_mm() 中为什么要 atomic_inc(&mm->mm_count) 呢? 并没有对应的dec。

解答:

首先要明白,对于一个可以被释放内存的进程也就是说一个拥有mm_struct的进程来说,它的task_struct中的mm字段和active_mm字段是一样的,这个在fork时,copy_mm中就决定了,接下来解释这个问题:

在exit_mm中:

atomic_inc(&mm->mm_count); //这里递增了引用计数,你说对了,没有dec但是inc了,多了一次inc,那么就要多一次dec

BUG_ON(mm != tsk->active_mm); //保证mm和tsk->active_mm一样

task_lock(tsk);

tsk->mm = NULL; //注意这里将tsk->mm设置为NULL

up_read(&mm->mmap_sem);

enter_lazy_tlb(mm, current);

clear_freeze_flag(tsk);

task_unlock(tsk); //第一次dec

然后注意在do_exit调用exit_mm之后,最终会调用schedule将这个退出进程切出去,因此最终释放task_struct将在新进程切到运行之后再进行,那么看context_switch中:

oldmm = prev->active_mm; //这里的prev就是那个退出进程,其active_mm就是上面的mm,由此可见上面的atomic_inc就是为了这里

...

if (unlikely(!prev->mm)) { //这里if为真,因为上面tsk->mm = NULL;

prev->active_mm = NULL;

rq->prev_mm = oldmm; //rq->prev_mm就是还差一个计数就释放的prev->mm,要不早就释放了,幸亏你说的那个atomic_inc;

}

最后安全切换了新进程以后,一切都可以释放了,看finish_task_switch:

static void finish_task_switch(struct rq *rq, struct task_struct *prev)

{

struct mm_struct *mm = rq->prev_mm; //这里的mm就是上面那个退出的mm

long prev_state;

rq->prev_mm = NULL;

prev_state = prev->state;

...

if (mm)

mmdrop(mm); //引用计数彻底为0,最后被释放

...

其实mm也好,task_struct也好,进程切换时可能会用到,因此都在安全切到新进程之后也就是finish_task_switch中被释放,为了不让mm在exit_mm就被释放,那么只有增加它的引用计数了,不知道这里情景分析你懂了没有。<--解答完毕

上述的解答如果大致理解的话那么就够了,但是如果真的较起真来的话,还真的有点让人发蒙,如果说linux在退出进程的时候不释放task_struct是因为schedule中要用到这个task_struct的话,那么在退出时保留其mm_struct是为什么呢?其实为题还有一大堆,比如为何linux在schedule时还要用到退出进程的task_struct呢?为何要这样呢?因为linux为了效率而没有另立一个调度器,进程切换必须由进程自己进行,也就是说切换前在调度器在切出进程的上下文运行,而切换后在换入进程的上下文运行。那么到底为何不能释放其mm_struct呢?因为当前退出进程所用的正是当前的页目录,也就是当前的页目录绝对不能释放,注意这里的页目录的768项之前的并无所谓,因为现在在内核,只会访问768项以后,这768项以后的每一项和swapper_pg_dir的768项以后一一对应,其实就是直接指向,即便如此,也不能释放pgd,毕竟mmu访问从pgd指向,因为在exit_mm中,该mm_struct的users引用计数已经成为1了,然后最后的mmput中的dec就会使其成为0,那么就要调用mmdrop了,一旦mm的引用计数递减后为0,在后者中会调用__mmdrop释放掉pgd,为了防止这一件事,但是还必须使得users顺利成为0,那么只要防止__mmdrop就可以了,于是就递增了mm的引用计数。

另外一个作用就要仔细研究一下context_switch函数:

static inline void context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next)

{

struct mm_struct *mm, *oldmm;

prepare_task_switch(rq, prev, next);

trace_sched_switch(rq, prev, next);

mm = next->mm;

oldmm = prev->active_mm;

arch_enter_lazy_cpu_mode();

if (unlikely(!mm)) { //如果下一个进程的mm为空就说明下一个进程是内核线程

next->active_mm = oldmm;

atomic_inc(&oldmm->mm_count); //又递增了一个引用计数

enter_lazy_tlb(oldmm, next); //进入懒惰模式

} else

switch_mm(oldmm, mm, next); //如果下一个不是内核线程,那么就切换mm_struct,也就是切换页表

if (unlikely(!prev->mm)) { //这里就是关键,标记为*

prev->active_mm = NULL;

rq->prev_mm = oldmm;

}

...

}

上面的标记为*的if判断很重要,prev->mm什么时候为假呢?有两种情况,第一就是前一个进程是内核线程,第二就是前一个进程是用户进程但是已经退出,我们这里的情况是第二种情况,只有在这种情况下rq->prev_mm被赋值为退出进程的active_mm,而这个和退出进程的mm一样,记住此时mm的引用计数在exit_mm由于被inc了变成了1,那么安全切换了以后在finish_task_switch中判断rq->prev_mm不为空从而被释放。这个解释很合理,但是和tlb刷新的懒惰模式没有关系,而只是保证了退出进程的pgd在mm切换之前不被释放,那么第二种情况就是要说懒惰模式相关的,如果下一个进程是内核线程,那么进入:

if (unlikely(!mm)) { //如果下一个进程的mm为空就说明下一个进程是内核线程

next->active_mm = oldmm;

atomic_inc(&oldmm->mm_count); //又递增了一个引用计数

enter_lazy_tlb(oldmm, next); //进入懒惰模式

}

可以看到,不但释放不了了mm,还又一次递增了它的引用计数,此时引用计数为2,可是不要急,不但进入了上面的if,连后面的if (unlikely(!prev->mm))也会进入,那么在切换以后还是会在mmdorp中递减一次mm的引用计数,这样这个内核线程保持了引用计数为1的mm,当内核线程被切换出去时,if (unlikely(!prev->mm))为真,因为内核线程没有mm(也可以有),那么在切换以后还是会调用mmdrop,此时mm被释放,pgd被释放,一切安然!

因此可以看出,linux对引用计数的设置是多么巧妙啊,一个mm_struct不但有users计数还有count计数,如果我设计的话,这俩肯定就合二为一了,linux的设计者考虑的真是细致,users为0了count可以不为0,为何呢?users为0说明没有进程使用了,可是count不为0说明调度器还要使用,前面说过,调度器在被调度的进程的上下文运行,调度器借用页目录,那么就要保留页目录到调度器不用为止,另外,不但调度器要借用页目录,内核线程还可以借用之,这是为了提高效率,可以少一次切换开销,内核认为少一次切换开销比释放一个mm_struct带来的收益要更有意义。

关于邮件列表一个问题的解释相关推荐

  1. 一个简单的PHP邮件列表管理器

    1.需求分析 管理员应该能够建立和修改邮件内容. 管理员应该能够将文本或HTML格式的新闻信件发送给一个列表中的所有订阅者. 用户应该能够通过注册使用一个站点,并且可以进入并修改他们的个人资料. 用户 ...

  2. 建立邮件列表的九种主要资源

    邮件列表意味着在线生意的一切,建立一个可以反复发送信息的列表是取得成功的关键,下面是建立邮件列表的九个基本资源. 1.现有客户 这是可充分利用的最好的资源,对于所有生意来说最困难的事情就是寻找新顾客, ...

  3. zz 邮件列表的文化与礼节

    http://ghostunix.org/blog/?p=161 计算机技术的革新速度极其惊人,在网络通讯交流技术上 更是如此,从最古老的UNIX上的talk命令(各类IM软件的前身)发展到今天的各种 ...

  4. 邮件服务器 之 基于FreeBSD和Postfix的邮件系统与邮件列表的web mail安装

    作者: 杨廷勇(scyzxp at toping.net) 来自: LinuxSir.Org 版权:杨廷勇 Copyright © 2004.2005.2006 摘要: 本文介绍使用FreeBSD + ...

  5. 获得邮件列表失败_新手在批发交易中会失败的5个领域

    任何房地产交易都有很多环节,很多人都会被其中一个环节卡住,批发房地产的一些环节也会让你出错,以下是新手投资者会在批发交易中失败的5个方面,虽然这个列表并不是包含所有的批发交易,但作为一个房地产新手,有 ...

  6. 邮件列表统计(网站推广)

    大家在做邮件列表发送时,每封邮件发送之后是否有这样的需求就是统计有多少人看了这封邮件.并且是哪些人看过这些邮件(IP,所在地,操作系统)之类的信息. 这样就需要在邮件列表中插入一个计数器. 也许有人说 ...

  7. python发送qq邮件列表_Python SMTP发送邮件

    发送邮件是个很常用的功能.比如自己写个脚本获取并分析股票或期货数据,如果发现有交易机会.此时可以发个邮件来提醒自己. SMTP即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控 ...

  8. 邮件退订_如何方便地退订邮件列表

    邮件退订 如果您在电子邮件讨论组中的时间足够长,则有时会看到列表成员发出的一封电子邮件,要求取消订阅. 通常,列表中的至少10个人将回复有关如何退订的说明,而另外10个人确认或评论说明将回答这10条回 ...

  9. 希望博客园可以开个邮件列表

    我到现在都在寻找中文的一个 dotnet 的类似论坛可以问问题有人解答的地方.用过很多的论坛 CSDN 我不是很喜欢,其他的地方人气很少,提问的问题很少有人解答.最近扎根博客园,很喜欢这里的气氛和人气 ...

最新文章

  1. LinkedHashMap and LinkedHashSet
  2. php mongo二级查询时间,php查询MongoDB遇到长整型的问题
  3. C++ Primer 5th笔记(chap 16 模板和泛型编程)模板实参推断和引用
  4. 国内有哪些自然语言处理(NLP)专业比较厉害的985高校?
  5. java去除json 转移,Spring MVC返回的json去除根节点名称的方法
  6. android ifw 启动广告,使用 IFW 完全控制 Android 应用行为 | 实用技巧
  7. 015. 深入JVM学习—Java引用类型
  8. 【registry】Reader schema missing default value for field: age
  9. 【LeetCode】231. Power of Two
  10. 使用electron-builder来打包
  11. 深入理解Java中间件Zookeeper
  12. macOS devtools安装github包失败解决
  13. java随机数种子_使用种子的Java随机数
  14. 打印系统开发(5)——书脊
  15. python 基础系列(四) — Python中的面向对象
  16. php datedif,Excel Datedif函数全面解析及BUG分析
  17. 热心肠行为?苹果“偷偷“给应用买广告
  18. 山东省一个区和天津市一个区,名字一模一样!
  19. BUG一词是如何来的?
  20. oracle pga建议值,对SGA和PGA的优化建议

热门文章

  1. 带你了解STM32中断系统
  2. 在ftp服务器新建虚拟目录,虚拟目录在FTP的设置方法
  3. termux目录_Termux系列教程:新手必做的初始化配置!
  4. 企业级项目实战讲解!微信小程序趋势及前景,复习指南
  5. Spring来一发 --- 目录
  6. 数据结构学习心得——顺序表
  7. 18.电影评论数据分类
  8. Python读写文件的七种模式(r,w,x,a,b,t,+)
  9. 英特尔处理器能够用鸿蒙系统吗,华为下月发布新型电脑,非英特尔、AMD平台,或将搭载鸿蒙系统?...
  10. ajax异步文件上传和进度条