健壮的信号接口
我们已经讨论了使用signal来发出与捕获信号,因为他们在较为旧的Unix程序中很常见。然而,X/Open与Unix规范推荐了一个更为健壮的用于信号处理的新的编程接口:sigaction。
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
sigaction结构定义在signal.h中,这个结构用于定义依据由sig所指定的信号而所要采取的动作,而且这个结构至少有以下几个成员:
void (*) (int) sa_handler /* function, SIG_DFL or SIG_IGN
sigset_t sa_mask /* signals to block in sa_handler
int sa_flags /* signal action modifiers
sigaction函数设置也sig信号相关联的动作。如果oact不为空,sigaction就会将前一个信号动作写入他所指向的位置。如果act为空,这就是sigaction所做的所有事情。如果act不为空,就会设置指定信号的动作。
作为信号,如果成功,sigaction就会返回0,否则返回-1。如果指定的信号不存在或是尝试捕获或忽略不能捕获或是忽略的信号时,错误变量errno就会被设置EINVAL。
在参数act所指向的sigaction结构内部,sa_handler是一个指针,指向当接收到sig信号时所调用的函数。这与我们在前面所见到过的传递给signal的函数func相类似。我们可以在sa_handler域使用特殊值SIG_IGN与SIG_DFL分别表示要忽略此信号或是重新载入默认动作。
sa_mask域指定了一个在sa_handler函数调用之前要添加到进程的信号掩码中信号集合。有一些被屏蔽或是不会传递给进程的信号集合。这就会阻止了我们在前面所看到的在处理器函数运行完成之前接收到信号的情况。使用sa_mask域可以减少竞争条件。
然而,由sigaction所设置的处理器捕获的信号默认情况下并不会重新设置,而如要我们希望获得我们在前面所看到的信号行为,那么就必须设置sa_flags域来包含SA_RESETHAND值。在我们详细深入了解sigaction之前,让我们来使用sigaction来替换信号重新编写ctrlc.c程序。
试验--sigaction
进行如下的修改,从而SIGINT可以被sigaction所解释。我们将这个新程序称之为ctrlc2.c。
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf(“OUCH! - I got signal %d\n”, sig);
}
int main()
{
struct sigaction act;
act.sa_handler = ouch;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
while(1) {
printf(“Hello World!\n”);
sleep(1);
}
}
当我们运行这个版本的程序时,当我们按下Ctrl+C时我们总会得到一条消息,因为SIGINT信号反复的被sigaction处理。要结束这个程序,我们必须按下Ctrl+-\,这在默认情况下会产生SIGQUIT信号。
$ ./ctrlc2
Hello World!
Hello World!
Hello World!
^C
OUCH! - I got signal 2
Hello World!
Hello World!
^C
OUCH! - I got signal 2
Hello World!
Hello World!
^\
Quit
$
工作原理
这个程序调用sigaction而不是signal来将Ctrl+C(SIGINT)的信号处理器设置为函数ouch。首先需要设置一个包含处理器,信号掩码与标志的sigaction结构。在这个例子中,我们并不需要任何标志,并且使用一个新函数sigemptyset设置一个空的信号掩码。
信号集合
头文件signal.h定义了类型sigset_t以及用来操作信号集合的函数。这个集合用在sigaction结构与其他的函数中,用来依据信号修改进程行为。
#include <signal.h>
int sigaddset(sigset_t *set, int signo);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigdelset(sigset_t *set, int signo);
这些函数的执行他们的名字所显示的操作。sigemptyset将一个信号集合初始化为一个空集合。sigfillset将一个信号集合初始化为一个包含所有定义的信号的集合。sigaddset与sigdelset由一个信号集合中添加和删除一个指定的信号(signo)。这些函数都会在成功时返回0,而失败时返回-1,并且设置errno变量。如果指定的信号不可用,那么唯一的错误就是EINVAL。
函数sigismember确定一个指定的信号是否是一个信号集合中的成员。如果此信号是信号集合中的一员,函数就会返回1,如果不是则会返回0,而如果信号不可用,则返回-1,并且将errno设置为EINVAL。
#include <signal.h>
int sigismember(sigset_t *set, int signo);
进程信号掩码是通过调用函数sigprocmask来设置或是检测的。信号掩码是当前被屏蔽掉的信号集合,因而并不会当前的进程接收到。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
sigprocmask函数会根据how参数以多种方式来改变进程信号掩码。如果参数set不为空,那么新的信号掩码值可以通过set传入,而前一个信号掩码值被写入信号集合oset。
how参数如下:
SIG_BLOCK set中的信号都被加入到信号掩码中
SIG_SETMASK 由set中设置信号掩码
SIG_UNBLOCK set中的信号由信号掩码中移除
如果set参数是一个空指,那么how参数并不会被使用,而这个调用的唯一作用就是获取当前的信号掩码值并写入oset。
如果函数调用成功,sigprocmask会返回0,如果how参数不可用则返回-1,并且将errno设置为EINVAL。
如果一个信号被一个进程屏蔽,那么他就不会被传输,但是仍保持等待状态。一个程序可以通过调用函数sigpending来他的被屏蔽的信号哪些处理等待状态。
#include <signal.h>
int sigpending(sigset_t *set);
这会输出一个被阻止传输的信号集合并且添加到由set所指向的信号集合中。如果成功会返回0,否则会返回-1,并且设置errno来表示错误。当一个程序需要处理信号并且要控制何时调用处理函数时,这个函数会很有用。
一个进程可以挂起执行直到通过调用sigsuspend传输一个信号。这就是我们在前面常见到的暂停功能的常见形式:
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
sigsuspend函数使用由sigmask信号集合来替换进程信号掩码,然后挂起执行。他会在信号处理函数执行之后恢复执行。如果接收到的信号终止程序,sigsuspend函数就不会再返回。如果接收的信号没有终止程序,sigsuspend函数会返回-1,并且将errno设置为EINTR。
sigaction标记
用于sigaction中的sigaction结构的sa_flags域也许会包含下列值来修改信号行为:
SA_NOCLDSTOP 当子进程停止时不要生成SIGCHLD信号
SA_RESETHAND 当收到信号时将信号动作重设为SIG_DFL
SA_RESTART 重新启动可中断函数而不是以EINTR报错
SA_NODEFER 当捕获时不要将信号添加到信号掩码中
SA_RESETHAND可以用于当捕获一个信号时自动清除一个信号函数,正如我们在前面所看到的。
程序所用的许多系统调用都是可中断的;也就是说,当他们接收到的一个信号时,他们会返回一个错误,并且将errno设置为EINTR表明函数是因为信号而返回的。这个行为对于使用信号的程序要格外小心。如果在sigaction调用中的sa_flags域被设置为SA_RESTART,那么这个函数就不会被中断,而在信号处理函数被执行时重新启动。
通常而言,当一个信号处理函数正在执行时,所接收到的信号会在信号处理函数执行期间添加到进程的信号掩码中。这是为了阻止后面发生了同样的信号,从而使得信号处理函数再次运行。如果函数不是可重入的,在他完成处理之前为另一个信号的发生所调用,那么前一个就会出现问题。然而,如果调用了SA_NODEFER标记,当接收到这个信号时,信号掩码并不会提示。
信号处理函数可以在执行期间被中断,并且在其他的函数再次调用。当我们返回到的前一个调用时,他可正常运行是很重要的。他不仅是递归(调用自身),而是重入(无问题的进入并再次运行)。在内核中同时处理多个设备的中断服务例程需要是可重入的,因为在相同代码的执行期间也许会有一个更高优先级的中断进入。
下面列出的是X/Open规范认为在一个信号处理器内部是可以安全调用的,他们或者是可重入的或者是自身不会发出信号。
access alarm cfgetispeed cfgetospeed
cfsetispeed cfsetospeed chdir chmod
chown close creat dup2
dup execle execve _exit
fcntl fork fstat getegid
geteuid getgid getgroups getpgrp
getpid getppid getuid kill
link lseek mkdir mkfifo
open pathconf pause pipe
read rename rmdir setgid
setpgid setsid setuid sigaction
sigaddset sigdelset sigemptyset sigfillset
sigismember signal sigpending sigprocmask
sigsuspend sleep stat sysconf
tcdrain tcflow tcflush tcgetattr
tcgetpgrp tcsendbreak tcsetattr tcsetpgrp
time times umask uname
unlink utime wait waitpid
write
通用信号参考
在这一部分,我们会列出Linux与Unix程序默认行为通常需要的信号。
下表所列出的信号默认动作都是使用_exit的进程非正常终止(类似于exit,但是在返回内核之前并没有执行任何清理)。然而,状态可以用于wait,而waitpid可以通过特定的信号来表明非正常的终止。
信号名 描述
SIGALRM 由alarm函数所调用的计时器生成的信号
SIGHUP 通过非连接终端发往控制进程,或是通过终端控制进程发往前台进程
SIGINIT 通常是在终端由Ctrl+C或是配置的中断字符生成的信号
SIGKILL 通常由shell使用来强制终止一个不确定进程,因为这个信号是不能被捕获或是忽略的
SIGPIPE 尝试写入一个没有相应读端管道时生成的信号
SIGTERM 作为一个请求进程完成的请求发送。Unix用于停机的情况来请求系统服务停止。这是由kill命令所发送的默认信号。
SIGUSR1,SIGUSR2 也许会被进程用于彼此通信,可能使得他们报告状态信息
默认情况下,下表中的信号也许会引起非正常终止。另外,与实现相关的动作,例如也许会发生创建核心文件的情况。
信号名 描述
SIGFPE 由浮点算术异常生成的信号
SIGILL 处理器执行了一条非法指令。通常是由一个恶意程序或是不正确的共享内存模块引起的
SIGQUIT 通常是在终端输入Ctrl+\或是配置的退出字符发出的信号
SIGSEGV 段错误,通常是由读写非法内存地址(或者是超出数组边界或是引用非法指针)引起的。覆盖一个局部数组变量并破坏堆栈,从而使得函数返回非法地址,引起SIGSEGV信号。
进程默认情况下接收到下表中的信号时会挂起执行。
信号名 描述
SIGSTOP 停止执行(不能被捕获或是忽略)
SIGTSTP 终端停止信号,通常是由Ctrl+Z引起的
SIGTTIN,SIGTTOU shell用来表明后台作业已经停止,因为他们需要由终端读取或是产生输出
SIGCONT可以使得一个停止进程重新启动,如果被一个并没有停止的进程接收到,则会被忽略。SIGCHLD默认会被忽略。
信号名 描述
SIGCONT 如果停止,继续执行
SIGCHLD 当一个子进程停止或是退出时生成信号
小结
在这一章,我们了解了进程如何成为Linux操作系统的基础部分。我们了解了他们如何启动,终止,查看,并且我们如何使用他们来解决编程问题。我们也了解了可以用于控制运行程序动作的信号,事件。我们了解到所有的Linux进程,包括init,使用与程序可用的相同的系统调用集合。
分享到:
相关推荐
信号量机制是实现进程同步的一种有效工具,由荷兰计算机科学家Dijkstra提出。信号量是一种特殊的变量,可以被多个进程共享,并通过P操作(wait)和V操作(signal)来控制对资源的访问。当一个进程访问某个资源时,会...
在操作系统中,进程同步与互斥是多任务环境下确保数据一致性、避免竞态条件的关键机制。System V 信号量是一种广泛使用的同步原语,尤其在Linux系统中被广泛应用。本篇将深入探讨System V 信号量的工作原理以及如何...
进程间通信之信号 sinal ) 唯一的异步通信方式 七种进程间通信方式: 一 无名管道( pipe ) 二 有名管道( fifo ) 三 共享内存 shared memory 四 信号 sinal 五 消息队列 message queue ) 六 信号量 ...
在 Linux 中,信号是一种异步通信机制,允许一个进程向另一个进程发送信号,从而通知其执行某些操作。信号可以分为两类:内核信号和用户信号。内核信号是由操作系统内核生成的,而用户信号是由用户进程生成的。 在...
本文将详细介绍五种主要的进程间通信方式:管道通信、消息队列通信、共享内存通信、信号量通信以及套接字通信。 1. **管道通信**: 管道是一种半双工通信机制,数据只能单向流动,通常用于父子进程或兄弟进程之间...
它只是建立了进程与信号之间的联系,允许进程捕捉到信号并执行预先约定的动作。 第二个问题是,若子进程向父进程发送信号,父进程接到信号后可以缺省操作、或忽视信号、或执行一函数,各是什么含义?答案是,缺省...
进程同步与互斥实验报告 操作系统实验报告《进程同步与互斥》实验的主要目的是掌握基本的进程同步与互斥算法,了解生产者-消费者问题,并学习使用 Windows 2000/XP 中基本的同步对象,掌握相关 API 的使用方法。...
操作系统实验报告实验五主要关注的是Linux环境下的进程同步与互斥机制,重点在于理解并应用信号量(Semaphore)以及P、V操作。实验目的是通过实际操作加深对进程同步互斥概念的理解,掌握信号量的使用方法和P、V操作...
#### 五、进程与线程的区别与联系 进程与线程的主要区别在于: - **资源拥有方式**:进程拥有自己独立的资源(如内存空间),而线程共享所属进程的所有资源。 - **上下文切换**:进程上下文切换涉及到更多的资源...
本文将深入探讨“进程编程”、“进程通讯”、“消息机制”、“信号量”以及“互斥”这五个核心概念。 首先,我们要理解什么是进程。进程是操作系统中的一个基本实体,代表了一个正在执行的程序的实例。每个进程都有...
而"Mcs51.cpp"和"Mcs51.h"可能包含了与8051微控制器相关的代码,因为MCS-51是8051系列单片机的典型代表,通常用于嵌入式系统,这可能与管道和进程控制不直接相关,但它们可能作为示例或工具被用于某些进程通信的实现...
本文将对进程的同步与互斥习题进行详细的解释和分析,涵盖了多种不同的场景和问题,包括进程的同步、互斥、信号量、缓冲区、生产者消费者问题、读者写者问题、司机与售票员问题、汽车过桥问题等。 一、进程的同步与...
signal()让父进程捕捉用alarm函数设置时钟的时间段终止时产生的信号,当捕捉到该信号后,父进程使用系统调用Kill()向两个子进程发出信号,子进程捕捉到信号后分别输出子进程被杀死信息后终止,父进程等待两个子...
#### 一、进程间通信与信号 在Linux操作系统中,进程间通信(IPC)是实现多个进程之间数据交换和同步的重要手段。进程间通信分为即时通信和非即时通信两种类型。其中,即时通信要求信息在到达目标进程时即刻被处理...
- 题目五举例说明了信号量在进程间的直接同步。在这个例子中,通过信号量count1和count2,以及信号量scout、scount1和scount2来实现进程间的同步。 4. 信号量的局限性和改进方法 在使用信号量进行进程同步时,可能...
本实验旨在深入理解Linux操作系统中的核心进程概念,包括进程的创建、调度和通信机制,特别是通过使用信号量解决多进程间的同步与互斥问题。通过对生产者-消费者模型的实现,学生将学习如何在Linux环境下运用无名...
编制一个程序,程序中创建5个子进程,代表五位哲学家,然后父进程结束。使用信号量机制解决哲学家进餐问题。当哲学家进餐时,屏幕输出: [进程号] eating! 当哲学家思考时,屏幕输出: [进程号] thinging! 相关的...
1. **与进程终止相关的信号**:此类信号主要用于通知进程其生命周期的变化,如进程退出或子进程终止。 - **SIGHUP**:当终端连接结束时发送,通常是因为控制终端的进程结束。 - **SIGTERM**:用于请求进程正常退出...