热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

进程管理(四)

接着上一文,我们看一下do_fork()函数:longdo_fork(unsignedlongclone_flags,unsignedlongstack_start,struc

接着上一文,我们看一下do_fork()函数:

long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
    //新建一个新的,空的task_strtuct
    struct task_struct *p;
    int trace = 0;
    //分配一个pid
    struct pid *pid = alloc_pid();
    long nr;
    if (!pid)
        return -EAGAIN;
    nr = pid->nr;
    //这个不太可能发生
    if (unlikely(current->ptrace)) {
        trace = fork_traceflag (clone_flags);
        if (trace)
            clone_flags |= CLONE_PTRACE;
    }
    p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, nr);
    /*
     * Do this prior waking up the new thread - the thread pointer
     * might get invalid after that point, if the thread exits quickly.
     */
    if (!IS_ERR(p)) {
        struct completion vfork;
        if (clone_flags & CLONE_VFORK) {
            p->vfork_dOne= &vfork;
            init_completion(&vfork);
        }
        if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
            /*
             * We‘ll start up with an immediate SIGSTOP 以SIGSTOP开始运行
             */
            sigaddset(&p->pending.signal, SIGSTOP);
            set_tsk_thread_flag(p, TIF_SIGPENDING);
        }
        if (!(clone_flags & CLONE_STOPPED))
            wake_up_new_task(p, clone_flags);
        else
            p->state = TASK_STOPPED;
        if (unlikely (trace)) {
            current->ptrace_message = nr;
            ptrace_notify ((trace <<8) | SIGTRAP);
        }
        if (clone_flags & CLONE_VFORK) {
            wait_for_completion(&vfork);
            if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) {
                current->ptrace_message = nr;
                ptrace_notify ((PTRACE_EVENT_VFORK_DONE <<8) | SIGTRAP);
            }
        }
    } else {
        free_pid(pid);
        nr = PTR_ERR(p);
    }
    return nr;
}

根据代码显示,当执行玩成copy_process()函数在之后,do_fork()有意选自子进程首先执行,因为子进程一般会马上调用exec()函数,这样以避免写时拷贝的额外开销。同样,如果让父进程先执行的话,有可能会开始向地址空间中写入。

(二):线程的实现

Linux实现线程的机制比较独特。从内核角度看,他并没有线程的概念。Linux把所有的线程都当作进程来实现。内核并没有准备特别的调度算法或定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一里属于自己的task_struct,所以在内核中,他看起来就像是一个普通的进程。

1:线程的创建

线程的创建和进程的创建类似,只不过就是像clone()中传递一些参数来指明需要共享的资源。

传递给clone()的参数标志决定了新创建进程的行为方式和父子进程之间共享的资源种类。下面列举了在linux/sched.h文件中定义的参数标志。

/*
 * cloning flags:
 * cloning 标志
 */
//在退出的时候,被发送的信号
#define CSIGNAL     0x000000ff  /* signal mask to be sent at exit */
//父子进程共享地址空间
#define CLONE_VM    0x00000100  /* set if VM shared between processes */
//父子进程共享文件系统信息
#define CLONE_FS    0x00000200  /* set if fs info shared between processes */
//父子进程共享打开的文件
#define CLONE_FILES 0x00000400  /* set if open files shared between processes */
//父子进程共享信号处理函数以及被阻断的信号
#define CLONE_SIGHAND   0x00000800  /* set if signal handlers and blocked signals shared */
//继续调试子进程
#define CLONE_PTRACE    0x00002000  /* set if we want to let tracing continue on the child too */
//调用vfork(),所以父进程准备睡眠等待子进程将其唤醒
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
//指定子进程和父进程有相同的父亲
#define CLONE_PARENT    0x00008000  /* set if we want to have the same parent as the cloner */
//父子进程放入相同的线程组
#define CLONE_THREAD    0x00010000  /* Same thread group? */
//为子进程创建新的命名空间
#define CLONE_NEWNS 0x00020000  /* New namespace group? */
//父子进程共享system V SEM_UNDO语义
#define CLONE_SYSVSEM   0x00040000  /* share system V SEM_UNDO semantics */
//为子进程创建一个新的TLS
#define CLONE_SETTLS    0x00080000  /* create a new TLS for the child */
//设置父进程的TID
#define CLONE_PARENT_SETTID 0x00100000  /* set the TID in the parent */
//清除子进程的TID
#define CLONE_CHILD_CLEARTID    0x00200000  /* clear the TID in the child */
//没有使用,被忽略了
#define CLONE_DETACHED      0x00400000  /* Unused, ignored */
//防止跟踪进程在子进程上强制进程CLONE_PTRACE
#define CLONE_UNTRACED      0x00800000  /* set if the tracing process can‘t force CLONE_PTRACE on this clone */
//设置子进程的TID
#define CLONE_CHILD_SETTID  0x01000000  /* set the TID in the child */
//以TASK_STOPPED状态开始进程
#define CLONE_STOPPED       0x02000000  /* Start in stopped state */

(三):进程终结

当一个进程终结的时候,内核必须释放他所占有的资源并把这一个消息告知其父进程。一般来说,进程的析构是由自身引起的,他发生在进程调用exit()系统调用。进程终结的大部分都要靠do_exit()(定义于kernel/exit.c)来完成。

下面我们来看一下:

fastcall NORET_TYPE void do_exit(long code)
{
    struct task_struct *tsk = current;
    struct taskstats *tidstats;
    int group_dead;
    unsigned int mycpu;
    profile_task_exit(tsk);
    WARN_ON(atomic_read(&tsk->fs_excl));
    if (unlikely(in_interrupt()))
        panic("Aiee, killing interrupt handler!");
    if (unlikely(!tsk->pid))
        panic("Attempted to kill the idle task!");
    if (unlikely(tsk == child_reaper))
        panic("Attempted to kill init!");
    if (unlikely(current->ptrace & PT_TRACE_EXIT)) {
        current->ptrace_message = code;
        ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP);
    }
    /*
     * We‘re taking recursive faults here in do_exit. Safest is to just
     * leave this task alone and wait for reboot.
     */
    if (unlikely(tsk->flags & PF_EXITING)) {
        printk(KERN_ALERT
            "Fixing recursive fault but reboot is needed!
");
        if (tsk->io_context)
            exit_io_context();
        set_current_state(TASK_UNINTERRUPTIBLE);
        schedule();
    }
    //设置进程状态为PF_EXITING
    tsk->flags |= PF_EXITING;
    if (unlikely(in_atomic()))
        printk(KERN_INFO "note: %s[%d] exited with preempt_count %d
",
                current->comm, current->pid,
                preempt_count());
    taskstats_exit_alloc(&tidstats, &mycpu);
    /*
     * 如果BSD的进程记账功能是开启的,do_exit()调用acct_update_integrals()来输出记账
     * 信息
     * 时间记账:就是记录进程已经运行了多长时间了,还要运行多长时间
     */
    acct_update_integrals(tsk);
    if (tsk->mm) {
        update_hiwater_rss(tsk->mm);
        update_hiwater_vm(tsk->mm);
    }
    group_dead = atomic_dec_and_test(&tsk->signal->live);
    if (group_dead) {
        hrtimer_cancel(&tsk->signal->real_timer);
        exit_itimers(tsk->signal);
    }
    acct_collect(code, group_dead);
    if (unlikely(tsk->robust_list))
        exit_robust_list(tsk);
#if defined(CONFIG_FUTEX) && defined(CONFIG_COMPAT)
    if (unlikely(tsk->compat_robust_list))
        compat_exit_robust_list(tsk);
#endif
    if (unlikely(tsk->audit_context))
        audit_free(tsk);
    taskstats_exit_send(tsk, tidstats, group_dead, mycpu);
    taskstats_exit_free(tidstats);
    /*
     * 调用exit_mm()函数释放进程占有的mm_struct,如果没有别的进程使用他们
     * (也就是说这个地址空间没有被共享),就彻底释放他们。
     *
     */
    exit_mm(tsk);
    if (group_dead)
        acct_process();
    //如果进程排队等待IPC信号,则离开队列
    exit_sem(tsk);
    //分别递减文件描述符和文件系统数据的引用计数
    __exit_files(tsk);
    __exit_fs(tsk);
    exit_namespace(tsk);
    exit_thread();
    cpuset_exit(tsk);
    exit_keys(tsk);
    if (group_dead && tsk->signal->leader)
        disassociate_ctty(1);
    module_put(task_thread_info(tsk)->exec_domain->module);
    if (tsk->binfmt)
        module_put(tsk->binfmt->module);
    //设置task_struct中的exit_code设置为exit()函数提供的退出代码
    tsk->exit_code = code;
    proc_exit_connector(tsk);
    /*
     * exit_notify()函数向父进程发送信号,给予子进程重新找养父,养父为进程组中的其他进程或者是
     * init进程,并把进程状态设置为EXIT_ZOMBIE
     *
     */
    exit_notify(tsk);
#ifdef CONFIG_NUMA
    mpol_free(tsk->mempolicy);
    tsk->mempolicy = NULL;
#endif
    /*
     * This must happen late, after the PID is not
     * hashed anymore:
     */
    if (unlikely(!list_empty(&tsk->pi_state_list)))
        exit_pi_state_list(tsk);
    if (unlikely(current->pi_state_cache))
        kfree(current->pi_state_cache);
    /*
     * Make sure we are holding no locks:
     */
    debug_check_no_locks_held(tsk);
    if (tsk->io_context)
        exit_io_context();
    if (tsk->splice_pipe)
        __free_pipe_info(tsk->splice_pipe);
    /* PF_DEAD causes final put_task_struct after we schedule. */
    preempt_disable();
    BUG_ON(tsk->flags & PF_DEAD);
    tsk->flags |= PF_DEAD;
    //调用schedule()函数切换到新的进程
    schedule();
    BUG();
    //do_exit()函数用不返回
    /* Avoid "noreturn function does return".  */
    for (;;) ;
}

当do_exit()执行完成之后,进程被设置为EXIT_ZOMBIE状态,其中,其占有的一些资源已经被释放了,他也不会在发生调度了。他存在的唯一目的就是向他的父进程提供信息。父进程检索到信息后或者通知内核那些无关的信息后,由进程所持有的剩余内存被释放,归还给系统使用。

1:删除进程描述符

wait()这一族函数都是通过唯一的一个系统调用wait4()来实现的。他的标准动作是挂起调用他的进程,知道其中的一个子进程退出。此时函数会返回孩子的pid.

当最终需要释放进程描述符的时候,release_task()函数会被调用,一下是他完成的工作。

1:他调用_exit_signal(),该函数会调用_unhash_process(),后者又调用detach_pid()从pidhash上删除该进程,同时也从任务队列中删除该进程。

?2:_exit_signal()释放当前僵死进程所使用的所有的剩余资源,并进行最终统计和记录。

?3:如果这个进程是线程组的最后一个进程,并且领头进程已经死掉,那么release_task()就要通知僵死的领头进程的父进程。

?4:release_task()调用put_task_struct()释放进程内核栈和thread_info结构所占的页,并释放task_struct所占的slab高速缓存。

2:孤儿进程
?
如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白耗尽内存。这个问题的解决办法是给子进程 在所在线程组中找一个线程作为父亲,或者是直接找init进程作为父亲。在do_exit()中会调用exit_notify()中会调用forget_original_parent(),然后调用find_new_reaper()函数,现在我们看一下这几个函数。

首先看一下forget_original_parent()函数

/*
 * When we die, we re-parent all our children.
 * Try to give them to another thread in our thread
 * group, and if no such member exists, give it to
 * the global child reaper process (ie "init")
 *
 * 当我们死了的时候,重新为我们的孩子找一个父亲。
 * 试着从我们的线程组中给他们找一个父亲,如果线程
 * 组中没有这样存在的成员了,就从全局的进程中
 * 给他们找一个父亲,例如init进程
 *
 */
static void
forget_original_parent(struct task_struct *father, struct list_head *to_release)
{
    struct task_struct *p, *reaper = father;
    struct list_head *_p, *_n;
    do {
        reaper = next_thread(reaper);
        if (reaper == father) {
            reaper = child_reaper;
            break;
        }
    } while (reaper->exit_state);
    /*
     * There are only two places where our children can be:
     *
     * - in our child list
     * - in our ptraced child list
     *
     * Search them and reparent children.
     */
    list_for_each_safe(_p, _n, &father->children) {
        int ptrace;
        p = list_entry(_p, struct task_struct, sibling);
        ptrace = p->ptrace;
        /* if father isn‘t the real parent, then ptrace must be enabled */
        BUG_ON(father != p->real_parent && !ptrace);
        if (father == p->real_parent) {
            /* reparent with a reaper, real father it‘s us */
            choose_new_parent(p, reaper);
            reparent_thread(p, father, 0);
        } else {
            /* reparent ptraced task to its real parent */
            __ptrace_unlink (p);
            if (p->exit_state == EXIT_ZOMBIE && p->exit_signal != -1 &&
                thread_group_empty(p))
                do_notify_parent(p, p->exit_signal);
        }
        /*
         * if the ptraced child is a zombie with exit_signal == -1
         * we must collect it before we exit, or it will remain
         * zombie forever since we prevented it from self-reap itself
         * while it was being traced by us, to be able to see it in wait4.
         */
        if (unlikely(ptrace && p->exit_state == EXIT_ZOMBIE && p->exit_signal == -1))
            list_add(&p->ptrace_list, to_release);
    }
    list_for_each_safe(_p, _n, &father->ptrace_children) {
        p = list_entry(_p, struct task_struct, ptrace_list);
        choose_new_parent(p, reaper);
        reparent_thread(p, father, 1);
    }
}

从代码来看,首先是从当前进程所在的线程组中找到可以作为父进程的线程:

do {
        reaper = next_thread(reaper);
        if (reaper == father) {
            reaper = child_reaper;
            break;
        }
    } while (reaper->exit_state);

接着,从children链表和ptrace children链表中,为每一个孩子进程寻找新的父进程。

list_for_each_safe(_p, _n, &father->children) {
        int ptrace;
        p = list_entry(_p, struct task_struct, sibling);
        ptrace = p->ptrace;
        /* if father isn‘t the real parent, then ptrace must be enabled */
        BUG_ON(father != p->real_parent && !ptrace);
        if (father == p->real_parent) {
            /* reparent with a reaper, real father it‘s us */
            choose_new_parent(p, reaper);
            reparent_thread(p, father, 0);
        } else {
            /* reparent ptraced task to its real parent */
            __ptrace_unlink (p);
            if (p->exit_state == EXIT_ZOMBIE && p->exit_signal != -1 &&
                thread_group_empty(p))
                do_notify_parent(p, p->exit_signal);
        }

现在我们看一下 choose_new_parent()函数,其实就相当于上面的find_new_reaper()函数:

static inline void
choose_new_parent(struct task_struct *p, struct task_struct *reaper)
{
    /*
     * Make sure we‘re not reparenting to ourselves and that
     * the parent is not a zombie.
     */
    BUG_ON(p == reaper || reaper->exit_state);
    p->real_parent = reaper;
}

这个函数相对比较简单,直接将p的真实父进程设置为找到的reaper。

一旦系统为进程成功地找到和设置了新的父进程,就不会再有出现驻留僵死进程的危了。init进程会例行调用wait()函数来检查子进程,清除所有与其相关的僵死进程。

进程管理(四)


推荐阅读
  • 本文详细解析了Autofac在高级应用场景中的具体实现,特别是如何通过注册泛型接口的类来优化依赖注入。示例代码展示了如何使用 `builder.RegisterAssemblyTypes` 方法,结合 `typeof(IEventHandler).Assembly` 和 `Where` 过滤条件,动态注册所有符合条件的类,从而简化配置并提高代码的可维护性。此外,文章还探讨了这一方法在复杂系统中的实际应用及其优势。 ... [详细]
  • 在分析和解决 Keepalived VIP 漂移故障的过程中,我们发现主备节点配置如下:主节点 IP 为 172.16.30.31,备份节点 IP 为 172.16.30.32,虚拟 IP 为 172.16.30.10。故障表现为监控系统显示 Keepalived 主节点状态异常,导致 VIP 漂移到备份节点。通过详细检查配置文件和日志,我们发现主节点上的 Keepalived 进程未能正常运行,最终通过优化配置和重启服务解决了该问题。此外,我们还增加了健康检查机制,以提高系统的稳定性和可靠性。 ... [详细]
  • Android 构建基础流程详解
    Android 构建基础流程详解 ... [详细]
  • 在 Android 开发中,`android:exported` 属性用于控制组件(如 Activity、Service、BroadcastReceiver 和 ContentProvider)是否可以被其他应用组件访问或与其交互。若将此属性设为 `true`,则允许外部应用调用或与之交互;反之,若设为 `false`,则仅限于同一应用内的组件进行访问。这一属性对于确保应用的安全性和隐私保护至关重要。 ... [详细]
  • Unity3D 中 AsyncOperation 实现异步场景加载及进度显示优化技巧
    在Unity3D中,通过使用`AsyncOperation`可以实现高效的异步场景加载,并结合进度条显示来提升用户体验。本文详细介绍了如何利用`AsyncOperation`进行异步加载,并提供了优化技巧,包括进度条的动态更新和加载过程中的性能优化方法。此外,还探讨了如何处理加载过程中可能出现的异常情况,确保加载过程的稳定性和可靠性。 ... [详细]
  • 本指南介绍了如何在ASP.NET Web应用程序中利用C#和JavaScript实现基于指纹识别的登录系统。通过集成指纹识别技术,用户无需输入传统的登录ID即可完成身份验证,从而提升用户体验和安全性。我们将详细探讨如何配置和部署这一功能,确保系统的稳定性和可靠性。 ... [详细]
  • 资源管理器的基础架构包括三个核心组件:1)资源池,用于将CPU和内存等资源分配给不同的容器;2)负载组,负责承载任务并将其分配到相应的资源池;3)分类函数,用于将不同的会话映射到合适的负载组。该系统提供了两种主要的资源管理策略。 ... [详细]
  • 在过去,我曾使用过自建MySQL服务器中的MyISAM和InnoDB存储引擎(也曾尝试过Memory引擎)。今年初,我开始转向阿里云的关系型数据库服务,并深入研究了其高效的压缩存储引擎TokuDB。TokuDB在数据压缩和处理大规模数据集方面表现出色,显著提升了存储效率和查询性能。通过实际应用,我发现TokuDB不仅能够有效减少存储成本,还能显著提高数据处理速度,特别适用于高并发和大数据量的场景。 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 在Eclipse中提升开发效率,推荐使用Google V8插件以增强Node.js的调试体验。安装方法有两种:一是通过Eclipse Marketplace搜索并安装;二是通过“Help”菜单中的“Install New Software”,在名称栏输入“googleV8”。此插件能够显著改善调试过程中的性能和响应速度,提高开发者的生产力。 ... [详细]
  • 深入解析Java虚拟机的内存分区与管理机制
    Java虚拟机的内存分区与管理机制复杂且精细。其中,某些内存区域在虚拟机启动时即创建并持续存在,而另一些则随用户线程的生命周期动态创建和销毁。例如,每个线程都拥有一个独立的程序计数器,确保线程切换后能够准确恢复到之前的执行位置。这种设计不仅提高了多线程环境下的执行效率,还增强了系统的稳定性和可靠性。 ... [详细]
  • Parallels Desktop for Mac 是一款功能强大的虚拟化软件,能够在不重启的情况下实现在同一台电脑上无缝切换和使用 Windows 和 macOS 系统中的各种应用程序。该软件不仅提供了高效稳定的性能,还支持多种高级功能,如拖放文件、共享剪贴板等,极大地提升了用户的生产力和使用体验。 ... [详细]
  • Python全局解释器锁(GIL)机制详解
    在Python中,线程是操作系统级别的原生线程。为了确保多线程环境下的内存安全,Python虚拟机引入了全局解释器锁(Global Interpreter Lock,简称GIL)。GIL是一种互斥锁,用于保护对解释器状态的访问,防止多个线程同时执行字节码。尽管GIL有助于简化内存管理,但它也限制了多核处理器上多线程程序的并行性能。本文将深入探讨GIL的工作原理及其对Python多线程编程的影响。 ... [详细]
  • 在PHP的设计中,预定义了9个超级全局变量、8个魔术变量和13个魔术函数,这些变量和函数无需声明即可在脚本的任意位置使用。这些特性在PHP开发中极为常见,能够显著提升开发效率和代码的灵活性。相比之下,Java并没有类似的内置机制,但通过其他方式如上下文对象和反射机制,也可以实现类似的功能。本文将详细探讨这两种语言中这些特殊变量和函数的使用方法及其应用场景。 ... [详细]
  • Java 模式原型在游戏服务器架构中的应用与优化 ... [详细]
author-avatar
好人好报
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有