Linux进程间通讯(一)信号(上)

Linux进程间通讯

Linux进程间通讯(一)信号(上)

Linux进程间通讯(二)信号(下)

Linux进程间通讯(三)管道

Linux进程间通讯(四)共享内存

Linux进程间通讯(五)信号量

Linux进程间通讯(一)信号(上)

一、信号概述

在Linux中,当遇到某些紧急的情况,可以给进程发送信号,紧急处理一些事情

在Linux中,定义了许多信号,可以使用 kill -l 命令来查看

# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

这些信号具体是做什么的,可以通过 man 7 signal 命令查看

Signal     Value     Action   Comment
──────────────────────────────────────────────────────────────────────
SIGHUP        1       Term    Hangup detected on controlling terminal
                              or death of controlling process
SIGINT        2       Term    Interrupt from keyboard
SIGQUIT       3       Core    Quit from keyboard
SIGILL        4       Core    Illegal Instruction


SIGABRT       6       Core    Abort signal from abort(3)
SIGFPE        8       Core    Floating point exception
SIGKILL       9       Term    Kill signal
SIGSEGV      11       Core    Invalid memory reference
SIGPIPE      13       Term    Broken pipe: write to pipe with no
                              readers
SIGALRM      14       Term    Timer signal from alarm(2)
SIGTERM      15       Term    Termination signal
SIGUSR1   30,10,16    Term    User-defined signal 1
SIGUSR2   31,12,17    Term    User-defined signal 2
……

进程对相应的信号都有默认的处理,当然我们也可以自定义某个信号的处理函数

一旦有信号产生,用户进程有以下几种处理方式

  • 执行默认方式:进程对每种信号都有默认的处理方式,例如上面的 Term 表示终止进程;Core 表示终止进程后,通过 Core Dump 将当前进程的运行状态保存到文件中,可以给程序员分析
  • 捕抓信号:我们可以自定义信号的处理函数,当进程接收到相应的信号时,会调用我们定义的信号处理函数
  • 忽略信号:当我们不希望处理某些信号的时候,可以选择忽略信号,不做任何处理。有两个信号是进程无法忽略的,SIGKILL 和 SEGSTOP,它们作用是结束或中断进程

信号的处理流程主要有两步,一个是注册信号,一个是发送信号,这篇文章先讲解注册信号,下篇文章再讲解发送信号

二、注册信号

如果我们不想让某一个信号执行默认的处理操作,我们可以通过 signal 函数来进程注册,其定义如下

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);
  • signum:表示处理的信号,这个对应 kill -l 命令得到的列表中的信号编号
  • handler:自定义信号处理函数

此外,还可以通过 sigaction 来注册信号

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

  • signum:信号编号
  • act:信号的处理动作
  • oldact:返回旧的处理动作

这两种方式有什么区别呢?

其实是类似的,signal 直接将信号和一个处理函数相关联,sigaction 将信号和一个动作相关联,这个动作由 struct sigaction 表示,定义如下

struct sigaction {
	__sighandler_t sa_handler;
	unsigned long sa_flags;
	__sigrestore_t sa_restorer;
	sigset_t sa_mask;		/* mask last for extensibility */
};

和 signal 类似,这里面包含有 sa_handler 信号处理函数,其它变量是让你可以更加精细的控制信号的处理行为(例如 sa_flags 可以设置一些标志来控制信号处理的行为;sa_mask 可以设置在信号处理的过程中屏蔽某些中断)

而 signal 却无法设置这些,signal 不是一个系统调用,而是 glibc 封装的一个函数,其定义如下

#  define signal __sysv_signal
__sighandler_t
__sysv_signal (int sig, __sighandler_t handler)
{
  struct sigaction act, oact;
......
  act.sa_handler = handler;
  __sigemptyset (&act.sa_mask);
  act.sa_flags = SA_NOMASK | SA_INTERRUPT;
  act.sa_flags &= ~SA_RESTART;
  if (__sigaction (sig, &act, &oact) < 0)
    return SIG_ERR;
  return oact.sa_handler;
}
weak_alias (__sysv_signal, sysv_signal)

可以看到,signal 其实也是定义一个动作 struct sigaction 来和信号关联,只不过这个动作的一部分参数被默认设置了

在 sa_flags 设置了 SA_NOMASK、SA_INTERRUPT,清除了 SA_RESTART

  • SA_NOMASK:这个表示表示不屏蔽信号,对应上面调用的 __sigemptyset (&act.sa_mask),将 sa_mask 设置为空。也就是,在中断处理函数中,允许处理其它的信号
  • SA_INTERRUPT:表示在信号到来的时候,如果当前进程正在进程系统调用,可以中断系统调用
  • SA_RESTART:如果设置了此标志,表示如果系统调用被中断后,在处理完信号后,会自动重新调用系统调用。如果将该标志清空,那么系统调用会直接返回 -EINTR

在设置完动作后,会调用 __sigaction,这个我们后面再继续分析

接下来看 sigaction 做了什么

glibc 里面有一个文件 syscall.list。这里定义了库函数对应的系统调用,在这里面找到 sigaction 函数

sigaction    -       sigaction       i:ipp   __sigaction     sigaction

它对应的系统调用是 __sigaction,发现没有,signal 函数里面调用的也是 __sigaction

所以 signal 和 signaction 是一样的,只不过 signal 多了一层封装,设置了 struct sigaction 的一些默认参数,而 sigaction 更加底层,所以也更加灵活

接下来我们分析 __sigaction,在 glibc 中,__sigaction 会调用 __libc_sigaction,并最终调用的系统调用是 rt_sigaction,其定义如下

int
__sigaction (int sig, const struct sigaction *act, struct sigaction *oact)
{
......
  return __libc_sigaction (sig, act, oact);
}


int
__libc_sigaction (int sig, const struct sigaction *act, struct sigaction *oact)
{
  int result;
  struct kernel_sigaction kact, koact;


  if (act)
    {
      kact.k_sa_handler = act->sa_handler;
      memcpy (&kact.sa_mask, &act->sa_mask, sizeof (sigset_t));
      kact.sa_flags = act->sa_flags | SA_RESTORER;


      kact.sa_restorer = &restore_rt;
    }


  result = INLINE_SYSCALL (rt_sigaction, 4,
                           sig, act ? &kact : NULL,
                           oact ? &koact : NULL, _NSIG / 8);
  if (oact && result >= 0)
    {
      oact->sa_handler = koact.k_sa_handler;
      memcpy (&oact->sa_mask, &koact.sa_mask, sizeof (sigset_t));
      oact->sa_flags = koact.sa_flags;
      oact->sa_restorer = koact.sa_restorer;
    }
  return result;
}

真正的系统调用 rt_sigaction 定义如下啊

SYSCALL_DEFINE4(rt_sigaction, int, sig,
		const struct sigaction __user *, act,
		struct sigaction __user *, oact,
		size_t, sigsetsize)
{
	struct k_sigaction new_sa, old_sa;
	int ret = -EINVAL;
......
	if (act) {
		if (copy_from_user(&new_sa.sa, act, sizeof(new_sa.sa)))
			return -EFAULT;
	}


	ret = do_sigaction(sig, act ? &new_sa : NULL, oact ? &old_sa : NULL);


	if (!ret && oact) {
		if (copy_to_user(oact, &old_sa.sa, sizeof(old_sa.sa)))
			return -EFAULT;
	}
out:
	return ret;
}

rt_sigaction 函数中,将用户空间的 sigaction 拷贝到内核空间,然后调用 do_sigaction

do_sigaction 的定义如下

int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
{
	struct task_struct *p = current, *t;
	struct k_sigaction *k;
	sigset_t mask;
......
	k = &p->sighand->action[sig-1];


	spin_lock_irq(&p->sighand->siglock);
	if (oact)
		*oact = *k;


	if (act) {
		sigdelsetmask(&act->sa.sa_mask,
			      sigmask(SIGKILL) | sigmask(SIGSTOP));
		*k = *act;
......
	}


	spin_unlock_irq(&p->sighand->siglock);
	return 0;
}

首先要知道,每个进程的 task_struct 中,都有一个 struct sighand_struct,它里面有一个 struct sigaction 数组,每一项表示一个信号对应的动作,数组的下标就是对应信号的编号

do_sigaction 就是找到信号对应的 struct sigaction,然后设置它

到这里,信号的注册就已经完成了

三、总结

  • 在用户程序中,可以调用两个函数注册信号的处理函数,分别为 signal 和 sigaction
  • signal 和 sigaction 最终调用的系统调用都是 rt_sigaction,signal 可以看作比 sigaction 多了一层封装,它用起来不如 sigaction 灵活
  • 每个进程的 task_struct 中都有一个 struct sighand_struct,它里面有一个 struct sigaction 数组,每一项对应一个信号的处理动作,下标对应着信号的编号。rt_sigaction 最终会把用户程序设置的 struct sigaction 拷贝到 struct sigaction 数组中对应的项
发布了107 篇原创文章 · 获赞 197 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/weixin_42462202/article/details/102630450