作用 和会话期 进程组_第五十四期-进程的结束

本文详细介绍了Linux系统中进程结束的两个关键函数do_exit()和do_group_exit(),它们负责清理进程或线程组资源,并启动调度。do_exit()用于结束单个进程,而do_group_exit()则用于结束整个线程组。这两个函数通过不同的机制确保进程或线程的正确终止,并释放占用的系统资源。
摘要由CSDN通过智能技术生成

8651f826-f738-eb11-8da9-e4434bdf6706.png

作者:熊轶翔@熊仙僧,中国科学院软件研究所智能软件研究中心

在之间的章节我们了解了进程的创建和执行,在进程执行完毕或者发生一些意料之外的事之后难免会被结束,因此Linux提供了exit()的系统调用来完成进程结束以及释放占用资源的功能。其中有两种系统调用能用来完成结束进程或者线程的功能。

sys_exit()以及sys_exit_group()

sys_exit()可以用来结束单个进程或线程,sys_exit_group()可以用来结束一个线程组,均需要传入一个错误码(error_code)来执行。 声明如下:(路径:/kernel-4.19/includelinuxsyscalls.h)

asmlinkage long sys_exit(int error_code);
asmlinkage long sys_exit_group(int error_code);

实现如下:(路径:/kernel-4.19/kernel/exit.c)

SYSCALL_DEFINE1(exit, int, error_code)
{
    do_exit((error_code&0xff)<<8);
}
...
SYSCALL_DEFINE1(exit_group, int, error_code)
{
    do_group_exit((error_code & 0xff) << 8);
    /* NOTREACHED */
    return 0;
}

从以上可以看出,系统调用sys_exit()是调用了do_exit()函数,系统调用sys_exit_group()是调用了do_group_exit()函数。
接下来再分别看看do_exit()以及do_group_exit()函数的实现过程。

do_exit()

do_exit()的作用是结束当前的进程,部分代码如下[1]:(路径:/kernel-4.19/kernel/exit.c)

void __noreturn do_exit(long code)
{
    struct task_struct *tsk = current;          //当前进程
    ...
    /*
     *检查进程的blk_plug是否为空
     *保证task_struct中的plug字段是空的,或者plug字段指向的队列是空的。
     */
    WARN_ON(blk_needs_flush_plug(tsk));
    //中断上下文不能执行do_exit函数, 也不能终止PID为0的进程。
    if (unlikely(in_interrupt()))
        panic("Aiee, killing interrupt handler!");
    if (unlikely(!tsk->pid))
        panic("Attempted to kill the idle task!");
    //设定进程可以使用的虚拟地址的上限(用户空间)
    set_fs(USER_DS);
    ...
    /*
     *首先是检查PF_EXITING标识, 标识表示进程正在退出,如果此标识已被设置,则进一步设置PF_EXITPIDONE标识,并将进程的状态设置为不可中断状态TASK_UNINTERRUPTIBLE,并进行一次进程调度。
     */
    if (unlikely(tsk->flags & PF_EXITING)) {
        pr_alert("Fixing recursive fault but reboot is needed!n");
        tsk->flags |= PF_EXITPIDONE;
        set_current_state(TASK_UNINTERRUPTIBLE);
        schedule();
    }
    //如果此PF_EXITING标识未被设置, 则通过exit_signals来设置
    exit_signals(tsk);  /* sets PF_EXITING */
    /*内存屏障,用于确保在它之后的操作开始执行之前,它之前的操作已经完成*/
    smp_mb();
    /*  一直等待,直到获得current->pi_lock自旋锁  */
    raw_spin_lock_irq(&tsk->pi_lock);
    raw_spin_unlock_irq(&tsk->pi_lock);
    ...
    /* 释放线性区描述符和页表 */
    exit_mm();
    /* 输出进程审计信息 */
    if (group_dead)
        acct_process();
    trace_sched_process_exit(tsk);
    /* 释放用户空间的“信号量” */
    exit_sem(tsk);
    /* 释放锁 */
    exit_shm(tsk);
    /* 释放文件对象相关资源 */
    exit_files(tsk);
    exit_fs(tsk);
    /* 脱离控制终端 */
    if (group_dead)
        disassociate_ctty(1);
    /* 释放命名空间 */
    exit_task_namespaces(tsk);
    exit_task_work(tsk);
    /* 释放task_struct中的thread_struct结构 */
    ...
    /* 更新所有子进程的父进程 */
    exit_notify(tsk, group_dead);
    /* 进程事件连接器(通过它来报告进程fork、exec、exit以及进程用户ID与组ID的变化) */
    proc_exit_connector(tsk);
    mpol_put_task_policy(tsk);
    ...
    /* 释放struct io_context结构体所占用的内存 */
    if (tsk->io_context)
        exit_io_context(tsk);
    /* 释放与进程描述符splice_pipe字段相关的资源 */
    if (tsk->splice_pipe)
        free_pipe_info(tsk->splice_pipe);
    ...
    /* 检查有多少未使用的进程内核栈 */
    check_stack_usage();
    ...
    do_task_dead();
}

在最后的do_task_dead()中调用了__schedule()切换到了其他就绪进程,至此该进程在操作系统中的生命周期就完全结束了。

do_group_exit()

我们如果了解Linux的线程实现机制的话,会知道所有的线程是属于一个线程组的。即使不是线程, Linux也允许多个进程组成进程组,多个进程组组成一个会话,因此我们了解到不管是多线程还是进程组,其本质都是多个进程组成的一个集合,那么我们的应用程序在退出的时候,自然希望一次性的退出组内所有的进程,do_group_exit()也就应运而生了[2]。

do_group_exit()函数将会杀死属于当前线程组的所有线程,它具体执行的是下述操作[2]:

  1. 检查当前进程的SIGNAL_GROUP_EXIT标志是否不为0,如果不为0,说明内核已经开始为线性组执行退出的过程。在这种情况下,就把存放在current->signal->group_exit_code的值当作退出码,然后跳转到第4步。
  2. 否则,设置进程的SIGNAL_GROUP_EXIT标志并把终止代号放到current->signal->group_exit_code字段。
  3. 调用zap_other_threads()函数杀死current线程组中的其它进程。为了完成这个步骤,函数扫描与current->tgid对应的PIDTYPE_TGID类型的散列表中的PID链表,向表中所有不同于current的进程发送SIGKILL信号,所有这样的进程都将执行do_exit()函数,从而被结束。
  4. 调用do_exit()函数,把进程的终止代码传递给它。正如我们将在之前看到的,do_exit()结束进程而且不再返回。

do_group_exit()的部分代码如下:(路径:/kernel-4.19/kernel/exit.c)

void do_group_exit(int exit_code)
{
    struct signal_struct *sig = current->signal;
    ...
     /*
        检查current->sig->flags的SIGNAL_GROUP_EXIT标志是否置位
        或者current->sig->group_exit_task是否不为NULL
    */
    if (signal_group_exit(sig))
        /*  group_exit_code存放的是线程组终止代码  */
        exit_code = sig->group_exit_code;       
    /*  检查线程组链表是否不为空  */
    else if (!thread_group_empty(current)) {    
        struct sighand_struct *const sighand = current->sighand;

        spin_lock_irq(&sighand->siglock);
        if (signal_group_exit(sig))
            /* Another thread got here before we took the lock.  */
            exit_code = sig->group_exit_code;
        else {
            sig->group_exit_code = exit_code;
            sig->flags = SIGNAL_GROUP_EXIT;
            zap_other_threads(current);
        }
        spin_unlock_irq(&sighand->siglock);
    }
    /*  调用do_exit() */
    do_exit(exit_code);
}

在上述的函数中,清除了当前进程所占用或者与它相关的数据结构,最后执行的一步都将是schedule()函数,在这个函数中就将跳转到其他的进程。由于当前进程的各种相关的数据结构都不再存在,因此永远也不会再跳转到这个进程来执行,也就是进程被永远宣告了死亡。

总结

在本章我们了解了进程结束的过程,分别学习了do_exit()do_group_exit()函数,简而言之,他们的作用都是清除关于进程或进程组所占据的资源并开启调度。关于进程的生命周期(创建、执行、结束)的讲解到这里就告一段落,在下一章我们将开始对进程地址空间的学习。


参考:

[1] https:// blog.csdn.net/tjcwt2011 /article/details/80272127
[2] https://www. cnblogs.com/linhaostudy /p/9662144.html
[3]《深入Linux内核架构》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值