【进程间通信】信号

来源:互联网 发布:unix时间戳转换 mysql 编辑:程序博客网 时间:2024/04/24 13:26

          信号(signal)机制是在软件层次上对中断机制的一种模拟;本节研究信号的具体实现;


信号与中断

(1)从概念上说,一个进程收到一个信号与一个处理器接收到一个中断请求是一样的;一个进程可以向另一个(或另一组)进程发送信号,也跟在多处理器中一个处理器可以向其他处理器发送中断请求一样,中断源可以是本处理器本身,也可能是各种外部设备;

(2)信号,也不一定来自其他进程,还可以来自本进程;

(3)处理器在执行一段程序并不需要停下来等待中断的发生,同时也不知道中断何时发生;信号也是一样,一个进程并不需要通过一个什么操作来等待信号的到达,也不知道什么时候就有信号到达

(4)在所有的进程间通信机制中只有信号是异步的;中断和信号二者之间的这种相似和类比不仅仅是概念上的,也体现在它们的实现上,就像中断机制中有一个中断向量表一样,在每一个进程的task_struct结构中都有一个指针sig,指向一个signal_struct结构,即为信号向量表;在中断机制中,对每个中断请求都可以加以屏蔽而不让处理器对之响应,在信号机制中也有类似的手段,但是信号总归是软件上来实现的,中断是软硬件结合实现的


信号基本知识

(1)早期的信号机制比较简单,是不可靠的;每一个进程的task_struct中有一个指针sig,指向一个signal_struct(action[]是一个信号向量表,数组中每一个元素就相当于一个信号向量,确定了当进程接收到一个具体的信号时应该采取的行动,不过它还有SIG_DFL和SIG_IGN分别是默认处理和忽略处理,但是信号向量表中处理程序一般是在用户空间的);

(2)对信号的检测和响应总是发生在系统空间,当前进程处于系统调用,中断或异常而进入系统空间以后,从系统空间返回用户空间前夕;也可以是当前进程在内核中进入睡眠以后刚被唤醒的时候,由于信号的存在而提前返回到用户空间;当有信号要响应时,处理器会提前从系统空间返回到用户空间执行信号处理程序(建立一个上下文,并在用户栈上执行该信号处理程序),然后再继续陷入到系统空间

(3)task_struct中有两个位图sigset_t,即sigpending类型pending和blocked,分别用来模拟中断控制器的中断请求寄存器以及中断屏蔽寄存器;信号准备一个队列,每产生一个

信号就把它挂入这个队列中,在sigpending中(signal和队列);sas_ss_sp记录当前进程在用户空间执行信号处理程序时的堆栈位置,另一个是sas_ss_size,那就是堆栈大小;

/* signal handlers *///信号处理程序struct signal_struct *signal;struct sighand_struct *sighand;//管理设置的信号处理程序sigset_t blocked, real_blocked;//所有的阻塞信号,sigset_t是一个位掩码sigset_t saved_sigmask;/* restored if set_restore_sigmask() was used */struct sigpending pending;//所有已经引发,仍然有内核处理的信号//在专门用于信号处理的栈上运行处理程序unsigned long sas_ss_sp;//地址size_t sas_ss_size;//长度

struct sigpending {struct list_head list;//所有待解决的信号的链表sigset_t signal;//仍然有待处理程序的所有信号的编号};


安装信号

(1)早期的信号向量表中,每一个向量都是函数指针,如今是一个k_sigaction,其中_sa_handler和_sa_sigaction仍是函数指针,sa_mask是一个位图,其中每一位对应着一种信号,如果位图中的某一位为0, 意味着该信号暂时被屏蔽了;不管位图中相应位是否为1,当前处理的信号总是自动屏蔽的,使得对同一中信号的处理不会嵌套发生,在这里的屏蔽并不是将信号丢弃,已经到达的信号仍旧存在;除非sa_mask中的SA_NODEFER |SA_NOMASK标志位1(表示使用传统的不可靠信号机制),当我么按下ctrl+C时,会使当前运行的程序流产,内核会向相应的进程发一个SIGINT,而这个信号默认处理就是do_exit();sa_mask值sigset类型,模拟中断控制器中的中断请求寄存器,以及中断屏蔽寄存器;而sa_flags标识,其中SA_SIGINFO表示信号处理程序有三个参数,其中一个指向siginfo_t数据结构的指针;

struct sighand_struct {atomic_tcount;//共享该实例的进程数目struct k_sigactionaction[_NSIG];//设置信号处理程序spinlock_tsiglock;wait_queue_head_tsignalfd_wqh;};


struct old_sigaction {__sighandler_t sa_handler;old_sigset_t sa_mask;unsigned long sa_flags;__sigrestore_t sa_restorer;};struct sigaction {__sighandler_t sa_handler;//指向内核在信号到达时调用的处理程序unsigned long sa_flags;//用于指定信号处理方式的一些约束__sigrestore_t sa_restorer;sigset_t sa_mask;/* mask last for extensibility *///位掩码,每一个bit位对应系统中的一个信号;阻塞其他信号};struct k_sigaction {struct sigaction sa;};

(2)在sigaction()中,signum是信号编号,而newact表示新的,待设置的向量sigaction,oldaction用来返回老向量的数据结构;在signal()中只有信号编号和对应的信号处理程序;

       int sigaction(int signum, const struct sigaction *act,                     struct sigaction *oldact);

首先是在sys_signal()中,是按传统来设置的,在k_signaction(new_sa设置成SA_ONESHOT一次性,使用完了就设置成SIG_DFL和SA_NOMSK不使用信号屏蔽);在

sys_rt_sigaction,首先使用new_sa从用户空间复制act;而在sys_sigaction中,由于成分次序不同,只好将各个成分依次复制过来了;这三个函数最终都熬调用do_sigacion();

(3)在do_sigaction()中,系统对信号SIGKILL和SIGSTOP的响应是不允许改变的,所以要放在开头时加以检查;同时这两个信号也是不允许屏蔽的,所以要在k->sa.sa_mask

将这两个信号对应的屏蔽位清除;同时要使用oact保存原始的k_sigaction,并设置新的k_sigactions;当新设置的向量为SIG_IGN或SIG_DFL,但是涉及的信号SIGCONT,

SIGCHLD和SIGWINCH之一时,如果已经有一个或这样的信号等待处理,则posix要求将这些已达到的信号丢弃,所以通过rm_sig_from_queue()将已经到达的信号丢弃;然后还

要将等到处理的总标志sigpending重新算一下,具体代码如下:

int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact){struct task_struct *t = current;struct k_sigaction *k;sigset_t mask;if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig)))return -EINVAL;k = &t->sighand->action[sig-1];     //找到对应的信号spin_lock_irq(¤t->sighand->siglock);if (oact)*oact = *k;if (act) {sigdelsetmask(&act->sa.sa_mask,//对SIGKILL和SIGSTOP移除屏蔽位,不允许改变      sigmask(SIGKILL) | sigmask(SIGSTOP));*k = *act;/* * POSIX 3.3.1.3: *  "Setting a signal action to SIG_IGN for a signal that is *   pending shall cause the pending signal to be discarded, *   whether or not it is blocked." * *  "Setting a signal action to SIG_DFL for a signal that is *   pending and whose default action is to ignore the signal *   (for example, SIGCHLD), shall cause the pending signal to *   be discarded, whether or not it is blocked" */if (sig_handler_ignored(sig_handler(t, sig), sig)) {  //当新设置的信号量为SIG_IGN或涉及到SIG_DFL的信号如SIGCONT,SIGCHLD,SIGWINCH时,丢弃若干信号sigemptyset(&mask);sigaddset(&mask, sig);rm_from_queue_full(&mask, &t->signal->shared_pending);do {rm_from_queue_full(&mask, &t->pending);t = next_thread(t);} while (t != current);}}spin_unlock_irq(¤t->sighand->siglock);return 0;}

发送信号

在sys_kill()中,首先准备一个siginfo结构,保存sig信号,设置错误码为0,以及code,本进程号,以及uid号,然后调用kill_something_info(),首先根据pid来判断确定是要将信号发送给一个特定进程,还是进程组,还是所有进程;

static int kill_something_info(int sig, struct siginfo *info, pid_t pid){int ret;if (pid > 0) {   //发给特定进程rcu_read_lock();ret = kill_pid_info(sig, info, find_vpid(pid));rcu_read_unlock();return ret;}read_lock(&tasklist_lock);if (pid != -1) {   //整个进程组 ret = __kill_pgrp_info(sig, info,pid ? find_vpid(-pid) : task_pgrp(current));} else {int retval = 0, count = 0;struct task_struct * p;    //发给系统中所有进程for_each_process(p) {if (task_pid_vnr(p) > 1 &&!same_thread_group(p, current)) {int err = group_send_sig_info(sig, info, p);++count;if (err != -EPERM)retval = err;}}ret = count ? retval : -ESRCH;}read_unlock(&tasklist_lock);return ret;}


发给指定进程

int kill_pid_info(int sig, struct siginfo *info, struct pid *pid){int error = -ESRCH;struct task_struct *p;rcu_read_lock();retry:p = pid_task(pid, PIDTYPE_PID);   //找到进程if (p) {error = group_send_sig_info(sig, info, p);if (unlikely(error == -ESRCH))/* * The task was unhashed in between, try again. * If it is dead, pid_task() will return NULL, * if we race with de_thread() it will find the * new leader. */goto retry;}rcu_read_unlock();return error;}


group_send_sig_info()->do_send_sig_info()->send_signal()->__send_signal()

if (sig < SIGRTMIN)   //老的编号override_rlimit = (is_si_special(info) || info->si_code >= 0);elseoverride_rlimit = 0;q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,override_rlimit);if (q) {list_add_tail(&q->list, &pending->list);    //放入,对于老编号,只会放入一次switch ((unsigned long) info) {case (unsigned long) SEND_SIG_NOINFO:q->info.si_signo = sig;q->info.si_errno = 0;q->info.si_code = SI_USER;q->info.si_pid = task_tgid_nr_ns(current,task_active_pid_ns(t));q->info.si_uid = current_uid();break;case (unsigned long) SEND_SIG_PRIV:q->info.si_signo = sig;q->info.si_errno = 0;q->info.si_code = SI_KERNEL;q->info.si_pid = 0;q->info.si_uid = 0;break;default:copy_siginfo(&q->info, info);if (from_ancestor_ns)q->info.si_pid = 0;break;}} else if (!is_si_special(info)) {if (sig >= SIGRTMIN && info->si_code != SI_USER) {/* * Queue overflow, abort.  We may abort if the * signal was rt and sent by user using something * other than kill(). */trace_signal_overflow_fail(sig, group, info);return -EAGAIN;} else {/* * This is a silent loss of information.  We still * send the signal, but the *info bits are lost. */trace_signal_lose_info(sig, group, info);}}

执行信号处理程序

在中断机制中,处理器的硬件在每条指令结束时都要检测中断是否有发生;而信号是在系统调用,中断处理,异常处理返回用户空间前夕,还有就是进程从睡眠中唤醒(一定是在系统调用中);用户空间的进程,即使信号到了也不会立即引起对信号的反应,只有该进程进入内核,然后返回用户空间时才有对信号的翻新;对于信号来自异常处理程序时,由于进程已经在内核中进行,在返回用户空间时就会反应了;


work_pending:      testb $_TIF_NEED_RESCHED, %cl      jz work_notifysig   //处理信号  work_resched:      call schedule       //调度      LOCKDEP_SYS_EXIT      DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt                      # setting need_resched or sigpending                      # between sampling and the iret      TRACE_IRQS_OFF      movl TI_flags(%ebp), %ecx      andl $_TIF_WORK_MASK, %ecx  # is there any work to be done other                      # than syscall tracing?      jz restore_all      testb $_TIF_NEED_RESCHED, %cl      jnz work_resched     //处理信号  work_notifysig:             # deal with pending signals and                      # notify-resume requests  #ifdef CONFIG_VM86      testl $X86_EFLAGS_VM, PT_EFLAGS(%esp)      movl %esp, %eax      jne work_notifysig_v86      # returning to kernel-space or                      # vm86-space      xorl %edx, %edx      call do_notify_resume      jmp resume_userspace_sig 


do_notify_resume()->do_signal

signr = get_signal_to_deliver(&info, &ka, regs, NULL);  //获得本进程要处理的信号if (signr > 0) {/* Whee! Actually deliver the signal.  */if (handle_signal(signr, &info, &ka, oldset, regs) == 0) {  //具体处理/* * A signal was successfully delivered; the saved * sigmask will have been stored in the signal frame, * and will be restored by sigreturn, so we can simply * clear the TS_RESTORE_SIGMASK flag. */current_thread_info()->status &= ~TS_RESTORE_SIGMASK;}return;}

static inthandle_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 (syscall_get_nr(current, regs) >= 0) {/* If so, check system call restarting.. */switch (syscall_get_error(current, regs)) {case -ERESTART_RESTARTBLOCK:case -ERESTARTNOHAND:regs->ax = -EINTR;break;case -ERESTARTSYS:if (!(ka->sa.sa_flags & SA_RESTART)) {regs->ax = -EINTR;break;}/* fallthrough */case -ERESTARTNOINTR:regs->ax = regs->orig_ax;regs->ip -= 2;break;}}/* * If TF is set due to a debugger (TIF_FORCED_TF), clear the TF * flag so that register information in the sigcontext is correct. */if (unlikely(regs->flags & X86_EFLAGS_TF) &&    likely(test_and_clear_thread_flag(TIF_FORCED_TF)))regs->flags &= ~X86_EFLAGS_TF;  //由于信号处理程序是在用户空间执行的ret = setup_rt_frame(sig, ka, info, oldset, regs);if (ret)return ret;


setup_rt_frame主要流程如下:

(1)在用户空间堆栈中为信号处理程序的执行预先创建一个frame,frame中包含局部变量的数据结构,并把系统空间堆栈中的原始框架保存到这个数据结构中

(2)由内核在信号处理程序中插入对系统调用sigreturn();

(3)将系统空间堆栈中原始frame修改成执行信号处理程序的frame;

(4)返回到用户空间,执行信号处理程序;

(5)信号处理程序执行完毕后,通过sireturn()返回系统空间;

(6)在系统空间sigreturn()中,从用户空间恢复原始frame

(7)再返回到用户空间,继续执行原先的用户程序;

0 0
原创粉丝点击