内核代码阅读(23) - 进程之exit和wait4

简介: 进程之exit和wait4

坚持就是胜利!

进程的退出exit

asmlinkage long sys_exit(int error_code)
    {
        do_exit((error_code&0xff)<<8);
    }
显然,主体是do_exit。

do_exit

当CPU进入do_exit以后,不会从这个函数返回。直接在内核态中退出了。
另外,只有进程和线程才有exit的概念。中断服务程序谈不上exit。
do_exit的大部分工作是清理和释放资源。
当进程把用户态的内存释放后进入了TASK_ZOMBIE状态,表示不能被调度了。
此时进程保留着最少的资源task_struct结构体和内核栈共计两个页面。
子进程并不会消除自己的task_struct,会通知父进程由父进程来读取退出码和清除。
为什么设计成父进程来清除呢?
一方面,父进程要读取退出码和一些统计信息。
另一方面,如果子进程自己清除了自己的task_struct,在调度到下一个进程之前,发生了中断,就没有内核栈来给中断运行了。
fastcall NORET_TYPE void do_exit(long code)
    {
        struct task_struct *tsk = current;
        int group_dead;
        profile_task_exit(tsk);
        if (unlikely(in_interrupt()))
                panic("Aiee, killing interrupt handler!");
        if (unlikely(!tsk->pid))
                panic("Attempted to kill the idle task!");
        if (unlikely(tsk->pid == 1))
                panic("Attempted to kill init!");
        if (tsk->io_context)
                exit_io_context();
        if (unlikely(current->ptrace & PT_TRACE_EXIT)) {
                current->ptrace_message = code;
                ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP);
        }
        tsk->flags |= PF_EXITING;
        del_timer_sync(&tsk->real_timer);
        if (unlikely(in_atomic()))
                printk(KERN_INFO "note: %s[%d] exited with preempt_count %d\n",
                                current->comm, current->pid,
                                preempt_count());
        acct_update_integrals();
        update_mem_hiwater();
        group_dead = atomic_dec_and_test(&tsk->signal->live);
        if (group_dead)
                acct_process(code);
        exit_mm(tsk);
        exit_sem(tsk);
        __exit_files(tsk);
        __exit_fs(tsk);
        exit_namespace(tsk);
        exit_thread();
        exit_keys(tsk);
        if (group_dead && tsk->signal->leader)
                disassociate_ctty(1);
        module_put(tsk->thread_info->exec_domain->module);
        if (tsk->binfmt)
                module_put(tsk->binfmt->module);
        tsk->exit_code = code;
        exit_notify(tsk);
        BUG_ON(!(current->flags & PF_DEAD));
        schedule();
        BUG();
        /* Avoid "noreturn function does return".  */
        for (;;) ;
    }
1) if (unlikely(in_interrupt()))
   检查是否处于中断服务程序。
   如何检查是否正处于中断服务程序的调用中呢?
   #define in_interrupt() ({ int __cpu = smp_processor_id(); \
    (local_irq_count(__cpu) + local_bh_count(__cpu) != 0); })
   每次进入中断服务调用irq_enter,会递增CPU数据local_irq_count[__cpu]。退出的时候irq_exit会递减。
   bh的判断也是一样。
2) if (unlikely(!tsk->pid))
   0号idle进程不允许退出。
3) if (unlikely(tsk->pid == 1))
   1号init进程不允许退出。
4) del_timer_sync(&tsk->real_timer);
   进程可能设置了实时定时器,也就是将tsk->real_timer挂入了内核的定时器队列。
   这里需要脱队列。
5) 在fork中申请的资源都要进行释放。
   exit_sem清理信号量。
6) tsk->exit_code = code;
   设置退出码,等待父进程来读取。
7) exit_notify(tsk);
   通知父进程。
8) schedule();
   主动触发一次调度,调度之后就会再返回到这个进程了,因为此时进程的状态被设置成了ZOMBIE

exit_notify

static void exit_notify(void)
    {
        struct task_struct * p, *t;
        forget_original_parent(current);
        t = current->p_pptr;
        if ((t->pgrp != current->pgrp) &&
            (t->session == current->session) &&
            will_become_orphaned_pgrp(current->pgrp, current) &&
            has_stopped_jobs(current->pgrp)) {
                kill_pg(current->pgrp,SIGHUP,1);
                kill_pg(current->pgrp,SIGCONT,1);
    }
这块代码主要给父进程进程发送信号,并唤醒父进程。同时还涉及进程组,session,tty的操作。

父进程进程的wait4

asmlinkage long sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru)
    {
        int flag, retval;
        DECLARE_WAITQUEUE(wait, current);
        struct task_struct *tsk;
        if (options & ~(WNOHANG|WUNTRACED|__WNOTHREAD|__WCLONE|__WALL))
                return -EINVAL;
        add_wait_queue(&current->wait_chldexit,&wait);
    repeat:
        flag = 0;
        current->state = TASK_INTERRUPTIBLE;
        read_lock(&tasklist_lock);
        tsk = current;
        do {
                struct task_struct *p;
                 for (p = tsk->p_cptr ; p ; p = p->p_osptr) {
                        if (pid>0) {
                                if (p->pid != pid)
                                        continue;
                        } else if (!pid) {
                                if (p->pgrp != current->pgrp)
                                        continue;
                        } else if (pid != -1) {
                                if (p->pgrp != -pid)
                                        continue;
                        }
                        if (((p->exit_signal != SIGCHLD) ^ ((options & __WCLONE) != 0))
                            && !(options & __WALL))
                                continue;
                        flag = 1;
                        switch (p->state) {
                        case TASK_STOPPED:
                                if (!p->exit_code)
                                        continue;
                                if (!(options & WUNTRACED) && !(p->ptrace & PT_PTRACED))
                                        continue;
                                read_unlock(&tasklist_lock);
                                retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0; 
                                if (!retval && stat_addr) 
                                        retval = put_user((p->exit_code << 8) | 0x7f, stat_addr);
                                if (!retval) {
                                        p->exit_code = 0;
                                        retval = p->pid;
                                }
                                goto end_wait4;
                        case TASK_ZOMBIE:
                                current->times.tms_cutime += p->times.tms_utime + p->times.tms_cutime;
                                current->times.tms_cstime += p->times.tms_stime + p->times.tms_cstime;
                                read_unlock(&tasklist_lock);
                                retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
                                if (!retval && stat_addr)
                                        retval = put_user(p->exit_code, stat_addr);
                                if (retval)
                                        goto end_wait4; 
                                retval = p->pid;
                                if (p->p_opptr != p->p_pptr) {
                                        write_lock_irq(&tasklist_lock);
                                        REMOVE_LINKS(p);
                                        p->p_pptr = p->p_opptr;
                                        SET_LINKS(p);
                                        do_notify_parent(p, SIGCHLD);
                                        write_unlock_irq(&tasklist_lock);
                                } else
                                        release_task(p);
                                goto end_wait4;
                        default:
                                continue;
                        }
                }
                if (options & __WNOTHREAD)
                        break;
                tsk = next_thread(tsk);
        } while (tsk != current);
        read_unlock(&tasklist_lock);
        if (flag) {
                retval = 0;
                if (options & WNOHANG)
                        goto end_wait4;
                retval = -ERESTARTSYS;
                if (signal_pending(current))
                        goto end_wait4;
                schedule();
                goto repeat;
        }
        retval = -ECHILD;
    end_wait4:
        current->state = TASK_RUNNING;
        remove_wait_queue(&current->wait_chldexit,&wait);
        return retval;
    }
1) DECLARE_WAITQUEUE(wait, current);
   add_wait_queue(&current->wait_chldexit,&wait);
   在栈上申请一个waitqueue。
   子进程在do_notify_parent会遍历wait_chldexit。
2) 依次当前进程的所有子进程,如果pid是想要找的pid,
   并且该子进程的状态是 TASK_STOPPED, TASK_ZOMBIE,说明这个子进程就是wait4要找的子进程。
3) tsk = next_thread(tsk);
   为什么要有next_thread呢?当前进程可能是一个线程,要遍历所有的线程组里的task_struct。
4) retval = put_user(p->exit_code, stat_addr);
   release_task(p);
   找到了一个已经退出的子进程TASK_ZOMBIE,除了要累计子进程的运行时间,获取子进程的退出码。好要释放子进程残留的资源task_struct和内核栈。
5) schedule();
   如果子进程pid的状态不是TASK_STOPPED, TASK_ZOMBIE,则发生一次调度。并把进程状态改成TASK_INTERRUPTIBLE。

release_task释放子进程

static void release_task(struct task_struct * p)
    {
        if (p != current) {
                atomic_dec(&p->user->processes);
                free_uid(p->user);
                unhash_process(p);
                release_thread(p);
                current->cmin_flt += p->min_flt + p->cmin_flt;
                current->cmaj_flt += p->maj_flt + p->cmaj_flt;
                current->cnswap += p->nswap + p->cnswap;
                current->counter += p->counter;
                if (current->counter >= MAX_COUNTER)
                        current->counter = MAX_COUNTER;
                free_task_struct(p);
        } else {
                printk("task releasing itself\n");
        }
    }
1) free_task_struct(p);
   终于把子进程的task_struct给释放了

如果父进程没有调用wait4,而子进程退出了怎么处理呢?

子进程退出一定会向父进程发送CHLD信号,父进程在进入内核态返回,或者父进程在运行过程中来了中断(时钟中断座位最后的保障),
从内核态返回时候,因为父进程有待处理的信号SIG_CHLD,会进入signal_return,然后调用
do_signal
        ka = &current->sig->action[signr-1];
        if (ka->sa.sa_handler == SIG_IGN) {
                if (signr != SIGCHLD)
                        continue;
                while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0)
                continue;
        }
如果当前进程收到了SIGCHLD信号,内核会在内核态主动调用sys_wait4
相关文章
|
20天前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
70 4
|
1月前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
1月前
|
算法 调度
探索操作系统的心脏:内核与进程管理
【10月更文挑战第25天】在数字世界的复杂迷宫中,操作系统扮演着关键角色,如同人体中的心脏,维持着整个系统的生命力。本文将深入浅出地剖析操作系统的核心组件——内核,以及它如何通过进程管理来协调资源的分配和使用。我们将从内核的概念出发,探讨它在操作系统中的地位和作用,进而深入了解进程管理的机制,包括进程调度、状态转换和同步。此外,文章还将展示一些简单的代码示例,帮助读者更好地理解这些抽象概念。让我们一起跟随这篇文章,揭开操作系统神秘的面纱,理解它如何支撑起我们日常的数字生活。
|
3月前
|
算法 调度 Python
探索操作系统的内核——一个简单的进程调度示例
【9月更文挑战第17天】在这篇文章中,我们将深入探讨操作系统的核心组件之一——进程调度。通过一个简化版的代码示例,我们将了解进程调度的基本概念、目的和实现方式。无论你是初学者还是有一定基础的学习者,这篇文章都将帮助你更好地理解操作系统中进程调度的原理和实践。
|
4月前
|
调度 虚拟化 容器
探索操作系统的心脏:内核与进程管理
【8月更文挑战第28天】在数字世界的复杂迷宫中,操作系统扮演着关键角色。它如同一座桥梁,连接硬件与软件,确保一切顺畅运行。本文将深入剖析操作系统的核心——内核和进程管理,揭示它们如何协同工作,保障系统的稳定与高效。通过简化的比喻,我们将一探究竟,了解操作系统背后的神秘面纱。
|
3月前
crash —— 查看进程的内核栈的内容
crash —— 查看进程的内核栈的内容
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
5月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
190 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
4月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。