Linux内核中的信号机制的介绍

   应用程序发送信号时,主要通过kill进行。注意:不要被“kill”迷惑,它并不是发送SIGKILL信号专用函数。这个函数主要通过系统调用sys_kill()进入内核,它接收两个参数:

  第一个参数为目标进程id,kill()可以向进程(或进程组),线程(轻权线程)发送信号,因此pid有以下几种情况:

  ● pid>0:目标进程(可能是轻权进程)由pid指定。

  ● pid=0:信号被发送到当前进程组中的每一个进程。

  ● pid=-1:信号被发送到任何一个进程,init进程(PID=1)和以及当前进程无法发送信号的进程除外。

  ● pid<-1:信号被发送到目标进程组,其id由参数中的pid的绝对值指定。

  第二个参数为需要发送的信号。

  由于sys_kill处理的情况比较多,分析起来比较复杂,我们从太累了入手,这个函数把信号发送到由参数指定pid指定的线程(轻权进程)中。tkill的内核入口是sys_tkill(kernel/signal.c),其定义如下:

  /*

  * Send a signal to only one task, even if it's a CLONE_THREAD task.

  */

  asmlinkage long

  sys_tkill(int pid, int sig)

  {

  struct siginfo info;

  int error;

  struct task_struct *p;

  /* This is only valid for single tasks */

  if (pid <= 0)//对参数pid进行检查

  return -EINVAL;

  info.si_signo = sig; //根据参数初始化一个siginfo结构

  info.si_errno = 0;

  info.si_code = SI_TKILL;

  info.si_pid = current->tgid;

  info.si_uid = current->uid;

  read_lock(&tasklist_lock);

  p = find_task_by_pid(pid);//获取由pid指定的线程的task_struct结构

  error = -ESRCH;

  if (p) {

  error = check_kill_permission(sig, &info, p);//权限检查

  /*

  * The null signal is a permissions and process existence

  * probe. No signal is actually delivered.

  */

  if (!error && sig && p->sighand) {

  spin_lock_irq(&p->sighand->siglock);

  handle_stop_signal(sig, p);

  //对某些特殊信号进程处理,例如当收到SIGSTOP时,需要把信号队列中的SIGCONT全部删除

  error = specific_send_sig_info(sig, &info, p);//把信号加入到信号队列

  spin_unlock_irq(&p->sighand->siglock);

  }

  }

  read_unlock(&tasklist_lock);

  return error;

  }

  sys_tkill函数主要是通过pecific_send_sig_info()函数实现的,下面我们看一下pecific_send_sig_info()(kernel/signal.c)的定义:

  static int

  specific_send_sig_info(int sig, struct siginfo *info, struct task_struct *t)

  {

  int ret = 0;

  if (!irqs_disabled())

  BUG();

  assert_spin_locked(&t->sighand->siglock);

  if (((unsigned long)info > 2) && (info->si_code == SI_TIMER))

  /*

  * Set up a return to indicate that we dropped the signal.

  */

  ret = info->si_sys_private;

  /*信号被忽略*/

  /* 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);//实际的发送工作

  if (!ret && !sigismember(&t->blocked, sig))

  signal_wake_up(t, sig == SIGKILL);

  out:

  return ret;

  }

  首先调用sig_ignored检查信号是否被忽略,然后检查发送的信号是不是普通信号,如果是普通信号,就需要根据信号位图来检查当前信号队列中是否已经存在该信号,如果已经存在,对于普通信号不需要做任何处理。然后调用send_signal来完成实际的发送工作,send_signal()是信号发送的重点,除sys_tkill之外的函数,最终都是通过send_signal()来完成信号的发送工作的。

  这里注意到想send_signal()传递的参数时t->pending,也就是连接Private Signal Queue的那条链。最后,如果发送成功就调用signal_wake_up()来唤醒目标进程,这样可以保证该进程进入就绪状态,从而有机会被调度执行信号处理函数。

  现在我们来看看send_signal()(kernel/signal.c)函数,这个函数的主要工作就是分配并初始化一个sigqueue结构,然后把它添加到信号队列中。

  static int send_signal(int sig, struct siginfo *info, struct task_struct *t,

  struct sigpending *signals)

  {

  struct sigqueue * q = NULL;

  int ret = 0;

  /*

  * fast-pathed signals for kernel-internal things like SIGSTOP

  * or SIGKILL.

  */

  if ((unsigned long)info == 2)

  goto out_set;

  /* 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. */

  q = __sigqueue_alloc(t, GFP_ATOMIC, (sig < SIGRTMIN &&

  ((unsigned long) info < 2 ||

  info->si_code >= 0)));//分配sigqueue结构

  if (q) {//如果成功分配到sigqueue结构,就把它添加到队列中,并对其初始化

  list_add_tail(&q->list, &signals->list);

  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);//拷贝sigqueue结构

  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;

  if (((unsigned long)info > 1) && (info->si_code == SI_TIMER))

  /*

  * Set up a return to indicate that we dropped

  * the signal.

  */

  ret = info->si_sys_private;

  }

  out_set:

  sigaddset(&signals->signal, sig);//设置信号位图

  return ret;

  }

  从上面的分析可以看出,我们看到信号被添加到信号队列之后,会调用signal_wake_up()唤醒这个进程,signal_wake_up()(kernel/signal.c)的定义如下:

  /*

  * Tell a process that it has a new active signal..

  *

  * NOTE! we rely on the previous spin_lock to

  * lock interrupts for us! We can only be called with

  * "siglock" held, and the local interrupt must

  * have been disabled when that got acquired!

  *

  * No need to set need_resched since signal event passing

  * goes through ->blocked

  */

  void signal_wake_up(struct task_struct *t, int resume)

  {

  unsigned int mask;

  set_tsk_thread_flag(t, TIF_SIGPENDING);//为进程设置TIF_SIGPENDING标志

  /*

  * For SIGKILL, we want to wake it up in the stopped/traced case.

  * We don't check t->state here because there is a race with it

  * executing another processor and just now entering stopped state.

  * By using wake_up_state, we ensure the process will wake up and

  * handle its death signal.

  */

  mask = TASK_INTERRUPTIBLE;

  if (resume)

  mask |= TASK_STOPPED | TASK_TRACED;

  if (!wake_up_state(t, mask))

  kick_process(t);

  }

  signal_wake_up()首先为进程设置TIF_SIGPENDING标志,说明该进程有延迟的信号要等待处理。然后再调用wake_up_state()唤醒目标进程,如果目标进程在其他的CPU上运行,wake_up_state()将返回0,此时调用kick_process()向该CPU发送一个处理器间中断。当中断返回前戏,会为当前进程处理延迟的信号。

  此后当该进程被调度时,在进程返回用户空间前,会调用do_notify_resume()处理该进程的信号。

时间: 2024-09-17 18:42:22

Linux内核中的信号机制的介绍的相关文章

Linux内核中的jiffies及其作用介绍及jiffies等相关函数详解

在LINUX的时钟中断中涉及至二个全局变量一个是xtime,它是timeval数据结构变量,另一个则是jiffies,首先看timeval结构struct timeval{time_t tv_sec; /***second***/susecond_t tv_usec;/***microsecond***/}到底microsecond是毫秒还是微秒?? 1秒=1000毫秒(3个零),1秒=1000 000微秒(6个零),1秒=1000 000 000纳秒(9个零),1秒=1000 000 000

大话Linux内核中锁机制之RCU、大内核锁

大话Linux内核中锁机制之RCU.大内核锁 在上篇博文中笔者分析了关于完成量和互斥量的使用以及一些经典的问题,下面笔者将在本篇博文中重点分析有关RCU机制的相关内容以及介绍目前已被淘汰出内核的大内核锁(BKL).文章的最后对<大话Linux内核中锁机制>系列博文进行了总结,并提出关于目前Linux内核中提供的锁机制的一些基本使用观点. 十.RCU机制 本节将讨论另一种重要锁机制:RCU锁机制.首先我们从概念上理解下什么叫RCU,其中读(Read):读者不需要获得任何锁就可访问RCU保护的临界

linux内核中异步通信机制--信号处理机制【转】

转自:http://blog.csdn.net/lu_embedded/article/details/51131663 什么是异步通信?很简单,一旦设备准备好,就主动通知应用程序,这种情况下应用程序就不需要查询设备状态,就像硬件上常提的"中断的概念".比较准确的说法其实应该叫做"信号驱动的异步I/O",信号是在软件层次上对中断机制的一种模拟.阻塞I/O意味着一直等待设备可访问再访问,非阻塞I/O意味着使用poll()来查询是否可访问,而异步通信则意味着设备通知应用

大话Linux内核中锁机制之原子操作、自旋锁【转】

转自:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实是由于操作系统中存在多进程对共享资源的并发访问,从而引起了进程间的竞态.这其中包括了我们所熟知的SMP系统,多核间的相互竞争资源,单CPU之间的相互竞争,中断和进程间的相互抢占等诸多问题. 通常情况下,如图1所示,对于一段程序,我们的理想是总是美好的,希望它能够这样执行:进程1先对临界区完成操作,然

大话Linux内核中锁机制之原子操作、自旋锁

转至:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 很多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实是由于操作系统中存在多进程对共享资源的并发访问,从而引起了进程间的竞态.这其中包括了我们所熟知的SMP系统,多核间的相互竞争资源,单CPU之间的相互竞争,中断和进程间的相互抢占等诸多问题. 通常情况下,如图1所示,对于一段程序,我们的理想是总是美好的,希望它能够这样执行:进程1先对临界区完成操作,

大话Linux内核中锁机制之信号量、读写信号量

大话Linux内核中锁机制之信号量.读写信号量 在上一篇博文中笔者分析了关于内存屏障.读写自旋锁以及顺序锁的相关内容,本篇博文将着重讨论有关信号量.读写信号量的内容.  六.信号量 关于信号量的内容,实际上它是与自旋锁类似的概念,只有得到信号量的进程才能执行临界区的代码:不同的是获取不到信号量时,进程不会原地打转而是进入休眠等待状态.它的定义是include\linux\semaphore.h文件中,结构体如图6.1所示.其中的count变量是计数作用,通过使用lock变量实现对count变量的

大话Linux内核中锁机制之完成量、互斥量

大话Linux内核中锁机制之完成量.互斥量 在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题.  八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等待另一个执行单元完成某事后方可执行,它是一种轻量级机制.事实上,它即是为了完成进程间的同步而设计的,故而仅仅提供了代替同步信号量的一种解决方法,初值被初始化为0.它在include\linux\completion.h定义. 如图8.1所示,对于执行单元

Linux内核文件cache管理机制介绍

1 操作系统和文件Cache管理 操作系统是计算机上最为重要的软件,他负责管理计算机的各种硬件资源,并将这些物理资源抽象成各种接口供上层应用使用.所以从程序的角度看,操作系统就好比是一个虚拟机,这个虚拟机不提供各种硬件资源的具体细节,而仅仅提供进程.文件.地址空间以及进程间通信等等逻辑概念. 对于存储设备上的数据,操作系统向应用程序提供的逻辑概念就是"文件".应用程序要存储或访问数据时,只需要向这个文件读或者写入内容即可,而文件与物理存储设备的交互则由操作系统提供. 2 文件Cache

大话Linux内核中锁机制之内存屏障、读写自旋锁及顺序锁

大话Linux内核中锁机制之内存屏障.读写自旋锁及顺序锁     在上一篇博文中笔者讨论了关于原子操作和自旋锁的相关内容,本篇博文将继续锁机制的讨论,包括内存屏障.读写自旋锁以及顺序锁的相关内容.下面首先讨论内存屏障的相关内容. 三.内存屏障 不知读者是是否记得在笔者讨论自旋锁的禁止或使能的时候,提到过一个内存屏障函数.OK,接下来,笔者将讨论内存屏障的具体细节内容.我们首先来看下它的概念,Memory Barrier是指编译器和处理器对代码进行优化(对读写指令进行重新排序)后,导致对内存的写入