码迷,mamicode.com
首页 > 系统相关 > 详细

Linux内核源代码情景分析-信号

时间:2015-04-21 09:37:03      阅读:231      评论:0      收藏:0      [点我收藏+]

标签:

    一、我们先来看下信号的所设计的数据结构:

struct task_struct {
	int sigpending;
	
	int exit_code, exit_signal;
		/* Protects signal and blocked */
	struct signal_struct *sig;

	sigset_t blocked;
	struct sigpending pending;

	
}
    sigpending,是个整数,用来表示这个进程是否有信号在等待处理。

    blocked表示信号遮蔽位,如果此位置1,说明不相应这个信号。

    pending结构如下:

struct sigpending {
	struct sigqueue *head, **tail;
	sigset_t signal;
};
    blocked和sigpending里面的signal都是64位的字段,sigset_t如下:

#define _NSIG		64
#define _NSIG_BPW	32
#define _NSIG_WORDS	(_NSIG / _NSIG_BPW)

typedef unsigned long old_sigset_t;		/* at least 32 bits */

typedef struct {
	unsigned long sig[_NSIG_WORDS];//两个32位就是64位
} sigset_t;
    sigpending里面的signal和blocked,分别用来模拟中断控制器硬件中的中断状态寄存器(有啥中断待处理)和中断屏蔽寄存器(哪些中断不能处理)。

    每当有一个中断请求到来时,中断状态寄存器中与之相应的某一位就被置为1,表示有相应的中断请求在等待处理,如果连续有两次中断请求到来,则有可能因为处理器来不及响应而被"合并"。

    可是信号机制中就不同了,信号机制为信号准备一个队列,每产生一个信号就把它挂入这个队列,这样就可以确保一个信号也不会丢失了。所以在sigpending结构里面会有sigqueue结构。

    sigpengding结构里面的sigqueue结构如下:

struct sigqueue {
	struct sigqueue *next;
	siginfo_t info;
};
    siginfo_t如下面所讲。

    发送信号,不但要将sigpengding结构里面的sig结构对应位置1,还要将分配一个sigqueue结构(里面siginfo_t结构si_signo代表哪个信号)链入到sigpengding结构里面的sigqueue。这样就不会丢失信号。只有sigpengding结构里面的sigqueue队列中对应信号所有的sigqueue结构全部移除后,sigpengding结构里面的sig结构对应位才置0。


    下面我们解释signal_struct数据结构:

struct signal_struct {
	atomic_t		count;
	struct k_sigaction	action[_NSIG];//64,信号处理函数一共64个,这就是为什么blocked和sigpending里面的signal是64位
	spinlock_t		siglock;
};
struct k_sigaction {
	struct sigaction sa;
};
struct sigaction {
	union {
	  __sighandler_t _sa_handler;
	  void (*_sa_sigaction)(int, struct siginfo *, void *);
	} _u;
	sigset_t sa_mask;
	unsigned long sa_flags;
	void (*sa_restorer)(void);	
};
    sigaction数据结构是信号处理函数。其中siginfo结构如下:

typedef struct siginfo {
	int si_signo;
	int si_errno;
	int si_code;

	union {
		int _pad[SI_PAD_SIZE];

		/* kill() */
		struct {
			pid_t _pid;		/* sender‘s pid */
			uid_t _uid;		/* sender‘s uid */
		} _kill;

		/* POSIX.1b timers */
		struct {
			unsigned int _timer1;
			unsigned int _timer2;
		} _timer;

		/* POSIX.1b signals */
		struct {
			pid_t _pid;		/* sender‘s pid */
			uid_t _uid;		/* sender‘s uid */
			sigval_t _sigval;
		} _rt;

		/* SIGCHLD */
		struct {
			pid_t _pid;		/* which child */
			uid_t _uid;		/* sender‘s uid */
			int _status;		/* exit code */
			clock_t _utime;
			clock_t _stime;
		} _sigchld;

		/* SIGILL, SIGFPE, SIGSEGV, SIGBUS */
		struct {
			void *_addr; /* faulting insn/memory ref. */
		} _sigfault;

		/* SIGPOLL */
		struct {
			int _band;	/* POLL_IN, POLL_OUT, POLL_MSG */
			int _fd;
		} _sigpoll;
	} _sifields;
}siginfo_t


    二、先看"信号向量",也就是信号处理程序的"安装",其作用类似于中断向量的设置。Linux为此提供了,sigaction系统调用。

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

asmlinkage unsigned long
sys_signal(int sig, __sighandler_t handler)
{
	struct k_sigaction new_sa, old_sa;
	int ret;

	new_sa.sa.sa_handler = handler;
	new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;

	ret = do_sigaction(sig, &new_sa, &old_sa);

	return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
}
int
do_sigaction(int sig, const struct k_sigaction *act, struct k_sigaction *oact)
{
	struct k_sigaction *k;

	if (sig < 1 || sig > _NSIG ||
	    (act && (sig == SIGKILL || sig == SIGSTOP)))//系统对信号SIGKILL和SIGSTOP的响应是不允许改变的
		return -EINVAL;

	k = &current->sig->action[sig-1];

	spin_lock(&current->sig->siglock);

	if (oact)
		*oact = *k;//返回原来的k_sigaction结构

	if (act) {
		*k = *act;//现在的k_sigaction结构赋值给current->sig->action[sig-1]
		sigdelsetmask(&k->sa.sa_mask, sigmask(SIGKILL) | sigmask(SIGSTOP));//SIGKILL和SIGSTOP相应屏蔽位也在每次设置"信号向量"时自动清0

		/*
		 * 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"
		 *
		 * Note the silly behaviour of SIGCHLD: SIG_IGN means that the
		 * signal isn‘t actually ignored, but does automatic child
		 * reaping, while SIG_DFL is explicitly said by POSIX to force
		 * the signal to be ignored.
		 */

		if (k->sa.sa_handler == SIG_IGN
		    || (k->sa.sa_handler == SIG_DFL
			&& (sig == SIGCONT ||
			    sig == SIGCHLD ||
			    sig == SIGWINCH))) {//新设置的向量为SIG_IGN时,或者为SIG_DFL而涉及的信号为SIGCONT、SIGCHLD和SIGWINCH之一时,如果已经有一个或几个这样的信号在等待处理,那么将这些已到达的信号丢弃
			spin_lock_irq(&current->sigmask_lock);
			if (rm_sig_from_queue(sig, current))//丢弃已经到达的信号
				recalc_sigpending(current);
			spin_unlock_irq(&current->sigmask_lock);
		}
	}

	spin_unlock(&current->sig->siglock);
	return 0;
}

    rm_from_queue丢弃已到达的信号:

static int rm_from_queue(int sig, struct sigpending *s)
{
	struct sigqueue *q, **pp;

	if (!sigismember(&s->signal, sig))
		return 0;

	sigdelset(&s->signal, sig);//signal对应的位置0

	pp = &s->head;

	while ((q = *pp) != NULL) {
		if (q->info.si_signo == sig) {//只移除sig对应的信号等待队列
			if ((*pp = q->next) == NULL)
				s->tail = pp;
			kmem_cache_free(sigqueue_cachep,q);
			atomic_dec(&nr_queued_signals);
			continue;
		}
		pp = &q->next;
	}
	return 1;
}

    三、“信号向量”设置好了,就作好了接收和处理信号的准备,下一步就要看怎样向一个进程发送信号了。

    发送信号的系统调用也有新旧不同的版本。老的版本是kill():

    int kill(pid_t pid,int sig);

    参数pid为目标进程的pid,当pid为0时,表示发送给当前进程所在进程组中所有的进程,为-1时则发送给系统中的所有进程。

    新的版本为sigqueue():

    int sigqueue(pid_t pid,int sig,const union sigval val);

    与kill()不同的是,sigqueue()发送的除信号sig本身外还有附加的信息,就是val。此外,sigqueue()只能将信号发送给一个特定的进程,而不像kill()那样可以通过将参数pid设成0来发送给整个进程组。参数val是一个union,它可以是一个长整数,但实际上总是一个指向siginfo数据结构的指针。

    系统调用kill()在内核中的主体是sys_kill(),代码如下:

asmlinkage long
sys_kill(int pid, int sig)
{
	struct siginfo info;

	info.si_signo = sig;
	info.si_errno = 0;
	info.si_code = SI_USER;
	info.si_pid = current->pid;
	info.si_uid = current->uid;

	return kill_something_info(sig, &info, pid);
}
static int kill_something_info(int sig, struct siginfo *info, int pid)
{
	if (!pid) {
		return kill_pg_info(sig, info, current->pgrp);//表示发送给当前进程所在进程组中所有的进程
	} else if (pid == -1) {
		int retval = 0, count = 0;
		struct task_struct * p;

		read_lock(&tasklist_lock);
		for_each_task(p) {
			if (p->pid > 1 && p != current) {
				int err = send_sig_info(sig, info, p);//-1时则发送给系统中的所有进程
				++count;
				if (err != -EPERM)
					retval = err;
			}
		}
		read_unlock(&tasklist_lock);
		return count ? retval : -ESRCH;
	} else if (pid < 0) {
		return kill_pg_info(sig, info, -pid);
	} else {
		return kill_proc_info(sig, info, pid);//发送个具体的进程
	}
}
    系统调用sigqueue()只允许将信号发送给一个特定的进程,并且随同信号发送的siginfo结构也是由用户进程自己设置的,所以它在内核中的实现sys_rt_sigqueue就要简单的多,代码如下:

asmlinkage long
sys_rt_sigqueueinfo(int pid, int sig, siginfo_t *uinfo)
{
	siginfo_t info;

	if (copy_from_user(&info, uinfo, sizeof(siginfo_t)))//把siginfo数据结构从用户空间拷贝到内核中
		return -EFAULT;

	/* Not even root can pretend to send signals from the kernel.
	   Nor can they impersonate a kill(), which adds source info.  */
	if (info.si_code >= 0)
		return -EPERM;
	info.si_signo = sig;

	/* POSIX.1b doesn‘t mention process groups.  */
	return kill_proc_info(sig, &info, pid);//最后还是调用kill_proc_info
}
    两个系统调用中,最后都最终调用了kill_proc_info,代码如下:
inline int
kill_proc_info(int sig, struct siginfo *info, pid_t pid)
{
	int error;
	struct task_struct *p;

	read_lock(&tasklist_lock);
	p = find_task_by_pid(pid);//根据pid找到对应的task_struct结构
	error = -ESRCH;
	if (p)
		error = send_sig_info(sig, info, p);//将信号发送给他们
	read_unlock(&tasklist_lock);
	return error;
}
    最后两个系统调用本质上调用的是send_sig_info,代码如下:

int
send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
	unsigned long flags;
	int ret;


#if DEBUG_SIG
printk("SIG queue (%s:%d): %d ", t->comm, t->pid, sig);
#endif

	ret = -EINVAL;
	if (sig < 0 || sig > _NSIG)
		goto out_nolock;
	/* The somewhat baroque permissions check... */
	ret = -EPERM;
	if (bad_signal(sig, info, t))
		goto out_nolock;

	/* The null signal is a permissions and process existance probe.
	   No signal is actually delivered.  Same goes for zombies. */
	ret = 0;
	if (!sig || !t->sig)
		goto out_nolock;

	spin_lock_irqsave(&t->sigmask_lock, flags);
	handle_stop_signal(sig, t);

	/* Optimize away the signal, if it‘s a signal that can be
	   handled immediately (ie non-blocked and untraced) and
	   that is ignored (either explicitly or by default).  */

	if (ignored_signal(sig, t))
		goto out;

	/* Support queueing exactly one non-rt signal, so that we
	   can get more detailed information about the cause of
	   the signal. */
	if (sig < SIGRTMIN && sigismember(&t->pending.signal, sig))//SIGRTMIN为32,对于"老编制"的信号,所谓"投递"本来是很简单的,因为那只是将目标进程的"接收信号位图"signal中相应的标志位设置成1,也将sigqueue结构挂入队列中,不过只挂入一次。对于新编制,可以将sigqueue结构挂入到队列很多次。
		goto out;

	ret = deliver_signal(sig, info, t);//要将sigpengding结构里面的sig结构对应位置1,还要将分配一个sigqueue结构(里面siginfo_t结构si_signo代表哪个信号)链入到sigpengding结构里面的sigqueue
out:
	spin_unlock_irqrestore(&t->sigmask_lock, flags);
	if ((t->state & TASK_INTERRUPTIBLE) && signal_pending(t))
		wake_up_process(t);//刚刚其实已经唤醒了
out_nolock:
#if DEBUG_SIG
printk(" %d -> %d\n", signal_pending(t), ret);
#endif

	return ret;
}
    deliver_signal,要将sigpengding结构里面的sig结构对应位置1,还要将分配一个sigqueue结构(里面siginfo_t结构si_signo代表哪个信号)链入到sigpengding结构里面的sigqueue,代码如下:

static int deliver_signal(int sig, struct siginfo *info, struct task_struct *t)
{
	int retval = send_signal(sig, info, &t->pending);

	if (!retval && !sigismember(&t->blocked, sig))//如果目标进程正在睡眠中,并且没有遮蔽所投递的信号,就要将其唤醒并立即进行调度
		signal_wake_up(t);

	return retval;
}

static int send_signal(int sig, struct siginfo *info, struct sigpending *signals)
{
	struct sigqueue * q = NULL;

	/* Real-time signals must be queued if sent by sigqueue, or
	   some other real-time mechanism.  It is implementation
	   defined whether kill() does so.  We attempt to do so, on
	   the principle of least surprise, but since kill is not
	   allowed to fail with EAGAIN when low on memory we just
	   make sure at least one signal gets delivered and don‘t
	   pass on the info struct.  */

	if (atomic_read(&nr_queued_signals) < max_queued_signals) {
		q = kmem_cache_alloc(sigqueue_cachep, GFP_ATOMIC);
	}

	if (q) {
		atomic_inc(&nr_queued_signals);
		q->next = NULL;
		*signals->tail = q;//新分配的sigqueue结构链入到sigpengding结构里面的sigqueue
		signals->tail = &q->next;
		switch ((unsigned long) info) {
			case 0:
				q->info.si_signo = sig;
				q->info.si_errno = 0;
				q->info.si_code = SI_USER;
				q->info.si_pid = current->pid;
				q->info.si_uid = current->uid;
				break;
			case 1:
				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);//q->info来源于info,里面的成员si_signo代表了哪个信号
				break;
		}
	} else if (sig >= SIGRTMIN && info && (unsigned long)info != 1
		   && 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().
		 */
		return -EAGAIN;
	}

	sigaddset(&signals->signal, sig);//sig结构对应位置1
	return 0;
}
static inline void signal_wake_up(struct task_struct *t)
{
	t->sigpending = 1;

	if (t->state & TASK_INTERRUPTIBLE) {
		wake_up_process(t);
		return;
	}
        ......
}
    wake_up_process:将目标进程唤醒以后,如果目标进程的优先级别高于当前进程,那么在当前进程从系统调用返回之际就有可能进行一次调度,而目标进程是否能被调度运行,则取决于其优先级别以及其它各种因素。


    并非所有的信号都是由某个进程在用户空间通过系统调用发送的。在页面异常处理程序do_page_fault()里,当页面异常无法恢复时就会通过force_sig()向当前进程发送一个SIGBUS信号,代码如下:

void
force_sig(int sig, struct task_struct *p)
{
	force_sig_info(sig, (void*)1L, p);
}
int
force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
{
	unsigned long int flags;

	spin_lock_irqsave(&t->sigmask_lock, flags);
	if (t->sig == NULL) {
		spin_unlock_irqrestore(&t->sigmask_lock, flags);
		return -ESRCH;
	}

	if (t->sig->action[sig-1].sa.sa_handler == SIG_IGN)
		t->sig->action[sig-1].sa.sa_handler = SIG_DFL;//不允许目标进程忽略该信号
	sigdelset(&t->blocked, sig);//将其遮蔽位也强制清除
	recalc_sigpending(t);
	spin_unlock_irqrestore(&t->sigmask_lock, flags);

	return send_sig_info(sig, info, t);
}


    四、至此,信号的投递已经完成了,接下来就是目标进程如何发现信号的到来以及如何对此作出反应了。

    一个进程在什么情况下检测信号的存在呢?首先是每当从系统调用、中断处理或异常处理返回到用户空间的前夕;还有就是当进程被从睡眠中唤醒(必定是在系统调用中)的时候,此时若发现有信号在等待就要提前从系统调用返回。总而言之,不管是正常返回还是提前返回,在返回到用户空间的前夕总是要检测信号的存在并作出反应。

ret_with_reschedule:
	cmpl $0,need_resched(%ebx)
	jne reschedule
	cmpl $0,sigpending(%ebx)//current->sigpending,就是那个long型的数据
	jne signal_return
restore_all:
	RESTORE_ALL

	ALIGN
signal_return:
	sti				# we can get here from an interrupt handler
	testl $(VM_MASK),EFLAGS(%esp)
	movl %esp,%eax
	jne v86_signal_return
	xorl %edx,%edx
	call SYMBOL_NAME(do_signal)
	jmp restore_all
    对信号作出反应的具体操作是通过do_signal()完成的。代码如下:

int do_signal(struct pt_regs *regs, sigset_t *oldset)
{
	siginfo_t info;
	struct k_sigaction *ka;

	/*
	 * We want the common case to go fast, which
	 * is why we may in certain cases get here from
	 * kernel mode. Just return without doing anything
	 * if so.
	 */
	if ((regs->xcs & 3) != 3)//reg->xcs为处理器进入这些程序前代码段寄存器CS的内容。如果处理器是从用户空间进入中断、异常或系统调用,则当时CS的最低两位必定是3,表示用户空间,反过来,如果regs->xcs的最低两位不等于3,本次中断或异常发生于系统空间,所以处理器并不是处于返回到用户空间的前夕,并不需要对信号作出反应。	
		return 1;

	if (!oldset)
		oldset = &current->blocked;//当前进程的遮蔽位

	for (;;) {
		unsigned long signr;

		spin_lock_irq(&current->sigmask_lock);
		signr = dequeue_signal(&current->blocked, &info);//取出一个未加遮蔽的信号加以处理
		spin_unlock_irq(&current->sigmask_lock);

		if (!signr)
			break;

		.....
		ka = &current->sig->action[signr-1];
		if (ka->sa.sa_handler == SIG_IGN) {//"忽略"一般来说对这个信号的处理就完成了,但有一个例外,那就是当信号为SIGCHLD时。这个信号通常是在子进程通过exit()系统调用结束其生命时向父进程发出的,所以此时接收到SIGCHLD信号的进程要调用sys_wait4()来检查其所有的子进程,只要找到一个已经结束生命的子进程就为其"料理后事",这里的第三个参数WNOHANG,表示如果没有发现已经结束生命的子进程也要立即返回,而不作等待。
			if (signr != SIGCHLD)
				continue;
			/* Check for SIGCHLD: it‘s special.  */
			while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0)
				/* nothing */;
			continue;
		}

		if (ka->sa.sa_handler == SIG_DFL) {
			int exit_code = signr;

			/* Init gets no signals it doesn‘t want.  */
			if (current->pid == 1)
				continue;

			switch (signr) {
			case SIGCONT: case SIGCHLD: case SIGWINCH://SIGCONT默认处理方式
				continue;

			case SIGTSTP: case SIGTTIN: case SIGTTOU:
				if (is_orphaned_pgrp(current->pgrp))
					continue;
				/* FALLTHRU */

			case SIGSTOP://SIGTOP默认处理方式
				current->state = TASK_STOPPED;
				current->exit_code = signr;
				if (!(current->p_pptr->sig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP))
					notify_parent(current, SIGCHLD);
				schedule();
				continue;

			case SIGQUIT: case SIGILL: case SIGTRAP:
			case SIGABRT: case SIGFPE: case SIGSEGV:
			case SIGBUS: case SIGSYS: case SIGXCPU: case SIGXFSZ:
				if (do_coredump(signr, regs))
					exit_code |= 0x80;
				/* FALLTHRU */

			default://多数信号(包括SIG_KILL)都会落入default部分,通过do_exit结束进程的运行。对于SIG_KILL来说,一来它的信号向量不允许设置;二来向量中的相应屏蔽位也在每次设置"信号向量"时自动清0。这两点都参见do_sigaction,可见对于目标进程,KILL信号是头号"杀手"
				sigaddset(&current->pending.signal, signr);
				recalc_sigpending(current);
				current->flags |= PF_SIGNALED;
				do_exit(exit_code);
				/* NOTREACHED */
			}
		}

		/* Reenable any watchpoints before delivering the
		 * signal to user space. The processor register will
		 * have been cleared if the watchpoint triggered
		 * inside the kernel.
		 */
		__asm__("movl %0,%%db7"	: : "r" (current->thread.debugreg[7]));

		/* Whee!  Actually deliver the signal.  */
		handle_signal(signr, ka, &info, oldset, regs);//如果信号向量指向了某个由用户设置的信号处理函数
		return 1;
	}

	/* Did we come from a system call? */
	if (regs->orig_eax >= 0) {
		/* Restart the system call - no handlers present */
		if (regs->eax == -ERESTARTNOHAND ||
		    regs->eax == -ERESTARTSYS ||
		    regs->eax == -ERESTARTNOINTR) {
			regs->eax = regs->orig_eax;
			regs->eip -= 2;
		}
	}
	return 0;
}
    为了理解这个函数,先看下面的这张图:

技术分享
    dequeue_signal,取出一个未加遮蔽的信号加以处理。

    1、直到信号队列中不再存在这样的信号。

	if (!signr)
			break;

    2、相应的"信号向量"为SIG_DFL,即对该信号采取默认的反应方式为止,而这默认的反应又是让接收到信号的进程"寿终正寝"。

			default:
				sigaddset(&current->pending.signal, signr);
				recalc_sigpending(current);
				current->flags |= PF_SIGNALED;
				do_exit(exit_code);
				/* NOTREACHED */
			}
    3、执行了一个用户设置的信号处理函数之后。可见如果是用户设置的信号处理函数,每次中断返回只执行一次。
		handle_signal(signr, ka, &info, oldset, regs);
		return 1;
    dequeue_signal,代码如下:

int
dequeue_signal(sigset_t *mask, siginfo_t *info)
{
	int sig = 0;

#if DEBUG_SIG
printk("SIG dequeue (%s:%d): %d ", current->comm, current->pid,
	signal_pending(current));
#endif

	sig = next_signal(current, mask);//找到一个未被遮蔽的信号位
	if (current->notifier) {
		if (sigismember(current->notifier_mask, sig)) {
			if (!(current->notifier)(current->notifier_data)) {
				current->sigpending = 0;
				return 0;
			}
		}
	}

	if (sig) {
		if (!collect_signal(sig, &current->pending, info))
			sig = 0;
				
		/* XXX: Once POSIX.1b timers are in, if si_code == SI_TIMER,
		   we need to xchg out the timer overrun values.  */
	}
	recalc_sigpending(current);

#if DEBUG_SIG
printk(" %d -> %d\n", signal_pending(current), sig);
#endif

	return sig;
}
static int collect_signal(int sig, struct sigpending *list, siginfo_t *info)
{
	if (sigismember(&list->signal, sig)) {
		/* Collect the siginfo appropriate to this signal.  */
		struct sigqueue *q, **pp;
		pp = &list->head;
		while ((q = *pp) != NULL) {
			if (q->info.si_signo == sig)//在信号队里中找到了要处理的那个信号位
				goto found_it;
			pp = &q->next;
		}

		/* Ok, it wasn‘t in the queue.  We must have
		   been out of queue space.  So zero out the
		   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;

found_it:
		if ((*pp = q->next) == NULL)
			list->tail = pp;

		/* Copy the sigqueue information and free the queue entry */
		copy_siginfo(info, &q->info);
		kmem_cache_free(sigqueue_cachep,q);//把当前的sigqueue结构释放
		atomic_dec(&nr_queued_signals);

		/* Non-RT signals can exist multiple times.. */
		if (sig >= SIGRTMIN) {
			while ((q = *pp) != NULL) {
				if (q->info.si_signo == sig)//如果是新机制,并且还有同样的信号等待处理,那么直接返回,不清零signal对应的标志位
					goto found_another;
				pp = &q->next;
			}
		}

		sigdelset(&list->signal, sig);//如果是旧机制,直接清零signal对应的标志位
found_another:
		return 1;
	}
	return 0;
}


    handle_signal,信号向量指向了某个由用户设置的信号处理函数,代码如下:

static void
handle_signal(unsigned long sig, struct k_sigaction *ka,
	      siginfo_t *info, sigset_t *oldset, struct pt_regs * regs)
{
	/* Are we from a system call? */
	......

	/* Set up the stack frame */
	if (ka->sa.sa_flags & SA_SIGINFO)
		setup_rt_frame(sig, ka, info, oldset, regs);
	else
		setup_frame(sig, ka, oldset, regs);

	if (ka->sa.sa_flags & SA_ONESHOT)
		ka->sa.sa_handler = SIG_DFL;

	if (!(ka->sa.sa_flags & SA_NODEFER)) {
		spin_lock_irq(&current->sigmask_lock);
		sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
		sigaddset(&current->blocked,sig);
		recalc_sigpending(current);
		spin_unlock_irq(&current->sigmask_lock);
	}
}
    整体的过程大致如下:

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

    2、在信号处理程序中插入对系统调用sigreturn()的调用。

    3、将系统空间堆栈中"原始框架"修改成为执行信号处理程序所需的框架。

    4、"返回"到用户空间,但是却执行信号处理程序。

    5、信号处理程序执行完毕以后,通过系统调用sigreturn()重返系统空间。

    6、在系统调用sigreturn()中从用户空间恢复"原始框架‘。

    7、再返回到用户空间,继续执行原先的用户程序。

    首先是用户空间中的框架,即sigframe数据结构:

struct sigframe
{
	char *pretcode;//堆栈的低位
	int sig;
	struct sigcontext sc;
	struct _fpstate fpstate;
	unsigned long extramask[_NSIG_WORDS-1];
	char retcode[8];//堆栈的高位
};
struct sigcontext {
	unsigned short gs, __gsh;
	unsigned short fs, __fsh;
	unsigned short es, __esh;
	unsigned short ds, __dsh;
	unsigned long edi;
	unsigned long esi;
	unsigned long ebp;
	unsigned long esp;
	unsigned long ebx;
	unsigned long edx;
	unsigned long ecx;
	unsigned long eax;
	unsigned long trapno;
	unsigned long err;
	unsigned long eip;
	unsigned short cs, __csh;
	unsigned long eflags;
	unsigned long esp_at_signal;
	unsigned short ss, __ssh;
	struct _fpstate * fpstate;
	unsigned long oldmask;
	unsigned long cr2;
}
    

    现在开始看setup_frame函数,如下:

static void setup_frame(int sig, struct k_sigaction *ka,
			sigset_t *set, struct pt_regs * regs)
{
	struct sigframe *frame;
	int err = 0;

	frame = get_sigframe(ka, regs, sizeof(*frame));//框架的结构确定了,还要确定其在用户空间中的sigcontext位置

	if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
		goto give_sigsegv;

	err |= __put_user((current->exec_domain
		           && current->exec_domain->signal_invmap
		           && sig < 32
		           ? current->exec_domain->signal_invmap[sig]
		           : sig),
		          &frame->sig);//将第一个参数复制到用户空间中由其第二个参数所指向的地方
	if (err)
		goto give_sigsegv;

	err |= setup_sigcontext(&frame->sc, &frame->fpstate, regs, set->sig[0]);//将当前堆栈中的数据复制到用户空间,set->sig[0]为前32位遮蔽位
	if (err)
		goto give_sigsegv;

	if (_NSIG_WORDS > 1) {
		err |= __copy_to_user(frame->extramask, &set->sig[1],
				      sizeof(frame->extramask));//set->sig[1]为后32位遮蔽位
	}
	if (err)
		goto give_sigsegv;

	/* Set up to return from userspace.  If provided, use a stub
	   already in userspace.  */
	if (ka->sa.sa_flags & SA_RESTORER) {
		err |= __put_user(ka->sa.sa_restorer, &frame->pretcode);
	} else {
		err |= __put_user(frame->retcode, &frame->pretcode);//pretcode指向了retcode
		/* This is popl %eax ; movl $,%eax ; int $0x80 */
		err |= __put_user(0xb858, (short *)(frame->retcode+0));
		err |= __put_user(__NR_sigreturn, (int *)(frame->retcode+2));
		err |= __put_user(0x80cd, (short *)(frame->retcode+6));//在信号处理程序中插入对系统调用sigreturn()的调用
	}

	if (err)
		goto give_sigsegv;

	/* Set up registers for signal handler */
	regs->esp = (unsigned long) frame;//修改当前进程堆栈中的堆栈指针esp,返回到执行信号处理程序时的堆栈
	regs->eip = (unsigned long) ka->sa.sa_handler;//修改当前进程堆栈的eip,返回到用户态,执行信号处理程序

	set_fs(USER_DS);
	regs->xds = __USER_DS;
	regs->xes = __USER_DS;
	regs->xss = __USER_DS;
	regs->xcs = __USER_CS;
	regs->eflags &= ~TF_MASK;

#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;

give_sigsegv:
	if (sig == SIGSEGV)
		ka->sa.sa_handler = SIG_DFL;
	force_sig(SIGSEGV, current);
}

    get_sigframe,框架的结构确定了,还要确定其在用户空间中的位置。

static inline void *
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.  */
	if (ka->sa.sa_flags & SA_ONSTACK) {
		if (! on_sig_stack(esp))
			esp = current->sas_ss_sp + current->sas_ss_size;
	}

	/* This is the legacy signal stack switching. */
	else if ((regs->xss & 0xffff) != __USER_DS &&
		 !(ka->sa.sa_flags & SA_RESTORER) &&
		 ka->sa.sa_restorer) {
		esp = (unsigned long) ka->sa.sa_restorer;
	}

	return (void *)((esp - frame_size) & -8ul);//用户空间堆栈指针,减去sigframe结构大小
}

    这样返回到用户态,开始执行信号处理函数。

技术分享

    如上图,当前进程返回到用户空间时,是返回而不是调用,进入信号处理程序的,所以pretcode的下面不会再压入一个"返回地址"。这样,pretcode就正好处在本来应该是信号处理程序运行框架中返回地址的位置上。由于pretcode指向了那8个字节的代码,所以信号处理程序返回后,开始执行如下:

popl %eax; //sig放入eax中,用户态堆栈指针指向sc
movl $__NR_sigreturn, %eax;//系统调用号
int $0x80;//系统调用
    信号处理程序执行完毕以后,又通过sigreturn()进入系统空间。内核中实现这个系统调用的主体是sys_sigreturn(),代码如下:

asmlinkage int sys_sigreturn(unsigned long __unused)
{
	struct pt_regs *regs = (struct pt_regs *) &__unused;//进程内核堆栈的起始地址
	struct sigframe *frame = (struct sigframe *)(regs->esp - 8);//进程用户态堆栈地址再减去8就指向了sigframe整体结构,由于信号处理程序返回esp会加4,popl %eax,esp会加4。所以这里要加上8,才能指向sigframe整体结构
	sigset_t set;
	int eax;

	if (verify_area(VERIFY_READ, frame, sizeof(*frame)))
		goto badframe;
	if (__get_user(set.sig[0], &frame->sc.oldmask)
	    || (_NSIG_WORDS > 1
		&& __copy_from_user(&set.sig[1], &frame->extramask,
				    sizeof(frame->extramask))))
		goto badframe;

	sigdelsetmask(&set, ~_BLOCKABLE);
	spin_lock_irq(&current->sigmask_lock);
	current->blocked = set;//恢复blocked
	recalc_sigpending(current);
	spin_unlock_irq(&current->sigmask_lock);
	
	if (restore_sigcontext(regs, &frame->sc, &eax))//从用户空间堆栈恢复数据到内核空间堆栈;再返回到用户空间,继续执行原先的用户程序
		goto badframe;
	return eax;

badframe:
	force_sig(SIGSEGV, current);
	return 0;
}

    五、总结: 一个进程在什么情况下检测信号的存在呢?

    1、首先是每当从系统调用、中断处理或异常处理返回到用户空间的前夕;

    2、有就是当进程被从睡眠中唤醒(必定是在系统调用中)的时候,此时若发现有信号在等待就要提前从系统调用返回。

    还记得信号的投递么,如果目标进程正在睡眠中,并且没有遮蔽所投递的信号,就要将其唤醒并立即进行调度。那么目标进程之前要么是主动调用schedule(),要么被动的调用schedule()完成的进程切换。现在调用该目标进程继续执行,也就是从schedule()继续执行。

    被动调用:

ret_with_reschedule:
	cmpl $0,need_resched(%ebx)
	jne reschedule
	cmpl $0,sigpending(%ebx)
	jne signal_return
restore_all:
	RESTORE_ALL

	ALIGN
signal_return:
	sti				# we can get here from an interrupt handler
	testl $(VM_MASK),EFLAGS(%esp)
	movl %esp,%eax
	jne v86_signal_return
	xorl %edx,%edx
	call SYMBOL_NAME(do_signal)
	jmp restore_all
    被动调用从schedule返回后,就会检查是否有信号需要处理,如果有需要处理的,就会调用do_signal。

    主动调用schedule,放回后,第一次返回用户态之前,也一定会检查sigpending是否为0,来决定是否调用do_signal。

    假设发送的信号是SIGCHLD,那么do_signal,是如下的处理:

if (ka->sa.sa_handler == SIG_IGN) {//"忽略"一般来说对这个信号的处理就完成了,但有一个例外,那就是当信号为SIGCHLD时。这个信号通常是在子进程通过exit()系统调用结束其生命时向父进程发出的,所以此时接收到SIGCHLD信号的进程要调用sys_wait4()来检查其所有的子进程,只要找到一个已经结束生命的子进程就为其"料理后事",这里的第三个参数WNOHANG,表示如果没有发现已经结束生命的子进程也要立即返回,而不作等待。
			if (signr != SIGCHLD)
				continue;
			/* Check for SIGCHLD: it‘s special.  */
			while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0)
				/* nothing */;
			continue;
		}
    continue继续处理下一个信号,如果下一个信号向量是用户自定义的,那么执行完这个信号处理函数后,也就从系统调用返回了。


    下面我们看,当进程被从睡眠中唤醒(必定是在系统调用中)的时候,此时若发现有信号在等待就要提前从系统调用返回。

    这是pipe_write的一段代码:

wake_up_interruptible_sync(PIPE_WAIT(*inode));//唤醒读端进程  
            PIPE_WAITING_WRITERS(*inode)++;//waiting_writers++  
            pipe_wait(inode);//写端进程睡眠等待  
            PIPE_WAITING_WRITERS(*inode)--;  
            if (signal_pending(current))  
                goto out; 
    进程被从睡眠中唤醒(必定是在系统调用中)的时候,若发现有信号,那么就提前从pipe_write返回,然后在马上返回用户空间时,检查是否有信号需要,如果有,先处理信号。如果是用户自定义的信号处理程序,先返回到用户态执行信号处理程序,再返回原来的内核态,之后又返回了原来的用户态。相当于系统调用提前返回了。如果不是用户自定义的信号处理程序,比如是发送一个KILL信号,从睡眠中唤醒,当前不能继续执行系统调用,要先执行do_signal,对于KILL信号来说,就是do_exit(exit_code)。

Linux内核源代码情景分析-信号

标签:

原文地址:http://blog.csdn.net/jltxgcy/article/details/45114329

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!