Linux102系列会详细讲解Linux0.11版本中的102个文件,本文讲解linux0.11的第36个文件
kernel/exit.c的文件源码。
1.kernel/exit.c 的主要作用
该程序主要描述了进程(任务)终止和退出的处理事宜。主要包含进程释放、会话终止和程序退出处理函数以及杀死进程、终止进程、挂起进程等系统调用函数。还包括进程信号发送函数send_sig()和通知父进程子进程终止的函数tell_father()。
释放进程的函数release()主要根据指定的任务数据结构(任务描述符)指针,在任务数组中删除指定的进程指针、释放相关内存页并立刻让内核重新调度任务的运行。
进程组终止函数kill_session()通过向会话号与当前进程相同的进程发送挂断进程的信号。
系统调用sys_kill()用于向进程发送任何指定的信号。根据参数pid(进程标识号)的数值的不同,该系统调用会向不同的进程或进程组发送信号。程序注释中已经列出了各种不同情况的处理方式。
程序退出处理函数do_exit()是在系统调用的中断处理程序中被调用的。它首先会释放当前进程的代码段和数据段所占的内存页面,然后向父进程发送子进程终止信号SIGCHLD。接着关闭当前进程打开的所有文件、释放使用的终端设备、协处理器设备,若当前进程是进程组的领头进程,则还需要终止所有相关进程。随后把当前进程置为僵死状态,设置退出码,并向其父进程发送子进程终止信号。最后让内核重新调度任务的运行。
系统调用waitpid()用于挂起当前进程,直到pid 指定的子进程退出(终止)或者收到要求终止该进程的信号,或者是需要调用一个信号句柄(信号处理程序)。如果pid所指的子进程早已退出(已成所谓的僵死进程), 则本调用将立刻返回。子进程使用的所有资源将释放。该函数的具体操作也要根据其参数进行不同的处理。详见代码中的相关注释。
这些函数共同构成了 Linux 进程生命周期管理的基础,特别是处理了进程退出时的资源清理、子进程托管给 init 进程、僵尸进程处理等关键逻辑。
2.源码用到的文件

| 😉【Linux102】25-include/errno.h | errno.h文件定义一系列标准错误码常量,用于标识系统操作中的具体错误类型。具体来说是通过声明全局错误变量errno,供程序获取最近一次错误状态。 |
| 😉【Linux102】18-include/signal.h | 首先介绍了信号机制,然后定义了信号处理相关数据结构,是理解Linux内核的信号机制的必知前提。 |
| 😉【Linux102】26-include/sys/wait.h | 定义解析子进程终止状态的宏(如WIFEXITED、WEXITSTATUS等),用于判断子进程是正常退出还是被信号终止,以及获取具体的退出码或信号值。 |
| 😉【Linux102】15-include/linux/sched.h | 本程序主要定义了进程调度的相关数据结构,如任务数据结构等等,理解这些数据结构是理解进程调度机制的关键,也是理解内核的关键。一句话:**我们常说的进程是XXX,但是进程究竟是什么?**就定义在这个文件里面!源码面前了,没有秘密! |
| 😉【Linux102】20-include/linux/kernel.h | 本程序主要定义了内核中最常用的一些基础函数声明,是内核运行最基本保障,比如malloc、printk、free、tyy_write、panic等函数声明,这些函数具体的实现则分布在各个.c文件中。 |
| 😉【Linux102】27-include/linux/tty.h | 本文件是让终端设备的 “输入输出、特殊按键、进程交互” 变得可管理、可扩展,是 Linux 终端子系统的 “骨架” 文件。 |
| 😉【Linux102】21-include/asm/segment.h | 本程序定义了一系列用于在 Linux 内核中访问用户空间内存的内联函数,主要通过内嵌汇编操作段寄存器fs来实现内核与用户空间的数据交互。 |
3.源码版
/*
* linux/kernel/exit.c
*
* (C) 1991 Linus Torvalds
*/
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/tty.h>
#include <asm/segment.h>
int sys_pause(void);
int sys_close(int fd);
void release(struct task_struct *p)
{
int i;
if (!p)
return;
for (i = 1; i < NR_TASKS; i++)
if (task[i] == p)
{
task[i] = NULL;
free_page((long)p);
schedule();
return;
}
panic("trying to release non-existent task");
}
static inline int send_sig(long sig, struct task_struct *p, int priv)
{
if (!p || sig < 1 || sig > 32)
return -EINVAL;
if (priv || (current->euid == p->euid) || suser())
p->signal |= (1 << (sig - 1));
else
return -EPERM;
return 0;
}
static void kill_session(void)
{
struct task_struct **p = NR_TASKS + task;
while (--p > &FIRST_TASK)
{
if (*p && (*p)->session == current->session)
(*p)->signal |= 1 << (SIGHUP - 1);
}
}
/*
* XXX need to check permissions needed to send signals to process
* groups, etc. etc. kill() permissions semantics are tricky!
*/
int sys_kill(int pid, int sig)
{
struct task_struct **p = NR_TASKS + task;
int err, retval = 0;
if (!pid)
while (--p > &FIRST_TASK)
{
if (*p && (*p)->pgrp == current->pid)
if ((err = send_sig(sig, *p, 1)))
retval = err;
}
else if (pid > 0)
while (--p > &FIRST_TASK)
{
if (*p && (*p)->pid == pid)
if ((err = send_sig(sig, *p, 0)))
retval = err;
}
else if (pid == -1)
while (--p > &FIRST_TASK)
{
if ((err = send_sig(sig, *p, 0)))
retval = err;
}
else
while (--p > &FIRST_TASK)
if (*p && (*p)->pgrp == -pid)
if ((err = send_sig(sig, *p, 0)))
retval = err;
return retval;
}
static void tell_father(int pid)
{
int i;
if (pid)
for (i = 0; i < NR_TASKS; i++)
{
if (!task[i])
continue;
if (task[i]->pid != pid)
continue;
task[i]->signal |= (1 << (SIGCHLD - 1));
return;
}
/* if we don't find any fathers, we just release ourselves */
/* This is not really OK. Must change it to make father 1 */
printk("BAD BAD - no father found\n\r");
release(current);
}
int do_exit(long code)
{
int i;
free_page_tables(get_base(current->ldt[1]), get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]), get_limit(0x17));
for (i = 0; i < NR_TASKS; i++)
if (task[i] && task[i]->father == current->pid)
{
task[i]->father = 1;
if (task[i]->state == TASK_ZOMBIE)
/* assumption task[1] is always init */
(void)send_sig(SIGCHLD, task[1], 1);
}
for (i = 0; i < NR_OPEN; i++)
if (current->filp[i])
sys_close(i);
iput(current->pwd);
current->pwd = NULL;
iput(current->root);
current->root = NULL;
iput(current->executable);
current->executable = NULL;
if (current->leader && current->tty >= 0)
tty_table[current->tty].pgrp = 0;
if (last_task_used_math == current)
last_task_used_math = NULL;
if (current->leader)
kill_session();
current->state = TASK_ZOMBIE;
current->exit_code = code;
tell_father(current->father);
schedule();
return (-1); /* just to suppress warnings */
}
int sys_exit(int error_code)
{
return do_exit((error_code & 0xff) << 8);
}
int sys_waitpid(pid_t pid, unsigned long *stat_addr, int options)
{
int flag, code;
struct task_struct **p;
verify_area(stat_addr, 4);
repeat:
flag = 0;
for (p = &LAST_TASK; p > &FIRST_TASK; --p)
{
if (!*p || *p == current)
continue;
if ((*p)->father != current->pid)
continue;
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;
}
switch ((*p)->state)
{
case TASK_STOPPED:
if (!(options & WUNTRACED))
continue;
put_fs_long(0x7f, stat_addr);
return (*p)->pid;
case TASK_ZOMBIE:
current->cutime += (*p)->utime;
current->cstime += (*p)->stime;
flag = (*p)->pid;
code = (*p)->exit_code;
release(*p);
put_fs_long(code, stat_addr);
return flag;
default:
flag = 1;
continue;
}
}
if (flag)
{
if (options & WNOHANG)
return 0;
current->state = TASK_INTERRUPTIBLE;
schedule();
if (!(current->signal &= ~(1 << (SIGCHLD - 1))))
goto repeat;
else
return -EINTR;
}
return -ECHILD;
}
4.源码注释版本
#include <errno.h> // 错误码定义
#include <signal.h> // 信号相关定义
#include <sys/wait.h> // wait系统调用相关定义
#include <linux/sched.h> // 进程调度相关结构定义
#include <linux/kernel.h> // 内核基本功能定义
#include <linux/tty.h> // 终端设备相关定义
#include <asm/segment.h> // 段操作相关函数
// 函数声明
int sys_pause(void); // 声明pause系统调用
int sys_close(int fd); // 声明close系统调用
/**
* 释放进程结构体所占用的内存
* @param p 要释放的进程结构体指针
*/
void release(struct task_struct * p)
{
int i;
if (!p) // 如果进程指针为空,直接返回
return;
// 遍历所有任务槽位,查找要释放的进程
for (i=1 ; i<NR_TASKS ; i++)
if (task[i]==p) { // 找到匹配的进程
task[i] = NULL;// 清空任务槽位
free_page((long)p); // 释放进程结构体占用的内存页
schedule(); // 重新调度进程
return;
}
// 如果没找到要释放的进程,触发内核恐慌
panic("trying to release non-existent task");
}
/**
* 向指定进程发送信号
* @param sig 要发送的信号
* @param p 目标进程结构体
* @param priv 是否拥有特权
* @return 0表示成功,错误码表示失败
*/
static inline int send_sig(long sig, struct task_struct * p, int priv)
{
// 检查参数合法性:进程不存在或信号值无效
if (!p || sig < 1 || sig > 32)
return -EINVAL;
// 检查权限:有特权,或同属一个用户,或超级用户
if (priv || (current->euid == p->euid) || suser())
p->signal |= (1 << (sig - 1)); // 设置对应信号位
else
return -EPERM; // 权限不足
return 0;
}
/**
* 向当前会话中的所有进程发送SIGHUP信号
* 通常在会话 leader 退出时调用
*/
static void kill_session(void)
{
struct task_struct **p = NR_TASKS + task; // 指向任务数组末尾的下一个位置
// 遍历所有任务
while (--p > &FIRST_TASK) {
// 如果进程存在且属于当前会话,发送SIGHUP信号
if (*p && (*p)->session == current->session)
(*p)->signal |= 1 << (SIGHUP - 1);
}
}
/*
* XXX 需要检查向进程组等发送信号的权限
* kill()的权限语义比较复杂!
*/
/**
* kill系统调用实现:向指定进程或进程组发送信号
* @param pid 进程ID或进程组ID(负数表示进程组)
* @param sig 要发送的信号
* @return 0表示成功,错误码表示失败
*/
int sys_kill(int pid, int sig)
{
struct task_struct **p = NR_TASKS + task; // 指向任务数组末尾的下一个位置
int err, retval = 0;
// 根据pid的不同值处理不同情况
if (!pid) // pid=0:向当前进程组的所有进程发送信号
while (--p > &FIRST_TASK) {
if (*p && (*p)->pgrp == current->pid)
if ((err = send_sig(sig, *p, 1)))
retval = err;
}
else if (pid > 0) // pid>0:向指定pid的进程发送信号
while (--p > &FIRST_TASK) {
if (*p && (*p)->pid == pid)
if ((err = send_sig(sig, *p, 0)))
retval = err;
}
else if (pid == -1) // pid=-1:向所有进程发送信号(除了init)
while (--p > &FIRST_TASK) {
if ((err = send_sig(sig, *p, 0)))
retval = err;
}
else // pid<0且不为-1:向进程组ID为-pid的所有进程发送信号
while (--p > &FIRST_TASK)
if (*p && (*p)->pgrp == -pid)
if ((err = send_sig(sig, *p, 0)))
retval = err;
return retval;
}
/**
* 通知父进程子进程状态变化(发送SIGCHLD信号)
* @param pid 父进程ID
*/
static void tell_father(int pid)
{
int i;
if (pid)
// 遍历所有任务查找父进程
for (i = 0; i < NR_TASKS; i++) {
if (!task[i])
continue;
if (task[i]->pid != pid)
continue;
// 找到父进程,发送SIGCHLD信号
task[i]->signal |= (1 << (SIGCHLD - 1));
return;
}
// 如果没找到父进程,释放当前进程
/* 这不是很好的处理方式,应该改为让init进程(1号)作为父进程 */
printk("BAD BAD - no father found\n\r");
release(current);
}
/**
* 处理进程退出的实际工作
* @param code 退出码
* @return 通常不会返回,-1仅用于抑制编译警告
*/
int do_exit(long code)
{
int i;
// 释放进程的代码段和数据段所占用的页表
free_page_tables(get_base(current->ldt[1]), get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]), get_limit(0x17));
// 处理子进程:将子进程的父进程改为init进程(1号)
for (i = 0; i < NR_TASKS; i++)
if (task[i] && task[i]->father == current->pid) {
task[i]->father = 1;
// 如果子进程已经是僵尸状态,通知init进程
if (task[i]->state == TASK_ZOMBIE)
/* 假设task[1]始终是init进程 */
(void) send_sig(SIGCHLD, task[1], 1);
}
// 关闭所有打开的文件描述符
for (i = 0; i < NR_OPEN; i++)
if (current->filp[i])
sys_close(i);
// 释放文件系统相关的inode引用
iput(current->pwd);
current->pwd = NULL;
iput(current->root);
current->root = NULL;
iput(current->executable);
current->executable = NULL;
// 如果是会话leader且有控制终端,释放终端
if (current->leader && current->tty >= 0)
tty_table[current->tty].pgrp = 0;
// 如果当前进程使用了数学协处理器,清除记录
if (last_task_used_math == current)
last_task_used_math = NULL;
// 如果是会话leader,终止会话中的所有进程
if (current->leader)
kill_session();
// 将进程状态设置为僵尸状态,保存退出码
current->state = TASK_ZOMBIE;
current->exit_code = code;
// 通知父进程
tell_father(current->father);
// 重新调度,让其他进程运行
schedule();
return (-1); /* 仅用于抑制编译警告 */
}
/**
* exit系统调用实现
* @param error_code 退出错误码
* @return 不会实际返回
*/
int sys_exit(int error_code)
{
// 将错误码处理后传递给do_exit
return do_exit((error_code & 0xff) << 8);
}
/**
* waitpid系统调用实现:等待子进程状态变化
* @param pid 要等待的进程ID
* @param stat_addr 存储子进程退出状态的地址
* @param options 等待选项
* @return 子进程ID,或错误码
*/
int sys_waitpid(pid_t pid, unsigned long * stat_addr, int options)
{
int flag, code;
struct task_struct ** p;
// 验证用户空间地址的可访问性
verify_area(stat_addr, 4);
repeat:
flag = 0;
// 遍历所有任务查找符合条件的子进程
for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
if (!*p || *p == current) // 跳过空进程和当前进程
continue;
if ((*p)->father != current->pid) // 只处理自己的子进程
continue;
// 根据pid参数筛选子进程
if (pid > 0) {
if ((*p)->pid != pid) // 等待特定pid的子进程
continue;
} else if (!pid) {
if ((*p)->pgrp != current->pgrp) // 等待同一进程组的子进程
continue;
} else if (pid != -1) {
if ((*p)->pgrp != -pid) // 等待特定进程组的子进程
continue;
}
// 根据子进程状态处理
switch ((*p)->state) {
case TASK_STOPPED: // 子进程已停止
if (!(options & WUNTRACED)) // 如果不关注停止状态则跳过
continue;
put_fs_long(0x7f, stat_addr); // 存储停止状态码
return (*p)->pid; // 返回子进程ID
case TASK_ZOMBIE: // 子进程已成为僵尸进程
// 累加子进程的用户时间和系统时间
current->cutime += (*p)->utime;
current->cstime += (*p)->stime;
flag = (*p)->pid;
code = (*p)->exit_code;
release(*p); // 释放子进程
put_fs_long(code, stat_addr); // 存储退出码
return flag; // 返回子进程ID
default:
flag = 1; // 标记有符合条件但未退出的子进程
continue;
}
}
// 处理等待逻辑
if (flag) {
if (options & WNOHANG) // 如果设置了WNOHANG,不阻塞直接返回0
return 0;
current->state = TASK_INTERRUPTIBLE; // 进入可中断等待状态
schedule(); // 调度其他进程运行
// 如果被信号唤醒且不是SIGCHLD,返回中断错误
if (!(current->signal &= ~(1 << (SIGCHLD - 1))))
goto repeat; // 继续等待
else
return -EINTR;
}
return -ECHILD; // 没有符合条件的子进程
}
5.源码图像版

6.源码注释版图像


本系列将带领大家从0开始循序渐进学习汇编语言,直至完全掌握这门底层语言。同时给出学习平台DOSBox的使用教程。
本系列将直击C语言的本质基础,流利处理出各个易错、实用的实战点,并非从零开始学习C。
专注讲解Linux中的常用命令,共计发布100+文章。
本系列将精讲Linux0.11内核中的每一个文件,共计会发布100+文章。
😉【Linux102】11-kernel/vsprintf.c
😉【Linux102】12-include/stdarg.h
😉【Linux102】14-kernel/system_call.s
😉【Linux102】15-include/linux/sched.h
😉【Linux102】18-include/signal.h
😉【Linux102】19-include/sys/types.h
😉【Linux102】20-include/linux/kernel.h
😉【Linux102】21-include/asm/segment.h
😉【Linux102】22-include/linux/head.h
😉【Linux102】23-include/linux/mm.h
😉【Linux102】24-include/linux/fs.h
😉【Linux102】26-include/sys/wait.h
😉【Linux102】27-include/inux/tty.h
😉【Linux102】28-include/termios.h
😉【Linux102】30-include/sys/times.h
😉【Linux102】31-include/sys/utsname.h
😉【Linux102】32-include/stddef.h
😉【Linux102】33-include/linux/sys.h
😉【Linux102】36-include/asm/system.h
和Linux内核102系列不同,本系列将会从全局描绘Linux内核的各个模块,而非逐行源码分析,适合想对Linux系统有宏观了解的家人阅读。
😉【Linux】Linux概述1-linux对物理内存的使用
关于小希
😉嘿嘿嘿,我是小希,专注Linux内核领域,同时讲解C语言、汇编等知识。
我的微信:C_Linux_Cloud,期待与您学习交流!

加微信请备注哦
小希的座右铭:
别看简单,简单也是难。别看难,难也是简单。我的文章都是讲述简单的知识,如果你喜欢这种风格:
下一期想看什么?在评论区留言吧!我们下期见!

974

被折叠的 条评论
为什么被折叠?



