问题:在 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带来的收益要更有意义。
分享到:
相关推荐
在Linux内核邮件列表中一个经常被问到的问题就是怎样为Linux内核打一个补丁,或者更具体一点说, 存在这么多的主干/分支,一个补丁到底要打在哪个版本的基础内核上。希望这篇文档能够为你解释明白这 一点。 除了解释...
7.4.4 在构造函数中使用初始化列表 320 7.5 类的私有成员 320 7.5.1 访问私有类成员 323 7.5.2 类的友元函数 324 7.5.3 默认复制构造函数 326 7.6 this指针 328 7.7 类的const对象 331 7.7.1 类的...
"counter" 扩展 - 一个连续的实例 PHP 5 构建系统 扩展的结构 PDO 驱动 扩展相关 FAQ Zend Engine 2 API 参考 Zend Engine 2 操作码列表 Zend Engine 1 FAQ — FAQ:常见问题 一般信息 邮件列表 获取 PHP 数据库问题...
"counter" 扩展 - 一个连续的实例 PHP 5 构建系统 扩展的结构 内存管理 变量的使用 函数的编写 类和对象的使用 资源的使用 INI 设置的使用 流的使用 PDO 驱动 扩展相关 FAQ Zend Engine 2...
"counter" 扩展 - 一个连续的实例 PHP 5 构建系统 扩展的结构 内存管理 变量的使用 函数的编写 类和对象的使用 资源的使用 INI 设置的使用 流的使用 PDO 驱动 扩展相关 FAQ Zend Engine 2 API 参考 Zend Engine 2 ...
目录 域服务器了解什么 2 路由原则概述 2 确定消息要发往哪儿 3 发出查询 3 解释MX资源记录列表 3 次要的特殊问题 4 例子 5
"counter" 扩展 - 一个连续的实例 PHP 5 构建系统 扩展的结构 内存管理 变量的使用 函数的编写 类和对象的使用 资源的使用 INI 设置的使用 流的使用 PDO Driver How-To 扩展相关 FAQ Zend Engine 2 API 参考 Zend ...
"counter" 扩展 - 一个连续的实例 PHP 5 构建系统 扩展的结构 内存管理 变量的使用 函数的编写 类和对象的使用 资源的使用 INI 设置的使用 流的使用 PDO Driver How-To 扩展相关 FAQ Zend Engine 2 API 参考 Zend ...
"counter" 扩展 - 一个连续的实例 PHP 5 构建系统 扩展的结构 内存管理 变量的使用 函数的编写 类和对象的使用 资源的使用 INI 设置的使用 流的使用 PDO 驱动 扩展相关 FAQ Zend Engine 2 API 参考 Zend Engine 2 ...
邮件列表 获取 PHP 数据库问题 安装 — 安装常见问题 编译问题 使用 PHP PHP 和 HTML PHP 和 COM PHP 和其它语言 从 PHP 4 移植到 PHP 5 杂类问题 附录 PHP 及其相关工程的历史 Migrating from ...
■"counter" 扩展 - 一个连续的实例 ■PHP 5 构建系统 ■扩展的结构 ■PDO 驱动 ■扩展相关 FAQ ■Zend Engine 2 API 参考 ■Zend Engine 2 操作码列表 ■Zend Engine 1 ■FAQ — FAQ:常见问题 ■一般信息 ■邮件...
■"counter" 扩展 - 一个连续的实例 ■PHP 5 构建系统 ■扩展的结构 ■内存管理 ■变量的使用 ■函数的编写 ■类和对象的使用 ■资源的使用 ■INI 设置的使用 ■流的使用 ■PDO 驱动 ■扩展相关 FAQ ■Zend Engine 2...
但是PHP的邮件列表很是有用而且除非你正在运行像Yahoo!或者Amazon.com这样的极受欢迎的站点,你不会感觉出PHP的速度与其他的有什么不同。最起码我就没有感觉出来!好了,让我们来看看PHP有那些优点: - 学习过程 ...
◦"counter" 扩展 - 一个连续的实例 ◦PHP 5 构建系统 ◦扩展的结构 ◦PDO 驱动 ◦扩展相关 FAQ ◦Zend Engine 2 API 参考 ◦Zend Engine 2 操作码列表 ◦Zend Engine 1 •FAQ — FAQ:常见问题◦一般信息 ◦邮件...
◦"counter" 扩展 - 一个连续的实例 ◦PHP 5 构建系统 ◦扩展的结构 ◦PDO 驱动 ◦扩展相关 FAQ ◦Zend Engine 2 API 参考 ◦Zend Engine 2 操作码列表 ◦Zend Engine 1 •FAQ — FAQ:常见问题◦一般信息 ◦邮件...
■"counter" 扩展 - 一个连续的实例 ■PHP 5 构建系统 ■扩展的结构 ■内存管理 ■变量的使用 ■函数的编写 ■类和对象的使用 ■资源的使用 ■INI 设置的使用 ■流的使用 ■PDO 驱动 ■扩展相关 FAQ ■Zend Engine 2...
一个好的测试管理工具应该能把以上几个阶段都管理起来。 测试人员每时每刻都在度量别人的工作成果,而测试人员的工作成果又由谁来度量呢?度量的标准和依据是什么呢?软件测试的度量是测试管理必须仔细思考的问题。...
4.1 创建一个示例应用程序:智能表单邮件 4.2 字符串的格式化 4.2.1 字符串的整理:chop()、ltrim()和trim() 4.2.2 格式化字符串以便显示 4.2.3 格式化字符串以便存储:addslashes()和stripslashes() 4.3 用字符串...
4.1 创建一个示例应用程序:智能表单邮件 4.2 字符串的格式化 4.2.1 字符串的整理:chop()、ltrim()和trim() 4.2.2 格式化字符串以便显示 4.2.3 格式化字符串以便存储:addslashes()和stripslashes() 4.3 用...
•"counter" 扩展 - 一个连续的实例 •PHP 5 构建系统 •扩展的结构 •PDO 驱动 •扩展相关 FAQ •Zend Engine 2 API 参考 •Zend Engine 2 操作码列表 •Zend Engine 1 •FAQ — FAQ:常见问题•一般信息 •邮件...