歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> linux進程管理之信號處理(2)

linux進程管理之信號處理(2)

日期:2017/3/3 16:40:55   编辑:關於Linux

另外,內核經常使用force_sig_info()/force_sig()來給進程發送信號.這樣的信號經常不可以忽略,不可以阻塞.我們來看一下它的處理.代碼如下:

int force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
unsigned long int flags;
int ret, blocked, ignored;
struct k_sigaction *action;
spin_lock_irqsave(&t->sighand->siglock, flags);
//取進程的信號的處理函數
action = &t->sighand->action[sig-1];
//如果該信號被忽略或者該信號被阻塞
ignored = action->sa.sa_handler == SIG_IGN;
blocked = sigismember(&t->blocked, sig);
if (blocked || ignored) {
//重信號處理函數為默認的處理
action->sa.sa_handler = SIG_DFL;
//如果信號被屏弊
if (blocked) {
//清除信號屏弊位
sigdelset(&t->blocked, sig);
//重新計算進程是否有末處理的信號
recalc_sigpending_and_wake(t);
}
}
//"特殊"的信號發送
ret = specific_send_sig_info(sig, info, t);
spin_unlock_irqrestore(&t->sighand->siglock, flags);
return ret;
}

當進程的信號阻塞標志被更改時,就會引起TIF_SIGPENDING標志的變化.對於TIF_SIGPENDING標志的檢測是在recalc_sigpending_and_wake()調用recalc_sigpending_tsk()來完成的.它實際是判斷等待隊列中是否有沒有被阻塞的信號.如果有,則設置TIF_SIGPENDING標志.

specific_send_sig_info()內核用於將信號發送到進程.我們比較一下它跟用戶空間的發送有什麼不同.它的代碼如下:
static int
specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
   int ret = 0;
   BUG_ON(!irqs_disabled());
   assert_spin_locked(&t->sighand->siglock);
   /* Short-circuit ignored signals. */
   //信號被忽略,退出
   if (sig_ignored(t, sig))
     goto out;
   /* Support queueing exactly one non-rt signal, so that we
     can get more detailed information about the cause of
     the signal. */
   //如果不是實時信號,且已經有信號在等待隊列中了.直接等待(不排隊)
   if (LEGACY_QUEUE(&t->pending, sig))
     goto out;
   //將信號發送到目標進程
   ret = send_signal(sig, info, t, &t->pending);
   // TODO: 這裡調用signal_wake_up()直接喚醒進程
   if (!ret && !sigismember(&t->blocked, sig))
     signal_wake_up(t, sig == SIGKILL);
out:
   return ret;
}

這樣,內核就將信號傳送給目標進程.無論進程用什麼樣的方式,都不能阻止對此信號的處理.

四:信號的處理

信號處理的時機:每次從內核空間返回用戶空間時,都會檢查當前進程是否有末處理的信號.如果有,則對信號進行處理

信號的處理函數如下:

static void fastcall do_signal(struct pt_regs *regs)
{
   siginfo_t info;
   int signr;
   struct k_sigaction ka;
   sigset_t *oldset;
   //判斷是否是處於返回到用戶空間的前夕.不需要處理
   if (!user_mode(regs))
     return;
   //要從task->saved_sigmask中恢復進程信號掩碼
   if (test_thread_flag(TIF_RESTORE_SIGMASK))
     oldset = &current->saved_sigmask;
   else
     oldset = &current->blocked;
   //對等待信號的處理
   //只有遇到用戶重設信號處理函數的信號或者處理完等待信號才會返回
   signr = get_signal_to_deliver(&info, &ka, regs, NULL);
   if (signr > 0) {
     //對用戶設置了信號處理函數的信號處理
     if (unlikely(current->thread.debugreg[7]))
       set_debugreg(current->thread.debugreg[7], 7);
     /* Whee! Actually deliver the signal. */
     if (handle_signal(signr, &info, &ka, oldset, regs) == 0) {
       if (test_thread_flag(TIF_RESTORE_SIGMASK))
          clear_thread_flag(TIF_RESTORE_SIGMASK);
     }
     return;
   }
   //沒有Catch信號的系統調用重啟
   /* Did we come from a system call? */
   if (regs->orig_eax >= 0) {
     /* Restart the system call - no handlers present */
     switch (regs->eax) {
     case -ERESTARTNOHAND:
     case -ERESTARTSYS:
     case -ERESTARTNOINTR:
       regs->eax = regs->orig_eax;
       regs->eip -= 2;
       break;
     //如果是返回-ERESTART_RESTARTBLOCK ,返回用戶空間後重新發起
     //系統調用.系統調用號為__NR_restart_syscall
     //一般用在與timer有關的系統調用中
     case -ERESTART_RESTARTBLOCK:
       regs->eax = __NR_restart_syscall;
       regs->eip -= 2;
       break;
     }
   }
   /* if there's no signal to deliver, we just put the saved sigmask
   * back */
   if (test_thread_flag(TIF_RESTORE_SIGMASK)) {
     //清除TIF_RESTORE_SIGMASK 並恢復信號掩碼
     clear_thread_flag(TIF_RESTORE_SIGMASK);
     sigprocmask(SIG_SETMASK, &current->saved_sigmask, NULL);
   }
}

正好我們在上節發送信號中所論述的一樣,信號可能會引起系統調用中斷.這裡必須要采取必要的措施來使系統調用重啟.

關於返回值與重啟還是忽略如下表如示(摘自<< understanding the linux kernel >>):

Signal

Action

EINTR ERESTARTSYS ERESTARTNOHAND

ERESTART_RESTARTBLOCKa

ERESTARTNOINTR Default Terminate Reexecute Reexecute Reexecute Ignore Terminate Reexecute Reexecute Reexecute Catch Terminate Depends Terminate Reexecute

有必要關注一下上面的系統調用重啟過程:

Regs參數表示用戶空的硬件環境.regs->eax是表示返回用戶空間後的eax寄存器的值.regs->eip是返回用戶空間後執行的指針地址. regs->orig_eax是表示系統調用時eax的值,裡面存放著系統調用號.請參閱本站的有關中斷初始化的文檔.

Regs->eip -= 2 ,為什麼eip要減2呢?因為發現系統調用是int 0x80 指令.中斷後,eip會指向int 80後面的一條指令.這樣,如果要重新執新int 0x80.那就必須要把eip返回兩條指令.

轉入get_signal_to_deliver():

int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,
        struct pt_regs *regs, void *cookie)
{
   sigset_t *mask = &current->blocked;
   int signr = 0;
   //選擇編譯函數
   try_to_freeze();
relock:
   spin_lock_irq(&current->sighand->siglock);
   for (;;) {
     struct k_sigaction *ka;
     if (unlikely(current->signal->group_stop_count > 0) &&
       handle_group_stop())
       goto relock;
     //從等待隊列中取信號
     signr = dequeue_signal(current, mask, info);
     //信號為空,退出
     if (!signr)
       break; /* will return 0 */
     //當前進程正在被跟蹤
     if ((current->ptrace & PT_PTRACED) && signr != SIGKILL) {
       ptrace_signal_deliver(regs, cookie);
       /* Let the debugger run. */
       ptrace_stop(signr, signr, info);
       /* We're back. Did the debugger cancel the sig? */
       signr = current->exit_code;
       if (signr == 0)
          continue;
current->exit_code = 0;
       /* Update the siginfo structure if the signal has
         changed. If the debugger wanted something
         specific in the siginfo structure then it should
         have updated *info via PTRACE_SETSIGINFO. */
       if (signr != info->si_signo) {
          info->si_signo = signr;
          info->si_errno = 0;
          info->si_code = SI_USER;
          info->si_pid = task_pid_vnr(current->parent);
          info->si_uid = current->parent->uid;
       }
       /* If the (new) signal is now blocked, requeue it. */
       if (sigismember(&current->blocked, signr)) {
          specific_send_sig_info(signr, info, current);
          continue;
       }
     }
     ka = &current->sighand->action[signr-1];
     //信號被忽略,不做任何處理
     if (ka->sa.sa_handler == SIG_IGN) /* Do nothing. */
       continue;
     //如果不為默認操作.也就是說用戶已經重置了該信號的處理
     //這樣情況下會調用break退出循環
     if (ka->sa.sa_handler != SIG_DFL) {
       /* Run the handler. */
       *return_ka = *ka;
       //如果定義了SA_ONESHOT 標志,指明信號處理完之後,恢復信號的默認處理
       if (ka->sa.sa_flags & SA_ONESHOT)
          ka->sa.sa_handler = SIG_DFL;
       break; /* will return non-zero "signr" value */
     }
     /*
     * Now we are doing the default action for this signal.
     */
     //如果是內核所忽略的信號,不做任何處理
     //這裡注意了.Child信號的默認處理是忽略.這就是形成僵屍進程
     //的主要原因
     if (sig_kernel_ignore(signr)) /* Default is nothing. */
       continue;
     /*
     * Global init gets no signals it doesn't want.
     */
     //判斷是否是INIT 進程
     if (is_global_init(current))
       continue;
     //引起進程掛起的信號 
     if (sig_kernel_stop(signr)) {
       //SIGSTOP的處理與其它會引起停止的信號有點不同
       //SIGSTOP總是停止進程,而其它信號只會停止不在孤兒進程組
       //中的進程
       if (signr != SIGSTOP) {
          spin_unlock_irq(&current->sighand->siglock);
          /* signals can be posted during this window */
          if (is_current_pgrp_orphaned())
            goto relock;
          spin_lock_irq(&current->sighand->siglock);
       }
       //停止進程
       if (likely(do_signal_stop(signr))) {
          /* It released the siglock. */
          goto relock;
       }
       /*
       * We didn't actually stop, due to a race
       * with SIGCONT or something like that.
       */
       continue;
     }
     spin_unlock_irq(&current->sighand->siglock);
     //除去內核忽略和引起進程停止的信號之處的所有信號都會讓過程
     //終止
     /*
     * Anything else is fatal, maybe with a core dump.
     */
     //置進程標志位PF_SIGNALED.表示該信號終止是由信號引起的
     current->flags |= PF_SIGNALED;
     if ((signr != SIGKILL) && print_fatal_signals)
       print_fatal_signal(regs, signr);
     //如果是一些會引起核心轉儲的信號
     //建立核心轉儲文件後退出
     if (sig_kernel_coredump(signr)) {
       /*
       * If it was able to dump core, this kills all
       * other threads in the group and synchronizes with
       * their demise. If we lost the race with another
       * thread getting here, it set group_exit_code
       * first and our do_group_exit call below will use
       * that value and ignore the one we pass it.
*/
       do_coredump((long)signr, signr, regs);
     }
      /*
     * Death signals, no core dump.
     */
     //進程組退出
     do_group_exit(signr);
     /* NOTREACHED */
   }
   spin_unlock_irq(&current->sighand->siglock);
   return signr;
}

這個函數比較簡單,基本上就是遍歷信號等待隊列.然後處理信號.一直遇到信號處理被重設或者沒有等待信號之後才會返回.

信號出列函數為dequeue_signal():

int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
{
   int signr = 0;
   /* We only dequeue private signals from ourselves, we don't let
   * signalfd steal them
   */
   //從pending 隊列中取出等待信號
   signr = __dequeue_signal(&tsk->pending, mask, info);
   //如果pending 隊列中沒有等待信號,則從shared_pending中取
   if (!signr) {
     signr = __dequeue_signal(&tsk->signal->shared_pending,
            mask, info);
     //如果是SIGALRM 信號
     //重啟計時器
     if (unlikely(signr == SIGALRM)) {
       struct hrtimer *tmr = &tsk->signal->real_timer;
       if (!hrtimer_is_queued(tmr) &&
         tsk->signal->it_real_incr.tv64 != 0) {
          hrtimer_forward(tmr, tmr->base->get_time(),
              tsk->signal->it_real_incr);
          hrtimer_restart(tmr);
       }
     }
   }
   //重新判斷是位還有末處理的信號,更新TIF_SIGPENDING 標志
   recalc_sigpending();
   //會引起進程終止的信號,置SIGNAL_STOP_DEQUEUED 標志
   //禁止信號出列,即阻止後續的信號處理
   if (signr && unlikely(sig_kernel_stop(signr))) {
     if (!(tsk->signal->flags & SIGNAL_GROUP_EXIT))
       tsk->signal->flags |= SIGNAL_STOP_DEQUEUED;
   }
   //__SI_TIMER : 定時器到期
   if (signr &&
      ((info->si_code & __SI_MASK) == __SI_TIMER) &&
      info->si_sys_private){
     spin_unlock(&tsk->sighand->siglock);
     do_schedule_next_timer(info);
     spin_lock(&tsk->sighand->siglock);
   }
   return signr;
}

__dequeue_signal()用於從等待隊列中取出信號.代碼如下:

static int __dequeue_signal(struct sigpending *pending, sigset_t *mask,
       siginfo_t *info)
{
   //取位圖中為第一個為1的標志位
   int sig = next_signal(pending, mask);
   if (sig) {
     //如果定義了進程通告?
     //task->notifier:指向一個函數指針. 設備驅動程序用它來阻塞某些信號
     if (current->notifier) {
       if (sigismember(current->notifier_mask, sig)) {
          if (!(current->notifier)(current->notifier_data)) {
            clear_thread_flag(TIF_SIGPENDING);
            return 0;
          }
       }
     }
     //將信號從等待隊列中移除,更新等待信號標志位
     if (!collect_signal(sig, pending, info))
       sig = 0;
   }
   return sig;
}

Cllect_signal()代碼如下:

static int collect_signal(int sig, struct sigpending *list, siginfo_t *info)
{
   struct sigqueue *q, *first = NULL;
   int still_pending = 0;
   //要處理的信號沒有包含在等待隊列中,退出
   if (unlikely(!sigismember(&list->signal, sig)))
     return 0;
   /*
   * Collect the siginfo appropriate to this signal. Check if
   * there is another siginfo for the same signal.
   */
   //遍歷等待隊列.如果不止有一個sig 信號在等待.still_pending為1
list_for_each_entry(q, &list->list, list) {
     if (q->info.si_signo == sig) {
       if (first) {
          still_pending = 1;
          break;
       }
       first = q;
     }
   }
   if (first) {
     //如果等待隊列中有此信號
     //在等待隊列中將它刪除
     list_del_init(&first->list);
     //將信號信號copy 到info
     copy_siginfo(info, &first->info);
     //釋放信號
     __sigqueue_free(first);
     if (!still_pending)
       //如果只有一個信號在等待,也就是說該類的等待信號已經處理完了
       //從等待位圖中刪除該位
       sigdelset(&list->signal, sig);
   } else {
     /* Ok, it wasn't in the queue. This must be
       a fast-pathed signal or we must have been
       out of queue space. So zero out the info.
     */
     //如果等待隊列中沒有此信號,將對應位圖置0.
     //info信號置空
     sigdelset(&list->signal, sig);
     info->si_signo = sig;
     info->si_errno = 0;
     info->si_code = 0;
     info->si_pid = 0;
     info->si_uid = 0;
   }
   return 1;
}

返回do_signal()中看看如果信號處理函數被重設會怎麼樣處理.這也是信號處理中比較難理解的部份.轉入具體的處理代碼之前,先思考一下:

用戶空間的函數地址傳遞給內核空間之後,可不可以在內核直接運行呢?(即設置好內核堆,再把eip設為fuction address)?

是有可能運行的.因為內核切占不會切換CR3.用戶進程切換會切換CR3.因此可以保證進程陷入內核後可以正常的對用戶空間的地址進行尋址.但是基於以下幾點原因.不建議直接在內核空間運行

1:安全因素.陷入內核空間後,對內核地址空間具有全部訪問權限,沒有內存保護進制

2:內核堆棧過小,最大只有8KB.

3:用戶空間的函數在運行的時候可能會發出系統調用.由於在最高特權級下,導致系統調用/異常處理失敗.

既然這樣,那怎麼運行信號處理函數呢?

我們只需要讓它在返回用戶空間後馬上運行信號處理函數,運行信號處理函數再系統調用返回內核就可以了.

先分析一下有關的數據結構:

struct sigframe
{
   //信號處理函數的返回地址,它指向同一個結構中的retcode字段
   char __user *pretcode;
   //信號數值
   int sig;
   //保存當前regs的一個結構
   struct sigcontext sc;
   //保存FPU,MMX,XMM等相關信息
   struct _fpstate fpstate;
   //被阻塞的實時信號的位數組
   unsigned long extramask[_NSIG_WORDS-1];
   //信號處理程序運行完後執行的執令
   char retcode[8];
}

現在我們轉入代碼看是如何處理的:

static int
handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka,
      sigset_t *oldset, struct pt_regs * regs)
{
   int ret;
   //在執行系統調用的時候,可能被信號給中斷了.
   //要根據返回值判斷是否可以重啟系統調用
   /* Are we from a system call? */
   if (regs->orig_eax >= 0) {
     /* If so, check system call restarting.. */
     switch (regs->eax) {
         case -ERESTART_RESTARTBLOCK:
       case -ERESTARTNOHAND:
          regs->eax = -EINTR;
          break;
       case -ERESTARTSYS:
          if (!(ka->sa.sa_flags & SA_RESTART)) {
            regs->eax = -EINTR;
            break;
          }
       /* fallthrough */
       case -ERESTARTNOINTR:
          regs->eax = regs->orig_eax;
          regs->eip -= 2;
     }
   }
   /*
   * If TF is set due to a debugger (PT_DTRACE), clear the TF flag so
   * that register information in the sigcontext is correct.
   */
   //如果處於跟蹤狀態
   //就像進行中斷處理程序,關閉中斷一樣
   if (unlikely(regs->eflags & TF_MASK)
     && likely(current->ptrace & PT_DTRACE)) {
     current->ptrace &= ~PT_DTRACE;
     regs->eflags &= ~TF_MASK;
   }
   /* Set up the stack frame */
   //SA_SIGINFO:為信號處理提供額外的信息
   //建立幀結構
   if (ka->sa.sa_flags & SA_SIGINFO)
     ret = setup_rt_frame(sig, ka, info, oldset, regs);
   else
     ret = setup_frame(sig, ka, oldset, regs);
   if (ret == 0) {
     spin_lock_irq(&current->sighand->siglock);
     sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
     //SA_NODEFER:在執行信號處理函數的時候,不屏弊信號
     if (!(ka->sa.sa_flags & SA_NODEFER))
       //如果沒有定義SA_NODEFER.那屏弊掉當前信號
       sigaddset(&current->blocked,sig);
     //更新TIF_SIGPENDING 標志位
     recalc_sigpending();
     spin_unlock_irq(&current->sighand->siglock);
   }
   return ret;
}

首先,也要恢復被中斷的系統調用.然後,再調用setup_frame()或者是setup_rt_frame().setup_rt_frame()是有跟實時信號有關的.在這裡以setup_frame()為例進行分析.代碼如下:

static int setup_frame(int sig, struct k_sigaction *ka,
         sigset_t *set, struct pt_regs * regs)
{
   void __user *restorer;
   struct sigframe __user *frame;
   int err = 0;
   int usig;
   //取得在用戶空間棧中存放frame的位置
   frame = get_sigframe(ka, regs, sizeof(*frame));
   //檢查是否是可寫的
   if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
     goto give_sigsegv;
   //在有的執行域裡.信號的數值可能不一樣,因此,需要轉換一下
   //signal_invmap:信號轉換表
   usig = current_thread_info()->exec_domain
     && current_thread_info()->exec_domain->signal_invmap
     && sig < 32
     ? current_thread_info()->exec_domain->signal_invmap[sig]
     : sig;
   err = __put_user(usig, &frame->sig);
   if (err)
     goto give_sigsegv;
   //保存當前內核棧裡保存的用戶空間的硬件環境
   err = setup_sigcontext(&frame->sc, &frame->fpstate, regs, set->sig[0]);
   if (err)
     goto give_sigsegv;
   //set:這裡是表示進程以前的信號掩碼
   //在extramask裡保存以前的信號掩碼
   if (_NSIG_WORDS > 1) {
     //將set->sig的高32位存於extramask中
     err = __copy_to_user(&frame->extramask, &set->sig[1],
             sizeof(frame->extramask));
     if (err)
       goto give_sigsegv;
   }
   if (current->binfmt->hasvdso)
     restorer = (void *)VDSO_SYM(&__kernel_sigreturn);
   else
     restorer = (void *)&frame->retcode;
   if (ka->sa.sa_flags & SA_RESTORER)
     restorer = ka->sa.sa_restorer;
   /* Set up to return from userspace. */
   //使frame->pretcode指向 frame->retcode
   err |= __put_user(restorer, &frame->pretcode);
    
   /*
   * This is popl %eax ; movl $,%eax ; int $0x80
   *
   * WE DO NOT USE IT ANY MORE! It's only left here for historical
   * reasons and because gdb uses it as a signature to notice
   * signal handler stack frames.
   */
   //frame->retcode:執行完信號處理函數後的下一條指令
   //這裡構建了一次系統調用.調用號是__NR_sigreturn
   err |= __put_user(0xb858, (short __user *)(frame->retcode+0));
   err |= __put_user(__NR_sigreturn, (int __user *)(frame->retcode+2));
   err |= __put_user(0x80cd, (short __user *)(frame->retcode+6));
   if (err)
     goto give_sigsegv;
   /* Set up registers for signal handler */
   //因為regs結構已經保存在frame之中了.這裡可以隨意的修改
   //修改用戶空間的棧指針位置,指向frame
   regs->esp = (unsigned long) frame;
   //返回到用戶空間的下一條指令
   //即返回到用戶空間後,執行信號處理程序
   regs->eip = (unsigned long) ka->sa.sa_handler;
   regs->eax = (unsigned long) sig;
   regs->edx = (unsigned long) 0;
   regs->ecx = (unsigned long) 0;
   //用戶空間的段寄存器都是__USER_DS
   //這裡是為了防止有意外的修改
   regs->xds = __USER_DS;
   regs->xes = __USER_DS;
   regs->xss = __USER_DS;
   regs->xcs = __USER_CS;
   /*
   * Clear TF when entering the signal handler, but
   * notify any tracer that was single-stepping it.
   * The tracer may want to single-step inside the
   * handler too.
   */
   //清除跟蹤標志
   //就像是處理中斷處理程序,清除中斷標志位一樣
   regs->eflags &= ~TF_MASK;
   if (test_thread_flag(TIF_SINGLESTEP))
     ptrace_notify(SIGTRAP);
#if DEBUG_SIG
   printk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p\n",
     current->comm, current->pid, frame, regs->eip, frame->pretcode);
#endif
   return 0;
give_sigsegv:
   force_sigsegv(sig, current);
   return -EFAULT;
}

get_sigframe()代碼如下:

static inline void __user *
get_sigframe(struct k_sigaction *ka, struct pt_regs * regs, size_t frame_size)
{
   unsigned long esp;
   /* Default to using normal stack */
   esp = regs->esp;
   /* This is the X/Open sanctioned signal stack switching. */
   //用戶指定了棧位置
   //sas__ss_flags:判斷指定的棧位置是否為於當前棧的下部有效空間
   //進程的地址空間中,棧空間占據著最上部
   if (ka->sa.sa_flags & SA_ONSTACK) {
     if (sas_ss_flags(esp) == 0)
       //獲得棧頂位置
       esp = current->sas_ss_sp + current->sas_ss_size;
   }
   /* This is the legacy signal stack switching. */
   //從Unix中遺留的調用.為了保持兼容性而設置
   //不提 倡使用
   else if ((regs->xss & 0xffff) != __USER_DS &&
     !(ka->sa.sa_flags & SA_RESTORER) &&
     ka->sa.sa_restorer) {
     esp = (unsigned long) ka->sa.sa_restorer;
   }
   //為frame結構空出位置
   esp -= frame_size;
   /* Align the stack pointer according to the i386 ABI,
   * i.e. so that on function entry ((sp + 4) & 15) == 0. */
   //按照i386 ABI規范.對齊棧指針
   esp = ((esp + 4) & -16ul) - 4;
   return (void __user *) esp;
}
setup_sigcontext()代碼如下:
static int
setup_sigcontext(struct sigcontext __user *sc, struct _fpstate __user *fpstate,
     struct pt_regs *regs, unsigned long mask)
{
   int tmp, err = 0;
   //保存regs
   err |= __put_user(regs->xfs, (unsigned int __user *)&sc->fs);
   savesegment(gs, tmp);
   err |= __put_user(tmp, (unsigned int __user *)&sc->gs);
   err |= __put_user(regs->xes, (unsigned int __user *)&sc->es);
   err |= __put_user(regs->xds, (unsigned int __user *)&sc->ds);
   err |= __put_user(regs->edi, &sc->edi);
   err |= __put_user(regs->esi, &sc->esi);
   err |= __put_user(regs->ebp, &sc->ebp);
   err |= __put_user(regs->esp, &sc->esp);
   err |= __put_user(regs->ebx, &sc->ebx);
   err |= __put_user(regs->edx, &sc->edx);
   err |= __put_user(regs->ecx, &sc->ecx);
   err |= __put_user(regs->eax, &sc->eax);
   err |= __put_user(current->thread.trap_no, &sc->trapno);
   err |= __put_user(current->thread.error_code, &sc->err);
   err |= __put_user(regs->eip, &sc->eip);
   err |= __put_user(regs->xcs, (unsigned int __user *)&sc->cs);
   err |= __put_user(regs->eflags, &sc->eflags);
   err |= __put_user(regs->esp, &sc->esp_at_signal);
   err |= __put_user(regs->xss, (unsigned int __user *)&sc->ss);
   //保存FPU,XMM.MXX等信息
   tmp = save_i387(fpstate);
   if (tmp < 0)
    err = 1;
   else
    err |= __put_user(tmp ? fpstate : NULL, &sc->fpstate);
   /* non-iBCS2 extensions.. */
   //mask:即為set->sig 的低32位
   err |= __put_user(mask, &sc->oldmask);
   err |= __put_user(current->thread.cr2, &sc->cr2);
   return err;
}

用下圖表示上述的操作:

注意到代碼中有以下兩條指令:

regs->esp = (unsigned long) frame;

regs->eip = (unsigned long) ka->sa.sa_handler;

第一條把用戶的棧指令指向了frame

第二條把返回用戶空間的eip設為了信號的處理函數.

這樣返回到用戶空間後就會執行ka->sa.sa_handler這個函數.注意到上面的堆棧結構,其實它模擬了一次函數調用.函數調用時,先把參數壓棧,再把返回地址壓棧.在上面的棧中,函數的參數為sig.返回地址為pretcode.這樣,在信號處理函數返回之後.就會把pretcode裝入eip.而pretcode又是指向retcode.也就是說函數返回之後,會運行retcode對應的指令.

Retcode在上面的代碼中是這樣被設置的:

   err |= __put_user(0xb858, (short __user *)(frame->retcode+0));
   err |= __put_user(__NR_sigreturn, (int __user *)(frame->retcode+2));
   err |= __put_user(0x80cd, (short __user *)(frame->retcode+6));

代碼中的0xb858 0x80cd可能對應的就是指令的機器碼.它相應於如下指令:

popl %eax ;
movl $,%eax ;
int $0x80
即會產生一個系統調用號為__NR_sigreturn的系統調用.它對應的入口是:

asmlinkage int sys_sigreturn(unsigned long __unused)
{
   //第一個參數地址就是棧指針位置
   struct pt_regs *regs = (struct pt_regs *) &__unused;
   //esp-8是因為在用戶空間運行的時候,棧出了兩個單元
   //即上圖中的pretcode出棧.sig出棧
   struct sigframe __user *frame = (struct sigframe __user *)(regs->esp - 8);
   sigset_t set;
   int eax;
   //檢查對應區域是否可讀
   if (!access_ok(VERIFY_READ, frame, sizeof(*frame)))
     goto badframe;
   //從frame->sc.oldmask 恢復set.sig的低32 位
   if (__get_user(set.sig[0], &frame->sc.oldmask)
     || (_NSIG_WORDS > 1
     //從frame->extramask 中恢復set.sig的高32位
     && __copy_from_user(&set.sig[1],frame->extramask,
            sizeof(frame->extramask))))
     goto badframe;
   sigdelsetmask(&set, ~_BLOCKABLE);
   spin_lock_irq(&current->sighand->siglock);
   current->blocked = set;
   //重新判斷是否還有末處理的信號
   recalc_sigpending();
   spin_unlock_irq(&current->sighand->siglock);
   //從frame->sc中恢復系統調用前的硬件環境
   if (restore_sigcontext(regs, &frame->sc, &eax))
     goto badframe;
   return eax;
badframe:
   if (show_unhandled_signals && printk_ratelimit())
     printk("%s%s[%d] bad frame in sigreturn frame:%p eip:%lx"
         " esp:%lx oeax:%lx\n",
       task_pid_nr(current) > 1 ? KERN_INFO : KERN_EMERG,
       current->comm, task_pid_nr(current), frame, regs->eip,
       regs->esp, regs->orig_eax);
   force_sig(SIGSEGV, current);
   return 0;
}
至此,內核棧又回復到以前的樣子了.

五:小結

本節中,在Linux內核中跟蹤了信號處理函數的設置,信號的發送.信號的處理.涉及到的代碼都不是很難理解.在理解了用戶自定義的信號函數的運行機制之後,我們也很容易調用用戶空間的一個特定操作.另外,雖然內核涉及到的信號處理比較簡單,但要在用戶空間使用好信號就要看一個人的程序設計功底了.

Copyright © Linux教程網 All Rights Reserved