电子商务企业网站的推广方式,宁波医院网站建设,wordpress qq音乐播放器,wordpress关注微信登陆信号的作用
信号(signal)是很短的消息#xff0c;可以被发送到一个进程或一组进程。
使用信号的两个主要目的是#xff1a;
1.让进程知道已经发生了一个特定的事件。
2.强迫进程执行它自己代码中的信号处理程序。
POSIX标准还引入了一类新的信号#xff0c;叫做…信号的作用
信号(signal)是很短的消息可以被发送到一个进程或一组进程。
使用信号的两个主要目的是
1.让进程知道已经发生了一个特定的事件。
2.强迫进程执行它自己代码中的信号处理程序。
POSIX标准还引入了一类新的信号叫做实时信号(real-time signal);
在Linux中它们的编码范围为3264。它们与常规信号有很大的不同
因为它们必须排队以便发送的多个信号能被接收到。
另一方面同种类型的常规信号并不排队
如果一个常规信号被连续发送多次
那么只有其中的一个发送到接收进程。
尽管Linux内核并不使用实时信号它还是通过几个特定的系统调用完全实现了POSIX标准。信号的一个重要特点是它们可以随时被发送给状态经常不可预知的进程。
发送给非运行进程的信号必须由内核保存直到进程恢复执行。
阻塞一个信号(后面描述)要求信号的传递拖延直到随后解除阻塞
这使得信号产生一段时间之后才能对其传递这一问题变得更加严重。
因此内核区分信号传递的两个不同阶段
1.信号产生
内核更新目标进程的数据结构以表示一个信号已经被发送
2.信号传递
内核强迫目标进程通过以下方式对信号做出反应
或改变目标进程的执行状态或开始执行一个特定的信号处理程序或两者都是。信号一旦已传递出去进程描述符中有关这个信号的所有信息都被取消。
已经产生但还没有传递的信号称为挂起信号(pending signal)。
任何时候一个进程仅存在给定类型的一个挂起信号
同一进程同种类型的其他信号不被排队只被简单地丢弃。
但是实时信号是不同的同种类型的挂起信号可以有好几个。
一般来说信号可以保留不可预知的挂起时间。必须考虑下列因素
1.信号通常只被当前正运行的进程传递(即由current进程传递)。
2.给定类型的信号可以由进程选择性地阻塞(blocked).
3.当进程执行一个信号处理程序的函数时通常“屏蔽”相应的信号
即自动阻塞这个信号直到处理程序结束。
因此所处理的信号的另一次出现不能中断信号处理程序
所以信号处理函数不必是可重入的。内核必须
1.记住每个进程阻塞哪些信号。
2.当从内核态切换到用户态时
对任何一个进程都要检查是否有一个信号已到达。
3.确定是否可以忽略信号。这发生在下列所有的条件都满足时
3.1.目标进程没有被另一个进程跟踪(进程描述符中ptrace字段的PT_PTRACED标志等于0)。
3.2.信号没有被目标进程阻塞。
3.3.信号被目标进程忽略
4.处理这样的信号
即信号可能在进程运行期间的任一时刻请求把进程切换到一个信号处理函数
并在这个函数返回以后恢复原来执行的上下文。传递信号之前所执行的操作 进程以三种方式对一个信号做出应答
1. 显式地忽略信号。
2. 执行与信号相关的缺省操作
Terminate进程被终止。
Dump进程被终止并且如果可能创建包含进程执行上下文的核心转储文件
lgnore信号被忽略。
Stop进程被停止即把进程置为TASK_STOPPED状态
Continue如果进程被停止(TASK_STOPPED)。就把它置为TASK_RUNNING状态。
3. 通过调用相应的信号处理函数捕获信号。如果一个进程正在被跟踪时接收到一个信号
内核就停止这个进程
并向跟踪进程发送一个SIGCHLD信号以通知它一下。
跟踪进程又可以使用SIGCOUNT信号重新恢复被跟踪进程的执行。SIGKILL和SIGSTOP信号不可以被显式地忽略、捕获或阻塞
因此通常必须执行它们的缺省操作。POSIX信号和多线程应用
POSIX 1003.1标准对多线程应用的信号处理有一些严格的要求
1.信号处理程序必须在多线程应用的所有线程之间共享
不过每个线程必须有自己的挂起信号掩码和阻塞信号掩码。
2.POSIX库函数kill()和sigqueue()必须向所有的多线程应用而不是某个特殊的线程发送信号。
所有由内核产生的信号同样如此(如SIGCHLD、SIGINT或SIGQUIT)。
3.每个发送给多线程应用的信号仅传送给一个线程
这个线程是由内核在从不会阻塞该信号的线程中随意选择出来的。
4.如果向多线程应用发送了一个致命的信号
那么内核将杀死该应用的所有线程而不仅仅是杀死接收信号的那个线程。
有两个例外不可能给进程0(swapper)发送信号
而发送给进程1(init)的信号在捕获到它们之前也总被丢弃。
因此进程0永不死亡而进程1只有当init程序终止时才死亡。如果一个挂起信号被发送给了某个特定进程那么这个信号是私有的
如果被发送给了整个线程组它就是共享的。与信号相关的数据结构 blocked字段存放进程当前所屏蔽的信号。
它是一个sigset_t位数组每种信号类型对应一个元素
信号的编号对应于sigset_t类型变量中的相应位下标加1。信号描述符和信号处理程序描述符 信号描述符被属于同一线程组的所有进程共享信号描述符中与信号处理有关的字段如表11-4所示sigaction数据结构
sa_handler指向信号处理程序的一个指针/SIG_DFL/SIG_IGN
sa_flags这是一个标志集
sa_mask当运行信号处理程序时要屏蔽的信号。挂起信号队列
有几个系统调用能产生发送给整个线程组的信号如kill()和rt_sigqueueinfo(),
而其他的一些则产生发送给特定进程的信号如tkill()和tgkill()。
内核把两个挂起信号队列与每个进程相关联
1.共享挂起信号队列
存放整个线程组的挂起信号。
2.私有挂起信号队列
存放特定进程(轻量级进程)的挂起信号。挂起信号队列由sigpending数据结构组成它的定义如下siginfo_t是一个128字节的数据结构其中存放有关出现特定信号的信息。
它包含下列字段
si_signo信号编号。
si_errno引起信号产生的指令的出错码或者如果没有错误则为0。
si_code发送信号者的代码(参见表11-8)。产生信号 很多内核函数都会产生信号即根据需要更新一个或多个进程的描述符。它们不直接执行第二步的信号传递操作而是可能根据信号的类型和目标进程的状态唤醒一些进程并促使这些进程接收信号。表11-9中的所有函数在结束时都调用specific_send_sig_info()函数表11-10中的所有函数在结束时都调用group_send_sig_info()函数specific_send_sig_info()函数
specific_send_sig_info()函数向指定进程发送信号它作用于三个参数必须在关本地中断和已经获得t-sighand-siglock自旋锁的情况
下调用specific_send_sig_info()函数。
函数执行下面的步骤
1. 检查进程是否忽略信号如果是就返回0(不产生信号)。
当下面的三个忽略信号的条件全部满足时信号就被忽略
· 进程没有被跟踪(t-ptrace中的PT_PTRACED标志被清0)
. 信号没有被阻塞(sigismember(t-blocked,sig)返回0)
· 或者显式地忽略信号(t-sighand-action[sig-1]的sa_handler字段等于SIG_IGN),
或者隐含地忽略信号(sa_handler字段等于SIG_DFL
而且信号是SIGCONT、SIGCHLD、SIGWINCH或SIGURG)
2. 检查信号是否是非实时的(sig32),
而且是否在进程的私有挂起信号队列上已经有另外一个相同的挂起信号
如果是就什么都不需要做因此返回0。
3. 调用send_signal( sig,info,t,t-pending)
把信号添加到进程的挂起信号集合中
4. 如果send_signal()成功地结束
而且信号不被阻塞(sigismember(t-blocked,sig)返回0),
就调用signal_wake_up()函数通知进程有新的挂起信号。
随后该函数执行下述步骤
a.把t-thread_info-flags中的TIF_SIGPENDING标志置位。
b.如果进程处于TASK_INTERRUPTIBLE或TASK_STOPPED状态
而且信号是SIGKILL,就调用try_to_wake_up()唤醒进程。
c.如果try_to_wake_up()返回0,那么说明进程已经是可运行的
这种情况下它检查进程是否已经在另外一个CPU上运行
如果是就向那个CPU发送一个处理器间中断
以强制当前进程的重新调度。
因为在从调度函数返回时每个进程都检查是否存在挂起信号
因此处理器间中断保证了目标进程能很快注意到新的挂起信号。
5.返回1(已经成功地产生信号)。send_signal()函数
send_sigmal()函数在挂起信号队列中插入一个新元素
它接收信号编号sig、siginfo_t 数据结构的地址info(或一个特殊编码
见上一节对specific_send_sig_info()的描述)、
目标进程描述符的地址t以及挂起信号队列的地址signals作为它的参数。
函数执行下面的步骤
1. 如果info的值是2,这个信号就是SIGKILL或SIGSTOP,
而且已经由内核通过force_sig_specific()函数产生
在这种情况下函数跳转到第9步
内核立即强制执行与这些信号相关的操作
因此函数不用把信号添加到挂起信号队列中。
2. 如果进程拥有者的挂起信号的数量(t-user-sigpending)
小于当前进程的资源限制(t-signal-rlim[RLIMIT_SIGPENDING].rlim_cur),
函数就为新出现的信号分配sigqueue数据结构。
q kmem_cache_alloc(sigqueue_cachep,GFP_ATOMIC);
3. 如果进程拥有者的挂起信号的数量太多或者上一步的内存分配失败
就跳转到第9步。
4. 递增拥有者挂起信号的数量
(t-user-sigpending)和t-user所指向的每用户数据结构的引用计数器。
5. 在挂起信号队列signals中增加sigqueue数据结构。
list_add_tail(q-list,signals-list);
6. 在sigqueue数据结构中填充表siginfo_t。if((unsigned long)info 0){q-info.si_signo sig;q-info,si_errno 0;q-info.si_code SI_USER;q-info._sifields._kill.pid current-pid;q-info._sifields._kill._uid current-uid;
} else if((unsigned long)info 1){q-info.si_signo sig;q-info.si_errno 0;q-info.si_code SI_KERNEL;q-info._sifields._kill.pid 0;q-info._sifields._kill._uid 0;
} elsecopy_siginfo(q-info,info);copy_siginfo()函数复制由调用者传递的siginfo_t表。
7.把队列位掩码中与信号相应的位置1:
sigaddset(signals-signal,sig);
8. 返回0:说明信号已被成功地追加到挂起信号队列中。
9. 此时不再向信号挂起队列中增加元素因为已经有太多的挂起信号
或已经没有可以分给sigqueue数据结构的空闲空间
或者信号已经由内核强制立即发送。
如果信号是实时的并已经通过内核函数发送给队列排队
则send_signal()函数返回错误代码-EAGAIN:if(sig32 info k(unsigned long)info !1 info-si_code ! SI_USER)return -EAGAIN;10.设置队列的位掩码中与信号相关的位
sigaddset(signals-signal,sig);
11.返回0:即使信号没有被追加到队列中挂起信号掩码中相应的位也被设置。
即使在挂起队列中没有空间存放相应的挂起信号
让目标进程能接收信号也是至关重要的。
假设一个进程正在消耗过多内存的情形。内核必须保证即使没有空闲内存
ki11()系统调用也能够成功执行
否则系统管理员就没有机会通过终止有害进程来恢复系统。group_send_sig_info()函数
group_send_sig_info()函数向整个线程组发送信号。
它作用于三个参数信号编号sig、siginfo_t表的地址info(可选的值为0、1或2,
如前面“specific_send_sig_info()函数“一节中所描述的)以及进程描述符的地址p。
该函数主要执行下面的步骤
1. 检查参数sig是否正确if(sig0 Il sig64)return -EINVAL;2. 如果信号是由用户态进程发送的则该函数确定是否允许这个操作。
下列条件中至少有一个成立时信号才能被传递
. 发送进程的拥有者具有适当的权能(这通常意味着通过系统管理员发布信号)。
· 信号为SIGCONT且目标进程与发送进程处于同一个注册会话中。
· 两个进程属于同一个用户。
如果不允许用户态进程发送信号函数就返回值-EPERM。
3. 如果参数sig的值为0,则函数不产生任何信号立即返回if(!sig ll !p-sighand)return 0;因为0是无效的信号编码
用于让发送进程检查它是否有向目标线程组发送信号所必需的特权。
如果目标进程正在被杀死(通过检查它的信号处理程序描述符是否已经被释放来获知),
那么函数也返回。
4. 获取p-sighand-siglock自旋锁并关闭本地中断。
5. 调用handle_stop_signal()函数
该函数检查信号的某些类型
这些类型可能使目标线程组的其他挂起信号无效。
handle_stop_signal()函数执行下面的步骤
a.如果线程组正在被杀死
(信号描述符的flags字段的SIGNAL_GROUP_EXIT标志被设置),则函数返回。
b.如果sig是SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU信号
就调用rm_from_queue()函数从共享挂起信号队列p-signal-shared_pending
和线程组所有成员的私有信号队列中删除SIGCONT信号。
c.如果sig是SIGCONT信号
就调用rm_from_queue()函数从共享挂起信号队列p-signal-shared_pending
中删除所有的SIGSTOP、 SIGTSTP、SIGTTIN和SIGTTOU信号
然后从属于线程组的进程的私有挂起信号队列中删除上述信号并唤醒进程rm_from_queue(0x003c0000,p-signal-shared_pending);
t p;
do {rm_from_queue(0x003c0000,t-pending);try_to_wake_up(t,TASK_STOPPED,0);t next_thread(t);
} while(t ! p);掩码0x003c0000选择以上四种停止信号。
宏next_thread每次循环都返回线程组中不同轻量级进程的描述符地址
6. 检查线程组是否忽略信号如果是就返回0值(成功)。
如果在前面“信号的作用”一节中所提到的忽略信号的三个条件都满足
(也可参见前面“specific-send-sig.info()函数”一节中的第1步),就忽略信号。
7. 检查信号是否是非实时的
并且在线程组的共享挂起信号队列中已经有另外一个相同的信号
如果是就什么都不需要做因此返回0值(成功)。if(sig32 sigismember(p-signal-shared_pending.signal,sig))return 0;8. 调用send_signal()函数把信号添加到共享挂起信号队列中
(参见前面“send_signal()函数”一节)。
如果send_signal()返回非0的错误代码则函数终止并返回相同的值。
9. 调用__group_complete_signal()函数唤醒线程组中的一个轻量级进程
10.释放p-sighand-siglock自旋锁并打开本地中断。
11.返回0(成功)。
函数__group_complete_sigmal()扫描线程组中的进程
查找能接收新信号的进程。
满足下述所有条件的进程可能被选中
. 进程不阻塞信号。
. 进程的状态不是EXIT_ZOMBIE、EXIT_DEAD、TASK_TRACED
或TASK_STOPPED
(作为一种异常情况如果信号是SIGKILL,
那么进程可能处于TASK_TRACED或者TASK_STOPPED状态)。
. 进程没有正在被杀死即它的PF_EXITING标志没有置位。
. 进程或者当前正在CPU上运行或者它的TIF_SIGPENDING标志还没有设置。
(实际上唤醒一个有挂起信号的进程是毫无意义的
通常唤醒操作已经由设置了TIF_SIGPENDING标志的内核控制路径执行
另一方面如果进程正在执行则应该向它通报有新的挂起信号。)
一个线程组可能有很多满足上述条件的进程函数按照下面的规则选择其中的一个进程
. 如果p标识的进程(由group_send_sig_info()的参数传递的描述符地址)满足所有的优先准则
并因此而能接收信号函数就选择该进程。
否则函数通过扫描线程组的成员搜索一个适当的进程
搜索从接收线程组最后一个信号的进程(p-signal-curr_target)开始。
如果函数__group_complete_signal()成功地找到一个适当的进程
就开始向被选中的进程传递信号。
首先函数检查信号是否是致命的
如果是通过向线程组中的所有轻量级进程发送SIGKILL信号杀死整个线程组。
否则函数调用signal_wake_up()函数通知被选中的进程
有新的挂起信号到来(见前面“specific_send_sig_info()函数”一节的第4步)。传递信号