专栏首页原创分享linux系统调用之sys_exit(基于linux0.11)

linux系统调用之sys_exit(基于linux0.11)

int sys_exit(int error_code)
{
    return do_exit((error_code&0xff)<<8);
}

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) {
            // 子进程的新父进程是进程id为1的进程
            task[i]->father = 1;
            /*
             如果子进程刚把自己的状态改成TASK_ZOMBIE,执行到tell_father里的代码时,时间片到了,
             然后调度父进程执行,这时候父进程退出了,再切换到子进程执行的时候,
             子进程给父进程发信号就丢失了,所以这里补充一下这个逻辑,给新的父进程发信号
            */
            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);
    // 回写inode到硬盘
    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);
    // 重新调度进程(tell_father里已经调度过了)
    schedule();
    return (-1);    /* just to suppress warnings */
}

释放地址

// from是线性地址。释放from开始,连续的n个大小为4MB的页面对应的物理地址。最后释放页表、页目录项
int free_page_tables(unsigned long from,unsigned long size)
{
    unsigned long *pg_table;
    unsigned long * dir, nr;
    // 判断是否按4MB对齐
    if (from & 0x3fffff)
        panic("free_page_tables called with wrong alignment");
    if (!from)
        panic("Trying to free up swapper memory space");
    // 算出size包含多少个MB,比如size是0 - 1>>22,则计算机后是1
    size = (size + 0x3fffff) >> 22;
    /*
        页目录在地址0开始的地方,首先右移得到页目录索引,
        根据索引得到页目录项内容,因为页目录项的内容占4个字节,
        其中高20位是页表地址,低12位是标记位,,所以要乘以4得到
        from对应的页目录项的地址。即dir = from >> 22 << 2 = from >> 20,
        但是代码里是直接右移20位,所以需要和0xffc与,把低两位置0,最后得到from
        对应的页目录项的地址
    */
    dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
    for ( ; size-->0 ; dir++) {
        // 低位是1说明该页目录项有效
        if (!(1 & *dir))
            continue;
        // *dir为页表首地址,与0xfffff000是因为高二十位是有效地址,低12位是标记位
        pg_table = (unsigned long *) (0xfffff000 & *dir);
        // 释放每个页表指向的物理地址
        for (nr=0 ; nr<1024 ; nr++) {
            // 页表是否有效,有效则释放*pg_table指向物理地址,以4kb对齐
            if (1 & *pg_table)
                // 与0xfffff000是因为高二十位是有效地址,低12位是标记位 
                free_page(0xfffff000 & *pg_table);
            // 置页表无效
            *pg_table = 0;
            // 下一个页表
            pg_table++;
        }
        // 释放页表占据的物理地址
        free_page(0xfffff000 & *dir);
        // 置页目录项为无效
        *dir = 0;
    }
    invalidate();
    return 0;
}

结束会话

// 结束会话,给该会话的所有进程发SIGHUP信号,因为子进程会继承父进程的sessionid,所以if可能会多次成立
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);
    }
}

通知父进程

// 子进程退出,通知进程id是pid的父进程
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;
            // 根据pid找到父进程,设置子进程退出的信号
            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");
    // 释放pcb结构
    release(current);
}
// 释放pcb的一页内存,重新调度进程
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");
}

本文分享自微信公众号 - 编程杂技(theanarkh)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 理解进程的退出

    当一个进程调用exit的时候,就意味着他退出了。我们看一下他退出的时候,都做了什么操作。

    theanarkh
  • linu0.11父进程等待子进程退出和进程退出原理分析

    theanarkh
  • 理解进程和线程

    进程和线程是操作系统里很重要的概念,但是所有的东西都会落实到代码。看起来很复杂的进程线程,其实在操作系统的代码里。也只是一些数据结构和算法。只不过他比一...

    theanarkh
  • Django-guardian实现对象级别的权限控制

    django-guardian是为Django提供额外的基于对象权限的身份验证后端。

    菲宇
  • Swift:轻量级API的设计(一)

    Swift的最强大功能之一就是在设计API方面给我们提供了极大的灵活性。这种灵活性不仅使我们能够定义易于理解和使用的函数和类型,还使我们能够创建给人以非常轻量级...

    韦弦zhy
  • react dva如何获取被form包裹的子组件函数

    杭州前端工程师
  • 【java基础】保留小数:java DecimalFormat format 方法的使用

    用户5640963
  • nginx: [emerg] unknown directive “ ” in /usr/local/nginx/nginx.conf.conf:xx报错处理

    当我们在修改Nginx的配置文件,然后加载配置文件./nginx -s reload 报错类似的错误,

    Arebirth
  • PHP 正则表达式后面接的/isU, /is, /s含义

    夏时
  • 适配器模式与SpringMVC

    适配器模式是将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作...

    java技术爱好者

扫码关注云+社区

领取腾讯云代金券