10.2 信号概念
core可以用来GDB调试:
当我们的程序崩溃时,内核有可能把该程序当前内存映射到core文件里,方便程序员找到程序出现问题的地方。最常出现的,几乎所有C程序员都出现过的错误就是“段错误”了。也是最难查出问题原因的一个错误。下面我们就针对“段错误”来分析core文件的产生、以及我们如何利用core文件找到出现崩溃的地方。
参考:注意里面的代码的const要去掉
常见信号介绍
这个文件可以得到信号的编号
(1)SIGINT 2 Ctrl+C时OS送给前台进程组中每个进程
(2)SIGABRT 6 调用abort函数,进程异常终止
(3)SIGPOLL SIGIO 8 指示一个异步IO事件,在高级IO中提及
(4)SIGKILL 9 杀死进程的终极办法,该信号不能被捕捉
(5)SIGSEGV 11 无效存储访问时OS发出该信号
(6)SIGPIPE 13 涉及管道和socket
(7)SIGALRM 14 涉及alarm函数的实现
(8)SIGTERM 15 kill命令发送的OS默认终止信号
(9)SIGCHLD 17 子进程终止或停止时OS向其父进程发此信号
(10)
SIGUSR1 10 用户自定义信号,作用和意义由应用自己定义
SIGUSR2 12
(11)SIGOUIT //实际验证头文件没有这个信号
当用户在终端上按退出键(一般采用Ctrl+\)时,中断驱动程序产生此信号,并发送给前台进程组中的所有进程(见图9-9),此信号不仅终止前台进程组 (如SIGINT所做的那样),同时产生一个core文件。
作业控制信号:
10.3 安装信号函数signal
signal函数的优点和缺点
typedef void (*sighandler_t)(int);
sighandler_t signal( int signum, sighandler_t handler);
(1)优点:简单好用,捕获信号常用
(2)缺点:无法简单直接得知之前设置的对信号的处理方法
sigaction函数介绍
int sigaction( int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
(1)2个都是API,但是sigaction比signal更具有可移植性
(2)用法关键是2个sigaction指针
sigaction比signal好的一点:sigaction可以一次得到设置新捕获函数和获取旧的捕获函数(其实还可以单独设置新的捕获或者单独只获取旧的捕获函数),而signal函数不能单独获取旧的捕获函数而必须在设置新的捕获函数的同时才获取旧的捕获函数。
sigaction的使用参考:
参数:handler
1.SIG_IGN:忽略此信号,有两个信号SIGKILL和SIGSTOP不能忽略
2.SIG_DFL:系统默认动作
3.接到此信号后要调用的函数的地址:信号处理函数
改进的发送信号函数:sigqueue
int sigqueue(pid_t pid,int signo,const union sigval value);
--新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与sigaction()函数配合使用 注意:
和kill函数相比int kill(pid_t pid,int signo)多了参数 参数:
第一个参数是指定接收信号的进程pid,
第二个参数确定即将发送的信号,
第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。 --sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组 --union sigval联合体 typedef union sigval { int sival_int; void * sival_ptr; }sigval_t;
10.4 不可靠的信号
之所以信号称为不可靠:
1.信号可能会丢失
2.进程对信号的控制能力差
3.不能阻塞,即不能像栈一样,一个一个处理
4.每个程序只能解决一次信号处理(现在已经解决)
5.在信号发生——>信号处理程序调用signal函数之间有一个时间差。在此段时间中,可能发生一次中断信号,但是由于处理函数还没定义,就会使用默认的。(现在还存在这个问题)
6.在进程不希望某种信号发生时,它不能关闭该信号。进程能做的一切就是忽略该信号。有时希望通知系统“阻止下列信号发生,如果它们确实产生了,请记住它们。”能够显现这种缺陷的的一个经典实例是下列程序段,它捕捉一个信号,然后设置一个表示该信号已发生的标志,
10.5 中断的系统调用
大概就是有些系统调用会被阻塞,有的系统调用会被中断而返回出错
参考:
javascript:void(0)
10.6 可重入函数
大概意思:
不可重入函数的缺点:
当执行一个系统调用时,一个信号来了 ,就要去处理这个信号相应的信号处理函数,这样就会打断原来的函数,如果是正在执行malloc函数则会造成进程存储空间被破坏
可重入函数的优点:
就是上面的问题,可重入函数可以保证,既可以及时处理信号,又不会让信号处理来干扰我
大多数函数是不可重入的:
(a)已知它们使用静态数据结构;
(b)它们调用malloc或 free;
(c)它们是标准IO函数。标准IO库的很多实现都以不可重入方式使用全局数据结构。
注意:
虽然在本书的某些实例中,信号处理程序也调用了printf函数,但这并不保证产生所期望的结果,信号处理程序可能中断主程序中的printf函数调用。
可重入函数的缺点:
当调用信号处理函数时可能会改变errno的值,而每个线程共享一个errno值,如果这是调用read 函数时,如果read出错,可能因为信号的原因,而执行一个不正常的perror函数
10.7 SIGCLD语义
SIGCLD和SIGCHLD
搞不懂
10.8 可靠信号术语和语义
10.9 kill和raise函数
kill的pid参数有以下4种不同的情况。
pid>0
将该信号发送给进程ID为pid的进程。
pid==0
将该信号发送给与发送进程属于同一进程组的所有进程(这些进程的进程组ID等于发送进程的进程组ID),而且发送进程具有权限向这些进程发送信号。
这里用的术语“所有进程”不包括实现定义的系统进程集。对于大多数UNX系统,系统进程集包括内核进程和init (pid为1),
pid<0
将该信号发送给,进程组ID等于pid绝对值的进程组的所有进程
而且发送进程具有权限向其发送信号的所有进程。如前所述,所有进程并不包括系统进程集中的进程。
pid==—1
将该信号发送给发送进程有权限向它们发送信号的所有进程。如前所述,所有进程不包括系统进程集中的进程。
10.10 函数alarm和pause
unsigned int alarm(unsigned int seconds);
alarm应该被称为闹钟函数,而不是定时器函数
1.每个进程只能有一个闹钟时间。如果在调用alarm时,之前已为该进程注册的闹钟时间还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回。以前注册的闹钟时间则被新值代替。
2.如果有以前注册的尚未超过的闹钟时间,而且本次调用的seconds值是0,则取消以前的闹钟,时间,其余留值仍作为alarm函数的返回值。
3.虽然SIGALRM的默认动作是终止进程,
但是大多数使用闹钟的进程捕捉此信号。
如果此时,进程要终止,则在终止之前它可以执行所需的清理操作。如果我们想捕捉SIGALRM信号,则必须在调用alarm之前安装该信号的处理程序。如果我们先调用alarm,然后在我们能够安装SIGALRM处理程序之前已接到该信号,那么进程将终止。
int pause(void);
pause函数使调用进程挂起直至捕捉到一个信号。
pause直到处理函数返回,pause函数才返回
10.11 信号集
能表示多个信号的数据类型称——信号集(signal set)
我们将在sigprocmask (下一节中说明)类函数中使用这种数据类型,以便告诉内核不允许发生该信号集中的信号。
由于:不同的信号的编号可能超过一个整型量所包含的位数,所以一般而言,
因此:不能用整型量中的一位代表一种信号,也就是不能用一个整型量表示信号集。POSX.1定义数据类型sigset_t以包含一个信号集,并且定义了下列5个处理信号集的函数。
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
函数sigemptyset初始化由set指向的信号集,清除其中所有信号。
函数sigfillset初始化由set指向的信号集,使其包括所有信号。
注意:
所有应用程序在使用信号集前,要对该信号集调用sigemptyset或siqfillset一次。
因为:
C编译程序将不赋初值的外部变量和静态变量都初始化为0,而这是否与给定系统上信号集的实现相对应却并不清楚。
一旦已经初始化了一个信号集,以后就可在该信号集中增、删特定的信号。
函数siqaddset将一个信号添加到已有的信号集中,
函数sigdelset则从信号集中删除一个信号。
函数sigismember测试一个指定位
注意:
对所有以信号集作为参数的函数,总是以信号集地址作为向其传送的参数。
10.12 函数sigprocmask
参考我的博客:
javascript:void(0)
10.13 函数sigpending
参考上面的博客:
10.14 函数sigaction
10.15 函数sigsetjmp和siglongjmp
10.16 函数sigsuspend
以后再研究
10.17 函数abort
void abort(void);
对应信号为SIGABRT
10.18 函数system
没研究
10.19 函数sleep、nanosleep和clock_nanosleep
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
nanosleep( )---------以纳秒为单位
struct timespec
{
time_t tv_sec; /* 秒seconds */
long tv_nsec; /* 纳秒nanoseconds */
};
rqtp:用来指定要休眠多少秒
rmtp:参数指向的timespec结构就会被设置为未休眠完的时间长度。如果对未休眠完的时间并不感兴趣,可以把该参数置为NULL。
这个函数功能是暂停某个进程直到你规定的时间后恢复,参数req就是你要暂停的时间,其中req->tv_sec是以秒为单位,而tv_nsec以毫微秒为单位(10的-9次方秒)。由于调用nanosleep是是进程进入TASK_INTERRUPTIBLE,这种状态是会相应信号而进入TASK_RUNNING状态的,这就意味着有可能会没有等到你规定的时间就因为其它信号而唤醒,此时函数返回-1,切还剩余的时间会被记录在rem中。
int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *rqtp, struct timespec *rmtp);
基于特定时钟的休眠
10.20 函数sigqueue——发送的信号可以排队
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};