《linux内核完全剖析》笔记05-任务退出

导语:进程的退出依赖于系统对信号的处理,所以应该先熟悉系统如何处理信号

1. 从一个例子开始

void handle(int sig) {
  printf("the signal is %d\n",sig);
  (void) signal(SIGINT,SIG_DFL);//恢复默认处理函数
}

int main() {
  (void) signal(SIGINT,handle); //设置SIGINT信号的信号处理函数
  while(1) {
    printf("signal test....\n");
    sleep(1);
  }
}

按ctrl-c时将会到handle函数处理,并将信号处理函数设置为SIG_DFL;

2. signal函数长什么样

上面调用的signal函数在libc库函数中,是对系统调用的封装

extern void ___sig_restore();
void (*signal(int sig,__sighandler_t func))(int) {
  void (*res)();
  register int __fooebx __asm__ ("bx") = sig;
  /*
  eax 为系统调用号 __NR_signal
  ebx 为 sig
  ecx 为信号处理函数
  edx 为__sig_restore函数
  以下语句包裹__NR_signal系统调用
  */
  __asm__("int $0x80":"=a" (res):
         "0"(__NR_signal),"r"(__fooebx),"c"(func),"d"((long)__sig_restore))
    return res;
}

3. 信号处理的分析过程

  • 信号的处理是在每次系统调用时或者时钟中断时
  • do_signal函数是内核在系统调用时对信号的预处理
3.1. 每次系统调用完成后的处理 sys_calls.s 第107行
ret_from_sys_call:
    ...
    movl signal(%eax),%ebx      #取信号位图
    movl blocked(%eax),%ecx     #取屏蔽信号位图
    notl %ecx
    andl %ebx,%ecx
    bsfl %ecx,%ecx              #从位0开始扫描有1的位
    je 3f
    btrl %ecx,%ebx
    movl %ebx,signal(%eax)
    incl %ecx
    pushl %ecx                  #信号值入栈
    call _do_signal             #!!!调用do_signal函数
    popl %ecx
    testl %eax, %eax
    jne 2b      # see if we need to switch tasks, or do more signals
3:  popl %eax
    popl %ebx
    popl %ecx
    popl %edx
    addl $4, %esp  # skip orig_eax
    pop %fs
    pop %es
    pop %ds
    iret
3.2. do_signal函数将信号处理句柄插入到用户程序堆栈执行

系统调用返回时有信号处理时,返回路径是执行信号的处理,没有则直接返回!!!

int do_signal(long signr,long eax,long ebx, long ecx, long edx, long orig_eax,
    long fs, long es, long ds,
    long eip, long cs, long eflags,
    unsigned long * esp, long ss)
{
    unsigned long sa_handler;
    long old_eip=eip;
    struct sigaction * sa = current->sigaction + signr - 1;
    int longs;
    ......
    sa_handler = (unsigned long) sa->sa_handler;
    if (sa_handler==1)
        return(1);   /* Ignore, see if there are more signals... */
    //信号的默认处理
    //代码已略,基本上都是结束当前进程
    ......
    /*
     * OK, we're invoking a handler 
     */
    if (sa->sa_flags & SA_ONESHOT)
        sa->sa_handler = NULL;
    //以下这段就是把信号处理函数插入到用户堆栈中
    //插入用户堆栈的目的就是系统调用返回后,调用用户自定义的信号处理函数
    *(&eip) = sa_handler;
    longs = (sa->sa_flags & SA_NOMASK)?7:8;
    *(&esp) -= longs;
    verify_area(esp,longs*4);
    tmp_esp=esp;
    //处理完用户的信号处理函数后的清理函数 sa->sa_restorer,也就是上面第2点的__sig_restore函数
    put_fs_long((long) sa->sa_restorer,tmp_esp++);
    put_fs_long(signr,tmp_esp++);
    if (!(sa->sa_flags & SA_NOMASK))
        put_fs_long(current->blocked,tmp_esp++);
    put_fs_long(eax,tmp_esp++);
    put_fs_long(ecx,tmp_esp++);
    put_fs_long(edx,tmp_esp++);
    put_fs_long(eflags,tmp_esp++);
    put_fs_long(old_eip,tmp_esp++);
    current->blocked |= sa->sa_mask;
    return(0);      /* Continue, execute handler */
}
3.3. __sig_restore函数做了什么

libc-2.2.2中实现了一下函数

.global ____sig_restore
____sig_restore:
    addl $4,%esp   #丢弃信号值
    popl %eax       #恢复系统调用返回值
    popl %ecx
    popl %edx
    popfl           #恢复用户程序的标志寄存器
    ret

4. 进程退出都做了什么

  • 释放占用的内核中的内存(task_struct)和分配的物理内存
  • 当前进程的结束(状态的改变) 对当前进程的父进程和当前进程的子进程,以及兄弟进程的影响
  • 内核重新调度
volatile void do_exit(long code)
{
    struct task_struct *p;
    int i;
    //释放分配的物理内存,0x0f和0x17都是选择子
    free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
    free_page_tables(get_base(current->ldt[2]),get_limit(0x17));

    ......

    /* 
     * Check to see if any process groups have become orphaned
     * as a result of our exiting, and if they have any stopped
     * jobs, send them a SIGUP and then a SIGCONT.  (POSIX 3.2.2.2)
     *
     * Case i: Our father is in a different pgrp than we are
     * and we were the only connection outside, so our pgrp
     * is about to become orphaned.
     */
    if ((current->p_pptr->pgrp != current->pgrp) &&
        (current->p_pptr->session == current->session) &&
        is_orphaned_pgrp(current->pgrp) &&
        has_stopped_jobs(current->pgrp)) {
        kill_pg(current->pgrp,SIGHUP,1);
        kill_pg(current->pgrp,SIGCONT,1);
    }
    /* Let father know we died */
    //告诉父进程,当前进程退出
    current->p_pptr->signal |= (1<<(SIGCHLD-1));

    /*
     * This loop does two things:
     * 
     * A.  Make init inherit all the child processes
     * B.  Check to see if any process groups have become orphaned
     *  as a result of our exiting, and if they have any stopped
     *  jons, send them a SIGUP and then a SIGCONT.  (POSIX 3.2.2.2)
     */
    if (p = current->p_cptr) {
        while (1) {
            //让所有的子进程都继承在init进程task[1]
            p->p_pptr = task[1];
            if (p->state == TASK_ZOMBIE)
                task[1]->signal |= (1<<(SIGCHLD-1));
            /*
             * process group orphan check
             * Case ii: Our child is in a different pgrp 
             * than we are, and it was the only connection
             * outside, so the child pgrp is now orphaned.
             */
            if ((p->pgrp != current->pgrp) &&
                (p->session == current->session) &&
                is_orphaned_pgrp(p->pgrp) &&
                has_stopped_jobs(p->pgrp)) {
                kill_pg(p->pgrp,SIGHUP,1);
                kill_pg(p->pgrp,SIGCONT,1);
            }

             ......
        }
    }

    ......

     //重新调度
    schedule();
}

5. 任务的状态:

  • 就绪两种 : 内核运行和用户运行
  • 睡眠两种: 不可中断和可中断,不可中断的进程要显示唤醒,可中断的可由调度程序唤醒
  • 退出两种: 暂停和僵死
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值